@noya-app/noya-designsystem 0.1.45 → 0.1.47
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 +10 -10
- package/CHANGELOG.md +15 -0
- package/dist/index.css +1 -1
- package/dist/index.d.mts +1149 -1064
- package/dist/index.d.ts +1149 -1064
- package/dist/index.js +10161 -9662
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +10465 -9972
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -3
- package/src/__tests__/combobox.test.ts +137 -89
- package/src/components/Breadcrumbs.tsx +29 -0
- package/src/components/Collection.tsx +74 -0
- package/src/components/Combobox.tsx +69 -52
- package/src/components/ComboboxMenu.tsx +37 -19
- package/src/components/Dialog.tsx +11 -19
- package/src/components/DropdownMenu.tsx +0 -1
- package/src/components/EditableText.tsx +203 -0
- package/src/components/Grid.tsx +243 -0
- package/src/components/GridView.tsx +86 -96
- package/src/components/List.tsx +268 -0
- package/src/components/ListView.tsx +10 -9
- package/src/components/SearchCompletionMenu.tsx +15 -31
- package/src/components/SegmentedControl.tsx +7 -4
- package/src/components/SelectMenu.tsx +9 -5
- package/src/components/Switch.tsx +1 -1
- package/src/components/Toolbar.tsx +5 -4
- package/src/components/TreeView.tsx +1 -0
- package/src/components/WorkspaceLayout.tsx +28 -4
- package/src/components/internal/Menu.tsx +55 -14
- package/src/components/internal/MenuViewport.tsx +78 -77
- package/src/contexts/DialogContext.tsx +53 -7
- package/src/index.css +2 -1
- package/src/index.tsx +6 -4
- package/src/utils/combobox.ts +115 -103
- package/src/utils/createSectionedMenu.ts +3 -9
- package/src/utils/fuzzyScorer.ts +11 -8
- package/src/utils/selection.ts +16 -4
- package/src/components/SidebarList.tsx +0 -252
|
@@ -2,15 +2,12 @@ import { getShortcutDisplayParts } from "@noya-app/noya-keymap";
|
|
|
2
2
|
import React, { memo, ReactNode } from "react";
|
|
3
3
|
import { useDesignSystemConfiguration } from "../../contexts/DesignSystemConfiguration";
|
|
4
4
|
import { cx } from "../../utils/classNames";
|
|
5
|
+
import { IScoredItem } from "../../utils/fuzzyScorer";
|
|
5
6
|
import withSeparatorElements from "../../utils/withSeparatorElements";
|
|
6
7
|
import { IconName } from "../Icons";
|
|
7
8
|
import { Spacer } from "../Spacer";
|
|
8
9
|
import { textStyles } from "../Text";
|
|
9
10
|
|
|
10
|
-
export type SeparatorItem = {
|
|
11
|
-
type: "separator";
|
|
12
|
-
};
|
|
13
|
-
|
|
14
11
|
// From electron
|
|
15
12
|
export type MenuItemRole =
|
|
16
13
|
| "undo"
|
|
@@ -61,35 +58,79 @@ export type MenuItemRole =
|
|
|
61
58
|
| "moveTabToNewWindow"
|
|
62
59
|
| "windowMenu";
|
|
63
60
|
|
|
64
|
-
export type
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
61
|
+
export type SeparatorItem = {
|
|
62
|
+
type: "separator";
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
type BaseMenuItem = {
|
|
68
66
|
checked?: boolean;
|
|
69
67
|
disabled?: boolean;
|
|
70
68
|
icon?: Exclude<ReactNode, string> | IconName;
|
|
71
|
-
items?: MenuItem<T>[];
|
|
72
69
|
role?: MenuItemRole;
|
|
73
|
-
type?: undefined;
|
|
74
70
|
alwaysInclude?: boolean;
|
|
71
|
+
title: NonNullable<ReactNode>;
|
|
75
72
|
};
|
|
76
73
|
|
|
77
74
|
export type SectionHeaderMenuItem = {
|
|
78
75
|
type: "sectionHeader";
|
|
79
76
|
id: string;
|
|
80
|
-
title:
|
|
77
|
+
title: ReactNode;
|
|
81
78
|
maxVisibleItems?: number;
|
|
82
79
|
variant?: "normal" | "label";
|
|
83
80
|
};
|
|
84
81
|
|
|
82
|
+
export type SelectableMenuItem<T extends string> = BaseMenuItem & {
|
|
83
|
+
type?: undefined;
|
|
84
|
+
shortcut?: string;
|
|
85
|
+
value: T;
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
type SubMenuItem<T extends string> = BaseMenuItem & {
|
|
89
|
+
type: "submenu";
|
|
90
|
+
items: MenuItem<T>[];
|
|
91
|
+
id: string;
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
export type NonSelectableMenuItem<T extends string> =
|
|
95
|
+
| SeparatorItem
|
|
96
|
+
| SectionHeaderMenuItem
|
|
97
|
+
| SubMenuItem<T>;
|
|
98
|
+
|
|
85
99
|
export type MenuItem<T extends string> =
|
|
86
100
|
| SeparatorItem
|
|
87
|
-
|
|
|
101
|
+
| SelectableMenuItem<T>
|
|
102
|
+
| SectionHeaderMenuItem
|
|
103
|
+
| SubMenuItem<T>;
|
|
104
|
+
|
|
105
|
+
type ScoredSelectableMenuItem<T extends string> = SelectableMenuItem<T> &
|
|
106
|
+
IScoredItem;
|
|
107
|
+
|
|
108
|
+
export type ScoredMenuItem<T extends string> =
|
|
109
|
+
| ScoredSelectableMenuItem<T>
|
|
88
110
|
| SectionHeaderMenuItem;
|
|
89
111
|
|
|
90
112
|
// Extract type T of RegularMenuItem<T> within MenuItem<T>
|
|
91
113
|
export type ExtractMenuItemType<T> =
|
|
92
|
-
T extends
|
|
114
|
+
T extends SelectableMenuItem<infer U> ? U : never;
|
|
115
|
+
|
|
116
|
+
export const isSelectableMenuItem = <T extends string>(
|
|
117
|
+
item: MenuItem<T>
|
|
118
|
+
): item is SelectableMenuItem<T> => {
|
|
119
|
+
return item.type === undefined;
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
export const isNonSelectableMenuItem = <T extends string>(
|
|
123
|
+
item: MenuItem<T>
|
|
124
|
+
): item is NonSelectableMenuItem<T> => !isSelectableMenuItem(item);
|
|
125
|
+
|
|
126
|
+
export const isMenuItemSectionHeader = (
|
|
127
|
+
item: MenuItem<string>
|
|
128
|
+
): item is SectionHeaderMenuItem => item.type === "sectionHeader";
|
|
129
|
+
|
|
130
|
+
export const isSelectableMenuItemWithScore = (
|
|
131
|
+
item: MenuItem<string>
|
|
132
|
+
): item is ScoredSelectableMenuItem<string> =>
|
|
133
|
+
isSelectableMenuItem(item) && "index" in item && "score" in item;
|
|
93
134
|
|
|
94
135
|
export const CHECKBOX_WIDTH = 16;
|
|
95
136
|
export const CHECKBOX_RIGHT_INSET = 8;
|
|
@@ -132,7 +173,7 @@ function getKeyboardShortcuts<T extends string>(
|
|
|
132
173
|
if (item.type === "separator") return [];
|
|
133
174
|
if (item.type === "sectionHeader") return [];
|
|
134
175
|
|
|
135
|
-
if (item.
|
|
176
|
+
if (item.type === "submenu") return item.items.flatMap(getKeyboardShortcuts);
|
|
136
177
|
|
|
137
178
|
if (item.disabled || !item.value || !item.shortcut) return [];
|
|
138
179
|
|
|
@@ -65,86 +65,87 @@ export const MenuViewport = <T extends string>({
|
|
|
65
65
|
return (
|
|
66
66
|
<>
|
|
67
67
|
{items.map((item, index) => {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
if (item.type === "sectionHeader") {
|
|
78
|
-
return (
|
|
79
|
-
<SectionHeader
|
|
80
|
-
isFirst={index === 0}
|
|
81
|
-
key={item.id}
|
|
82
|
-
{...item}
|
|
83
|
-
indented={hasCheckedItem}
|
|
84
|
-
/>
|
|
85
|
-
);
|
|
86
|
-
}
|
|
68
|
+
switch (item.type) {
|
|
69
|
+
case "separator":
|
|
70
|
+
return (
|
|
71
|
+
<Components.Separator
|
|
72
|
+
key={index}
|
|
73
|
+
className={styles.separatorStyle}
|
|
74
|
+
/>
|
|
75
|
+
);
|
|
87
76
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
77
|
+
case "sectionHeader":
|
|
78
|
+
return (
|
|
79
|
+
<SectionHeader
|
|
80
|
+
isFirst={index === 0}
|
|
81
|
+
key={item.id}
|
|
82
|
+
{...item}
|
|
83
|
+
indented={hasCheckedItem}
|
|
84
|
+
/>
|
|
85
|
+
);
|
|
86
|
+
case "submenu":
|
|
87
|
+
if (
|
|
88
|
+
item.items &&
|
|
89
|
+
Components.Sub &&
|
|
90
|
+
Components.SubTrigger &&
|
|
91
|
+
Components.SubContent &&
|
|
92
|
+
Components.Portal
|
|
93
|
+
) {
|
|
94
|
+
return (
|
|
95
|
+
<Components.Sub key={item.id}>
|
|
96
|
+
<Components.SubTrigger
|
|
97
|
+
className={styles.itemStyle({ disabled: item.disabled })}
|
|
98
|
+
asChild
|
|
99
|
+
onClick={(e) => {
|
|
100
|
+
e.stopPropagation();
|
|
101
|
+
e.preventDefault();
|
|
102
|
+
}}
|
|
103
|
+
>
|
|
104
|
+
<SelectItem
|
|
105
|
+
value={item.id}
|
|
106
|
+
icon={item.icon}
|
|
107
|
+
checked={item.checked}
|
|
108
|
+
onSelect={onSelect}
|
|
109
|
+
Components={Components}
|
|
110
|
+
indented={hasCheckedItem}
|
|
111
|
+
>
|
|
112
|
+
<div className="flex items-center flex-1">
|
|
113
|
+
{item.title}
|
|
114
|
+
</div>
|
|
115
|
+
<ChevronRightIcon className="-mr-1" />
|
|
116
|
+
</SelectItem>
|
|
117
|
+
</Components.SubTrigger>
|
|
118
|
+
<Components.Portal>
|
|
119
|
+
<Components.SubContent
|
|
120
|
+
alignOffset={-5}
|
|
121
|
+
className={styles.contentStyle}
|
|
122
|
+
>
|
|
123
|
+
<MenuViewport
|
|
124
|
+
items={item.items}
|
|
125
|
+
Components={Components}
|
|
126
|
+
onSelect={onSelect}
|
|
127
|
+
/>
|
|
128
|
+
</Components.SubContent>
|
|
129
|
+
</Components.Portal>
|
|
130
|
+
</Components.Sub>
|
|
131
|
+
);
|
|
132
|
+
} else return null;
|
|
133
|
+
default:
|
|
134
|
+
return (
|
|
135
|
+
<SelectItem
|
|
136
|
+
key={item.value}
|
|
137
|
+
value={item.value}
|
|
138
|
+
icon={item.icon}
|
|
139
|
+
checked={item.checked}
|
|
140
|
+
shortcut={item.shortcut}
|
|
141
|
+
onSelect={onSelect}
|
|
142
|
+
Components={Components}
|
|
143
|
+
indented={hasCheckedItem}
|
|
102
144
|
>
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
checked={item.checked}
|
|
107
|
-
shortcut={item.shortcut}
|
|
108
|
-
onSelect={onSelect}
|
|
109
|
-
Components={Components}
|
|
110
|
-
indented={hasCheckedItem}
|
|
111
|
-
>
|
|
112
|
-
<div className="flex items-center flex-1">
|
|
113
|
-
{item.title ?? value}
|
|
114
|
-
</div>
|
|
115
|
-
<ChevronRightIcon className="-mr-1" />
|
|
116
|
-
</SelectItem>
|
|
117
|
-
</Components.SubTrigger>
|
|
118
|
-
<Components.Portal>
|
|
119
|
-
<Components.SubContent
|
|
120
|
-
alignOffset={-5}
|
|
121
|
-
className={styles.contentStyle}
|
|
122
|
-
>
|
|
123
|
-
<MenuViewport
|
|
124
|
-
items={item.items}
|
|
125
|
-
Components={Components}
|
|
126
|
-
onSelect={onSelect}
|
|
127
|
-
/>
|
|
128
|
-
</Components.SubContent>
|
|
129
|
-
</Components.Portal>
|
|
130
|
-
</Components.Sub>
|
|
131
|
-
);
|
|
145
|
+
{item.title ?? item.value}
|
|
146
|
+
</SelectItem>
|
|
147
|
+
);
|
|
132
148
|
}
|
|
133
|
-
|
|
134
|
-
return (
|
|
135
|
-
<SelectItem
|
|
136
|
-
key={value}
|
|
137
|
-
value={value}
|
|
138
|
-
icon={item.icon}
|
|
139
|
-
checked={item.checked}
|
|
140
|
-
shortcut={item.shortcut}
|
|
141
|
-
onSelect={onSelect}
|
|
142
|
-
Components={Components}
|
|
143
|
-
indented={hasCheckedItem}
|
|
144
|
-
>
|
|
145
|
-
{item.title ?? value}
|
|
146
|
-
</SelectItem>
|
|
147
|
-
);
|
|
148
149
|
})}
|
|
149
150
|
</>
|
|
150
151
|
);
|
|
@@ -25,6 +25,12 @@ function createDeferredPromise<T>() {
|
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
export type DialogContextValue = {
|
|
28
|
+
openDialog(options: {
|
|
29
|
+
title: string;
|
|
30
|
+
description?: ReactNode;
|
|
31
|
+
children: ReactNode;
|
|
32
|
+
}): Promise<void>;
|
|
33
|
+
|
|
28
34
|
openInputDialog(
|
|
29
35
|
options:
|
|
30
36
|
| string
|
|
@@ -66,7 +72,18 @@ type ConfirmationDialogContents = {
|
|
|
66
72
|
resolve: (value: boolean) => void;
|
|
67
73
|
};
|
|
68
74
|
|
|
69
|
-
type
|
|
75
|
+
type CustomDialogContents = {
|
|
76
|
+
type: "custom";
|
|
77
|
+
title: string;
|
|
78
|
+
description?: ReactNode;
|
|
79
|
+
children: ReactNode;
|
|
80
|
+
resolve: () => void;
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
type DialogContents =
|
|
84
|
+
| InputDialogContents
|
|
85
|
+
| ConfirmationDialogContents
|
|
86
|
+
| CustomDialogContents;
|
|
70
87
|
|
|
71
88
|
export const DialogProvider = function DialogProvider({
|
|
72
89
|
children,
|
|
@@ -136,6 +153,26 @@ export const DialogProvider = function DialogProvider({
|
|
|
136
153
|
return promise;
|
|
137
154
|
}, []);
|
|
138
155
|
|
|
156
|
+
const openCustomDialog: DialogContextValue["openDialog"] = useCallback(
|
|
157
|
+
(options) => {
|
|
158
|
+
const { promise, resolve } = createDeferredPromise<void>();
|
|
159
|
+
|
|
160
|
+
setContents({
|
|
161
|
+
type: "custom",
|
|
162
|
+
title: options.title,
|
|
163
|
+
description: options.description,
|
|
164
|
+
children: options.children,
|
|
165
|
+
resolve: () => {
|
|
166
|
+
resolve();
|
|
167
|
+
setContents(undefined);
|
|
168
|
+
},
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
return promise;
|
|
172
|
+
},
|
|
173
|
+
[]
|
|
174
|
+
);
|
|
175
|
+
|
|
139
176
|
const handleKeyDown = useCallback(
|
|
140
177
|
(event: React.KeyboardEvent<HTMLInputElement>) => {
|
|
141
178
|
if (event.key !== "Enter") return;
|
|
@@ -162,11 +199,12 @@ export const DialogProvider = function DialogProvider({
|
|
|
162
199
|
<DialogContext.Provider
|
|
163
200
|
value={useMemo(
|
|
164
201
|
() => ({
|
|
202
|
+
openDialog: openCustomDialog,
|
|
165
203
|
openInputDialog: open,
|
|
166
204
|
openConfirmationDialog: openConfirmation,
|
|
167
205
|
containsElement,
|
|
168
206
|
}),
|
|
169
|
-
[containsElement, open, openConfirmation]
|
|
207
|
+
[containsElement, open, openConfirmation, openCustomDialog]
|
|
170
208
|
)}
|
|
171
209
|
>
|
|
172
210
|
{children}
|
|
@@ -221,12 +259,16 @@ export const DialogProvider = function DialogProvider({
|
|
|
221
259
|
OK
|
|
222
260
|
</Button>
|
|
223
261
|
</div>
|
|
262
|
+
) : contents?.type === "custom" ? (
|
|
263
|
+
<>{contents.children}</>
|
|
224
264
|
) : (
|
|
225
265
|
<>
|
|
226
266
|
<InputField.Root>
|
|
227
267
|
<InputField.Input
|
|
228
268
|
ref={inputRef}
|
|
229
|
-
placeholder={
|
|
269
|
+
placeholder={
|
|
270
|
+
contents?.type === "input" ? contents.placeholder : undefined
|
|
271
|
+
}
|
|
230
272
|
value={contents?.type === "input" ? contents.inputValue : ""}
|
|
231
273
|
onChange={(value: string) => {
|
|
232
274
|
setContents((contents) =>
|
|
@@ -262,7 +304,7 @@ export const DialogProvider = function DialogProvider({
|
|
|
262
304
|
);
|
|
263
305
|
};
|
|
264
306
|
|
|
265
|
-
function
|
|
307
|
+
export function useDialogContext(): DialogContextValue {
|
|
266
308
|
const value = useContext(DialogContext);
|
|
267
309
|
|
|
268
310
|
if (!value) {
|
|
@@ -273,13 +315,17 @@ function useDialog(): DialogContextValue {
|
|
|
273
315
|
}
|
|
274
316
|
|
|
275
317
|
export function useOpenInputDialog() {
|
|
276
|
-
return
|
|
318
|
+
return useDialogContext().openInputDialog;
|
|
277
319
|
}
|
|
278
320
|
|
|
279
321
|
export function useDialogContainsElement() {
|
|
280
|
-
return
|
|
322
|
+
return useDialogContext().containsElement;
|
|
281
323
|
}
|
|
282
324
|
|
|
283
325
|
export function useOpenConfirmationDialog() {
|
|
284
|
-
return
|
|
326
|
+
return useDialogContext().openConfirmationDialog;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
export function useOpenDialog() {
|
|
330
|
+
return useDialogContext().openDialog;
|
|
285
331
|
}
|
package/src/index.css
CHANGED
|
@@ -104,7 +104,8 @@
|
|
|
104
104
|
--popover-divider: rgba(255, 255, 255, 0.08);
|
|
105
105
|
--listview-raised-background: rgba(181, 178, 255, 0.1);
|
|
106
106
|
--listview-editing-background: #000;
|
|
107
|
-
--slider-thumb-background:
|
|
107
|
+
--slider-thumb-background: var(--input-background-light);
|
|
108
|
+
--slider-border: var(--divider);
|
|
108
109
|
--mask: rgb(102, 187, 106);
|
|
109
110
|
--transparent-checker: rgba(255, 255, 255, 0.3);
|
|
110
111
|
--scrollbar: rgba(199, 199, 199, 0.2);
|
package/src/index.tsx
CHANGED
|
@@ -2,9 +2,11 @@
|
|
|
2
2
|
export * from "./components/ActivityIndicator";
|
|
3
3
|
export * from "./components/AnimatePresence";
|
|
4
4
|
export * from "./components/Avatar";
|
|
5
|
+
export * from "./components/Breadcrumbs";
|
|
5
6
|
export * from "./components/Button";
|
|
6
7
|
export * from "./components/Checkbox";
|
|
7
8
|
export * from "./components/Chip";
|
|
9
|
+
export * from "./components/Collection";
|
|
8
10
|
export * from "./components/Combobox";
|
|
9
11
|
export * from "./components/ComboboxMenu";
|
|
10
12
|
export * from "./components/CommandPalette";
|
|
@@ -13,11 +15,13 @@ export * from "./components/Dialog";
|
|
|
13
15
|
export * from "./components/Divider";
|
|
14
16
|
export * from "./components/DraggableMenuButton";
|
|
15
17
|
export * from "./components/DropdownMenu";
|
|
18
|
+
export * from "./components/EditableText";
|
|
16
19
|
export * from "./components/Fade";
|
|
17
20
|
export * from "./components/FillInputField";
|
|
18
21
|
export * from "./components/FillPreviewBackground";
|
|
19
22
|
export * from "./components/FloatingWindow";
|
|
20
23
|
export * from "./components/GradientPicker";
|
|
24
|
+
export * from "./components/Grid";
|
|
21
25
|
export * from "./components/GridView";
|
|
22
26
|
export * from "./components/IconButton";
|
|
23
27
|
export * from "./components/Icons";
|
|
@@ -27,14 +31,13 @@ export * from "./components/internal/Menu";
|
|
|
27
31
|
export type {
|
|
28
32
|
ExtractMenuItemType,
|
|
29
33
|
MenuItem,
|
|
30
|
-
|
|
31
|
-
SectionHeaderMenuItem,
|
|
32
|
-
SeparatorItem,
|
|
34
|
+
SelectableMenuItem,
|
|
33
35
|
} from "./components/internal/Menu";
|
|
34
36
|
export * from "./components/internal/MenuViewport";
|
|
35
37
|
export * from "./components/Label";
|
|
36
38
|
export * from "./components/LabeledElementView";
|
|
37
39
|
export * from "./components/LabeledField";
|
|
40
|
+
export * from "./components/List";
|
|
38
41
|
export * from "./components/ListView";
|
|
39
42
|
export * from "./components/Message";
|
|
40
43
|
export * from "./components/Popover";
|
|
@@ -43,7 +46,6 @@ export * from "./components/ScrollArea";
|
|
|
43
46
|
export * from "./components/SearchCompletionMenu";
|
|
44
47
|
export * from "./components/SegmentedControl";
|
|
45
48
|
export * from "./components/SelectMenu";
|
|
46
|
-
export * from "./components/SidebarList";
|
|
47
49
|
export * from "./components/Slider";
|
|
48
50
|
export * from "./components/Sortable";
|
|
49
51
|
export type { RelativeDropPosition } from "./components/Sortable";
|