@firecms/ui 3.0.1 → 3.1.0-canary.24c8270

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 (59) hide show
  1. package/README.md +9 -7
  2. package/dist/components/Card.d.ts +1 -1
  3. package/dist/components/ColorPicker.d.ts +30 -0
  4. package/dist/components/DateTimeField.d.ts +7 -0
  5. package/dist/components/Dialog.d.ts +2 -1
  6. package/dist/components/FileUpload.d.ts +1 -1
  7. package/dist/components/Menu.d.ts +2 -1
  8. package/dist/components/Menubar.d.ts +2 -1
  9. package/dist/components/MultiSelect.d.ts +2 -1
  10. package/dist/components/SearchBar.d.ts +11 -1
  11. package/dist/components/Select.d.ts +2 -1
  12. package/dist/components/Sheet.d.ts +1 -0
  13. package/dist/components/ToggleButtonGroup.d.ts +30 -0
  14. package/dist/components/index.d.ts +2 -0
  15. package/dist/hooks/PortalContainerContext.d.ts +31 -0
  16. package/dist/hooks/index.d.ts +1 -0
  17. package/dist/hooks/useOutsideAlerter.d.ts +1 -1
  18. package/dist/index.css +57 -6
  19. package/dist/index.es.js +1731 -949
  20. package/dist/index.es.js.map +1 -1
  21. package/dist/index.umd.js +1731 -949
  22. package/dist/index.umd.js.map +1 -1
  23. package/dist/styles.d.ts +11 -11
  24. package/package.json +7 -7
  25. package/src/components/BooleanSwitch.tsx +3 -3
  26. package/src/components/Button.tsx +5 -5
  27. package/src/components/Card.tsx +7 -7
  28. package/src/components/Checkbox.tsx +1 -1
  29. package/src/components/ColorPicker.tsx +134 -0
  30. package/src/components/DateTimeField.tsx +123 -34
  31. package/src/components/DebouncedTextField.tsx +3 -3
  32. package/src/components/Dialog.tsx +25 -16
  33. package/src/components/DialogActions.tsx +1 -1
  34. package/src/components/ExpandablePanel.tsx +1 -1
  35. package/src/components/FileUpload.tsx +25 -24
  36. package/src/components/IconButton.tsx +3 -2
  37. package/src/components/Menu.tsx +44 -30
  38. package/src/components/Menubar.tsx +14 -3
  39. package/src/components/MultiSelect.tsx +91 -74
  40. package/src/components/Popover.tsx +11 -3
  41. package/src/components/SearchBar.tsx +37 -19
  42. package/src/components/Select.tsx +86 -73
  43. package/src/components/Separator.tsx +2 -2
  44. package/src/components/Sheet.tsx +12 -3
  45. package/src/components/Slider.tsx +4 -4
  46. package/src/components/Table.tsx +1 -1
  47. package/src/components/Tabs.tsx +121 -36
  48. package/src/components/TextField.tsx +19 -8
  49. package/src/components/ToggleButtonGroup.tsx +67 -0
  50. package/src/components/Tooltip.tsx +9 -2
  51. package/src/components/index.tsx +2 -0
  52. package/src/hooks/PortalContainerContext.tsx +48 -0
  53. package/src/hooks/index.ts +1 -0
  54. package/src/hooks/useInjectStyles.tsx +12 -3
  55. package/src/hooks/useOutsideAlerter.tsx +1 -1
  56. package/src/index.css +57 -6
  57. package/src/styles.ts +11 -11
  58. package/src/util/cls.ts +1 -1
  59. package/tailwind.config.js +2 -3
@@ -28,22 +28,22 @@ export type FileUploadProps = {
28
28
  title?: React.ReactNode;
29
29
  uploadDescription?: React.ReactNode;
30
30
  preventDropOnDocument?: boolean;
31
- size?: "medium" | "large";
31
+ size?: "small" | "medium" | "large";
32
32
  };
33
33
 
