@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.
- package/.turbo/turbo-build.log +20 -12
- package/CHANGELOG.md +15 -0
- package/dist/chunk-D57E6H3M.mjs +36 -0
- package/dist/chunk-D57E6H3M.mjs.map +1 -0
- package/dist/chunk-FJ6ZGZIA.mjs +43 -0
- package/dist/chunk-FJ6ZGZIA.mjs.map +1 -0
- package/dist/emojis.d.mts +1 -0
- package/dist/emojis.d.ts +1 -0
- package/dist/emojis.js +31 -0
- package/dist/emojis.js.map +1 -0
- package/dist/emojis.mjs +8 -0
- package/dist/emojis.mjs.map +1 -0
- package/dist/index.css +1 -1
- package/dist/index.d.mts +142 -90
- package/dist/index.d.ts +142 -90
- package/dist/index.js +30559 -6412
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +35081 -10928
- package/dist/index.mjs.map +1 -1
- package/package.json +13 -17
- package/src/__tests__/__snapshots__/fuzzyScorer.test.ts.snap +1 -1
- package/src/components/Avatar.tsx +10 -10
- package/src/components/Banner.tsx +1 -1
- package/src/components/BaseToolbar.tsx +1 -1
- package/src/components/Button.tsx +2 -1
- package/src/components/Chip.tsx +1 -1
- package/src/components/ColorSwatchControl.tsx +22 -14
- package/src/components/CommandPalette.tsx +10 -6
- package/src/components/ContextMenu.tsx +249 -38
- package/src/components/Dialog.tsx +70 -67
- package/src/components/Drawer.tsx +56 -17
- package/src/components/DropdownMenu.tsx +305 -46
- package/src/components/EditableText.tsx +1 -1
- package/src/components/EmojiPicker.tsx +645 -0
- package/src/components/GridView.tsx +5 -1
- package/src/components/IVirtualizedList.tsx +21 -1
- package/src/components/InputField.tsx +2 -10
- package/src/components/InspectorContainer.tsx +4 -2
- package/src/components/Message.tsx +5 -16
- package/src/components/Popover.tsx +73 -107
- package/src/components/Progress.tsx +18 -18
- package/src/components/ScrollArea.tsx +66 -31
- package/src/components/ScrollableSidebar.tsx +1 -1
- package/src/components/SegmentedControl.tsx +166 -38
- package/src/components/SelectMenu.tsx +193 -101
- package/src/components/Slider.tsx +40 -38
- package/src/components/Switch.tsx +4 -4
- package/src/components/TextArea.tsx +1 -1
- package/src/components/Toast.tsx +99 -26
- package/src/components/Toolbar.tsx +114 -16
- package/src/components/Tooltip.tsx +18 -8
- package/src/components/Virtualized.tsx +193 -14
- package/src/components/VisuallyHidden.tsx +20 -0
- package/src/components/__tests__/Virtualized.math.test.ts +426 -1
- package/src/components/__tests__/Virtualized.test.tsx +129 -1
- package/src/components/ai-assistant/AIAssistantLayout.tsx +11 -6
- package/src/components/internal/Menu.tsx +4 -0
- package/src/components/listView/ListViewEditableRowTitle.tsx +1 -1
- package/src/components/listView/ListViewRoot.tsx +5 -1
- package/src/components/resizablePanels/Panel.tsx +94 -0
- package/src/components/resizablePanels/PanelGroup.tsx +498 -0
- package/src/components/resizablePanels/PanelGroupContext.tsx +14 -0
- package/src/components/resizablePanels/PanelResizeHandle.tsx +61 -0
- package/src/components/resizablePanels/index.ts +7 -0
- package/src/components/resizablePanels/types.ts +65 -0
- package/src/components/workspace/DrawerWorkspaceLayout.tsx +22 -7
- package/src/components/workspace/PanelWorkspaceLayout.tsx +30 -84
- package/src/components/workspace/WorkspaceLayout.tsx +28 -58
- package/src/components/workspace/types.ts +6 -4
- package/src/contexts/DialogContext.tsx +15 -24
- package/src/emojis.ts +1 -0
- package/src/hooks/useTriggerToggle.ts +95 -0
- package/src/index.css +2 -0
- package/src/index.tsx +1 -2
- package/src/theme/proseTheme.ts +2 -0
- package/src/utils/mergeProps.ts +57 -0
- package/src/utils/skinTone.ts +90 -0
- package/tsup.config.ts +3 -1
- package/src/__tests__/workspaceLayout.test.ts +0 -281
- package/src/components/ScrollArea2.tsx +0 -76
- package/src/components/internal/MenuViewport.tsx +0 -178
- package/src/components/internal/SelectItem.tsx +0 -138
- package/src/components/workspace/panelStorage.ts +0 -216
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import { Slider as
|
|
4
|
-
import React, { FocusEventHandler, memo, useCallback
|
|
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
|
|
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
|
-
(
|
|
57
|
-
onValueChange(
|
|
53
|
+
(newValue: number) => {
|
|
54
|
+
onValueChange(newValue);
|
|
58
55
|
},
|
|
59
56
|
[onValueChange]
|
|
60
57
|
);
|
|
61
58
|
|
|
62
|
-
const ratio = (
|
|
59
|
+
const ratio = (clampedValue - min) / (max - min);
|
|
63
60
|
|
|
64
61
|
return (
|
|
65
|
-
<
|
|
62
|
+
<BaseSlider.Root
|
|
66
63
|
min={min}
|
|
67
64
|
max={max}
|
|
68
65
|
step={step}
|
|
69
66
|
id={id}
|
|
70
|
-
value={
|
|
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
|
-
<
|
|
82
|
-
<
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
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
|
-
</
|
|
97
|
-
|
|
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 "
|
|
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-[
|
|
44
|
-
colorScheme === "secondary" && "data-[
|
|
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-[
|
|
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-
|
|
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}
|
package/src/components/Toast.tsx
CHANGED
|
@@ -1,50 +1,123 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
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
|
-
|
|
7
|
-
|
|
8
|
-
content,
|
|
9
|
-
children,
|
|
10
|
-
...props
|
|
11
|
-
}: {
|
|
8
|
+
// Types for toast data
|
|
9
|
+
interface ToastData {
|
|
12
10
|
title?: string;
|
|
13
11
|
content: ReactNode;
|
|
14
|
-
|
|
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
|
-
{
|
|
34
|
-
<ToastPrimitive.Action
|
|
35
|
-
{
|
|
55
|
+
{toast.data?.action && (
|
|
56
|
+
<ToastPrimitive.Action render={<span />}>
|
|
57
|
+
{toast.data.action}
|
|
36
58
|
</ToastPrimitive.Action>
|
|
37
59
|
)}
|
|
38
|
-
<ToastPrimitive.Close
|
|
39
|
-
|
|
40
|
-
|
|
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
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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={
|
|
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=
|
|
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={
|
|
199
|
+
onOpenChange={handleOpenChange}
|
|
109
200
|
side="top"
|
|
110
201
|
showArrow={false}
|
|
111
202
|
sideOffset={SIDE_OFFSET}
|
|
112
203
|
portalContainer={portalContainer}
|
|
113
|
-
trigger={
|
|
114
|
-
|
|
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: () =>
|
|
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
|
|
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
|
|
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
|
-
|
|
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?:
|
|
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
|
|
32
|
+
<TooltipPrimitive.Trigger
|
|
33
|
+
render={(triggerProps) =>
|
|
34
|
+
cloneElementWithMergedProps(children, triggerProps)
|
|
35
|
+
}
|
|
36
|
+
/>
|
|
31
37
|
<TooltipPrimitive.Portal container={portalContainer}>
|
|
32
|
-
<TooltipPrimitive.
|
|
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=
|
|
44
|
+
className="n-z-menu"
|
|
39
45
|
>
|
|
40
|
-
|
|
41
|
-
|
|
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>
|