@noya-app/noya-designsystem 0.1.77 → 0.1.79

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 (83) hide show
  1. package/.turbo/turbo-build.log +20 -12
  2. package/CHANGELOG.md +15 -0
  3. package/dist/chunk-D57E6H3M.mjs +36 -0
  4. package/dist/chunk-D57E6H3M.mjs.map +1 -0
  5. package/dist/chunk-FJ6ZGZIA.mjs +43 -0
  6. package/dist/chunk-FJ6ZGZIA.mjs.map +1 -0
  7. package/dist/emojis.d.mts +1 -0
  8. package/dist/emojis.d.ts +1 -0
  9. package/dist/emojis.js +31 -0
  10. package/dist/emojis.js.map +1 -0
  11. package/dist/emojis.mjs +8 -0
  12. package/dist/emojis.mjs.map +1 -0
  13. package/dist/index.css +1 -1
  14. package/dist/index.d.mts +142 -90
  15. package/dist/index.d.ts +142 -90
  16. package/dist/index.js +30559 -6412
  17. package/dist/index.js.map +1 -1
  18. package/dist/index.mjs +35081 -10928
  19. package/dist/index.mjs.map +1 -1
  20. package/package.json +13 -17
  21. package/src/__tests__/__snapshots__/fuzzyScorer.test.ts.snap +1 -1
  22. package/src/components/Avatar.tsx +10 -10
  23. package/src/components/Banner.tsx +1 -1
  24. package/src/components/BaseToolbar.tsx +1 -1
  25. package/src/components/Button.tsx +2 -1
  26. package/src/components/Chip.tsx +1 -1
  27. package/src/components/ColorSwatchControl.tsx +22 -14
  28. package/src/components/CommandPalette.tsx +10 -6
  29. package/src/components/ContextMenu.tsx +249 -38
  30. package/src/components/Dialog.tsx +70 -67
  31. package/src/components/Drawer.tsx +56 -17
  32. package/src/components/DropdownMenu.tsx +305 -46
  33. package/src/components/EditableText.tsx +1 -1
  34. package/src/components/EmojiPicker.tsx +645 -0
  35. package/src/components/GridView.tsx +5 -1
  36. package/src/components/IVirtualizedList.tsx +21 -1
  37. package/src/components/InputField.tsx +2 -10
  38. package/src/components/InspectorContainer.tsx +4 -2
  39. package/src/components/Message.tsx +5 -16
  40. package/src/components/Popover.tsx +73 -107
  41. package/src/components/Progress.tsx +18 -18
  42. package/src/components/ScrollArea.tsx +66 -31
  43. package/src/components/ScrollableSidebar.tsx +1 -1
  44. package/src/components/SegmentedControl.tsx +166 -38
  45. package/src/components/SelectMenu.tsx +193 -101
  46. package/src/components/Slider.tsx +40 -38
  47. package/src/components/Switch.tsx +4 -4
  48. package/src/components/TextArea.tsx +1 -1
  49. package/src/components/Toast.tsx +99 -26
  50. package/src/components/Toolbar.tsx +114 -16
  51. package/src/components/Tooltip.tsx +18 -8
  52. package/src/components/Virtualized.tsx +193 -14
  53. package/src/components/VisuallyHidden.tsx +20 -0
  54. package/src/components/__tests__/Virtualized.math.test.ts +426 -1
  55. package/src/components/__tests__/Virtualized.test.tsx +129 -1
  56. package/src/components/ai-assistant/AIAssistantLayout.tsx +11 -6
  57. package/src/components/internal/Menu.tsx +4 -0
  58. package/src/components/listView/ListViewEditableRowTitle.tsx +1 -1
  59. package/src/components/listView/ListViewRoot.tsx +5 -1
  60. package/src/components/resizablePanels/Panel.tsx +94 -0
  61. package/src/components/resizablePanels/PanelGroup.tsx +498 -0
  62. package/src/components/resizablePanels/PanelGroupContext.tsx +14 -0
  63. package/src/components/resizablePanels/PanelResizeHandle.tsx +61 -0
  64. package/src/components/resizablePanels/index.ts +7 -0
  65. package/src/components/resizablePanels/types.ts +65 -0
  66. package/src/components/workspace/DrawerWorkspaceLayout.tsx +22 -7
  67. package/src/components/workspace/PanelWorkspaceLayout.tsx +30 -84
  68. package/src/components/workspace/WorkspaceLayout.tsx +28 -58
  69. package/src/components/workspace/types.ts +6 -4
  70. package/src/contexts/DialogContext.tsx +15 -24
  71. package/src/emojis.ts +1 -0
  72. package/src/hooks/useTriggerToggle.ts +95 -0
  73. package/src/index.css +2 -0
  74. package/src/index.tsx +1 -2
  75. package/src/theme/proseTheme.ts +2 -0
  76. package/src/utils/mergeProps.ts +57 -0
  77. package/src/utils/skinTone.ts +90 -0
  78. package/tsup.config.ts +3 -1
  79. package/src/__tests__/workspaceLayout.test.ts +0 -281
  80. package/src/components/ScrollArea2.tsx +0 -76
  81. package/src/components/internal/MenuViewport.tsx +0 -178
  82. package/src/components/internal/SelectItem.tsx +0 -138
  83. package/src/components/workspace/panelStorage.ts +0 -216
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
 
