Skip to content

Commit 3608c0e

Browse files
committed
WIP: create new tags input
1 parent c9874d1 commit 3608c0e

File tree

2 files changed

+290
-209
lines changed

2 files changed

+290
-209
lines changed
Lines changed: 227 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,227 @@
1+
import { cva, type VariantProps } from "class-variance-authority";
2+
import {
3+
ChevronDown,
4+
XIcon
5+
} from "lucide-react";
6+
import * as React from "react";
7+
8+
import { Badge } from "@/components/ui/badge";
9+
import { Button } from "@/components/ui/button";
10+
import {
11+
Command,
12+
CommandEmpty,
13+
CommandGroup,
14+
CommandInput,
15+
CommandItem,
16+
CommandList,
17+
CommandSeparator,
18+
} from "@/components/ui/command";
19+
import {
20+
Popover,
21+
PopoverContent,
22+
PopoverTrigger,
23+
} from "@/components/ui/popover";
24+
import { Separator } from "@/components/ui/separator";
25+
import { cn, getTagColor } from "@/lib/utils";
26+
27+
28+
interface TagsSelectFormFieldProps
29+
extends React.ButtonHTMLAttributes<HTMLButtonElement> {
30+
asChild?: boolean;
31+
options: string[];
32+
defaultValue?: string[];
33+
disabled?: boolean;
34+
placeholder: string;
35+
className?: string;
36+
onValueChange: (value: string[]) => void;
37+
}
38+
39+
const TagsSelectFormField = React.forwardRef<
40+
HTMLButtonElement,
41+
TagsSelectFormFieldProps
42+
>(
43+
(
44+
{
45+
className,
46+
asChild = false,
47+
options,
48+
defaultValue,
49+
onValueChange,
50+
disabled,
51+
placeholder,
52+
...props
53+
},
54+
ref
55+
) => {
56+
const [selectedValues, setSelectedValues] = React.useState<string[]>(
57+
defaultValue || []
58+
);
59+
const selectedValuesSet = React.useRef(new Set(selectedValues));
60+
const [isPopoverOpen, setIsPopoverOpen] = React.useState(false);
61+
const [search, setSearch] = React.useState('')
62+
63+
React.useEffect(() => {
64+
setSelectedValues(defaultValue || []);
65+
selectedValuesSet.current = new Set(defaultValue);
66+
}, [defaultValue]);
67+
68+
const handleInputKeyDown = (event: any) => {
69+
if (event.key === "Enter") {
70+
if(search){
71+
toggleOption(search)
72+
}
73+
}
74+
};
75+
76+
const toggleOption = (value: string) => {
77+
const currentTag = selectedValues.find(t => t.toLocaleLowerCase() === value.trim().toLocaleLowerCase());
78+
79+
if (currentTag) {
80+
selectedValuesSet.current.delete(currentTag);
81+
setSelectedValues(selectedValues.filter((v) => v !== value.trim()));
82+
} else {
83+
selectedValuesSet.current.add(value.trim());
84+
setSelectedValues([...selectedValues, value.trim()]);
85+
}
86+
87+
onValueChange(Array.from(selectedValuesSet.current));
88+
};
89+
90+
return (
91+
<Popover open={isPopoverOpen} onOpenChange={setIsPopoverOpen}>
92+
<PopoverTrigger asChild>
93+
<Button
94+
ref={ref}
95+
{...props}
96+
onClick={() => setIsPopoverOpen(!isPopoverOpen)}
97+
className="flex w-full p-1 rounded-md border min-h-10 h-auto items-center justify-between bg-inherit hover:bg-card"
98+
>
99+
{selectedValues.length > 0 ? (
100+
<div className="flex justify-between items-center w-full">
101+
<div className="flex flex-wrap items-center">
102+
{selectedValues.map((value) => {
103+
return (
104+
<Badge
105+
key={value}
106+
className="m-1 text-slate-600"
107+
style={{ backgroundColor: getTagColor(value) }}
108+
>
109+
{value}
110+
<XIcon
111+
className="ml-2 h-4 w-4 cursor-pointer"
112+
onClick={(event) => {
113+
event.stopPropagation();
114+
toggleOption(value);
115+
}}
116+
/>
117+
</Badge>
118+
);
119+
})}
120+
</div>
121+
<div className="flex items-center justify-between">
122+
<XIcon
123+
className="h-4 mx-2 cursor-pointer text-muted-foreground"
124+
onClick={(event) => {
125+
setSelectedValues([]);
126+
selectedValuesSet.current.clear();
127+
onValueChange([]);
128+
event.stopPropagation();
129+
}}
130+
/>
131+
<Separator
132+
orientation="vertical"
133+
className="flex min-h-6 h-full"
134+
/>
135+
<ChevronDown className="h-4 mx-2 cursor-pointer text-muted-foreground" />
136+
</div>
137+
</div>
138+
) : (
139+
<div className="flex items-center justify-between w-full mx-auto">
140+
<span className="text-sm text-muted-foreground mx-3">
141+
{placeholder}
142+
</span>
143+
<ChevronDown className="h-4 cursor-pointer text-muted-foreground mx-2" />
144+
</div>
145+
)}
146+
</Button>
147+
</PopoverTrigger>
148+
<PopoverContent
149+
className="w-[200px] p-0 drop-shadow-sm"
150+
align="start"
151+
onEscapeKeyDown={() => setIsPopoverOpen(false)}
152+
>
153+
<Command>
154+
<CommandInput
155+
placeholder="Search..."
156+
onKeyDown={handleInputKeyDown}
157+
value={search}
158+
onValueChange={setSearch}
159+
/>
160+
<CommandList className="w-full">
161+
{/* <CommandEmpty>Press enter to create this tag.</CommandEmpty> */}
162+
<CommandGroup>
163+
{options.map((option) => {
164+
return (
165+
<CommandItem
166+
key={option}
167+
onSelect={() => toggleOption(option)}
168+
style={{
169+
pointerEvents: "auto",
170+
opacity: 1,
171+
}}
172+
className="cursor-pointer"
173+
>
174+
<span>{option}</span>
175+
</CommandItem>
176+
);
177+
})}
178+
</CommandGroup>
179+
<CommandSeparator />
180+
<CommandGroup>
181+
<div className="flex items-center justify-between">
182+
{selectedValues.length > 0 && (
183+
<>
184+
<CommandItem
185+
onSelect={() => {
186+
setSelectedValues([]);
187+
selectedValuesSet.current.clear();
188+
onValueChange([]);
189+
}}
190+
style={{
191+
pointerEvents: "auto",
192+
opacity: 1,
193+
}}
194+
className="flex-1 justify-center cursor-pointer"
195+
>
196+
Clear
197+
</CommandItem>
198+
<Separator
199+
orientation="vertical"
200+
className="flex min-h-6 h-full"
201+
/>
202+
</>
203+
)}
204+
<CommandSeparator />
205+
<CommandItem
206+
onSelect={() => setIsPopoverOpen(false)}
207+
style={{
208+
pointerEvents: "auto",
209+
opacity: 1,
210+
}}
211+
className="flex-1 justify-center cursor-pointer"
212+
>
213+
Close
214+
</CommandItem>
215+
</div>
216+
</CommandGroup>
217+
</CommandList>
218+
</Command>
219+
</PopoverContent>
220+
</Popover>
221+
);
222+
}
223+
);
224+
225+
TagsSelectFormField.displayName = "TagsSelectFormField";
226+
227+
export default TagsSelectFormField;

0 commit comments

Comments
 (0)