@firecms/ui 3.0.0-canary.13 → 3.0.0-canary.130

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