34
34
  export function FileUpload({
35
- accept,
36
- onFilesAdded,
37
- onFilesRejected,
38
- maxSize,
39
- disabled,
40
- maxFiles,
41
- title,
42
- uploadDescription,
43
- children,
44
- preventDropOnDocument = true,
45
- size
46
- }: React.PropsWithChildren<FileUploadProps>) {
35
+ accept,
36
+ onFilesAdded,
37
+ onFilesRejected,
38
+ maxSize,
39
+ disabled,
40
+ maxFiles,
41
+ title,
42
+ uploadDescription,
43
+ children,
44
+ preventDropOnDocument = true,
45
+ size
46
+ }: React.PropsWithChildren<FileUploadProps>) {
47
47
 
48
48
  const {
49
49
  getRootProps,
@@ -52,15 +52,15 @@ export function FileUpload({
52
52
  isDragAccept,
53
53
  isDragReject
54
54
  } = useDropzone({
55
- accept,
56
- noDragEventsBubbling: true,
57
- maxSize,
58
- onDrop: onFilesAdded,
59
- onDropRejected: onFilesRejected,
60
- disabled,
61
- maxFiles,
62
- preventDropOnDocument
63
- }
55
+ accept,
56
+ noDragEventsBubbling: true,
57
+ maxSize,
58
+ onDrop: onFilesAdded,
59
+ onDropRejected: onFilesRejected,
60
+ disabled,
61
+ maxFiles,
62
+ preventDropOnDocument
63
+ }
64
64
  );
65
65
  return <div
66
66
  {...getRootProps()}
@@ -71,6 +71,7 @@ export function FileUpload({
71
71
  {
72
72
  "h-44": size === "large",
73
73
  "h-28": size === "medium",
74
+ "h-16": size === "small",
74
75
  "cursor-pointer": !disabled,
75
76
  [fieldBackgroundHoverMixin]: !isDragActive,
76
77
  "transition-colors duration-200 ease-[cubic-bezier(0,0,0.2,1)] border-red-500": isDragReject,
@@ -89,8 +90,8 @@ export function FileUpload({
89
90
  <div
90
91
  className="flex-grow h-28 box-border flex flex-col items-center justify-center text-center">
91
92
  <Typography align={"center"}
92
- variant={"label"}
93
- className={"flex flex-row gap-2 justify-center"}>
93
+ variant={"label"}
94
+ className={"flex flex-row gap-2 justify-center"}>
94
95
  {uploadDescription}
95
96
  </Typography>
96
97
  </div>
@@ -14,7 +14,7 @@ export type IconButtonProps<C extends React.ElementType> =
14
14
  onClick?: React.MouseEventHandler<any>
15
15
  }
16
16
 
17
- const buttonClasses = "hover:bg-surface-accent-200 hover:bg-opacity-75 dark:hover:bg-surface-accent-800 hover:scale-105 transition-transform";
17
+ const buttonClasses = "hover:bg-surface-accent-200 hover:bg-opacity-75 hover:bg-surface-accent-200/75 dark:hover:bg-surface-accent-800 hover:scale-105 transition-transform";
18
18
  const baseClasses = "inline-flex items-center justify-center p-2 text-sm font-medium focus:outline-none transition-colors ease-in-out duration-150";
19
19
  const colorClasses = "text-surface-accent-600 visited:text-surface-accent-600 dark:text-surface-accent-300 dark:visited:text-surface-300";
20
20
  const sizeClasses = {
@@ -40,7 +40,7 @@ const IconButtonInner = <C extends React.ElementType = "button">({
40
40
  ...props
41
41
  }: IconButtonProps<C>, ref: React.ForwardedRef<HTMLButtonElement>) => {
42
42
 
43
- const bgClasses = variant === "ghost" ? "bg-transparent" : "bg-surface-accent-200 bg-opacity-50 dark:bg-surface-950 dark:bg-opacity-50";
43
+ const bgClasses = variant === "ghost" ? "bg-transparent" : "bg-surface-accent-200 bg-opacity-50 bg-surface-accent-200/50 dark:bg-surface-950 dark:bg-opacity-50 dark:bg-surface-950/50";
44
44
  const Component: React.ElementType<any> = component || "button";
45
45
  return (
46
46
  <Component
@@ -50,6 +50,7 @@ const IconButtonInner = <C extends React.ElementType = "button">({
50
50
  className={cls(
51
51
  disabled ? "opacity-50 pointer-events-none" : "cursor-pointer",
52
52
  toggled ? "outline outline-2 outline-primary" : "",
53
+ "text-inherit dark:text-inherit",
53
54
  colorClasses,
54
55
  bgClasses,
55
56
  baseClasses,
@@ -3,6 +3,7 @@ import React from "react";
3
3
  import * as DropdownMenu from "@radix-ui/react-dropdown-menu";
4
4
  import { focusedDisabled, paperMixin } from "../styles";
5
5
  import { cls } from "../util";
6
+ import { usePortalContainer } from "../hooks/PortalContainerContext";
6
7
 
7
8
  export type MenuProps = {
8
9
  children: React.ReactNode;
@@ -33,28 +34,36 @@ const Menu = React.forwardRef<
33
34
  onOpenChange,
34
35
  portalContainer,
35
36
  sideOffset = 4,
36
- className
37
- }, ref) => (
38
- <DropdownMenu.Root
39
- open={open}
40
- defaultOpen={defaultOpen}
41
- onOpenChange={onOpenChange}>
42
- <DropdownMenu.Trigger
43
- ref={ref}
44
- asChild>
45
- {trigger}
46
- </DropdownMenu.Trigger>
47
- <DropdownMenu.Portal container={portalContainer}>
48
- <DropdownMenu.Content
49
- side={side}
50
- sideOffset={sideOffset}
51
- align={align}
52
- className={cls(paperMixin, focusedDisabled, "py-2 z-30", className)}>
53
- {children}
54
- </DropdownMenu.Content>
55
- </DropdownMenu.Portal>
56
- </DropdownMenu.Root>
57
- ))
37
+ className
38
+ }, ref) => {
39
+ // Get the portal container from context
40
+ const contextContainer = usePortalContainer();
41
+
42
+ // Prioritize manual prop, fallback to context container
43
+ const finalContainer = (portalContainer ?? contextContainer ?? undefined) as HTMLElement | undefined;
44
+
45
+ return (
46
+ <DropdownMenu.Root
47
+ open={open}
48
+ defaultOpen={defaultOpen}
49
+ onOpenChange={onOpenChange}>
50
+ <DropdownMenu.Trigger
51
+ ref={ref}
52
+ asChild>
53
+ {trigger}
54
+ </DropdownMenu.Trigger>
55
+ <DropdownMenu.Portal container={finalContainer}>
56
+ <DropdownMenu.Content
57
+ side={side}
58
+ sideOffset={sideOffset}
59
+ align={align}
60
+ className={cls(paperMixin, focusedDisabled, "py-2 z-30", className)}>
61
+ {children}
62
+ </DropdownMenu.Content>
63
+ </DropdownMenu.Portal>
64
+ </DropdownMenu.Root>
65
+ );
66
+ })
58
67
  Menu.displayName = "Menu"
59
68
 
60
69
  export { Menu }
@@ -62,20 +71,24 @@ export { Menu }
62
71
  export type MenuItemProps = {
63
72
  children: React.ReactNode;
64
73
  dense?: boolean;
74
+ disabled?: boolean;
65
75
  onClick?: (event: React.MouseEvent) => void;
66
76
  className?: string;
67
77
  };
68
78
 
69
79
  export const MenuItem = React.memo(({
70
- children,
71
- dense = false, // Default value is false if not provided
72
- onClick,
73
- className
74
- }: MenuItemProps) => {
80
+ children,
81
+ dense = false, // Default value is false if not provided
82
+ disabled = false,
83
+ onClick,
84
+ className
85
+ }: MenuItemProps) => {
75
86
  // Dynamically adjusting the class based on the "dense" prop
76
87
  const classNames = cls(
77
- onClick && "cursor-pointer",
78
- "rounded-md text-sm font-medium text-surface-accent-700 dark:text-surface-accent-300 hover:bg-surface-accent-100 dark:hover:bg-surface-accent-900 flex items-center gap-4",
88
+ onClick && !disabled && "cursor-pointer",
89
+ disabled && "opacity-50 cursor-not-allowed",
90
+ "rounded-md text-sm font-medium text-surface-accent-700 dark:text-surface-accent-300 flex items-center gap-4",
91
+ !disabled && "hover:bg-surface-accent-100 dark:hover:bg-surface-accent-900",
79
92
  dense ? "px-4 py-1.5" : "px-4 py-2",
80
93
  className
81
94
  );
@@ -83,7 +96,8 @@ export const MenuItem = React.memo(({
83
96
  return (
84
97
  <DropdownMenu.Item
85
98
  className={classNames}
86
- onClick={onClick}>
99
+ disabled={disabled}
100
+ onClick={disabled ? undefined : onClick}>
87
101
  {children}
88
102
  </DropdownMenu.Item>
89
103
  );
@@ -3,6 +3,7 @@ import * as React from "react";
3
3
  import * as MenubarPrimitive from "@radix-ui/react-menubar";
4
4
  import { cls } from "../util";
5
5
  import { CheckIcon, ChevronRightIcon } from "../icons";
6
+ import { usePortalContainer } from "../hooks/PortalContainerContext";
6
7
 
7
8
  export function Menubar({
8
9
  children,
@@ -44,7 +45,7 @@ export function MenubarTrigger({
44
45
  return (
45
46
  <MenubarPrimitive.Trigger
46
47
  onSelect={onSelect}
47
- className={cls("py-2 px-3 outline-none select-none font-medium leading-none rounded text-text-primary dark:text-text-primary-dark text-[13px] flex items-center justify-between gap-[2px] data-[highlighted]:bg-surface-accent-100 data-[highlighted]:dark:bg-surface-800 data-[state=open]:bg-surface-accent-100 data-[state=open]:dark:bg-surface-800 hover:bg-surface-accent-200 hover:bg-opacity-75 dark:hover:bg-surface-accent-800",
48
+ className={cls("py-2 px-3 outline-none select-none font-medium leading-none rounded text-text-primary dark:text-text-primary-dark text-[13px] flex items-center justify-between gap-[2px] data-[highlighted]:bg-surface-accent-100 data-[highlighted]:dark:bg-surface-800 data-[state=open]:bg-surface-accent-100 data-[state=open]:dark:bg-surface-800 hover:bg-surface-accent-200 hover:bg-opacity-75 hover:bg-surface-accent-200/75 dark:hover:bg-surface-accent-800",
48
49
  className)}>
49
50
  {children}
50
51
  </MenubarPrimitive.Trigger>
@@ -53,9 +54,19 @@ export function MenubarTrigger({
53
54
 
54
55
  export function MenubarPortal({
55
56
  children,
56
- }: { children: React.ReactNode }) {
57
+ portalContainer,
58
+ }: {
59
+ children: React.ReactNode;
60
+ portalContainer?: HTMLElement | null;
61
+ }) {
62
+ // Get the portal container from context
63
+ const contextContainer = usePortalContainer();
64
+
65
+ // Prioritize manual prop, fallback to context container
66
+ const finalContainer = (portalContainer ?? contextContainer ?? undefined) as HTMLElement | undefined;
67
+
57
68
  return (
58
- <MenubarPrimitive.Portal>
69
+ <MenubarPrimitive.Portal container={finalContainer}>
59
70
  {children}
60
71
  </MenubarPrimitive.Portal>
61
72
  )
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
  import * as PopoverPrimitive from "@radix-ui/react-popover";
3
3
  import * as React from "react";
4
- import { ChangeEvent, Children, useEffect } from "react";
4
+ import { ChangeEvent, Children, useEffect, useState } from "react";
5
5
  import { Command as CommandPrimitive } from "cmdk";
6
6
  import { cls } from "../util";
7
7
  import { CheckIcon, CloseIcon, KeyboardArrowDownIcon } from "../icons";
@@ -17,6 +17,7 @@ import {
17
17
  focusedDisabled
18
18
  } from "../styles";
19
19
  import { useInjectStyles } from "../hooks";
20
+ import { usePortalContainer } from "../hooks/PortalContainerContext";
20
21
 
21
22
  export type MultiSelectValue = string | number | boolean;
22
23
 
@@ -54,11 +55,12 @@ interface MultiSelectProps<T extends MultiSelectValue = string> {
54
55
  multiple?: boolean,
55
56
  includeSelectAll?: boolean,
56
57
  includeClear?: boolean,
57
- inputRef?: React.RefObject<HTMLButtonElement>,
58
+ inputRef?: React.RefObject<HTMLButtonElement | null>,
58
59
  padding?: boolean,
59
60
  invisible?: boolean,
60
61
  children: React.ReactNode;
61
62
  renderValues?: (values: T[]) => React.ReactNode;
63
+ portalContainer?: HTMLElement | null;
62
64
  }
63
65
 
64
66
  // Use generic type for the forwarded ref
@@ -81,16 +83,29 @@ export const MultiSelect = React.forwardRef<
81
83
  includeSelectAll = true,
82
84
  useChips = true,
83
85
  className,
86
+ inputClassName,
87
+ inputRef,
84
88
  children,
85
89
  renderValues,
86
90
  open,
87
91
  onOpenChange,
92
+ portalContainer,
88
93
  },
89
94
  ref
90
95
  ) => {
91
- // Properly type the state variables to match the generic props
92
- const [isPopoverOpen, setIsPopoverOpen] = React.useState(open ?? false);
93
- const [selectedValues, setSelectedValues] = React.useState<any[]>(value ?? []);
96
+ const [isMounted, setIsMounted] = useState(false);
97
+ const [isPopoverOpen, setIsPopoverOpen] = useState(open ?? false);
98
+ const [selectedValues, setSelectedValues] = useState<any[]>(value ?? []);
99
+
100
+ // Get the portal container from context
101
+ const contextContainer = usePortalContainer();
102
+
103
+ // Prioritize manual prop, fallback to context container
104
+ const finalContainer = (portalContainer ?? contextContainer ?? undefined) as HTMLElement | undefined;
105
+
106
+ useEffect(() => {
107
+ setIsMounted(true);
108
+ }, []);
94
109
 
95
110
  const onPopoverOpenChange = (open: boolean) => {
96
111
  setIsPopoverOpen(open);
@@ -103,20 +118,18 @@ export const MultiSelect = React.forwardRef<
103
118
 
104
119
  const allValues = React.useMemo(() => children
105
120
  ?
106
- // @ts-ignore
107
121
  Children.map(children, (child) => {
108
- if (React.isValidElement(child)) {
122
+ if (React.isValidElement<MultiSelectItemProps>(child)) {
109
123
  return child.props.value;
110
124
  }
111
125
  return null;
112
- }).filter(Boolean) as any[]
126
+ })?.filter(Boolean) ?? []
113
127
  : [], [children]);
114
128
 
115
129
  const optionsMap = React.useMemo(() => {
116
130
  const map = new Map<string, React.ReactNode>();
117
131
  Children.forEach(children, (child) => {
118
- if (React.isValidElement(child)) {
119
- // @ts-ignore
132
+ if (React.isValidElement<MultiSelectItemProps>(child)) {
120
133
  map.set(String(child.props.value), child.props.children);
121
134
  }
122
135
  });
@@ -195,13 +208,13 @@ export const MultiSelect = React.forwardRef<
195
208
  {typeof label === "string" ? <SelectInputLabel error={error}>{label}</SelectInputLabel> : label}
196
209
 
197
210
  <PopoverPrimitive.Root
198
- open={isPopoverOpen}
211
+ open={isMounted && isPopoverOpen}
199
212
  onOpenChange={onPopoverOpenChange}
200
213
  modal={modalPopover}
201
214
  >
202
215
  <PopoverPrimitive.Trigger asChild>
203
216
  <button
204
- ref={ref}
217
+ ref={inputRef ?? ref}
205
218
  onClick={handleTogglePopover}
206
219
  className={cls(
207
220
  {
@@ -219,10 +232,12 @@ export const MultiSelect = React.forwardRef<
219
232
  "px-4": size === "medium" || size === "large",
220
233
  },
221
234
  "select-none rounded-md text-sm",
235
+ "focus:ring-0 focus-visible:ring-0 outline-none focus:outline-none focus-visible:outline-none",
222
236
  invisible ? fieldBackgroundInvisibleMixin : fieldBackgroundMixin,
223
237
  disabled ? fieldBackgroundDisabledMixin : fieldBackgroundHoverMixin,
224
238
  "relative flex items-center",
225
- className
239
+ className,
240
+ inputClassName
226
241
  )}
227
242
  >
228
243
  {selectedValues.length > 0 ? (
@@ -264,73 +279,75 @@ export const MultiSelect = React.forwardRef<
264
279
  />}
265
280
  <div className={cls("px-2 h-full flex items-center")}>
266
281
  <KeyboardArrowDownIcon size={size === "large" ? "medium" : "small"}
267
- className={cls("transition", isPopoverOpen ? "rotate-180" : "")}/>
282
+ className={cls("transition", isPopoverOpen ? "rotate-180" : "")} />
268
283
  </div>
269
284
  </div>
270
285
  </div>
271
286
  ) : (
272
287
  <div className="flex items-center justify-between w-full mx-auto">
273
288
  <span className="text-sm">
274
- {placeholder}
289
+ {placeholder}
275
290
  </span>
276
291
  <div className={cls("px-2 h-full flex items-center")}>
277
292
  <KeyboardArrowDownIcon size={size === "large" ? "medium" : "small"}
278
- className={cls("transition", isPopoverOpen ? "rotate-180" : "")}/>
293
+ className={cls("transition", isPopoverOpen ? "rotate-180" : "")} />
279
294
  </div>
280
295
  </div>
281
296
  )}
282
297
  </button>
283
298
  </PopoverPrimitive.Trigger>
284
- <PopoverPrimitive.Content
285
- className={cls("z-50 relative overflow-hidden border bg-white dark:bg-surface-900 rounded-lg w-[400px]", defaultBorderMixin)}
286
- align="start"
287
- sideOffset={8}
288
- onEscapeKeyDown={() => onPopoverOpenChange(false)}
289
- >
290
- <CommandPrimitive>
291
- <div className={"flex flex-row items-center"}>
292
- <CommandPrimitive.Input
293
- className={cls(focusedDisabled, "bg-transparent outline-none flex-1 h-full w-full m-4 flex-grow ")}
294
- placeholder="Search..."
295
- onKeyDown={handleInputKeyDown}
296
- />
297
- {selectedValues.length > 0 && (
298
- <div
299
- onClick={handleClear}
300
- className="text-sm justify-center cursor-pointer py-3 px-4 text-text-secondary dark:text-text-secondary-dark">
301
- Clear
302
- </div>
303
- )}
304
- </div>
305
- <Separator orientation={"horizontal"} className={"my-0"}/>
306
- <CommandPrimitive.List>
307
- <CommandPrimitive.Empty className={"px-4 py-2"}>
308
- No results found.
309
- </CommandPrimitive.Empty>
310
- <CommandPrimitive.Group>
311
- {includeSelectAll && <CommandPrimitive.Item
312
- key="all"
313
- onSelect={toggleAll}
314
- className={
315
- cls(
316
- "flex flex-row items-center gap-1.5",
317
- "cursor-pointer",
318
- "m-1",
319
- "ring-offset-transparent",
320
- "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",
321
- "aria-[selected=true]:bg-surface-accent-100 aria-[selected=true]:dark:bg-surface-accent-900",
322
- "cursor-pointer p-2 rounded aria-[selected=true]:bg-surface-accent-100 aria-[selected=true]:dark:bg-surface-accent-900"
323
- )
324
- }
325
- >
326
- <InnerCheckBox checked={selectedValues.length === allValues.length}/>
327
- <span className={"text-sm text-text-secondary dark:text-text-secondary-dark"}>(Select All)</span>
328
- </CommandPrimitive.Item>}
329
- {children}
330
- </CommandPrimitive.Group>
331
- </CommandPrimitive.List>
332
- </CommandPrimitive>
333
- </PopoverPrimitive.Content>
299
+ <PopoverPrimitive.Portal container={finalContainer}>
300
+ <PopoverPrimitive.Content
301
+ className={cls("z-50 overflow-hidden border bg-white dark:bg-surface-900 rounded-lg w-[400px]", defaultBorderMixin)}
302
+ align="start"
303
+ sideOffset={8}
304
+ onEscapeKeyDown={() => onPopoverOpenChange(false)}
305
+ >
306
+ <CommandPrimitive>
307
+ <div className={"flex flex-row items-center"}>
308
+ <CommandPrimitive.Input
309
+ className={cls(focusedDisabled, "bg-transparent outline-none flex-1 h-full w-full m-4 flex-grow text-surface-accent-900 dark:text-white")}
310
+ placeholder="Search..."
311
+ onKeyDown={handleInputKeyDown}
312
+ />
313
+ {selectedValues.length > 0 && (
314
+ <div
315
+ onClick={handleClear}
316
+ className="text-sm justify-center cursor-pointer py-3 px-4 text-text-secondary dark:text-text-secondary-dark">
317
+ Clear
318
+ </div>
319
+ )}
320
+ </div>
321
+ <Separator orientation={"horizontal"} className={"my-0"} />
322
+ <CommandPrimitive.List>
323
+ <CommandPrimitive.Empty className={"px-4 py-2 text-sm text-text-secondary dark:text-text-secondary-dark"}>
324
+ No results found.
325
+ </CommandPrimitive.Empty>
326
+ <CommandPrimitive.Group>
327
+ {includeSelectAll && <CommandPrimitive.Item
328
+ key="all"
329
+ onSelect={toggleAll}
330
+ className={
331
+ cls(
332
+ "flex flex-row items-center gap-1.5",
333
+ "cursor-pointer",
334
+ "m-1",
335
+ "ring-offset-transparent",
336
+ "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-primary/75 aria-[selected=true]:ring-offset-2",
337
+ "aria-[selected=true]:bg-surface-accent-100 aria-[selected=true]:dark:bg-surface-accent-900",
338
+ "cursor-pointer p-2 rounded aria-[selected=true]:bg-surface-accent-100 aria-[selected=true]:dark:bg-surface-accent-900"
339
+ )
340
+ }
341
+ >
342
+ <InnerCheckBox checked={selectedValues.length === allValues.length} />
343
+ <span className={"text-sm text-text-secondary dark:text-text-secondary-dark"}>(Select All)</span>
344
+ </CommandPrimitive.Item>}
345
+ {children}
346
+ </CommandPrimitive.Group>
347
+ </CommandPrimitive.List>
348
+ </CommandPrimitive>
349
+ </PopoverPrimitive.Content>
350
+ </PopoverPrimitive.Portal>
334
351
  </PopoverPrimitive.Root>
335
352
  </MultiSelectContext.Provider>
336
353
  );
@@ -346,10 +363,10 @@ export interface MultiSelectItemProps<T extends MultiSelectValue = string> {
346
363
  }
347
364
 
348
365
  export const MultiSelectItem = React.memo(function MultiSelectItem<T extends MultiSelectValue = string>({
349
- children,
350
- value,
351
- className
352
- }: MultiSelectItemProps<T>) {
366
+ children,
367
+ value,
368
+ className
369
+ }: MultiSelectItemProps<T>) {
353
370
  const context = React.useContext(MultiSelectContext);
354
371
  if (!context) throw new Error("MultiSelectItem must be used inside a MultiSelect");
355
372
  const {
@@ -373,13 +390,14 @@ export const MultiSelectItem = React.memo(function MultiSelectItem<T extends Mul
373
390
  "cursor-pointer",
374
391
  "m-1",
375
392
  "ring-offset-transparent",
376
- "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",
393
+ "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-primary/75 aria-[selected=true]:ring-offset-2",
377
394
  "aria-[selected=true]:bg-surface-accent-100 aria-[selected=true]:dark:bg-surface-accent-900",
378
395
  "cursor-pointer p-2 rounded aria-[selected=true]:bg-surface-accent-100 aria-[selected=true]:dark:bg-surface-accent-900",
396
+ "text-surface-accent-700 dark:text-surface-accent-300",
379
397
  className
380
398
  )}
381
399
  >
382
- <InnerCheckBox checked={isSelected}/>
400
+ <InnerCheckBox checked={isSelected} />
383
401
  {children}
384
402
  </CommandPrimitive.Item>;
385
403
  });
@@ -398,8 +416,7 @@ const InnerCheckBox = React.memo(function InnerCheckBox({ checked }: { checked:
398
416
  (checked) ? "text-surface-accent-100 dark:text-surface-accent-900" : "",
399
417
  (checked ? "border-transparent" : "border-surface-accent-800 dark:border-surface-accent-200")
400
418
  )}>
401
- {checked && <CheckIcon size={16} className={"absolute"}/>}
419
+ {checked && <CheckIcon size={16} className={"absolute"} />}
402
420
  </div>
403
421
  </div>
404
422
  });
405
-
@@ -5,6 +5,7 @@ import * as PopoverPrimitive from "@radix-ui/react-popover";
5
5
  import { paperMixin } from "../styles";
6
6
  import { cls } from "../util";
7
7
  import { useInjectStyles } from "../hooks";
8
+ import { usePortalContainer } from "../hooks/PortalContainerContext";
8
9
 
9
10
  export type PopoverSide = "top" | "right" | "bottom" | "left";
10
11
  export type PopoverAlign = "start" | "center" | "end";
@@ -49,16 +50,24 @@ export function Popover({
49
50
 
50
51
  useInjectStyles("Popover", popoverStyles);
51
52
 
53
+ // Get the portal container from context
54
+ const contextContainer = usePortalContainer();
55
+
56
+ // Prioritize manual prop, fallback to context container
57
+ const finalContainer = (portalContainer ?? contextContainer ?? undefined) as HTMLElement | undefined;
58
+
52
59
  if (!enabled)
53
60
  return <>{trigger}</>;
54
61
 
55
62
  return <PopoverPrimitive.Root open={open}
56
63
  onOpenChange={onOpenChange}
57
64
  modal={modal}>
65
+
58
66
  <PopoverPrimitive.Trigger asChild>
59
67
  {trigger}
60
68
  </PopoverPrimitive.Trigger>
61
- <PopoverPrimitive.Portal container={portalContainer}>
69
+
70
+ <PopoverPrimitive.Portal container={finalContainer}>
62
71
  <PopoverPrimitive.Content
63
72
  className={cls(paperMixin,
64
73
  "PopoverContent z-40", className)}
@@ -79,7 +88,7 @@ export function Popover({
79
88
  }
80
89
 
81
90
  const popoverStyles = `
82
-
91
+ /* ... (styles remain unchanged) ... */
83
92
  .PopoverContent {
84
93
  animation-duration: 400ms;
85
94
  animation-timing-function: cubic-bezier(0.16, 1, 0.3, 1);
@@ -98,7 +107,6 @@ const popoverStyles = `
98
107
  animation-name: slideRightAndFade;
99
108
  }
100
109
 
101
-
102
110
  @keyframes slideUpAndFade {
103
111
  from {
104
112
  opacity: 0;