3
- import { Slider as RadixSlider } from "radix-ui";
4
- import React, { FocusEventHandler, memo, useCallback, useMemo } from "react";
3
+ import { Slider as BaseSlider } from "@base-ui/react/slider";
4
+ import React, { FocusEventHandler, memo, useCallback } from "react";
5
5
  import { useLabel } from "../hooks/useLabel";
6
6
  import { cx } from "../utils/classNames";
7
7
 
@@ -43,31 +43,28 @@ export const Slider = memo(function Slider({
43
43
  readOnly = false,
44
44
  ...props
45
45
  }: Props) {
46
- const arrayValue = useMemo(
47
- () => [Math.min(Math.max(value, min), max)],
48
- [value, min, max]
49
- );
46
+ const clampedValue = Math.min(Math.max(value, min), max);
50
47
 
51
48
  const { fieldId: id } = useLabel({
52
49
  fieldId: props.id,
53
50
  });
54
51
 
55
52
  const handleValueChange = useCallback(
56
- (arrayValue: number[]) => {
57
- onValueChange(arrayValue[0]);
53
+ (newValue: number) => {
54
+ onValueChange(newValue);
58
55
  },
59
56
  [onValueChange]
60
57
  );
61
58
 
62
- const ratio = (arrayValue[0] - min) / (max - min);
59
+ const ratio = (clampedValue - min) / (max - min);
63
60
 
64
61
  return (
65
- <RadixSlider.Root
62
+ <BaseSlider.Root
66
63
  min={min}
67
64
  max={max}
68
65
  step={step}
69
66
  id={id}
70
- value={arrayValue}
67
+ value={clampedValue}
71
68
  onValueChange={handleValueChange}
72
69
  className={cx(
73
70
  "n-flex n-relative n-items-center n-select-none n-touch-none n-h-input-height n-rounded n-flex-grow n-max-h-input-height n-cursor-pointer",
@@ -77,34 +74,39 @@ export const Slider = memo(function Slider({
77
74
  onFocus={onFocus}
78
75
  onBlur={onBlur}
79
76
  disabled={readOnly}
77
+ thumbAlignment="edge"
80
78
  >
81
- <div className="n-relative n-flex-grow n-h-[3px] n-overflow-hidden">
82
- <div
83
- className="n-absolute n-h-full n-rounded-l n-bg-slider-active-background"
84
- style={{
85
- left: 0,
86
- width: `calc(${ratio * 100}% - ${ratio * 8}px - 2px)`,
87
- }}
88
- />
89
- <div
90
- className="n-absolute n-h-full n-rounded-r n-bg-input-background"
91
- style={{
92
- right: 0,
93
- width: `calc(100% - ${ratio * 100}% + ${ratio * 8}px - 10px)`,
94
- }}
79
+ <BaseSlider.Control className="n-relative n-flex-grow n-h-[21px] n-flex n-items-center">
80
+ <BaseSlider.Track className="n-relative n-flex-grow n-h-[3px] n-overflow-hidden">
81
+ <div
82
+ className="n-absolute n-h-full n-rounded-l n-bg-slider-active-background"
83
+ style={{
84
+ left: 0,
85
+ // With thumbAlignment="edge", thumb ranges from 0 to (100% - THUMB_WIDTH)
86
+ // Active track fills from 0 to thumb left edge, minus a small gap
87
+ width: `calc(${ratio * 100}% - ${ratio * THUMB_WIDTH + 2}px)`,
88
+ }}
89
+ />
90
+ <div
91
+ className="n-absolute n-h-full n-rounded-r n-bg-input-background"
92
+ style={{
93
+ right: 0,
94
+ // Inactive track fills from thumb right edge to 100%, minus a small gap
95
+ width: `calc(${(1 - ratio) * 100}% - ${(1 - ratio) * THUMB_WIDTH + 2}px)`,
96
+ }}
97
+ />
98
+ </BaseSlider.Track>
99
+ <BaseSlider.Thumb
100
+ style={thumbStyle}
101
+ className={cx(
102
+ "n-block n-h-[21px] n-rounded-sm n-bg-slider-thumb-background n-shadow-sliderThumb n-transition-colors focus:n-outline-primary focus:n-outline-2",
103
+ "n-cursor-ew-resize",
104
+ colorScheme === undefined && "n-border-slider-border",
105
+ colorScheme === "primary" && "n-border-primary",
106
+ colorScheme === "secondary" && "n-border-secondary"
107
+ )}
95
108
  />
96
- </div>
97
- <RadixSlider.Thumb
98
- style={thumbStyle}
99
- className={cx(
100
- "n-block n-h-[21px] n-rounded-sm n-bg-slider-thumb-background n-shadow-sliderThumb n-transition-colors focus:n-outline-primary focus:n-outline-2",
101
- "n-cursor-ew-resize",
102
- // "outline-none outline outline-2 outline-offset-[0px] outline-background",
103
- colorScheme === undefined && "n-border-slider-border",
104
- colorScheme === "primary" && "n-border-primary",
105
- colorScheme === "secondary" && "n-border-secondary"
106
- )}
107
- />
108
- </RadixSlider.Root>
109
+ </BaseSlider.Control>
110
+ </BaseSlider.Root>
109
111
  );
110
112
  });
@@ -1,6 +1,6 @@
1
1
  "use client";
2
2
 
3
- import { Switch as SwitchPrimitive } from "radix-ui";
3
+ import { Switch as SwitchPrimitive } from "@base-ui/react/switch";
4
4
  import * as React from "react";
5
5
  import { cx } from "../utils/classNames";
6
6
 
@@ -40,15 +40,15 @@ export const Switch = function Switch({
40
40
  onChange(newValue);
41
41
  }}
42
42
  className={cx(
43
- "n-all-unset n-w-8 n-h-[19px] n-bg-active-background n-rounded-full n-relative n-cursor-pointer n-ring-offset-background n-[-webkit-tap-highlight-color:rgba(0,0,0,0)] focus:n-ring-2 focus:n-ring-primary focus:n-ring-offset-[1px] n-outline-none data-[state=checked]:n-bg-primary n-transition-all",
44
- colorScheme === "secondary" && "data-[state=checked]:n-bg-secondary",
43
+ "n-all-unset n-w-8 n-h-[19px] n-bg-active-background n-rounded-full n-relative n-cursor-pointer n-ring-offset-background n-[-webkit-tap-highlight-color:rgba(0,0,0,0)] focus:n-ring-2 focus:n-ring-primary focus:n-ring-offset-[1px] n-outline-none data-[checked]:n-bg-primary n-transition-all",
44
+ colorScheme === "secondary" && "data-[checked]:n-bg-secondary",
45
45
  "focus:n-z-interactable",
46
46
  className
47
47
  )}
48
48
  onFocus={onFocus}
49
49
  onBlur={onBlur}
50
50
  >
51
- <SwitchPrimitive.Thumb className="n-block n-w-[15px] n-h-[15px] n-bg-background n-rounded-full n-transition-transform n-translate-x-[2px] data-[state=checked]:n-translate-x-[15px]" />
51
+ <SwitchPrimitive.Thumb className="n-block n-w-[15px] n-h-[15px] n-bg-background n-rounded-full n-transition-transform n-translate-x-[2px] n-translate-y-[2px] data-[checked]:n-translate-x-[15px]" />
52
52
  </SwitchPrimitive.Root>
53
53
  );
54
54
  };
@@ -68,7 +68,7 @@ export const TextArea = memo(
68
68
  return (
69
69
  <textarea
70
70
  className={cx(
71
- `n-font-sans n-text-heading5 n-font-normal n-text-text n-bg-input-background n-flex-1 n-py-1 n-px-1.5 n-border-none n-outline-none n-min-h-6 n-w-full n-rounded n-resize-none placeholder:n-text-text-disabled focus:n-ring-2 focus:n-ring-primary read-only:n-text-text-disabled focus:n-z-interactable n-transition-[box-shadow]`,
71
+ `n-font-sans n-text-heading5 n-font-normal n-text-text n-bg-input-background n-flex-1 n-py-1 n-px-input-padding-x n-border-none n-outline-none n-min-h-6 n-w-full n-rounded n-resize-none placeholder:n-text-text-disabled focus:n-ring-2 focus:n-ring-primary read-only:n-text-text-disabled focus:n-z-interactable n-transition-[box-shadow]`,
72
72
  className
73
73
  )}
74
74
  {...rest}
@@ -1,50 +1,123 @@
1
- import { Toast as ToastPrimitive } from "radix-ui";
2
- import React, { ReactNode } from "react";
1
+ "use client";
2
+
3
+ import { Toast as ToastPrimitive } from "@base-ui/react/toast";
4
+ import React, { ReactNode, createContext, useCallback, useContext } from "react";
3
5
  import { IconButton } from "./IconButton";
4
6
  import { textStyles } from "./Text";
5
7
 
6
- export const Toast = ({
7
- title,
8
- content,
9
- children,
10
- ...props
11
- }: {
8
+ // Types for toast data
9
+ interface ToastData {
12
10
  title?: string;
13
11
  content: ReactNode;
14
- children?: React.ReactNode;
15
- }) => {
12
+ action?: ReactNode;
13
+ }
14
+
15
+ // Context for toast functionality
16
+ interface ToastContextValue {
17
+ showToast: (data: ToastData) => void;
18
+ }
19
+
20
+ const ToastContext = createContext<ToastContextValue | null>(null);
21
+
22
+ export function useToast() {
23
+ const context = useContext(ToastContext);
24
+ if (!context) {
25
+ throw new Error("useToast must be used within ToastProvider");
26
+ }
27
+ return context;
28
+ }
29
+
30
+ // Toast object type (inferred from toast manager)
31
+ type ToastObject = {
32
+ id: string;
33
+ data?: ToastData;
34
+ };
35
+
36
+ // Individual toast content renderer
37
+ function ToastContent({ toast }: { toast: ToastObject }) {
16
38
  return (
17
39
  <ToastPrimitive.Root
40
+ toast={toast}
18
41
  className="n-px-2 n-py-2.5 n-grid n-grid-cols-[auto_max-content] n-gap-x-2.5 n-items-center n-rounded n-text-sm n-bg-popover-background n-shadow-popover n-text-text-muted"
19
- {...props}
20
42
  >
21
- {title && (
43
+ {toast.data?.title && (
22
44
  <ToastPrimitive.Title
23
45
  className={`n-mb-[5px] ${textStyles.label} n-font-bold n-text-text`}
24
46
  >
25
- {title}
47
+ {toast.data.title}
26
48
  </ToastPrimitive.Title>
27
49
  )}
28
50
  <ToastPrimitive.Description
29
51
  className={`${textStyles.small} n-text-text-muted`}
30
52
  >
31
- {content}
53
+ {toast.data?.content}
32
54
  </ToastPrimitive.Description>
33
- {children && (
34
- <ToastPrimitive.Action asChild altText="">
35
- {children}
55
+ {toast.data?.action && (
56
+ <ToastPrimitive.Action render={<span />}>
57
+ {toast.data.action}
36
58
  </ToastPrimitive.Action>
37
59
  )}
38
- <ToastPrimitive.Close aria-label="Close" asChild>
39
- <IconButton iconName="Cross1Icon" />
40
- </ToastPrimitive.Close>
60
+ <ToastPrimitive.Close
61
+ aria-label="Close"
62
+ render={<IconButton iconName="Cross1Icon" />}
63
+ />
41
64
  </ToastPrimitive.Root>
42
65
  );
66
+ }
67
+
68
+ // Inner component that uses the toast manager hook (must be inside Provider)
69
+ function ToastProviderInner({ children }: { children: React.ReactNode }) {
70
+ const toastManager = ToastPrimitive.useToastManager();
71
+
72
+ const showToast = useCallback(
73
+ (data: ToastData) => {
74
+ toastManager.add({ data });
75
+ },
76
+ [toastManager]
77
+ );
78
+
79
+ return (
80
+ <ToastContext.Provider value={{ showToast }}>
81
+ {children}
82
+ <ToastPrimitive.Viewport className="n-fixed n-bottom-0 n-right-0 n-flex n-flex-col n-p-5 n-gap-2.5 n-min-w-[200px] n-max-w-[100vw] n-m-0 n-list-none n-z-[2147483647] n-outline-none">
83
+ {toastManager.toasts.map((toast) => (
84
+ <ToastContent key={toast.id} toast={toast} />
85
+ ))}
86
+ </ToastPrimitive.Viewport>
87
+ </ToastContext.Provider>
88
+ );
89
+ }
90
+
91
+ // Provider component that maintains backwards compatibility
92
+ export const ToastProvider = ({ children }: { children: React.ReactNode }) => {
93
+ return (
94
+ <ToastPrimitive.Provider>
95
+ <ToastProviderInner>{children}</ToastProviderInner>
96
+ </ToastPrimitive.Provider>
97
+ );
43
98
  };
44
99
 
45
- export const ToastProvider = ({ children }: { children: React.ReactNode }) => (
46
- <ToastPrimitive.Provider>
47
- {children}
48
- <ToastPrimitive.Viewport className="n-fixed n-bottom-0 n-right-0 n-flex n-flex-col n-p-5 n-gap-2.5 n-min-w-[200px] n-max-w-[100vw] n-m-0 n-list-none n-z-[2147483647] n-outline-none" />
49
- </ToastPrimitive.Provider>
50
- );
100
+ // Legacy Toast component for direct rendering (if needed)
101
+ export const Toast = ({
102
+ title,
103
+ content,
104
+ children,
105
+ }: {
106
+ title?: string;
107
+ content: ReactNode;
108
+ children?: React.ReactNode;
109
+ }) => {
110
+ // This is a simplified version for backwards compatibility
111
+ // In most cases, useToast().showToast() should be used instead
112
+ return (
113
+ <div className="n-px-2 n-py-2.5 n-grid n-grid-cols-[auto_max-content] n-gap-x-2.5 n-items-center n-rounded n-text-sm n-bg-popover-background n-shadow-popover n-text-text-muted">
114
+ {title && (
115
+ <div className={`n-mb-[5px] ${textStyles.label} n-font-bold n-text-text`}>
116
+ {title}
117
+ </div>
118
+ )}
119
+ <div className={`${textStyles.small} n-text-text-muted`}>{content}</div>
120
+ {children}
121
+ </div>
122
+ );
123
+ };
@@ -31,6 +31,16 @@ const SIDE_OFFSET = 6;
31
31
 
32
32
  export const ToolbarMenuContext = createContext<{
33
33
  dividerOverflow?: number;
34
+ /** When true, PopoverMenuItem items show as dialogs on small screens */
35
+ useDialogOnSmallScreen?: boolean;
36
+ /** Function to detect if screen is small. Required when useDialogOnSmallScreen is true */
37
+ isSmallScreen?: boolean;
38
+ /** Function to open a dialog. Required when useDialogOnSmallScreen is true */
39
+ openDialog?: (options: {
40
+ title: ReactNode;
41
+ className?: string;
42
+ children: ReactNode | ((props: { close: () => void }) => ReactNode);
43
+ }) => Promise<void> & { close: () => void };
34
44
  }>({});
35
45
 
36
46
  export const ToolbarMenuDropdown = memoGeneric(function ToolbarMenuDropdown<
@@ -38,16 +48,26 @@ export const ToolbarMenuDropdown = memoGeneric(function ToolbarMenuDropdown<
38
48
  >({
39
49
  item,
40
50
  onSelectMenuItem,
51
+ onPopoverOpenChange,
41
52
  }: {
42
53
  item: SubMenuItem<T>;
43
54
  onSelectMenuItem?: (value: T) => void;
55
+ onPopoverOpenChange?: (open: boolean) => void;
44
56
  }) {
45
57
  const [open, setOpen] = React.useState(false);
46
58
 
59
+ const handleOpenChange = React.useCallback(
60
+ (newOpen: boolean) => {
61
+ setOpen(newOpen);
62
+ onPopoverOpenChange?.(newOpen);
63
+ },
64
+ [onPopoverOpenChange]
65
+ );
66
+
47
67
  return (
48
68
  <DropdownMenu
49
69
  open={open}
50
- onOpenChange={setOpen}
70
+ onOpenChange={handleOpenChange}
51
71
  items={item.items}
52
72
  modal={false}
53
73
  onSelect={(value) => {
@@ -60,7 +80,11 @@ export const ToolbarMenuDropdown = memoGeneric(function ToolbarMenuDropdown<
60
80
  disabled={item.disabled}
61
81
  active={item.checked || open}
62
82
  icon={item.icon}
63
- iconRight="DropdownChevronIcon"
83
+ iconRight={
84
+ item.icon === "DotsVerticalIcon" || item.icon === "DotsHorizontalIcon"
85
+ ? undefined
86
+ : "DropdownChevronIcon"
87
+ }
64
88
  >
65
89
  {item.title}
66
90
  </Button>
@@ -87,9 +111,11 @@ export const ToolbarShortcut = ({
87
111
  export const ToolbarMenuPopover = memoGeneric(function ToolbarMenuPopover({
88
112
  item,
89
113
  portalContainer,
114
+ onPopoverOpenChange,
90
115
  }: {
91
116
  item: PopoverMenuItem;
92
117
  portalContainer?: HTMLElement | null;
118
+ onPopoverOpenChange?: (open: boolean) => void;
93
119
  }) {
94
120
  const {
95
121
  disabled,
@@ -98,31 +124,88 @@ export const ToolbarMenuPopover = memoGeneric(function ToolbarMenuPopover({
98
124
  tooltip,
99
125
  content: itemContent,
100
126
  checked,
127
+ popoverClassName,
128
+ triggerProps: buttonProps,
101
129
  } = item;
102
130
  const [open, setOpen] = React.useState(false);
103
131
 
132
+ const handleOpenChange = React.useCallback(
133
+ (newOpen: boolean) => {
134
+ setOpen(newOpen);
135
+ onPopoverOpenChange?.(newOpen);
136
+ },
137
+ [onPopoverOpenChange]
138
+ );
139
+
140
+ const { useDialogOnSmallScreen, isSmallScreen, openDialog } =
141
+ useContext(ToolbarMenuContext);
142
+
143
+ const shouldUseDialog = useDialogOnSmallScreen && isSmallScreen && openDialog;
144
+
145
+ const handleOpenDialog = React.useCallback(() => {
146
+ if (!openDialog) return;
147
+
148
+ handleOpenChange(true);
149
+ const dialogContent =
150
+ typeof itemContent === "function" ? (
151
+ (props: { close: () => void }) => (
152
+ <div className="-n-m-3">{itemContent(props)}</div>
153
+ )
154
+ ) : (
155
+ <div className="-n-m-3">{itemContent}</div>
156
+ );
157
+
158
+ const dialog = openDialog({
159
+ title,
160
+ className: "n-w-[90vw] n-max-w-[360px]",
161
+ children: dialogContent,
162
+ });
163
+ dialog.then(() => handleOpenChange(false));
164
+ }, [openDialog, itemContent, title, handleOpenChange]);
165
+
166
+ const trigger = (
167
+ <Button
168
+ disabled={disabled}
169
+ active={checked || open}
170
+ icon={icon}
171
+ iconRight="DropdownChevronIcon"
172
+ onClick={shouldUseDialog ? handleOpenDialog : undefined}
173
+ {...buttonProps}
174
+ >
175
+ {title}
176
+ </Button>
177
+ );
178
+
179
+ // On small screens with dialog mode enabled, just render the trigger button
180
+ if (shouldUseDialog) {
181
+ return tooltip && !open ? (
182
+ <Tooltip
183
+ sideOffset={SIDE_OFFSET}
184
+ side="top"
185
+ content={tooltip}
186
+ portalContainer={portalContainer}
187
+ >
188
+ {trigger}
189
+ </Tooltip>
190
+ ) : (
191
+ trigger
192
+ );
193
+ }
194
+
104
195
  const content = (
105
196
  <span>
106
197
  <Popover
107
198
  open={open}
108
- onOpenChange={setOpen}
199
+ onOpenChange={handleOpenChange}
109
200
  side="top"
110
201
  showArrow={false}
111
202
  sideOffset={SIDE_OFFSET}
112
203
  portalContainer={portalContainer}
113
- trigger={
114
- <Button
115
- disabled={disabled}
116
- active={checked || open}
117
- icon={icon}
118
- iconRight="DropdownChevronIcon"
119
- >
120
- {title}
121
- </Button>
122
- }
204
+ trigger={trigger}
205
+ className={popoverClassName}
123
206
  >
124
207
  {typeof itemContent === "function"
125
- ? itemContent({ close: () => setOpen(false) })
208
+ ? itemContent({ close: () => handleOpenChange(false) })
126
209
  : itemContent}
127
210
  </Popover>
128
211
  </span>
@@ -204,6 +287,7 @@ export const ToolbarMenuItem = memoGeneric(function ToolbarMenuItem<
204
287
  portalContainer,
205
288
  displayFilter,
206
289
  dividerOverflow: dividerOverflowProp,
290
+ onPopoverOpenChange,
207
291
  }: {
208
292
  item: MenuItem<T>;
209
293
  onSelectMenuItem?: (value: T) => void;
@@ -213,6 +297,7 @@ export const ToolbarMenuItem = memoGeneric(function ToolbarMenuItem<
213
297
  portalContainer?: HTMLElement | null;
214
298
  displayFilter?: DisplayFilter;
215
299
  dividerOverflow?: number;
300
+ onPopoverOpenChange?: (open: boolean) => void;
216
301
  }) {
217
302
  const { dividerOverflow: contextDividerOverflow } =
218
303
  useContext(ToolbarMenuContext);
@@ -226,7 +311,13 @@ export const ToolbarMenuItem = memoGeneric(function ToolbarMenuItem<
226
311
  }
227
312
 
228
313
  if (item.type === "popover") {
229
- return <ToolbarMenuPopover item={item} portalContainer={portalContainer} />;
314
+ return (
315
+ <ToolbarMenuPopover
316
+ item={item}
317
+ portalContainer={portalContainer}
318
+ onPopoverOpenChange={onPopoverOpenChange}
319
+ />
320
+ );
230
321
  }
231
322
 
232
323
  if (isSelectableMenuItem(item)) {
@@ -243,7 +334,11 @@ export const ToolbarMenuItem = memoGeneric(function ToolbarMenuItem<
243
334
  }
244
335
 
245
336
  return (
246
- <ToolbarMenuDropdown item={item} onSelectMenuItem={onSelectMenuItem} />
337
+ <ToolbarMenuDropdown
338
+ item={item}
339
+ onSelectMenuItem={onSelectMenuItem}
340
+ onPopoverOpenChange={onPopoverOpenChange}
341
+ />
247
342
  );
248
343
  });
249
344
 
@@ -258,6 +353,7 @@ export const ToolbarMenu = memoGeneric(function ToolbarMenu<T extends string>({
258
353
  portalContainer,
259
354
  displayFilter = "iconAndText",
260
355
  dividerOverflow,
356
+ onPopoverOpenChange,
261
357
  }: {
262
358
  items: MenuItem<T>[];
263
359
  onSelectMenuItem?: (value: T) => void;
@@ -267,6 +363,7 @@ export const ToolbarMenu = memoGeneric(function ToolbarMenu<T extends string>({
267
363
  portalContainer?: HTMLElement | null;
268
364
  displayFilter?: DisplayFilter;
269
365
  dividerOverflow?: number;
366
+ onPopoverOpenChange?: (open: boolean) => void;
270
367
  }) {
271
368
  return (
272
369
  <>
@@ -282,6 +379,7 @@ export const ToolbarMenu = memoGeneric(function ToolbarMenu<T extends string>({
282
379
  portalContainer={portalContainer}
283
380
  displayFilter={displayFilter}
284
381
  dividerOverflow={dividerOverflow}
382
+ onPopoverOpenChange={onPopoverOpenChange}
285
383
  />
286
384
  );
287
385
  })}
@@ -1,17 +1,19 @@
1
- import { Tooltip as TooltipPrimitive } from "radix-ui";
1
+ "use client";
2
+
3
+ import { Tooltip as TooltipPrimitive } from "@base-ui/react/tooltip";
2
4
  import * as React from "react";
3
- import { ComponentProps } from "react";
4
5
  import {
5
6
  portalScopeProps,
6
7
  usePortalScopeId,
7
8
  } from "../contexts/PortalScopeContext";
9
+ import { cloneElementWithMergedProps } from "../utils/mergeProps";
8
10
  import { textStyles } from "./Text";
9
11
 
10
12
  interface Props {
11
13
  children: React.ReactNode;
12
14
  content: React.ReactNode;
13
15
  sideOffset?: number;
14
- side?: ComponentProps<typeof TooltipPrimitive.Content>["side"];
16
+ side?: "top" | "bottom" | "left" | "right";
15
17
  portalContainer?: HTMLElement | null;
16
18
  }
17
19
 
@@ -27,18 +29,26 @@ export const Tooltip = React.memo(function Tooltip({
27
29
  return (
28
30
  <TooltipPrimitive.Provider>
29
31
  <TooltipPrimitive.Root>
30
- <TooltipPrimitive.Trigger asChild>{children}</TooltipPrimitive.Trigger>
32
+ <TooltipPrimitive.Trigger
33
+ render={(triggerProps) =>
34
+ cloneElementWithMergedProps(children, triggerProps)
35
+ }
36
+ />
31
37
  <TooltipPrimitive.Portal container={portalContainer}>
32
- <TooltipPrimitive.Content
38
+ <TooltipPrimitive.Positioner
33
39
  {...portalScopeProps(portalScopeId)}
34
40
  side={side}
35
41
  align="center"
36
42
  sideOffset={sideOffset}
37
43
  collisionPadding={8}
38
- className={`${textStyles.small} n-text-text n-rounded-sm n-p-2 n-bg-popover-background n-shadow-tooltip n-z-menu`}
44
+ className="n-z-menu"
39
45
  >
40
- {content}
41
- </TooltipPrimitive.Content>
46
+ <TooltipPrimitive.Popup
47
+ className={`${textStyles.small} n-text-text n-rounded-sm n-p-2 n-bg-popover-background n-shadow-tooltip n-z-menu`}
48
+ >
49
+ {content}
50
+ </TooltipPrimitive.Popup>
51
+ </TooltipPrimitive.Positioner>
42
52
  </TooltipPrimitive.Portal>
43
53
  </TooltipPrimitive.Root>
44
54
  </TooltipPrimitive.Provider>