enabled add, delete, and deselect tags

This commit is contained in:
Meliora Ho 2024-04-03 02:28:04 +00:00
parent 95bf764e19
commit a050d58e91
6 changed files with 50 additions and 30 deletions

View File

@ -12,6 +12,8 @@ import { useState } from "react";
import { RowOptionMenu } from "./RowOptionMenu"; import { RowOptionMenu } from "./RowOptionMenu";
import { RowOpenAction } from "./RowOpenAction"; import { RowOpenAction } from "./RowOpenAction";
import { TableAction } from "./TableAction"; import { TableAction } from "./TableAction";
import { AtSymbolIcon, Bars2Icon } from "@heroicons/react/24/solid";
import TagsInput from "../TagsInput/Index";
const usersExample = usersImport as unknown as User[]; const usersExample = usersImport as unknown as User[];
@ -40,7 +42,7 @@ export const Table = () => {
cell: (info) => <RowOpenAction title={info.getValue()} />, cell: (info) => <RowOpenAction title={info.getValue()} />,
}), }),
columnHelper.accessor("role", { columnHelper.accessor("role", {
cell: (info) => info.renderValue(), cell: (info) => <TagsInput presetValue={info.getValue() }presetOptions={["administrator","volunteer","employee"]} />,
}), }),
columnHelper.accessor("email", { columnHelper.accessor("email", {
header: () => <><AtSymbolIcon className="inline align-top h-4" /> Email</>, header: () => <><AtSymbolIcon className="inline align-top h-4" /> Email</>,

View File

@ -1,7 +1,7 @@
import { EllipsisHorizontalIcon } from "@heroicons/react/24/solid"; import { EllipsisHorizontalIcon, TrashIcon } from "@heroicons/react/24/solid";
import { useState } from "react"; import { useState } from "react";
export const DropdownAction = ({ tag }) => { export const DropdownAction = ({ tag, handleDeleteTag, handleAddTag }) => {
const [isVisible, setVisible] = useState(false); const [isVisible, setVisible] = useState(false);
const [inputValue, setInputValue] = useState(tag); const [inputValue, setInputValue] = useState(tag);
@ -9,6 +9,11 @@ export const DropdownAction = ({ tag }) => {
setVisible(false); setVisible(false);
}; };
const editTagOption = () => {
handleDeleteTag(tag)
handleAddTag(inputValue)
}
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => { const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setInputValue(e.target.value); setInputValue(e.target.value);
}; };
@ -21,14 +26,20 @@ export const DropdownAction = ({ tag }) => {
return ( return (
<div> <div>
{isVisible ? ( {isVisible ? (
<div className="absolute z-50 bg-white"> <div className="absolute flex flex-col justify-start z-50 rounded-md bg-white border border-gray-200 shadow p-2 space-y-2">
<input <input
type="text" type="text"
value={inputValue} value={inputValue}
onChange={handleInputChange} onChange={handleInputChange}
onKeyDown={editTagOption}
onBlur={handleBlur} onBlur={handleBlur}
autoFocus autoFocus
className="bg-gray-50 text-2xs focus:outline-none rounded-md font-normal text-gray-800 p-1 border-2 focus:border-blue-200"
/> />
<button onClick={() => handleDeleteTag(inputValue)} className="justify-start flex flex-row space-x-4 hover:bg-gray-100 rounded-md items-center p-1 px-2">
<TrashIcon className="w-3 h-3" />
<p>Delete</p>
</button>
</div> </div>
) : ( ) : (
<EllipsisHorizontalIcon <EllipsisHorizontalIcon

View File

@ -10,8 +10,8 @@ interface TagsInputProps {
const TagsInput: React.FC<TagsInputProps> = ({ presetValue, presetOptions }) => { const TagsInput: React.FC<TagsInputProps> = ({ presetValue, presetOptions }) => {
const [inputValue, setInputValue] = useState(''); const [inputValue, setInputValue] = useState('');
const [cellSelected, setCellSelected] = useState(false); const [cellSelected, setCellSelected] = useState(false);
const [tags, setTags] = useState<string[]>([presetValue]); const [tags, setTags] = useState<Set<string>>(new Set(presetValue ? [presetValue] : []));
const [options, setOptions] = useState<string[]>(presetOptions); const [options, setOptions] = useState<Set<string>>(new Set(presetOptions));
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => { const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setInputValue(e.target.value); setInputValue(e.target.value);
@ -19,43 +19,52 @@ const TagsInput: React.FC<TagsInputProps> = ({ presetValue, presetOptions }) =>
const handleAddTag = (e: React.KeyboardEvent<HTMLInputElement>) => { const handleAddTag = (e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === 'Enter' && inputValue.trim()) { if (e.key === 'Enter' && inputValue.trim()) {
setTags(prevTags => [...prevTags, inputValue]); // To add to a Set, you need to create a new Set for immutability in React state updates.
setOptions(prevOptions => [...prevOptions, inputValue]); setTags(prevTags => new Set(prevTags).add(inputValue));
setOptions(prevOptions => new Set(prevOptions).add(inputValue));
setInputValue(''); setInputValue('');
} }
}; };
const handleDeleteTag = (tagToDelete: string) => { const handleSelectTag = (tagToAdd: string) => {
setTags(tags.filter(tag => tag !== tagToDelete)); if (!tags.has(tagToAdd)){ // Corrected syntax for checking if a Set contains an item
}; setTags(prevTags => new Set(prevTags).add(tagToAdd));
}
}
const handleDeleteTag = (tagToDelete: string) => {
setTags(prevTags => {
const updatedTags = new Set(prevTags);
updatedTags.delete(tagToDelete);
return updatedTags;
});
};
const handleBlur = () => { const handleBlur = () => {
setCellSelected(false); setCellSelected(false);
}; };
return ( return (
<div className="w-50 cursor-pointer" onClick={() => setCellSelected(true)}> <div className="cursor-pointer" onClick={() => setCellSelected(true)}>
{!cellSelected ? ( {!cellSelected ? (
<TagsArray tags={tags} /> <TagsArray handleDelete={handleDeleteTag} tags={tags} />
) : ( ) : (
<div className="absolute z-50 -mt-6"> <div className="absolute w-64 z-50 -ml-3 -mt-7">
<div className="rounded-md border border-gray-200 shadow"> <div className="rounded-md border border-gray-200 shadow">
<div className="flex flex-wrap rounded-t-md items-center gap-2 bg-gray-50 p-2"> <div className="flex flex-wrap rounded-t-md items-center gap-2 bg-gray-50 p-2">
<TagsArray active tags={tags} /> <TagsArray handleDelete={handleDeleteTag} active tags={tags} />
<input <input
type="text" type="text"
value={inputValue} value={inputValue}
placeholder="Search for an option..." placeholder="Search for an option..."
onChange={handleInputChange} onChange={handleInputChange}
onKeyDown={handleAddTag} onKeyDown={handleAddTag}
onBlur={handleBlur}
className="focus:outline-none bg-transparent" className="focus:outline-none bg-transparent"
autoFocus autoFocus
/> />
</div> </div>
<div className="flex rounded-b-md bg-white flex-col border-t border-gray-100 text-2xs font-medium text-gray-500 p-2"> <div className="flex rounded-b-md bg-white flex-col border-t border-gray-100 text-2xs font-medium text-gray-500 p-2">
Select an option or create one Select an option or create one
<TagDropdown tags={options} /> <TagDropdown handleAdd={handleSelectTag} tags={options} />
</div> </div>
</div> </div>
</div> </div>

View File

@ -1,12 +1,10 @@
import { XMarkIcon } from "@heroicons/react/24/solid" import { XMarkIcon } from "@heroicons/react/24/solid"
export const Tag = ({children, active = false}) => { export const Tag = ({children, handleDelete, active = false}) => {
const handleClick = () => {
}
return ( return (
<span className="font-normal text-gray-800 flex flex-row p-1 px-2 rounded-lg bg-blue-100">{children} <span className="font-normal text-gray-800 flex flex-row p-1 px-2 rounded-lg bg-blue-100">{children}
{active && <XMarkIcon className="ml-1 w-3 text-blue-500" />} {active && <button className="hover:bg-gray-200" onClick={() => handleDelete(children)}><XMarkIcon className=" ml-1 w-3 text-blue-500" /></button>}
</span> </span>
) )
} }

View File

@ -2,11 +2,11 @@
import { Tag } from "./Tag"; import { Tag } from "./Tag";
import { DropdownAction } from "./DropdownAction"; import { DropdownAction } from "./DropdownAction";
export const TagDropdown = ({ tags }) => { export const TagDropdown = ({ tags, handleAdd }) => {
return ( return (
<div className="flex flex-col space-y-2 mt-2"> <div className="flex flex-col space-y-2 mt-2">
{tags.map((tag, index) => ( {Array.from(tags).map((tag, index) => (
<div key={index} className="items-center rounded-md p-1 flex flex-row justify-between hover:bg-gray-100"> <div onClick={() => handleAdd(tag)} key={index} className="items-center rounded-md p-1 flex flex-row justify-between hover:bg-gray-100">
<Tag>{tag}</Tag> <Tag>{tag}</Tag>
<DropdownAction tag={tag} /> <DropdownAction tag={tag} />
</div> </div>

View File

@ -1,12 +1,12 @@
import { Tag } from "./Tag" import { Tag } from "./Tag"
export const TagsArray = ({ tags, active = false }) => { export const TagsArray = ({ tags, handleDelete, active = false }) => {
return( return(
<div className="flex items-center space-x-2"> <div className="flex flex-wrap gap-2 items-center">
{ {
tags.map((tag) => { Array.from(tags).map((tag) => {
return ( return (
<Tag active={active}>{tag}</Tag> <Tag handleDelete={handleDelete} active={active}>{tag}</Tag>
) )
}) })
} }