@firecms/ui 3.0.0-canary.120 → 3.0.0-canary.122

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,370 +0,0 @@
1
- // src/components/multi-select.tsx
2
- import * as PopoverPrimitive from "@radix-ui/react-popover";
3
-
4
- import * as React from "react";
5
- import { Command as CommandPrimitive } from "cmdk";
6
- import { cls } from "../util";
7
- import { CloseIcon, ExpandMoreIcon } from "../icons";
8
- import { Separator } from "./Separator";
9
- import { Checkbox } from "./Checkbox";
10
- import { Chip } from "./Chip";
11
- import {
12
- defaultBorderMixin,
13
- fieldBackgroundDisabledMixin,
14
- fieldBackgroundHoverMixin,
15
- fieldBackgroundInvisibleMixin,
16
- fieldBackgroundMixin,
17
- focusedDisabled
18
- } from "../styles";
19
-
20
- /**
21
- * Props for MultiSelect component
22
- */
23
- interface MultiSelectProps
24
- extends React.ButtonHTMLAttributes<HTMLButtonElement> {
25
- /**
26
- * An array of option objects to be displayed in the multi-select component.
27
- * Each option object has a label, value, and an optional icon.
28
- */
29
- options: {
30
- /** The text to display for the option. */
31
- label: string;
32
- /** The unique value associated with the option. */
33
- value: string;
34
- }[];
35
-
36
- /**
37
- * Callback function triggered when the selected values change.
38
- * Receives an array of the new selected values.
39
- */
40
- onValueChange: (value: string[]) => void;
41
-
42
- /** The default selected values when the component mounts. */
43
- defaultValue: string[];
44
-
45
- /**
46
- * Placeholder text to be displayed when no values are selected.
47
- * Optional, defaults to "Select options".
48
- */
49
- placeholder?: string;
50
-
51
- /**
52
- * Animation duration in seconds for the visual effects (e.g., bouncing badges).
53
- * Optional, defaults to 0 (no animation).
54
- */
55
- animation?: number;
56
-
57
- /**
58
- * Maximum number of items to display. Extra selected items will be summarized.
59
- * Optional, defaults to 3.
60
- */
61
- maxCount?: number;
62
-
63
- /**
64
- * The modality of the popover. When set to true, interaction with outside elements
65
- * will be disabled and only popover content will be visible to screen readers.
66
- * Optional, defaults to false.
67
- */
68
- modalPopover?: boolean;
69
-
70
- /**
71
- * If true, renders the multi-select component as a child of another component.
72
- * Optional, defaults to false.
73
- */
74
- asChild?: boolean;
75
-
76
- /**
77
- * Additional class names to apply custom styles to the multi-select component.
78
- * Optional, can be used to add custom styles.
79
- */
80
- className?: string;
81
-
82
- size?: "small" | "medium",
83
-
84
- invisible?: boolean;
85
- disabled?: boolean;
86
-
87
- variant?: "default" | "secondary" | "destructive";
88
- }
89
-
90
- export const NewMultiSelect = React.forwardRef<
91
- HTMLButtonElement,
92
- MultiSelectProps
93
- >(
94
- (
95
- {
96
- size,
97
- options,
98
- onValueChange,
99
- invisible,
100
- disabled,
101
- variant,
102
- defaultValue = [],
103
- placeholder = "Select options",
104
- animation = 0,
105
- maxCount = 3,
106
- modalPopover = false,
107
- asChild = false,
108
- className,
109
- ...props
110
- },
111
- ref
112
- ) => {
113
- const [selectedValues, setSelectedValues] =
114
- React.useState<string[]>(defaultValue);
115
- const [isPopoverOpen, setIsPopoverOpen] = React.useState(false);
116
- // const [isAnimating, setIsAnimating] = React.useState(false);
117
-
118
- React.useEffect(() => {
119
- setSelectedValues(defaultValue);
120
- }, [defaultValue]);
121
-
122
- const handleInputKeyDown = (
123
- event: React.KeyboardEvent<HTMLInputElement>
124
- ) => {
125
- if (event.key === "Enter") {
126
- setIsPopoverOpen(true);
127
- } else if (event.key === "Backspace" && !event.currentTarget.value) {
128
- const newSelectedValues = [...selectedValues];
129
- newSelectedValues.pop();
130
- setSelectedValues(newSelectedValues);
131
- onValueChange(newSelectedValues);
132
- }
133
- };
134
-
135
- const toggleOption = (value: string) => {
136
- const newSelectedValues = selectedValues.includes(value)
137
- ? selectedValues.filter((v) => v !== value)
138
- : [...selectedValues, value];
139
- setSelectedValues(newSelectedValues);
140
- onValueChange(newSelectedValues);
141
- };
142
-
143
- const handleClear = () => {
144
- setSelectedValues([]);
145
- onValueChange([]);
146
- };
147
-
148
- const handleTogglePopover = () => {
149
- setIsPopoverOpen((prev) => !prev);
150
- };
151
-
152
- const clearExtraOptions = () => {
153
- const newSelectedValues = selectedValues.slice(0, maxCount);
154
- setSelectedValues(newSelectedValues);
155
- onValueChange(newSelectedValues);
156
- };
157
-
158
- const toggleAll = () => {
159
- if (selectedValues.length === options.length) {
160
- handleClear();
161
- } else {
162
- const allValues = options.map((option) => option.value);
163
- setSelectedValues(allValues);
164
- onValueChange(allValues);
165
- }
166
- };
167
-
168
- return (
169
- <PopoverPrimitive.Root
170
- open={isPopoverOpen}
171
- onOpenChange={setIsPopoverOpen}
172
- modal={modalPopover}
173
- >
174
- <PopoverPrimitive.Trigger asChild>
175
- <button
176
- ref={ref}
177
- {...props}
178
- onClick={handleTogglePopover}
179
- className={cls(
180
- size === "small" ? "min-h-[42px]" : "min-h-[64px]",
181
- "select-none rounded-md text-sm",
182
- invisible ? fieldBackgroundInvisibleMixin : fieldBackgroundMixin,
183
- disabled ? fieldBackgroundDisabledMixin : fieldBackgroundHoverMixin,
184
- "relative flex items-center",
185
- className
186
- )}
187
- >
188
- {selectedValues.length > 0 ? (
189
- <div className="flex justify-between items-center w-full">
190
- <div className="flex flex-wrap items-center">
191
- {selectedValues.slice(0, maxCount).map((value) => {
192
- const option = options.find((o) => o.value === value);
193
- return (
194
- <Chip
195
- key={value}
196
- className={cls(
197
- "m-1 transition ease-in-out delay-150 hover:-translate-y-1 hover:scale-110 duration-300",
198
- {
199
- "border-foreground/10 text-foreground bg-card hover:bg-card/80": variant === "default",
200
- "border-foreground/10 bg-secondary text-secondary-foreground hover:bg-secondary/80": variant === "secondary",
201
- "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80": variant === "destructive",
202
- }
203
- )}
204
- style={{ animationDuration: `${animation}s` }}
205
- >
206
- {option?.label}
207
- <CloseIcon
208
- size={"smallest"}
209
- onClick={(event) => {
210
- event.stopPropagation();
211
- toggleOption(value);
212
- }}
213
- />
214
- </Chip>
215
- );
216
- })}
217
- {selectedValues.length > maxCount && (
218
- <Chip
219
- className={cls(
220
- "bg-transparent text-foreground border-foreground/1 hover:bg-transparent",
221
- "m-1 transition ease-in-out delay-150 hover:-translate-y-1 hover:scale-110 duration-300",
222
- {
223
- "border-foreground/10 text-foreground bg-card hover:bg-card/80": variant === "default",
224
- "border-foreground/10 bg-secondary text-secondary-foreground hover:bg-secondary/80": variant === "secondary",
225
- "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80": variant === "destructive",
226
- }
227
- )}
228
- style={{ animationDuration: `${animation}s` }}
229
- >
230
- {`+ ${selectedValues.length - maxCount} more`}
231
- <CloseIcon
232
- size={"smallest"}
233
- onClick={(event) => {
234
- event.stopPropagation();
235
- clearExtraOptions();
236
- }}
237
- />
238
- </Chip>
239
- )}
240
- </div>
241
- <div className="flex items-center justify-between">
242
- <CloseIcon
243
- size={"small"}
244
- onClick={(event) => {
245
- event.stopPropagation();
246
- handleClear();
247
- }}
248
- />
249
- <Separator
250
- orientation="vertical"
251
- />
252
-
253
- <div className={cls("px-2 h-full flex items-center")}>
254
- <ExpandMoreIcon size={"small"}
255
- className={cls("transition", isPopoverOpen ? "rotate-180" : "")}/>
256
- </div>
257
- </div>
258
- </div>
259
- ) : (
260
- <div className="flex items-center justify-between w-full mx-auto">
261
- <span className="text-sm text-muted-foreground mx-3">
262
- {placeholder}
263
- </span>
264
- <div className={cls("px-2 h-full flex items-center")}>
265
- <ExpandMoreIcon size={"small"}
266
- className={cls("transition", isPopoverOpen ? "rotate-180" : "")}/>
267
- </div>
268
- </div>
269
- )}
270
- </button>
271
- </PopoverPrimitive.Trigger>
272
- <PopoverPrimitive.Content
273
- className={cls("z-50 relative overflow-hidden border bg-white dark:bg-gray-900 p-2 rounded-lg", defaultBorderMixin)}
274
- align="start"
275
- onEscapeKeyDown={() => setIsPopoverOpen(false)}
276
- >
277
- <CommandPrimitive>
278
- <CommandPrimitive.Input
279
- className={cls(focusedDisabled, "bg-transparent outline-none flex-1 h-full w-full m-4")}
280
- placeholder="Search..."
281
- onKeyDown={handleInputKeyDown}
282
- />
283
- <CommandPrimitive.List>
284
- <CommandPrimitive.Empty>No results found.</CommandPrimitive.Empty>
285
- <CommandPrimitive.Group>
286
- <CommandPrimitive.Item
287
- key="all"
288
- onSelect={toggleAll}
289
- className={
290
- cls(
291
- // (fieldValue ?? []).includes(value) ? "bg-slate-200 dark:bg-slate-950" : "",
292
- "cursor-pointer",
293
- "flex flex-row items-center gap-2",
294
- "ring-offset-transparent",
295
- "p-1 rounded aria-[selected=true]:outline-none aria-[selected=true]:ring-2 aria-[selected=true]:ring-primary aria-[selected=true]:ring-opacity-75 aria-[selected=true]:ring-offset-2",
296
- "aria-[selected=true]:bg-slate-100 aria-[selected=true]:dark:bg-slate-900",
297
- "cursor-pointer p-1 rounded aria-[selected=true]:bg-slate-100 aria-[selected=true]:dark:bg-slate-900",
298
- className
299
- )
300
- }
301
- >
302
- <Checkbox checked={selectedValues.length === options.length} size={"small"}/>
303
- <span>(Select All)</span>
304
- </CommandPrimitive.Item>
305
- {options.map((option) => {
306
- const isSelected = selectedValues.includes(option.value);
307
- return (
308
- <CommandPrimitive.Item
309
- key={option.value}
310
- onSelect={() => toggleOption(option.value)}
311
- className={cls(
312
- // (fieldValue ?? []).includes(value) ? "bg-slate-200 dark:bg-slate-950" : "",
313
- "cursor-pointer",
314
- "flex flex-row items-center gap-2",
315
- "ring-offset-transparent",
316
- "p-1 rounded aria-[selected=true]:outline-none aria-[selected=true]:ring-2 aria-[selected=true]:ring-primary aria-[selected=true]:ring-opacity-75 aria-[selected=true]:ring-offset-2",
317
- "aria-[selected=true]:bg-slate-100 aria-[selected=true]:dark:bg-slate-900",
318
- "cursor-pointer p-1 rounded aria-[selected=true]:bg-slate-100 aria-[selected=true]:dark:bg-slate-900",
319
- className
320
- )}
321
- >
322
-
323
- <Checkbox checked={isSelected} size={"small"}/>
324
- <span>{option.label}</span>
325
- </CommandPrimitive.Item>
326
- );
327
- })}
328
- </CommandPrimitive.Group>
329
- <CommandPrimitive.Separator/>
330
- <CommandPrimitive.Group>
331
- <div className="flex items-center justify-between">
332
- {selectedValues.length > 0 && (
333
- <>
334
- <CommandPrimitive.Item
335
- onSelect={handleClear}
336
- className="flex-1 justify-center cursor-pointer"
337
- >
338
- Clear
339
- </CommandPrimitive.Item>
340
- <Separator
341
- orientation="vertical"
342
- />
343
- </>
344
- )}
345
- <CommandPrimitive.Item
346
- onSelect={() => setIsPopoverOpen(false)}
347
- className="flex-1 justify-center cursor-pointer max-w-full"
348
- >
349
- Close
350
- </CommandPrimitive.Item>
351
- </div>
352
- </CommandPrimitive.Group>
353
- </CommandPrimitive.List>
354
- </CommandPrimitive>
355
- </PopoverPrimitive.Content>
356
- {/*{animation > 0 && selectedValues.length > 0 && (*/}
357
- {/* <WandSparkles*/}
358
- {/* className={cls(*/}
359
- {/* "cursor-pointer my-2 text-foreground bg-background w-3 h-3",*/}
360
- {/* isAnimating ? "" : "text-muted-foreground"*/}
361
- {/* )}*/}
362
- {/* onClick={() => setIsAnimating(!isAnimating)}*/}
363
- {/* />*/}
364
- {/*)}*/}
365
- </PopoverPrimitive.Root>
366
- );
367
- }
368
- );
369
-
370
- NewMultiSelect.displayName = "MultiSelect";