@firecms/ui 3.0.0-canary.16 → 3.0.0-canary.160

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.
Files changed (111) hide show
  1. package/README.md +60 -150
  2. package/dist/components/Avatar.d.ts +1 -0
  3. package/dist/components/BooleanSwitch.d.ts +1 -1
  4. package/dist/components/BooleanSwitchWithLabel.d.ts +4 -1
  5. package/dist/components/Button.d.ts +1 -1
  6. package/dist/components/CenteredView.d.ts +4 -2
  7. package/dist/components/Checkbox.d.ts +3 -2
  8. package/dist/components/Chip.d.ts +3 -2
  9. package/dist/components/DateTimeField.d.ts +5 -7
  10. package/dist/components/Dialog.d.ts +4 -1
  11. package/dist/components/DialogContent.d.ts +2 -1
  12. package/dist/components/DialogTitle.d.ts +10 -0
  13. package/dist/components/ExpandablePanel.d.ts +2 -1
  14. package/dist/components/FileUpload.d.ts +2 -2
  15. package/dist/components/InputLabel.d.ts +2 -2
  16. package/dist/components/Label.d.ts +4 -1
  17. package/dist/components/Markdown.d.ts +1 -0
  18. package/dist/components/Menu.d.ts +6 -2
  19. package/dist/components/Menubar.d.ts +79 -0
  20. package/dist/components/MultiSelect.d.ts +32 -16
  21. package/dist/components/Popover.d.ts +2 -1
  22. package/dist/components/RadioGroup.d.ts +26 -3
  23. package/dist/components/Select.d.ts +8 -11
  24. package/dist/components/Separator.d.ts +2 -1
  25. package/dist/components/Sheet.d.ts +6 -0
  26. package/dist/components/Slider.d.ts +21 -0
  27. package/dist/components/Table.d.ts +10 -10
  28. package/dist/components/Tabs.d.ts +4 -2
  29. package/dist/components/TextField.d.ts +1 -1
  30. package/dist/components/TextareaAutosize.d.ts +3 -34
  31. package/dist/components/Tooltip.d.ts +6 -2
  32. package/dist/components/Typography.d.ts +5 -4
  33. package/dist/components/index.d.ts +3 -1
  34. package/dist/hooks/index.d.ts +3 -0
  35. package/dist/icons/Icon.d.ts +3 -3
  36. package/dist/index.css +73 -0
  37. package/dist/index.d.ts +1 -0
  38. package/dist/index.es.js +50174 -20590
  39. package/dist/index.es.js.map +1 -1
  40. package/dist/index.umd.js +50441 -857
  41. package/dist/index.umd.js.map +1 -1
  42. package/dist/styles.d.ts +9 -9
  43. package/dist/util/{cn.d.ts → cls.d.ts} +4 -0
  44. package/dist/util/index.d.ts +1 -3
  45. package/package.json +119 -118
  46. package/src/components/Alert.tsx +4 -4
  47. package/src/components/Autocomplete.tsx +7 -5
  48. package/src/components/Avatar.tsx +41 -26
  49. package/src/components/Badge.tsx +2 -2
  50. package/src/components/BooleanSwitch.tsx +14 -13
  51. package/src/components/BooleanSwitchWithLabel.tsx +17 -8
  52. package/src/components/Button.tsx +31 -23
  53. package/src/components/Card.tsx +4 -3
  54. package/src/components/CenteredView.tsx +26 -15
  55. package/src/components/Checkbox.tsx +16 -14
  56. package/src/components/Chip.tsx +13 -10
  57. package/src/components/CircularProgress.tsx +3 -3
  58. package/src/components/Collapse.tsx +4 -2
  59. package/src/components/Container.tsx +3 -2
  60. package/src/components/DateTimeField.tsx +144 -921
  61. package/src/components/DebouncedTextField.tsx +1 -0
  62. package/src/components/Dialog.tsx +17 -7
  63. package/src/components/DialogActions.tsx +3 -3
  64. package/src/components/DialogContent.tsx +7 -3
  65. package/src/components/DialogTitle.tsx +41 -0
  66. package/src/components/ExpandablePanel.tsx +20 -12
  67. package/src/components/FileUpload.tsx +11 -13
  68. package/src/components/IconButton.tsx +7 -11
  69. package/src/components/InfoLabel.tsx +2 -2
  70. package/src/components/InputLabel.tsx +12 -9
  71. package/src/components/Label.tsx +18 -4
  72. package/src/components/Markdown.tsx +15 -3
  73. package/src/components/Menu.tsx +50 -31
  74. package/src/components/Menubar.tsx +322 -0
  75. package/src/components/MultiSelect.tsx +341 -167
  76. package/src/components/Paper.tsx +2 -2
  77. package/src/components/Popover.tsx +19 -15
  78. package/src/components/RadioGroup.tsx +42 -9
  79. package/src/components/SearchBar.tsx +12 -11
  80. package/src/components/Select.tsx +142 -130
  81. package/src/components/Separator.tsx +10 -4
  82. package/src/components/Sheet.tsx +53 -31
  83. package/src/components/Skeleton.tsx +9 -6
  84. package/src/components/Slider.tsx +110 -0
  85. package/src/components/Table.tsx +54 -35
  86. package/src/components/Tabs.tsx +17 -15
  87. package/src/components/TextField.tsx +25 -23
  88. package/src/components/TextareaAutosize.tsx +4 -3
  89. package/src/components/Tooltip.tsx +33 -16
  90. package/src/components/Typography.tsx +42 -26
  91. package/src/components/common/SelectInputLabel.tsx +3 -3
  92. package/src/components/index.tsx +3 -1
  93. package/src/hooks/index.ts +3 -0
  94. package/src/{util → hooks}/useDebounceValue.tsx +2 -0
  95. package/src/{util → hooks}/useInjectStyles.tsx +1 -0
  96. package/src/{util → hooks}/useOutsideAlerter.tsx +2 -0
  97. package/src/icons/Icon.tsx +48 -43
  98. package/src/icons/icon_keys.ts +114 -1301
  99. package/src/index.css +73 -0
  100. package/src/index.ts +1 -0
  101. package/src/scripts/generateIconKeys.ts +20 -11
  102. package/src/styles.ts +9 -9
  103. package/src/util/cls.ts +14 -0
  104. package/src/util/index.ts +1 -3
  105. package/tailwind.config.js +42 -26
  106. package/dist/components/Spinner.d.ts +0 -1
  107. package/src/components/Spinner.tsx +0 -18
  108. package/src/util/cn.ts +0 -6
  109. /package/dist/{util → hooks}/useDebounceValue.d.ts +0 -0
  110. /package/dist/{util → hooks}/useInjectStyles.d.ts +0 -0
  111. /package/dist/{util → hooks}/useOutsideAlerter.d.ts +0 -0
