Edited the dropdown action

This commit is contained in:
Meliora Ho 2024-04-04 20:28:41 +00:00
parent a050d58e91
commit 167fa009ae
7 changed files with 115 additions and 56 deletions

View File

@ -21,17 +21,17 @@ type User = {
id: number; id: number;
created_at: any; created_at: any;
username: string; username: string;
role: "ADMIN" | "EMPLOYEE" | "VOLUNTEER"; role: "administrator" | "employee" | "volunteer";
email: string; email: string;
program: "DOMESTIC" | "ECONOMIC" | "COMMUNITY"; program: "domestic" | "economic" | "community";
experience: number; experience: number;
group?: string; group?: string;
}; };
export const Table = () => { export const Table = () => {
const columnHelper = createColumnHelper<User>(); const columnHelper = createColumnHelper<User>();
const columns = [ const columns = [
columnHelper.display({ columnHelper.display({
id: "options", id: "options",

View File

@ -0,0 +1,10 @@
import { Tag } from "./Tag"
export const CreateNewTagAction = ({ input }) => {
return (
<div className="flex flex-row space-x-2 hover:bg-gray-100 rounded-md py-2 p-2 items-center">
<p className="capitalize">Create</p>
<Tag active={false} onDelete={null} >{input}</Tag>
</div>
)
}

View File

@ -1,51 +1,48 @@
import { EllipsisHorizontalIcon, TrashIcon } 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, handleDeleteTag, handleAddTag }) => { export const DropdownAction = ({ tag, handleDeleteTag, handleEditTag }) => {
const [isVisible, setVisible] = useState(false); const [isVisible, setVisible] = useState(false);
const [inputValue, setInputValue] = useState(tag); const [inputValue, setInputValue] = useState(tag);
const handleBlur = () => {
setVisible(false); const editTagOption = (e) => {
if (e.key === 'Enter') {
handleEditTag(tag, inputValue)
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);
}; };
const handleClick = (event: React.MouseEvent<SVGSVGElement, MouseEvent>) => {
event.stopPropagation(); // This stops the click event from propagating
setVisible(!isVisible); // Toggle visibility
};
return ( return (
<div> <div>
{isVisible ? ( <EllipsisHorizontalIcon
className="w-5 text-gray-500"
onClick={() => setVisible(!isVisible)}
/>
{isVisible && (
<div className="absolute flex flex-col justify-start z-50 rounded-md bg-white border border-gray-200 shadow p-2 space-y-2"> <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} onKeyDown={editTagOption}
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" 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"> <button onClick={() => {
handleDeleteTag(inputValue)
setVisible(false)
}} className="justify-start flex flex-row space-x-4 hover:bg-gray-100 rounded-md items-center p-2 px-2">
<TrashIcon className="w-3 h-3" /> <TrashIcon className="w-3 h-3" />
<p>Delete</p> <p>Delete</p>
</button> </button>
</div> </div>
) : (
<EllipsisHorizontalIcon
className="w-5 text-gray-500"
onClick={handleClick} // Attach the click event handler here
/>
)} )}
</div> </div>
); );

View File

@ -1,16 +1,22 @@
import React, { useState } from 'react'; import React, { useState } from "react";
import 'tailwindcss/tailwind.css'; import "tailwindcss/tailwind.css";
import { TagsArray } from './TagsArray'; // Corrected path assumption import { TagsArray } from "./TagsArray";
import { TagDropdown } from './TagDropdown'; import { TagDropdown } from "./TagDropdown";
import { CreateNewTagAction } from "./CreateNewTagAction";
interface TagsInputProps { interface TagsInputProps {
presetOptions: string[]; presetOptions: string[];
} }
const TagsInput: React.FC<TagsInputProps> = ({ presetValue, presetOptions }) => { const TagsInput: React.FC<TagsInputProps> = ({
const [inputValue, setInputValue] = useState(''); presetValue,
presetOptions,
}) => {
const [inputValue, setInputValue] = useState("");
const [cellSelected, setCellSelected] = useState(false); const [cellSelected, setCellSelected] = useState(false);
const [tags, setTags] = useState<Set<string>>(new Set(presetValue ? [presetValue] : [])); const [tags, setTags] = useState<Set<string>>(
new Set(presetValue ? [presetValue] : [])
);
const [options, setOptions] = useState<Set<string>>(new Set(presetOptions)); const [options, setOptions] = useState<Set<string>>(new Set(presetOptions));
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => { const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
@ -18,31 +24,63 @@ 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()) {
// To add to a Set, you need to create a new Set for immutability in React state updates. setTags((prevTags) => new Set(prevTags).add(inputValue));
setTags(prevTags => new Set(prevTags).add(inputValue)); setOptions((prevOptions) => new Set(prevOptions).add(inputValue));
setOptions(prevOptions => new Set(prevOptions).add(inputValue)); setInputValue("");
setInputValue('');
} }
}; };
const handleSelectTag = (tagToAdd: string) => { const handleSelectTag = (tagToAdd: string) => {
if (!tags.has(tagToAdd)){ // Corrected syntax for checking if a Set contains an item if (!tags.has(tagToAdd)) {
setTags(prevTags => new Set(prevTags).add(tagToAdd)); // Corrected syntax for checking if a Set contains an item
setTags((prevTags) => new Set(prevTags).add(tagToAdd));
} }
} };
const handleDeleteTag = (tagToDelete: string) => { const handleDeleteTag = (tagToDelete: string) => {
setTags(prevTags => { setTags((prevTags) => {
const updatedTags = new Set(prevTags); const updatedTags = new Set(prevTags);
updatedTags.delete(tagToDelete); updatedTags.delete(tagToDelete);
return updatedTags; return updatedTags;
}); });
}; };
const handleBlur = () => {
setCellSelected(false); const handleDeleteTagOption = (tagToDelete: string) => {
setOptions((prevOptions) => {
const updatedOptions = new Set(prevOptions);
updatedOptions.delete(tagToDelete);
return updatedOptions;
});
if (tags.has(tagToDelete)) {
handleDeleteTag(tagToDelete);
}
}; };
const handleEditTag = (oldTag: string, newTag: string) => {
if (oldTag !== newTag) {
setTags((prevTags) => {
const tagsArray = Array.from(prevTags);
const oldTagIndex = tagsArray.indexOf(oldTag);
if (oldTagIndex !== -1) {
tagsArray.splice(oldTagIndex, 1, newTag);
}
return new Set(tagsArray);
});
setOptions((prevOptions) => {
const optionsArray = Array.from(prevOptions);
const oldTagIndex = optionsArray.indexOf(oldTag);
if (oldTagIndex !== -1) {
optionsArray.splice(oldTagIndex, 1, newTag);
}
return new Set(optionsArray);
});
}
};
return ( return (
<div className="cursor-pointer" onClick={() => setCellSelected(true)}> <div className="cursor-pointer" onClick={() => setCellSelected(true)}>
{!cellSelected ? ( {!cellSelected ? (
@ -50,7 +88,7 @@ const TagsInput: React.FC<TagsInputProps> = ({ presetValue, presetOptions }) =>
) : ( ) : (
<div className="absolute w-64 z-50 -ml-3 -mt-7"> <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 handleDelete={handleDeleteTag} active tags={tags} /> <TagsArray handleDelete={handleDeleteTag} active tags={tags} />
<input <input
type="text" type="text"
@ -63,8 +101,16 @@ const TagsInput: React.FC<TagsInputProps> = ({ presetValue, presetOptions }) =>
/> />
</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 <p className="capitalize">Select an option or create one</p>
<TagDropdown handleAdd={handleSelectTag} tags={options} /> <TagDropdown
handleDeleteTag={handleDeleteTagOption}
handleEditTag={handleEditTag}
handleAdd={handleSelectTag}
tags={options}
/>
{inputValue.length > 0 && (
<CreateNewTagAction input={inputValue} />
)}
</div> </div>
</div> </div>
</div> </div>

View File

@ -1,10 +1,15 @@
import { XMarkIcon } from "@heroicons/react/24/solid" import { XMarkIcon } from "@heroicons/react/24/solid";
export const Tag = ({children, handleDelete, active = false}) => { export const Tag = ({ children, handleDelete, active = false }) => {
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 bg-cyan-100 text-gray-800 flex flex-row p-1 px-2 rounded-lg`}>
{active && <button className="hover:bg-gray-200" onClick={() => handleDelete(children)}><XMarkIcon className=" ml-1 w-3 text-blue-500" /></button>} {children}
</span> {active && handleDelete && (
) <button onClick={() => handleDelete(children)}>
} <XMarkIcon className={`ml-1 w-3 text-cyan-500`} />
</button>
)}
</span>
);
};

View File

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

View File

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