@firecms/ui 3.0.0-canary.12 → 3.0.0-canary.121

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 +1 -1
  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 +3 -4
  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 +5 -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 +13176 -13540
  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 +11 -13
  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 +38 -48
  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 +336 -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 +7 -8
  71. package/src/components/Select.tsx +105 -124
  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 -32
  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 +27 -13
  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,332 @@
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 ?? []);
97
+ console.log("selectedValues", selectedValues);
44
98
 
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]);
99
+ const onPopoverOpenChange = (open: boolean) => {
100
+ setIsPopoverOpen(open);
101
+ onOpenChange?.(open);
76
102
  }
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);
103
+
104
+ useEffect(() => {
105
+ setIsPopoverOpen(open ?? false);
106
+ }, [open]);
107
+
108
+ const allValues = children
109
+ ?
110
+ // @ts-ignore
111
+ Children.map(children, (child) => {
112
+ if (React.isValidElement(child)) {
113
+ return child.props.value;
90
114
  }
115
+ return null;
116
+ }).filter(Boolean) as string[]
117
+ : [];
118
+
119
+ React.useEffect(() => {
120
+ setSelectedValues(value ?? []);
121
+ }, [value]);
122
+
123
+ function onItemClick(newValue: string) {
124
+ let newSelectedValues: string[];
125
+ if (selectedValues.includes(newValue)) {
126
+ newSelectedValues = selectedValues.filter((v) => v !== newValue);
127
+ } else {
128
+ newSelectedValues = [...selectedValues, newValue];
91
129
  }
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
- }
130
+ updateValues(newSelectedValues);
98
131
  }
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)}
132
+
133
+ function updateValues(values: string[]) {
134
+ setSelectedValues(values);
135
+ onValueChange?.(values);
136
+ }
137
+
138
+ const handleInputKeyDown = (
139
+ event: React.KeyboardEvent<HTMLInputElement>
140
+ ) => {
141
+ if (event.key === "Enter") {
142
+ onPopoverOpenChange(true);
143
+ } else if (event.key === "Backspace" && !event.currentTarget.value) {
144
+ const newSelectedValues = [...selectedValues];
145
+ newSelectedValues.pop();
146
+ updateValues(newSelectedValues);
147
+ }
148
+ };
149
+
150
+ const toggleOption = (value: string) => {
151
+ const newSelectedValues = selectedValues.includes(value)
152
+ ? selectedValues.filter((v) => v !== value)
153
+ : [...selectedValues, value];
154
+ updateValues(newSelectedValues);
155
+ };
156
+
157
+ const handleClear = () => {
158
+ updateValues([]);
159
+ };
160
+
161
+ const handleTogglePopover = () => {
162
+ onPopoverOpenChange(!isPopoverOpen);
163
+ };
164
+
165
+ const toggleAll = () => {
166
+ if (selectedValues.length === allValues.length) {
167
+ handleClear();
168
+ } else {
169
+ updateValues(allValues);
170
+ }
171
+ };
172
+
173
+ useInjectStyles("MultiSelect", `
174
+ [cmdk-group] {
175
+ max-height: 45vh;
176
+ overflow-y: auto;
177
+ // width: 400px;
178
+ } `)
179
+
180
+ return (
181
+ <MultiSelectContext.Provider
182
+ value={{
183
+ fieldValue: selectedValues,
184
+ onItemClick
185
+ }}>
186
+
187
+ {typeof label === "string" ? <SelectInputLabel error={error}>{label}</SelectInputLabel> : label}
188
+
189
+ <PopoverPrimitive.Root
190
+ open={isPopoverOpen}
191
+ onOpenChange={onPopoverOpenChange}
192
+ modal={modalPopover}
133
193
  >
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
- >
194
+ <PopoverPrimitive.Trigger asChild>
195
+ <button
196
+ ref={ref}
197
+ onClick={handleTogglePopover}
198
+ className={cls(
199
+ size === "small" ? "min-h-[42px]" : "min-h-[64px]",
200
+ "py-2",
201
+ "px-4",
202
+ "select-none rounded-md text-sm",
203
+ invisible ? fieldBackgroundInvisibleMixin : fieldBackgroundMixin,
204
+ disabled ? fieldBackgroundDisabledMixin : fieldBackgroundHoverMixin,
205
+ "relative flex items-center",
206
+ className
207
+ )}
208
+ >
209
+ {selectedValues.length > 0 ? (
210
+ <div className="flex justify-between items-center w-full">
211
+ <div className="flex flex-wrap items-center gap-1.5 text-start">
212
+ {renderValues && renderValues(selectedValues)}
213
+ {!renderValues && selectedValues.map((value) => {
214
+
215
+ // @ts-ignore
216
+ const childrenProps: MultiSelectItemProps[] = Children.map(children, (child) => {
217
+ if (React.isValidElement(child)) {
218
+ return child.props;
219
+ }
220
+ }).filter(Boolean);
177
221
 
222
+ const option = childrenProps.find((o) => o.value === value);
223
+ if (!useChips) {
224
+ return option?.children;
225
+ }
226
+ return (
227
+ <Chip
228
+ size={"small"}
229
+ key={value}
230
+ className={cls("flex flex-row items-center p-1")}
231
+ >
232
+ {option?.children}
233
+ <CloseIcon
234
+ size={"smallest"}
235
+ onClick={(event) => {
236
+ event.stopPropagation();
237
+ toggleOption(value);
238
+ }}
239
+ />
240
+ </Chip>
241
+ );
242
+ })}
243
+ </div>
244
+ <div className="flex items-center justify-between">
245
+ {includeClear && <CloseIcon
246
+ className={"ml-4"}
247
+ size={"small"}
248
+ onClick={(event) => {
249
+ event.stopPropagation();
250
+ handleClear();
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">
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 rounded-lg w-[400px]", defaultBorderMixin)}
274
+ align="start"
275
+ sideOffset={8}
276
+ onEscapeKeyDown={() => onPopoverOpenChange(false)}
277
+ >
278
+ <CommandPrimitive>
279
+ <div className={"flex flex-row items-center"}>
280
+ <CommandPrimitive.Input
281
+ className={cls(focusedDisabled, "bg-transparent outline-none flex-1 h-full w-full m-4 flex-grow")}
282
+ placeholder="Search..."
283
+ onKeyDown={handleInputKeyDown}
284
+ />
285
+ {selectedValues.length > 0 && (
286
+ <div
287
+ onClick={handleClear}
288
+ className="text-sm justify-center cursor-pointer py-3 px-4 text-text-secondary dark:text-text-secondary-dark">
289
+ Clear
290
+ </div>
291
+ )}
292
+ </div>
293
+ <Separator orientation={"horizontal"} className={"my-0"}/>
294
+ <CommandPrimitive.List>
295
+ <CommandPrimitive.Empty className={"px-4 py-2"}>
296
+ No results found.
297
+ </CommandPrimitive.Empty>
298
+ <CommandPrimitive.Group>
299
+ <CommandPrimitive.Item
300
+ key="all"
301
+ onSelect={toggleAll}
302
+ className={
303
+ cls(
304
+ "flex flex-row items-center gap-1.5",
305
+ "cursor-pointer",
306
+ "m-1",
307
+ "ring-offset-transparent",
308
+ "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",
309
+ "aria-[selected=true]:bg-slate-100 aria-[selected=true]:dark:bg-slate-900",
310
+ "cursor-pointer p-2 rounded aria-[selected=true]:bg-slate-100 aria-[selected=true]:dark:bg-slate-900"
311
+ )
312
+ }
313
+ >
314
+ <InnerCheckBox checked={selectedValues.length === allValues.length}/>
315
+ <span className={"text-sm text-text-secondary dark:text-text-secondary-dark"}>(Select All)</span>
316
+ </CommandPrimitive.Item>
178
317
  {children}
179
318
  </CommandPrimitive.Group>
180
319
 
181
- </div>
182
- </MultiSelectContext.Provider>
183
- </Dialog.Portal>
184
- </Dialog.Root>
185
- </CommandPrimitive>
320
+ </CommandPrimitive.List>
321
+ </CommandPrimitive>
322
+ </PopoverPrimitive.Content>
323
+ </PopoverPrimitive.Root>
324
+ </MultiSelectContext.Provider>
325
+ );
326
+ }
327
+ );
186
328
 
187
- </>
188
- )
189
- }
329
+ MultiSelect.displayName = "MultiSelect";
190
330
 
191
331
  export interface MultiSelectItemProps {
192
332
  value: string;
@@ -194,32 +334,63 @@ export interface MultiSelectItemProps {
194
334
  className?: string;
195
335
  }
196
336
 
197
- export function MultiSelectItem({ children, value, className }: MultiSelectItemProps) {
337
+ export function MultiSelectItem({
338
+ children,
339
+ value,
340
+ className
341
+ }: MultiSelectItemProps) {
198
342
 
199
343
  const context = React.useContext(MultiSelectContext);
200
344
  if (!context) throw new Error("MultiSelectItem must be used inside a MultiSelect");
201
- const { fieldValue, setInputValue, onValueChangeInternal } = context;
345
+ const {
346
+ fieldValue,
347
+ onItemClick
348
+ } = context;
202
349
 
350
+ const isSelected = (fieldValue ?? []).includes(value);
203
351
  return <CommandPrimitive.Item
352
+ // value={value}
204
353
  onMouseDown={(e) => {
205
354
  e.preventDefault();
206
355
  e.stopPropagation();
207
356
  }}
208
357
  onSelect={(_) => {
209
- setInputValue("");
210
- onValueChangeInternal(value);
358
+ onItemClick(value);
211
359
  }}
212
- className={cn(
213
- (fieldValue ?? []).includes(value) ? "bg-slate-200 dark:bg-slate-950" : "",
360
+ className={cls(
361
+ "flex flex-row items-center gap-1.5",
362
+ isSelected ? "bg-slate-200 dark:bg-slate-950" : "",
214
363
  "cursor-pointer",
215
364
  "m-1",
216
365
  "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",
366
+ "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
367
  "aria-[selected=true]:bg-slate-100 aria-[selected=true]:dark:bg-slate-900",
219
368
  "cursor-pointer p-2 rounded aria-[selected=true]:bg-slate-100 aria-[selected=true]:dark:bg-slate-900",
220
369
  className
221
370
  )}
222
371
  >
372
+ <InnerCheckBox checked={isSelected}/>
223
373
  {children}
224
374
  </CommandPrimitive.Item>;
375
+
225
376
  }
377
+
378
+ function InnerCheckBox({ checked }: { checked: boolean }) {
379
+ return <div className={cls(
380
+ "p-2",
381
+ "w-8 h-8",
382
+ "inline-flex items-center justify-center text-sm font-medium focus:outline-none transition-colors ease-in-out duration-150",
383
+ )}>
384
+ <div
385
+ className={cls(
386
+ "border-2 relative transition-colors ease-in-out duration-150",
387
+ "w-4 h-4 rounded flex items-center justify-center",
388
+ (checked ? "bg-primary" : "bg-white dark:bg-slate-900"),
389
+ (checked) ? "text-slate-100 dark:text-slate-900" : "",
390
+ (checked ? "border-transparent" : "border-slate-800 dark:border-slate-200")
391
+ )}>
392
+ {checked && <Icon iconKey={"check"} size={16} className={"absolute"}/>}
393
+ </div>
394
+ </div>
395
+ }
396
+
@@ -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"/>