@@ -1,192 +1,335 @@
1
+ "use client";
2
+ import * as PopoverPrimitive from "@radix-ui/react-popover";
1
3
  import * as React from "react";
2
- import { useEffect } from "react";
3
- import * as Dialog from "@radix-ui/react-dialog";
4
-
4
+ import { ChangeEvent, Children, useEffect } from "react";
5
5
  import { Command as CommandPrimitive } from "cmdk";
6
-
7
- import { ExpandMoreIcon } from "../icons";
8
- import { fieldBackgroundDisabledMixin, fieldBackgroundHoverMixin, fieldBackgroundMixin, focusedMixin } from "../styles";
9
- import { cn, useOutsideAlerter } from "../util";
6
+ import { cls } from "../util";
7
+ import { CloseIcon, ExpandMoreIcon, Icon } from "../icons";
8
+ import { Separator } from "./Separator";
9
+ import { Chip } from "./Chip";
10
10
  import { SelectInputLabel } from "./common/SelectInputLabel";
11
+ import {
12
+ defaultBorderMixin,
13
+ fieldBackgroundDisabledMixin,
14
+ fieldBackgroundHoverMixin,
15
+ fieldBackgroundInvisibleMixin,
16
+ fieldBackgroundMixin,
17
+ focusedDisabled
18
+ } from "../styles";
19
+ import { useInjectStyles } from "../hooks";
20
+
21
+ interface MultiSelectContextProps {
22
+ fieldValue?: string[];
23
+ onItemClick: (v: string) => void;
24
+ }
25
+
26
+ export const MultiSelectContext = React.createContext<MultiSelectContextProps>({} as any);
27
+
28
+ /**
29
+ * Props for MultiSelect component
30
+ */
31
+ interface MultiSelectProps {
32
+
33
+ /**
34
+ * The modality of the popover. When set to true, interaction with outside elements
35
+ * will be disabled and only popover content will be visible to screen readers.
36
+ * Optional, defaults to false.
37
+ */
38
+ modalPopover?: boolean;
39
+
40
+ /**
41
+ * Additional class names to apply custom styles to the multi-select component.
42
+ * Optional, can be used to add custom styles.
43
+ */
44
+ className?: string;
11
45
 
12
- export type MultiSelectProps = {
13
46
  open?: boolean,
14
47
  name?: string,
15
48
  id?: string,
16
49
  onOpenChange?: (open: boolean) => void,
17
50
  value?: string[],
18
- containerClassName?: string,
19
- className?: string,
20
51
  inputClassName?: string,
21
- onMultiValueChange?: (updatedValue: string[]) => void,
52
+ onChange?: React.EventHandler<ChangeEvent<HTMLSelectElement>>,
53
+ onValueChange?: (updatedValue: string[]) => void,
22
54
  placeholder?: React.ReactNode,
23
- renderValue?: (values: string, index:number) => React.ReactNode,
24
- renderValues?: (values: string[]) => React.ReactNode,
25
55
  size?: "small" | "medium",
26
- label?: React.ReactNode,
56
+ useChips?: boolean,
57
+ label?: React.ReactNode | string,
27
58
  disabled?: boolean,
28
59
  error?: boolean,
29
60
  position?: "item-aligned" | "popper",
30
61
  endAdornment?: React.ReactNode,
62
+ multiple?: boolean,
63
+ includeSelectAll?: boolean,
64
+ includeClear?: boolean,
31
65
  inputRef?: React.RefObject<HTMLButtonElement>,
32
66
  padding?: boolean,
33
- includeFocusOutline?: boolean,
34
- children?: React.ReactNode,
35
- };
36
-
37
- interface MultiSelectContextProps {
38
- fieldValue?: string[];
39
- setInputValue: (v: string) => void;
40
- onValueChangeInternal: (v: string) => void;
67
+ invisible?: boolean,
68
+ children: React.ReactNode;
69
+ renderValues?: (values: string[]) => React.ReactNode;
41
70
  }
42
71
 
43
- export const MultiSelectContext = React.createContext<MultiSelectContextProps>({} as any);
72
+ export const MultiSelect = React.forwardRef<
73
+ HTMLButtonElement,
74
+ MultiSelectProps
75
+ >(
76
+ (
77
+ {
78
+ value,
79
+ size,
80
+ label,
81
+ error,
82
+ onValueChange,
83
+ invisible,
84
+ disabled,
85
+ placeholder,
86
+ modalPopover = true,
87
+ includeClear = true,
88
+ includeSelectAll = true,
89
+ useChips = true,
90
+ className,
91
+ children,
92
+ renderValues,
93
+ open,
94
+ onOpenChange,
95
+ },
96
+ ref
97
+ ) => {
98
+ const [isPopoverOpen, setIsPopoverOpen] = React.useState(open ?? false);
99
+ const [selectedValues, setSelectedValues] = React.useState<string[]>(value ?? []);
44
100
 
45
- export function MultiSelect({
46
- value,
47
- open,
48
- onMultiValueChange,
49
- size = "medium",
50
- label,
51
- disabled,
52
- renderValue,
53
- renderValues,
54
- includeFocusOutline = true,
55
- containerClassName,
56
- className,
57
- children,
58
- error
59
- }: MultiSelectProps) {
60
-
61
- const containerRef = React.useRef<HTMLInputElement>(null);
62
- const inputRef = React.useRef<HTMLInputElement>(null);
63
- const listRef = React.useRef<HTMLDivElement>(null);
64
- useOutsideAlerter(listRef, () => setOpenInternal(false));
65
-
66
- const [openInternal, setOpenInternal] = React.useState(false);
67
- useEffect(() => {
68
- setOpenInternal(open ?? false);
69
- }, [open]);
70
-
71
- const onValueChangeInternal = React.useCallback((newValue: string) => {
72
- if (Array.isArray(value) && value.includes(newValue)) {
73
- onMultiValueChange?.(value.filter(v => v !== newValue));
74
- } else {
75
- onMultiValueChange?.([...(value ?? []), newValue]);
101
+ const onPopoverOpenChange = (open: boolean) => {
102
+ setIsPopoverOpen(open);
103
+ onOpenChange?.(open);
76
104
  }
77
- }, [value, onMultiValueChange]);
78
-
79
- const [inputValue, setInputValue] = React.useState("");
80
- const [boundingRect, setBoundingRect] = React.useState<DOMRect | null>(null);
81
-
82
- const handleKeyDown = React.useCallback((e: React.KeyboardEvent<HTMLDivElement>) => {
83
- const input = inputRef.current
84
- if (input) {
85
- if (e.key === "Delete" || e.key === "Backspace") {
86
- if (input.value === "") {
87
- const newSelected = [...(value ?? [])];
88
- newSelected.pop();
89
- onMultiValueChange?.(newSelected);
105
+
106
+ useEffect(() => {
107
+ setIsPopoverOpen(open ?? false);
108
+ }, [open]);
109
+
110
+ const allValues = children
111
+ ?
112
+ // @ts-ignore
113
+ Children.map(children, (child) => {
114
+ if (React.isValidElement(child)) {
115
+ return child.props.value;
90
116
  }
117
+ return null;
118
+ }).filter(Boolean) as string[]
119
+ : [];
120
+
121
+ React.useEffect(() => {
122
+ setSelectedValues(value ?? []);
123
+ }, [value]);
124
+
125
+ function onItemClick(newValue: string) {
126
+ let newSelectedValues: string[];
127
+ if (selectedValues.includes(newValue)) {
128
+ newSelectedValues = selectedValues.filter((v) => v !== newValue);
129
+ } else {
130
+ newSelectedValues = [...selectedValues, newValue];
91
131
  }
92
- // This is not a default behaviour of the <input /> field
93
- if (e.key === "Escape") {
94
- input.blur();
95
- setOpenInternal(false);
96
- e.stopPropagation();
97
- }
132
+ updateValues(newSelectedValues);
98
133
  }
99
- }, [onMultiValueChange, value]);
100
-
101
- const openDialog = React.useCallback(() => {
102
- setBoundingRect(containerRef.current?.getBoundingClientRect() ?? null);
103
- setOpenInternal(true);
104
- }, []);
105
-
106
- const usedBoundingRect = boundingRect ?? containerRef.current?.getBoundingClientRect();
107
- const maxHeight = window.innerHeight - (usedBoundingRect?.top ?? 0) - (usedBoundingRect?.height ?? 0) - 16;
108
-
109
- return (<>
110
-
111
- {typeof label === "string" ? <SelectInputLabel error={error}>{label}</SelectInputLabel> : label}
112
-
113
- <CommandPrimitive onKeyDown={handleKeyDown}
114
- onClick={() => {
115
- inputRef.current?.focus();
116
- openDialog()
117
- }}
118
- className={cn("relative overflow-visible bg-transparent", containerClassName)}>
119
- <div
120
- ref={containerRef}
121
- className={cn(
122
- "flex flex-row",
123
- size === "small" ? "min-h-[42px]" : "min-h-[64px]",
124
- "select-none rounded-md text-sm",
125
- fieldBackgroundMixin,
126
- disabled ? fieldBackgroundDisabledMixin : fieldBackgroundHoverMixin,
127
- "relative flex items-center",
128
- "p-4",
129
- error ? "text-red-500 dark:text-red-600" : "focus:text-text-primary dark:focus:text-text-primary-dark",
130
- error ? "border border-red-500 dark:border-red-600" : "",
131
- includeFocusOutline ? focusedMixin : "",
132
- className)}
134
+
135
+ function updateValues(values: string[]) {
136
+ setSelectedValues(values);
137
+ onValueChange?.(values);
138
+ }
139
+
140
+ const handleInputKeyDown = (
141
+ event: React.KeyboardEvent<HTMLInputElement>
142
+ ) => {
143
+ if (event.key === "Enter") {
144
+ onPopoverOpenChange(true);
145
+ } else if (event.key === "Backspace" && !event.currentTarget.value) {
146
+ const newSelectedValues = [...selectedValues];
147
+ newSelectedValues.pop();
148
+ updateValues(newSelectedValues);
149
+ }
150
+ };
151
+
152
+ const toggleOption = (value: string) => {
153
+ const newSelectedValues = selectedValues.includes(value)
154
+ ? selectedValues.filter((v) => v !== value)
155
+ : [...selectedValues, value];
156
+ updateValues(newSelectedValues);
157
+ };
158
+
159
+ const handleClear = () => {
160
+ updateValues([]);
161
+ };
162
+
163
+ const handleTogglePopover = () => {
164
+ onPopoverOpenChange(!isPopoverOpen);
165
+ };
166
+
167
+ const toggleAll = () => {
168
+ if (selectedValues.length === allValues.length) {
169
+ handleClear();
170
+ } else {
171
+ updateValues(allValues);
172
+ }
173
+ onPopoverOpenChange(false);
174
+ };
175
+
176
+ useInjectStyles("MultiSelect", `
177
+ [cmdk-group] {
178
+ max-height: 45vh;
179
+ overflow-y: auto;
180
+ // width: 400px;
181
+ } `)
182
+
183
+ return (
184
+ <MultiSelectContext.Provider
185
+ value={{
186
+ fieldValue: selectedValues,
187
+ onItemClick
188
+ }}>
189
+
190
+ {typeof label === "string" ? <SelectInputLabel error={error}>{label}</SelectInputLabel> : label}
191
+
192
+ <PopoverPrimitive.Root
193
+ open={isPopoverOpen}
194
+ onOpenChange={onPopoverOpenChange}
195
+ modal={modalPopover}
133
196
  >
134
- <div className={cn("flex-grow flex gap-1.5 flex-wrap items-center")}>
135
- {renderValue && (value ?? []).map((v, i) => renderValue(v, i))}
136
- {renderValues && renderValues(value ?? [])}
137
- <CommandPrimitive.Input
138
- ref={inputRef}
139
- value={inputValue}
140
- onValueChange={setInputValue}
141
- // onBlur={() => setOpenInternal(false)}
142
- onFocus={openDialog}
143
- className="ml-2 bg-transparent outline-none flex-1 h-full w-full "
144
- />
145
- </div>
146
- <div className={"px-2 h-full flex items-center"}>
147
- <ExpandMoreIcon size={"small"}
148
- className={cn("transition ", openInternal ? "rotate-180" : "")}/>
149
- </div>
150
-
151
- </div>
152
-
153
- <Dialog.Root open={openInternal} onOpenChange={setOpenInternal}>
154
- <Dialog.Portal>
155
- <MultiSelectContext.Provider
156
- value={{
157
- fieldValue: value,
158
- setInputValue,
159
- onValueChangeInternal
160
- }}>
161
- <div
162
- ref={listRef}
163
- className={"z-50 absolute overflow-auto outline-none"}
164
- style={{
165
- pointerEvents: openInternal ? "auto" : "none",
166
- top: (usedBoundingRect?.top ?? 0) + (usedBoundingRect?.height ?? 0),
167
- left: usedBoundingRect?.left,
168
- // right: boundingRect?.right,
169
- width: usedBoundingRect?.width,
170
- maxHeight: maxHeight,
171
-
172
- }}>
173
-
174
- <CommandPrimitive.Group
175
- className="mt-2 text-slate-900 dark:text-white animate-in z-50 border border-slate-200 dark:border-slate-800 bg-white dark:bg-slate-800 p-2 rounded-lg shadow-lg flex flex-col outline-none w-full"
176
- >
197
+ <PopoverPrimitive.Trigger asChild>
198
+ <button
199
+ ref={ref}
200
+ onClick={handleTogglePopover}
201
+ className={cls(
202
+ size === "small" ? "min-h-[42px]" : "min-h-[64px]",
203
+ "py-2",
204
+ "px-4",
205
+ "select-none rounded-md text-sm",
206
+ invisible ? fieldBackgroundInvisibleMixin : fieldBackgroundMixin,
207
+ disabled ? fieldBackgroundDisabledMixin : fieldBackgroundHoverMixin,
208
+ "relative flex items-center",
209
+ className
210
+ )}
211
+ >
212
+ {selectedValues.length > 0 ? (
213
+ <div className="flex justify-between items-center w-full">
214
+ <div className="flex flex-wrap items-center gap-1.5 text-start">
215
+ {renderValues && renderValues(selectedValues)}
216
+ {!renderValues && selectedValues.map((value) => {
217
+
218
+ // @ts-ignore
219
+ const childrenProps: MultiSelectItemProps[] = Children.map(children, (child) => {
220
+ if (React.isValidElement(child)) {
221
+ return child.props;
222
+ }
223
+ }).filter(Boolean);
177
224
 
225
+ const option = childrenProps.find((o) => o.value === value);
226
+ if (!useChips) {
227
+ return option?.children;
228
+ }
229
+ return (
230
+ <Chip
231
+ size={"medium"}
232
+ key={value}
233
+ className={cls("flex flex-row items-center p-1")}
234
+ >
235
+ {option?.children}
236
+ <CloseIcon
237
+ size={"smallest"}
238
+ onClick={(event) => {
239
+ event.stopPropagation();
240
+ toggleOption(value);
241
+ }}
242
+ />
243
+ </Chip>
244
+ );
245
+ })}
246
+ </div>
247
+ <div className="flex items-center justify-between">
248
+ {includeClear && <CloseIcon
249
+ className={"ml-4"}
250
+ size={"small"}
251
+ onClick={(event) => {
252
+ event.stopPropagation();
253
+ handleClear();
254
+ }}
255
+ />}
256
+ <div className={cls("px-2 h-full flex items-center")}>
257
+ <ExpandMoreIcon size={"small"}
258
+ className={cls("transition", isPopoverOpen ? "rotate-180" : "")}/>
259
+ </div>
260
+ </div>
261
+ </div>
262
+ ) : (
263
+ <div className="flex items-center justify-between w-full mx-auto">
264
+ <span className="text-sm">
265
+ {placeholder}
266
+ </span>
267
+ <div className={cls("px-2 h-full flex items-center")}>
268
+ <ExpandMoreIcon size={"small"}
269
+ className={cls("transition", isPopoverOpen ? "rotate-180" : "")}/>
270
+ </div>
271
+ </div>
272
+ )}
273
+ </button>
274
+ </PopoverPrimitive.Trigger>
275
+ <PopoverPrimitive.Content
276
+ className={cls("z-50 relative overflow-hidden border bg-white dark:bg-surface-900 rounded-lg w-[400px]", defaultBorderMixin)}
277
+ align="start"
278
+ sideOffset={8}
279
+ onEscapeKeyDown={() => onPopoverOpenChange(false)}
280
+ >
281
+ <CommandPrimitive>
282
+ <div className={"flex flex-row items-center"}>
283
+ <CommandPrimitive.Input
284
+ className={cls(focusedDisabled, "bg-transparent outline-none flex-1 h-full w-full m-4 flex-grow ")}
285
+ placeholder="Search..."
286
+ onKeyDown={handleInputKeyDown}
287
+ />
288
+ {selectedValues.length > 0 && (
289
+ <div
290
+ onClick={handleClear}
291
+ className="text-sm justify-center cursor-pointer py-3 px-4 text-text-secondary dark:text-text-secondary-dark">
292
+ Clear
293
+ </div>
294
+ )}
295
+ </div>
296
+ <Separator orientation={"horizontal"} className={"my-0"}/>
297
+ <CommandPrimitive.List>
298
+ <CommandPrimitive.Empty className={"px-4 py-2"}>
299
+ No results found.
300
+ </CommandPrimitive.Empty>
301
+ <CommandPrimitive.Group>
302
+ {includeSelectAll && <CommandPrimitive.Item
303
+ key="all"
304
+ onSelect={toggleAll}
305
+ className={
306
+ cls(
307
+ "flex flex-row items-center gap-1.5",
308
+ "cursor-pointer",
309
+ "m-1",
310
+ "ring-offset-transparent",
311
+ "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",
312
+ "aria-[selected=true]:bg-surface-accent-100 aria-[selected=true]:dark:bg-surface-accent-900",
313
+ "cursor-pointer p-2 rounded aria-[selected=true]:bg-surface-accent-100 aria-[selected=true]:dark:bg-surface-accent-900"
314
+ )
315
+ }
316
+ >
317
+ <InnerCheckBox checked={selectedValues.length === allValues.length}/>
318
+ <span className={"text-sm text-text-secondary dark:text-text-secondary-dark"}>(Select All)</span>
319
+ </CommandPrimitive.Item>}
178
320
  {children}
179
321
  </CommandPrimitive.Group>
180
322
 
181
- </div>
182
- </MultiSelectContext.Provider>
183
- </Dialog.Portal>
184
- </Dialog.Root>
185
- </CommandPrimitive>
323
+ </CommandPrimitive.List>
324
+ </CommandPrimitive>
325
+ </PopoverPrimitive.Content>
326
+ </PopoverPrimitive.Root>
327
+ </MultiSelectContext.Provider>
328
+ );
329
+ }
330
+ );
186
331
 
187
- </>
188
- )
189
- }
332
+ MultiSelect.displayName = "MultiSelect";
190
333
 
191
334
  export interface MultiSelectItemProps {
192
335
  value: string;
@@ -194,32 +337,63 @@ export interface MultiSelectItemProps {
194
337
  className?: string;
195
338
  }
196
339
 
197
- export function MultiSelectItem({ children, value, className }: MultiSelectItemProps) {
340
+ export function MultiSelectItem({
341
+ children,
342
+ value,
343
+ className
344
+ }: MultiSelectItemProps) {
198
345
 
199
346
  const context = React.useContext(MultiSelectContext);
200
347
  if (!context) throw new Error("MultiSelectItem must be used inside a MultiSelect");
201
- const { fieldValue, setInputValue, onValueChangeInternal } = context;
348
+ const {
349
+ fieldValue,
350
+ onItemClick
351
+ } = context;
202
352
 
353
+ const isSelected = (fieldValue ?? []).includes(value);
203
354
  return <CommandPrimitive.Item
355
+ // value={value}
204
356
  onMouseDown={(e) => {
205
357
  e.preventDefault();
206
358
  e.stopPropagation();
207
359
  }}
208
360
  onSelect={(_) => {
209
- setInputValue("");
210
- onValueChangeInternal(value);
361
+ onItemClick(value);
211
362
  }}
212
- className={cn(
213
- (fieldValue ?? []).includes(value) ? "bg-slate-200 dark:bg-slate-950" : "",
363
+ className={cls(
364
+ "flex flex-row items-center gap-1.5",
365
+ isSelected ? "bg-surface-accent-200 dark:bg-surface-accent-950" : "",
214
366
  "cursor-pointer",
215
367
  "m-1",
216
368
  "ring-offset-transparent",
217
- "p-2 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",
218
- "aria-[selected=true]:bg-slate-100 aria-[selected=true]:dark:bg-slate-900",
219
- "cursor-pointer p-2 rounded aria-[selected=true]:bg-slate-100 aria-[selected=true]:dark:bg-slate-900",
369
+ "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",
370
+ "aria-[selected=true]:bg-surface-accent-100 aria-[selected=true]:dark:bg-surface-accent-900",
371
+ "cursor-pointer p-2 rounded aria-[selected=true]:bg-surface-accent-100 aria-[selected=true]:dark:bg-surface-accent-900",
220
372
  className
221
373
  )}
222
374
  >
375
+ <InnerCheckBox checked={isSelected}/>
223
376
  {children}
224
377
  </CommandPrimitive.Item>;
378
+
225
379
  }
380
+
381
+ function InnerCheckBox({ checked }: { checked: boolean }) {
382
+ return <div className={cls(
383
+ "p-2",
384
+ "w-8 h-8",
385
+ "inline-flex items-center justify-center text-sm font-medium focus:outline-none transition-colors ease-in-out duration-150",
386
+ )}>
387
+ <div
388
+ className={cls(
389
+ "border-2 relative transition-colors ease-in-out duration-150",
390
+ "w-4 h-4 rounded flex items-center justify-center",
391
+ (checked ? "bg-primary" : "bg-white dark:bg-surface-accent-900"),
392
+ (checked) ? "text-surface-accent-100 dark:text-surface-accent-900" : "",
393
+ (checked ? "border-transparent" : "border-surface-accent-800 dark:border-surface-accent-200")
394
+ )}>
395
+ {checked && <Icon iconKey={"check"} size={16} className={"absolute"}/>}
396
+ </div>
397
+ </div>
398
+ }
399
+
@@ -1,7 +1,7 @@
1
1
  import React from "react";
2
2
 
3
3
  import { paperMixin } from "../styles";
4
- import { cn } from "../util";
4
+ import { cls } from "../util";
5
5
 
6
6
  export function Paper({
7
7
  children,
@@ -15,7 +15,7 @@ export function Paper({
15
15
  }) {
16
16
  return (
17
17
  <div
18
- className={cn(paperMixin, className)}
18
+ className={cls(paperMixin, className)}
19
19
  style={style}>
20
20
  {children}
21
21
  </div>
@@ -1,8 +1,10 @@
1
+ "use client";
1
2
  import React from "react";
2
3
  import * as PopoverPrimitive from "@radix-ui/react-popover";
3
4
 
4
5
  import { paperMixin } from "../styles";
5
- import { cn, useInjectStyles } from "../util";
6
+ import { cls } from "../util";
7
+ import { useInjectStyles } from "../hooks";
6
8
 
7
9
  export type PopoverSide = "top" | "right" | "bottom" | "left";
8
10
  export type PopoverAlign = "start" | "center" | "end";
@@ -23,6 +25,7 @@ export interface PopoverProps {
23
25
  enabled?: boolean;
24
26
  modal?: boolean;
25
27
  className?: string;
28
+ portalContainer?: HTMLElement | null;
26
29
  }
27
30
 
28
31
  export function Popover({
@@ -40,6 +43,7 @@ export function Popover({
40
43
  avoidCollisions,
41
44
  enabled = true,
42
45
  modal = false,
46
+ portalContainer,
43
47
  className
44
48
  }: PopoverProps) {
45
49
 
@@ -50,25 +54,25 @@ export function Popover({
50
54
 
51
55
  return <PopoverPrimitive.Root open={open}
52
56
  onOpenChange={onOpenChange}
53
- modal={modal}
54
- >
57
+ modal={modal}>
55
58
  <PopoverPrimitive.Trigger asChild>
56
59
  {trigger}
57
60
  </PopoverPrimitive.Trigger>
58
- <PopoverPrimitive.Portal>
59
- <PopoverPrimitive.Content className={cn(paperMixin,
60
- "PopoverContent shadow z-40", className)}
61
- side={side}
62
- sideOffset={sideOffset}
63
- align={align}
64
- alignOffset={alignOffset}
65
- arrowPadding={arrowPadding}
66
- sticky={sticky}
67
- hideWhenDetached={hideWhenDetached}
68
- avoidCollisions={avoidCollisions}>
61
+ <PopoverPrimitive.Portal container={portalContainer}>
62
+ <PopoverPrimitive.Content
63
+ className={cls(paperMixin,
64
+ "PopoverContent z-40", className)}
65
+ side={side}
66
+ sideOffset={sideOffset}
67
+ align={align}
68
+ alignOffset={alignOffset}
69
+ arrowPadding={arrowPadding}
70
+ sticky={sticky}
71
+ hideWhenDetached={hideWhenDetached}
72
+ avoidCollisions={avoidCollisions}>
69
73
 
70
74
  {children}
71
- <PopoverPrimitive.Arrow className="fill-white dark:fill-slate-950"/>
75
+ <PopoverPrimitive.Arrow className="fill-white dark:fill-surface-accent-950"/>
72
76
  </PopoverPrimitive.Content>
73
77
  </PopoverPrimitive.Portal>
74
78
  </PopoverPrimitive.Root>;