@noya-app/noya-designsystem 0.1.43 → 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 +12 -9
- package/CHANGELOG.md +15 -0
- package/dist/index.css +1 -1
- package/dist/index.d.mts +323 -80
- package/dist/index.d.ts +323 -80
- package/dist/index.js +2126 -1373
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +2012 -1272
- 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 +28 -14
- 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 +108 -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,
|
|
@@ -45,7 +46,7 @@ export type BaseComboboxProps = {
|
|
|
45
46
|
|
|
46
47
|
export type StringModeOnlyProps = {
|
|
47
48
|
mode: "string";
|
|
48
|
-
|
|
49
|
+
value?: string;
|
|
49
50
|
onChange?: (value: string) => void;
|
|
50
51
|
onSelectItem?: (value: string) => void;
|
|
51
52
|
onBlur?: (didSubmit: boolean, value: string) => void;
|
|
@@ -57,7 +58,7 @@ export type StringModeProps = BaseComboboxProps & StringModeOnlyProps;
|
|
|
57
58
|
|
|
58
59
|
export type OptionModeOnlyProps = {
|
|
59
60
|
mode?: "option";
|
|
60
|
-
|
|
61
|
+
value?: ComboboxOption;
|
|
61
62
|
onChange?: (value: ComboboxOption) => void;
|
|
62
63
|
onSelectItem?: (value: ComboboxOption) => void;
|
|
63
64
|
onBlur?: (didSubmit: boolean, value: ComboboxOption) => void;
|
|
@@ -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,
|
|
@@ -103,7 +103,7 @@ export const Combobox = memo(
|
|
|
103
103
|
if (rest.mode === "string") {
|
|
104
104
|
return {
|
|
105
105
|
mode: rest.mode,
|
|
106
|
-
|
|
106
|
+
value: rest.value,
|
|
107
107
|
onChange: rest.onChange,
|
|
108
108
|
onSelectItem: rest.onSelectItem,
|
|
109
109
|
onHoverItem: rest.onHoverItem,
|
|
@@ -113,7 +113,7 @@ export const Combobox = memo(
|
|
|
113
113
|
} else {
|
|
114
114
|
return {
|
|
115
115
|
mode: rest.mode,
|
|
116
|
-
|
|
116
|
+
value: rest.value,
|
|
117
117
|
onChange: rest.onChange,
|
|
118
118
|
onSelectItem: rest.onSelectItem,
|
|
119
119
|
onHoverItem: rest.onHoverItem,
|
|
@@ -122,7 +122,7 @@ export const Combobox = memo(
|
|
|
122
122
|
}
|
|
123
123
|
}, [
|
|
124
124
|
rest.mode,
|
|
125
|
-
rest.
|
|
125
|
+
rest.value,
|
|
126
126
|
rest.onChange,
|
|
127
127
|
rest.onSelectItem,
|
|
128
128
|
rest.onHoverItem,
|
|
@@ -131,10 +131,17 @@ export const Combobox = memo(
|
|
|
131
131
|
]);
|
|
132
132
|
|
|
133
133
|
const ref = useRef<HTMLInputElement>(null);
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
134
|
+
|
|
135
|
+
function getInitialValueString() {
|
|
136
|
+
if (props.mode === "string") {
|
|
137
|
+
return props.value ?? "";
|
|
138
|
+
} else {
|
|
139
|
+
return props.value?.name ?? "";
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const initialValueString = getInitialValueString();
|
|
144
|
+
|
|
138
145
|
const [comboboxState] = useState(
|
|
139
146
|
() =>
|
|
140
147
|
new ComboboxState(
|
|
@@ -147,12 +154,19 @@ export const Combobox = memo(
|
|
|
147
154
|
const [isFocused, setIsFocused] = useState(false);
|
|
148
155
|
const listRef = useRef<ListView.VirtualizedList>(null);
|
|
149
156
|
const [shouldBlur, setShouldBlur] = useState(false);
|
|
157
|
+
const { fieldId: id } = useLabel({
|
|
158
|
+
fieldId: rest.id,
|
|
159
|
+
});
|
|
150
160
|
|
|
151
161
|
// Keep state in sync with items prop
|
|
152
162
|
useEffect(() => {
|
|
153
163
|
comboboxState.setItems(items);
|
|
154
164
|
}, [items, comboboxState]);
|
|
155
165
|
|
|
166
|
+
useEffect(() => {
|
|
167
|
+
comboboxState.setFilter(initialValueString);
|
|
168
|
+
}, [comboboxState, initialValueString]);
|
|
169
|
+
|
|
156
170
|
const { filter, selectedIndex, filteredItems } = state;
|
|
157
171
|
|
|
158
172
|
const shouldShowMenu =
|
|
@@ -401,9 +415,10 @@ export const Combobox = memo(
|
|
|
401
415
|
|
|
402
416
|
useEffect(() => {
|
|
403
417
|
if (!shouldBlur) return;
|
|
418
|
+
if (document.activeElement !== ref.current) return;
|
|
419
|
+
setShouldBlur(false);
|
|
404
420
|
ref.current?.focus();
|
|
405
421
|
handleBlur();
|
|
406
|
-
setShouldBlur(false);
|
|
407
422
|
}, [shouldBlur, handleBlur]);
|
|
408
423
|
|
|
409
424
|
return (
|
|
@@ -488,11 +503,10 @@ export const Combobox = memo(
|
|
|
488
503
|
)}
|
|
489
504
|
{children}
|
|
490
505
|
{loading && (
|
|
491
|
-
<InputField.Label>
|
|
506
|
+
<InputField.Label htmlFor={id}>
|
|
492
507
|
<ActivityIndicator />
|
|
493
508
|
</InputField.Label>
|
|
494
509
|
)}
|
|
495
|
-
<InputField.Label>{label}</InputField.Label>
|
|
496
510
|
{!readOnly && (
|
|
497
511
|
<InputField.Button variant="floating" onClick={handleChevronClick}>
|
|
498
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
|
|