@noya-app/noya-designsystem 0.1.44 → 0.1.45
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 +13 -10
- package/CHANGELOG.md +9 -0
- package/dist/index.css +1 -1
- package/dist/index.d.mts +320 -78
- package/dist/index.d.ts +320 -78
- package/dist/index.js +2113 -1374
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +2000 -1274
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -3
- package/src/components/AnimatePresence.tsx +10 -1
- package/src/components/Avatar.tsx +2 -0
- package/src/components/Checkbox.tsx +6 -1
- package/src/components/Combobox.tsx +6 -4
- package/src/components/ComboboxMenu.tsx +30 -15
- package/src/components/CommandPalette.tsx +69 -0
- package/src/components/ContextMenu.tsx +33 -134
- package/src/components/DropdownMenu.tsx +47 -155
- package/src/components/Fade.tsx +62 -0
- package/src/components/InputField.tsx +109 -133
- package/src/components/Label.tsx +81 -7
- package/src/components/LabeledField.tsx +112 -0
- package/src/components/ListView.tsx +55 -52
- package/src/components/Popover.tsx +12 -1
- package/src/components/SearchCompletionMenu.tsx +206 -0
- package/src/components/SegmentedControl.tsx +5 -2
- package/src/components/SelectMenu.tsx +104 -124
- package/src/components/SidebarList.tsx +252 -0
- package/src/components/Slider.tsx +18 -26
- package/src/components/Text.tsx +1 -0
- package/src/components/TextArea.tsx +4 -1
- package/src/components/Toolbar.tsx +6 -2
- package/src/components/TreeView.tsx +3 -0
- package/src/components/WorkspaceLayout.tsx +38 -18
- package/src/components/internal/Menu.tsx +58 -10
- package/src/components/internal/MenuViewport.tsx +151 -0
- package/src/components/internal/SelectItem.tsx +111 -0
- package/src/hooks/useIndent.ts +12 -0
- package/src/hooks/useLabel.ts +51 -0
- package/src/hooks/usePreservePanelSize.tsx +50 -19
- package/src/index.tsx +12 -5
- package/src/utils/combobox.ts +4 -1
- package/src/utils/createSectionedMenu.ts +11 -8
- package/src/utils/selection.ts +36 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@noya-app/noya-designsystem",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.45",
|
|
4
4
|
"main": "./dist/index.js",
|
|
5
5
|
"module": "./dist/index.mjs",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -23,10 +23,10 @@
|
|
|
23
23
|
"@dnd-kit/core": "3.1.1",
|
|
24
24
|
"@dnd-kit/modifiers": "3.0.0",
|
|
25
25
|
"@dnd-kit/sortable": "4.0.0",
|
|
26
|
-
"@noya-app/noya-colorpicker": "0.1.
|
|
26
|
+
"@noya-app/noya-colorpicker": "0.1.16",
|
|
27
27
|
"@noya-app/noya-utils": "0.1.3",
|
|
28
28
|
"@noya-app/noya-geometry": "0.1.6",
|
|
29
|
-
"@noya-app/noya-icons": "0.1.
|
|
29
|
+
"@noya-app/noya-icons": "0.1.6",
|
|
30
30
|
"@noya-app/noya-keymap": "0.1.3",
|
|
31
31
|
"@radix-ui/primitive": "^1.0.0",
|
|
32
32
|
"@radix-ui/react-avatar": "^1.0.1",
|
|
@@ -32,6 +32,7 @@ type AnimatePresenceProps = {
|
|
|
32
32
|
* "crossfade" - crossfade between the previous and next animation
|
|
33
33
|
*/
|
|
34
34
|
mode?: "wait" | "crossfade";
|
|
35
|
+
className?: string;
|
|
35
36
|
};
|
|
36
37
|
|
|
37
38
|
const defaultDuration = 150;
|
|
@@ -42,6 +43,7 @@ type PresenceChildProps = {
|
|
|
42
43
|
isPresent: boolean;
|
|
43
44
|
onExitComplete: () => void;
|
|
44
45
|
waitingToEnter?: boolean;
|
|
46
|
+
className?: string;
|
|
45
47
|
} & Pick<
|
|
46
48
|
AnimatePresenceProps,
|
|
47
49
|
"animationStyles" | "children" | "duration" | "skipInitialAnimation"
|
|
@@ -71,6 +73,7 @@ const PresenceChild = ({
|
|
|
71
73
|
animationStyles,
|
|
72
74
|
onExitComplete,
|
|
73
75
|
skipInitialAnimation,
|
|
76
|
+
className,
|
|
74
77
|
}: PresenceChildProps) => {
|
|
75
78
|
const [animationState, setAnimationState] = useState<AnimationState>(() =>
|
|
76
79
|
skipInitialAnimation && isPresent
|
|
@@ -131,7 +134,11 @@ const PresenceChild = ({
|
|
|
131
134
|
return null;
|
|
132
135
|
}
|
|
133
136
|
|
|
134
|
-
return
|
|
137
|
+
return (
|
|
138
|
+
<div style={mergedAnimationStyles[animationState]} className={className}>
|
|
139
|
+
{children}
|
|
140
|
+
</div>
|
|
141
|
+
);
|
|
135
142
|
};
|
|
136
143
|
|
|
137
144
|
type MountedChild = {
|
|
@@ -146,6 +153,7 @@ export const AnimatePresence = ({
|
|
|
146
153
|
animationStyles,
|
|
147
154
|
skipInitialAnimation,
|
|
148
155
|
mode = "wait",
|
|
156
|
+
className,
|
|
149
157
|
}: AnimatePresenceProps) => {
|
|
150
158
|
const [mountedChildren, setMountedChildren] = useState<MountedChild[]>([]);
|
|
151
159
|
const previousChildrenRef = useRef<React.ReactElement[]>([]);
|
|
@@ -250,6 +258,7 @@ export const AnimatePresence = ({
|
|
|
250
258
|
isPresent={isPresent}
|
|
251
259
|
skipInitialAnimation={skipInitialAnimation}
|
|
252
260
|
onExitComplete={() => removeChild(child.key?.toString() ?? "")}
|
|
261
|
+
className={className}
|
|
253
262
|
>
|
|
254
263
|
{child}
|
|
255
264
|
</PresenceChild>
|
|
@@ -140,6 +140,7 @@ export const Avatar = React.memo(
|
|
|
140
140
|
backgroundColor,
|
|
141
141
|
variant = "normal",
|
|
142
142
|
children,
|
|
143
|
+
className,
|
|
143
144
|
...props
|
|
144
145
|
}: AvatarProps,
|
|
145
146
|
forwardedRef: React.ForwardedRef<HTMLDivElement>
|
|
@@ -153,6 +154,7 @@ export const Avatar = React.memo(
|
|
|
153
154
|
size={size}
|
|
154
155
|
backgroundColor={getBackgroundColor}
|
|
155
156
|
variant={variant}
|
|
157
|
+
className={className}
|
|
156
158
|
{...props}
|
|
157
159
|
>
|
|
158
160
|
{image ? (
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
|
+
import { useLabel } from "../hooks/useLabel";
|
|
2
3
|
import { cx } from "../utils/classNames";
|
|
3
4
|
|
|
4
5
|
interface CheckboxProps
|
|
@@ -17,6 +18,7 @@ export const Checkbox = React.memo(
|
|
|
17
18
|
{ className, style, colorScheme = "secondary", ...props },
|
|
18
19
|
ref
|
|
19
20
|
) {
|
|
21
|
+
const { fieldId: id } = useLabel({ fieldId: props.id });
|
|
20
22
|
return (
|
|
21
23
|
<div
|
|
22
24
|
className={cx(
|
|
@@ -47,8 +49,10 @@ export const Checkbox = React.memo(
|
|
|
47
49
|
focus:ring-offset-1
|
|
48
50
|
focus:z-interactable
|
|
49
51
|
transition-all
|
|
50
|
-
|
|
52
|
+
`}
|
|
53
|
+
aria-checked={props.checked}
|
|
51
54
|
{...props}
|
|
55
|
+
id={id}
|
|
52
56
|
/>
|
|
53
57
|
<svg
|
|
54
58
|
className={`
|
|
@@ -66,6 +70,7 @@ export const Checkbox = React.memo(
|
|
|
66
70
|
viewBox="0 0 16 16"
|
|
67
71
|
fill="none"
|
|
68
72
|
xmlns="http://www.w3.org/2000/svg"
|
|
73
|
+
aria-hidden="true"
|
|
69
74
|
>
|
|
70
75
|
<path
|
|
71
76
|
d="M3 6L4.5 7.5L8 4"
|
|
@@ -11,6 +11,7 @@ import React, {
|
|
|
11
11
|
useRef,
|
|
12
12
|
useState,
|
|
13
13
|
} from "react";
|
|
14
|
+
import { useLabel } from "../hooks/useLabel";
|
|
14
15
|
import { cx } from "../utils/classNames";
|
|
15
16
|
import {
|
|
16
17
|
ComboboxItem,
|
|
@@ -78,14 +79,13 @@ export interface ComboboxRef {
|
|
|
78
79
|
export const Combobox = memo(
|
|
79
80
|
forwardRef(function Combobox(
|
|
80
81
|
{
|
|
81
|
-
id,
|
|
82
82
|
loading,
|
|
83
83
|
placeholder,
|
|
84
84
|
items,
|
|
85
85
|
size,
|
|
86
86
|
style,
|
|
87
87
|
className,
|
|
88
|
-
label,
|
|
88
|
+
label: labelProp,
|
|
89
89
|
children,
|
|
90
90
|
hideMenuWhenEmptyValue = false,
|
|
91
91
|
readOnly = false,
|
|
@@ -154,6 +154,9 @@ export const Combobox = memo(
|
|
|
154
154
|
const [isFocused, setIsFocused] = useState(false);
|
|
155
155
|
const listRef = useRef<ListView.VirtualizedList>(null);
|
|
156
156
|
const [shouldBlur, setShouldBlur] = useState(false);
|
|
157
|
+
const { fieldId: id } = useLabel({
|
|
158
|
+
fieldId: rest.id,
|
|
159
|
+
});
|
|
157
160
|
|
|
158
161
|
// Keep state in sync with items prop
|
|
159
162
|
useEffect(() => {
|
|
@@ -500,11 +503,10 @@ export const Combobox = memo(
|
|
|
500
503
|
)}
|
|
501
504
|
{children}
|
|
502
505
|
{loading && (
|
|
503
|
-
<InputField.Label>
|
|
506
|
+
<InputField.Label htmlFor={id}>
|
|
504
507
|
<ActivityIndicator />
|
|
505
508
|
</InputField.Label>
|
|
506
509
|
)}
|
|
507
|
-
<InputField.Label>{label}</InputField.Label>
|
|
508
510
|
{!readOnly && (
|
|
509
511
|
<InputField.Button variant="floating" onClick={handleChevronClick}>
|
|
510
512
|
<DropdownChevronIcon
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import { Size } from "@noya-app/noya-geometry";
|
|
2
|
+
import { CheckIcon } from "@noya-app/noya-icons";
|
|
2
3
|
import React, { ForwardedRef, forwardRef, memo } from "react";
|
|
3
4
|
import { cx } from "../utils/classNames";
|
|
4
5
|
import { InternalComboboxItem } from "../utils/combobox";
|
|
5
6
|
import { fuzzyTokenize } from "../utils/fuzzyScorer";
|
|
7
|
+
import { renderIcon } from "./Icons";
|
|
6
8
|
import { IVirtualizedList, ListView } from "./ListView";
|
|
7
9
|
import { Spacer } from "./Spacer";
|
|
10
|
+
import { KeyboardShortcut, styles } from "./internal/Menu";
|
|
8
11
|
|
|
9
12
|
interface ComboboxMenuProps {
|
|
10
13
|
items: InternalComboboxItem[];
|
|
@@ -55,28 +58,40 @@ export const ComboboxMenu = memo(
|
|
|
55
58
|
<ListView.Row
|
|
56
59
|
key={item.id}
|
|
57
60
|
selected={i === selectedIndex}
|
|
58
|
-
|
|
61
|
+
disabled={item.disabled}
|
|
62
|
+
onPress={() => !item.disabled && onSelectItem(item)}
|
|
59
63
|
onHoverChange={(hovered) => {
|
|
60
|
-
if (hovered) {
|
|
64
|
+
if (hovered && !item.disabled) {
|
|
61
65
|
onHoverIndex(i);
|
|
62
66
|
}
|
|
63
67
|
}}
|
|
64
68
|
>
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
69
|
+
<div className="flex flex-1 items-center">
|
|
70
|
+
{item.checked && (
|
|
71
|
+
<div className={styles.itemIndicatorStyle}>
|
|
72
|
+
<CheckIcon />
|
|
73
|
+
</div>
|
|
74
|
+
)}
|
|
75
|
+
{tokens.map((token, j) => (
|
|
76
|
+
<span
|
|
77
|
+
key={`${item.id}-token-${j}`}
|
|
78
|
+
className={cx(
|
|
79
|
+
token.type === "match" ? "font-bold" : "font-normal",
|
|
80
|
+
"whitespace-pre"
|
|
81
|
+
)}
|
|
82
|
+
>
|
|
83
|
+
{token.text}
|
|
84
|
+
</span>
|
|
85
|
+
))}
|
|
86
|
+
</div>
|
|
87
|
+
{(item.shortcut || item.icon) && (
|
|
77
88
|
<>
|
|
78
89
|
<Spacer.Horizontal />
|
|
79
|
-
{item.
|
|
90
|
+
{item.shortcut ? (
|
|
91
|
+
<KeyboardShortcut shortcut={item.shortcut} />
|
|
92
|
+
) : (
|
|
93
|
+
item.icon && renderIcon(item.icon)
|
|
94
|
+
)}
|
|
80
95
|
</>
|
|
81
96
|
)}
|
|
82
97
|
</ListView.Row>
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { Dialog } from "@noya-app/noya-designsystem";
|
|
2
|
+
import { AutoSizer } from "@noya-app/react-utils";
|
|
3
|
+
import React, { memo } from "react";
|
|
4
|
+
import {
|
|
5
|
+
ISearchCompletionMenu,
|
|
6
|
+
SearchCompletionMenu,
|
|
7
|
+
} from "./SearchCompletionMenu";
|
|
8
|
+
|
|
9
|
+
export const CommandPalette = memo(function CommandPalette({
|
|
10
|
+
showCommandPalette,
|
|
11
|
+
setShowCommandPalette,
|
|
12
|
+
items,
|
|
13
|
+
onSelect,
|
|
14
|
+
onHover,
|
|
15
|
+
}: {
|
|
16
|
+
showCommandPalette: boolean;
|
|
17
|
+
setShowCommandPalette: (value: boolean) => void;
|
|
18
|
+
} & Pick<
|
|
19
|
+
React.ComponentProps<typeof SearchCompletionMenu>,
|
|
20
|
+
"items" | "onSelect" | "onHover"
|
|
21
|
+
>) {
|
|
22
|
+
const menuRef = React.useRef<ISearchCompletionMenu>(null);
|
|
23
|
+
|
|
24
|
+
const handleClose = React.useCallback(() => {
|
|
25
|
+
setShowCommandPalette(false);
|
|
26
|
+
}, [setShowCommandPalette]);
|
|
27
|
+
|
|
28
|
+
return (
|
|
29
|
+
<Dialog
|
|
30
|
+
style={{
|
|
31
|
+
display: "flex",
|
|
32
|
+
flexDirection: "column",
|
|
33
|
+
maxWidth: "500px",
|
|
34
|
+
minHeight: "100px",
|
|
35
|
+
padding: "1px",
|
|
36
|
+
top: "100px",
|
|
37
|
+
left: "50%",
|
|
38
|
+
transform: "translate(-50%, 0)",
|
|
39
|
+
}}
|
|
40
|
+
showCloseButton={false}
|
|
41
|
+
open={showCommandPalette}
|
|
42
|
+
onOpenChange={(value) => {
|
|
43
|
+
setShowCommandPalette(value);
|
|
44
|
+
}}
|
|
45
|
+
closeOnInteractOutside
|
|
46
|
+
onOpenAutoFocus={() => {
|
|
47
|
+
menuRef.current?.focus();
|
|
48
|
+
setTimeout(() => {
|
|
49
|
+
menuRef.current?.focus();
|
|
50
|
+
}, 0);
|
|
51
|
+
}}
|
|
52
|
+
>
|
|
53
|
+
<div className="flex flex-col flex-1">
|
|
54
|
+
<AutoSizer>
|
|
55
|
+
{(size) => (
|
|
56
|
+
<SearchCompletionMenu
|
|
57
|
+
ref={menuRef}
|
|
58
|
+
width={size.width}
|
|
59
|
+
items={items}
|
|
60
|
+
onSelect={onSelect}
|
|
61
|
+
onHover={onHover}
|
|
62
|
+
onClose={handleClose}
|
|
63
|
+
/>
|
|
64
|
+
)}
|
|
65
|
+
</AutoSizer>
|
|
66
|
+
</div>
|
|
67
|
+
</Dialog>
|
|
68
|
+
);
|
|
69
|
+
});
|
|
@@ -1,19 +1,13 @@
|
|
|
1
|
-
import { CheckIcon, ChevronRightIcon } from "@noya-app/noya-icons";
|
|
2
1
|
import { useKeyboardShortcuts } from "@noya-app/noya-keymap";
|
|
3
2
|
import { memoGeneric } from "@noya-app/react-utils";
|
|
4
3
|
import * as RadixContextMenu from "@radix-ui/react-context-menu";
|
|
5
4
|
import React, { ReactNode, useCallback, useMemo } from "react";
|
|
6
|
-
import { renderIcon } from "./Icons";
|
|
7
|
-
import { Spacer } from "./Spacer";
|
|
8
5
|
import {
|
|
9
|
-
CHECKBOX_RIGHT_INSET,
|
|
10
|
-
CHECKBOX_WIDTH,
|
|
11
|
-
KeyboardShortcut,
|
|
12
6
|
MenuItem,
|
|
13
|
-
SEPARATOR_ITEM,
|
|
14
7
|
getKeyboardShortcutsForMenuItems,
|
|
15
8
|
styles,
|
|
16
9
|
} from "./internal/Menu";
|
|
10
|
+
import { MenuViewport } from "./internal/MenuViewport";
|
|
17
11
|
|
|
18
12
|
export interface MenuItemProps<T extends string> {
|
|
19
13
|
value?: T;
|
|
@@ -27,90 +21,17 @@ export interface MenuItemProps<T extends string> {
|
|
|
27
21
|
items?: MenuItem<T>[];
|
|
28
22
|
}
|
|
29
23
|
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
}
|
|
41
|
-
// The pointer event within the context menu will bubble outside of the
|
|
42
|
-
// context menu unless we stop it here.
|
|
43
|
-
const handlePointerDown = useCallback(
|
|
44
|
-
(event: React.PointerEvent) => event.stopPropagation(),
|
|
45
|
-
[]
|
|
46
|
-
);
|
|
47
|
-
|
|
48
|
-
const handleSelectItem = useCallback(() => {
|
|
49
|
-
if (!value) return;
|
|
50
|
-
|
|
51
|
-
onSelect(value);
|
|
52
|
-
}, [onSelect, value]);
|
|
53
|
-
|
|
54
|
-
if (checked) {
|
|
55
|
-
return (
|
|
56
|
-
<RadixContextMenu.CheckboxItem
|
|
57
|
-
checked={checked}
|
|
58
|
-
disabled={disabled}
|
|
59
|
-
onSelect={handleSelectItem}
|
|
60
|
-
className={styles.itemStyle({ disabled })}
|
|
61
|
-
>
|
|
62
|
-
<RadixContextMenu.ItemIndicator className={styles.itemIndicatorStyle}>
|
|
63
|
-
<CheckIcon />
|
|
64
|
-
</RadixContextMenu.ItemIndicator>
|
|
65
|
-
{children}
|
|
66
|
-
</RadixContextMenu.CheckboxItem>
|
|
67
|
-
);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
const element = (
|
|
71
|
-
<RadixContextMenu.Item
|
|
72
|
-
className={styles.itemStyle({ disabled })}
|
|
73
|
-
disabled={disabled}
|
|
74
|
-
onSelect={handleSelectItem}
|
|
75
|
-
onPointerDown={handlePointerDown}
|
|
76
|
-
>
|
|
77
|
-
{indented && (
|
|
78
|
-
<Spacer.Horizontal size={CHECKBOX_WIDTH - CHECKBOX_RIGHT_INSET} />
|
|
79
|
-
)}
|
|
80
|
-
{icon && (
|
|
81
|
-
<>
|
|
82
|
-
{renderIcon(icon)}
|
|
83
|
-
<Spacer.Horizontal size={8} />
|
|
84
|
-
</>
|
|
85
|
-
)}
|
|
86
|
-
{children}
|
|
87
|
-
{shortcut && (
|
|
88
|
-
<>
|
|
89
|
-
<Spacer.Horizontal />
|
|
90
|
-
<Spacer.Horizontal size={24} />
|
|
91
|
-
<KeyboardShortcut shortcut={shortcut} />
|
|
92
|
-
</>
|
|
93
|
-
)}
|
|
94
|
-
{items && items.length > 0 && (
|
|
95
|
-
<>
|
|
96
|
-
<Spacer.Horizontal />
|
|
97
|
-
<Spacer.Horizontal size={16} />
|
|
98
|
-
<ChevronRightIcon />
|
|
99
|
-
</>
|
|
100
|
-
)}
|
|
101
|
-
</RadixContextMenu.Item>
|
|
102
|
-
);
|
|
103
|
-
|
|
104
|
-
if (items && items.length > 0) {
|
|
105
|
-
return (
|
|
106
|
-
<ContextMenuRoot isNested items={items} onSelect={onSelect}>
|
|
107
|
-
{element}
|
|
108
|
-
</ContextMenuRoot>
|
|
109
|
-
);
|
|
110
|
-
} else {
|
|
111
|
-
return element;
|
|
112
|
-
}
|
|
113
|
-
});
|
|
24
|
+
const Components = {
|
|
25
|
+
Separator: RadixContextMenu.Separator,
|
|
26
|
+
ItemText: React.Fragment,
|
|
27
|
+
Item: RadixContextMenu.Item,
|
|
28
|
+
CheckboxItem: RadixContextMenu.CheckboxItem,
|
|
29
|
+
ItemIndicator: RadixContextMenu.ItemIndicator,
|
|
30
|
+
SubTrigger: RadixContextMenu.SubTrigger,
|
|
31
|
+
SubContent: RadixContextMenu.SubContent,
|
|
32
|
+
Sub: RadixContextMenu.Sub,
|
|
33
|
+
Portal: RadixContextMenu.Portal,
|
|
34
|
+
};
|
|
114
35
|
|
|
115
36
|
/* ----------------------------------------------------------------------------
|
|
116
37
|
* Root
|
|
@@ -120,7 +41,6 @@ export interface MenuProps<T extends string> {
|
|
|
120
41
|
children: ReactNode;
|
|
121
42
|
items: MenuItem<T>[];
|
|
122
43
|
onSelect: (value: T) => void;
|
|
123
|
-
isNested?: boolean;
|
|
124
44
|
shouldBindKeyboardShortcuts?: boolean;
|
|
125
45
|
onOpenChange?: (open: boolean) => void;
|
|
126
46
|
}
|
|
@@ -129,20 +49,15 @@ function ContextMenuRoot<T extends string>({
|
|
|
129
49
|
items,
|
|
130
50
|
children,
|
|
131
51
|
onSelect,
|
|
132
|
-
isNested,
|
|
133
52
|
shouldBindKeyboardShortcuts,
|
|
134
53
|
onOpenChange,
|
|
135
54
|
}: MenuProps<T>) {
|
|
136
|
-
const hasCheckedItem = items.some(
|
|
137
|
-
(item) => item !== SEPARATOR_ITEM && item.checked
|
|
138
|
-
);
|
|
139
|
-
|
|
140
55
|
const keymap = useMemo(
|
|
141
56
|
() =>
|
|
142
|
-
|
|
57
|
+
shouldBindKeyboardShortcuts === false
|
|
143
58
|
? {}
|
|
144
59
|
: getKeyboardShortcutsForMenuItems(items, onSelect),
|
|
145
|
-
[
|
|
60
|
+
[items, onSelect, shouldBindKeyboardShortcuts]
|
|
146
61
|
);
|
|
147
62
|
|
|
148
63
|
useKeyboardShortcuts(keymap);
|
|
@@ -155,46 +70,30 @@ function ContextMenuRoot<T extends string>({
|
|
|
155
70
|
event.preventDefault();
|
|
156
71
|
}, []);
|
|
157
72
|
|
|
158
|
-
const
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
73
|
+
const handleSelectItem = useCallback(
|
|
74
|
+
(value: T) => {
|
|
75
|
+
if (!value) return;
|
|
76
|
+
|
|
77
|
+
onSelect(value);
|
|
78
|
+
},
|
|
79
|
+
[onSelect]
|
|
80
|
+
);
|
|
165
81
|
|
|
166
82
|
return (
|
|
167
|
-
<
|
|
168
|
-
<
|
|
83
|
+
<RadixContextMenu.Root onOpenChange={onOpenChange}>
|
|
84
|
+
<RadixContextMenu.Trigger asChild onPointerDown={onPointerDown}>
|
|
169
85
|
{children}
|
|
170
|
-
</
|
|
86
|
+
</RadixContextMenu.Trigger>
|
|
171
87
|
<RadixContextMenu.Portal>
|
|
172
|
-
<
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
) : (
|
|
180
|
-
<ContextMenuItem
|
|
181
|
-
key={item.value ?? index}
|
|
182
|
-
value={item.value}
|
|
183
|
-
indented={hasCheckedItem}
|
|
184
|
-
checked={item.checked ?? false}
|
|
185
|
-
disabled={item.disabled ?? false}
|
|
186
|
-
icon={item.icon}
|
|
187
|
-
onSelect={onSelect}
|
|
188
|
-
items={item.items}
|
|
189
|
-
shortcut={item.shortcut}
|
|
190
|
-
>
|
|
191
|
-
{item.title}
|
|
192
|
-
</ContextMenuItem>
|
|
193
|
-
)
|
|
194
|
-
)}
|
|
195
|
-
</ContentComponent>
|
|
88
|
+
<RadixContextMenu.Content className={styles.contentStyle}>
|
|
89
|
+
<MenuViewport
|
|
90
|
+
items={items}
|
|
91
|
+
Components={Components}
|
|
92
|
+
onSelect={handleSelectItem}
|
|
93
|
+
/>
|
|
94
|
+
</RadixContextMenu.Content>
|
|
196
95
|
</RadixContextMenu.Portal>
|
|
197
|
-
</
|
|
96
|
+
</RadixContextMenu.Root>
|
|
198
97
|
);
|
|
199
98
|
}
|
|
200
99
|
|