@noya-app/noya-designsystem 0.1.41 → 0.1.42

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 (52) hide show
  1. package/.turbo/turbo-build.log +10 -10
  2. package/CHANGELOG.md +10 -0
  3. package/dist/index.css +1 -1
  4. package/dist/index.d.mts +217 -121
  5. package/dist/index.d.ts +217 -121
  6. package/dist/index.js +7435 -6796
  7. package/dist/index.js.map +1 -1
  8. package/dist/index.mjs +5257 -4660
  9. package/dist/index.mjs.map +1 -1
  10. package/package.json +4 -5
  11. package/src/__tests__/combobox.test.ts +578 -0
  12. package/src/components/ActivityIndicator.tsx +4 -4
  13. package/src/components/AnimatePresence.tsx +5 -5
  14. package/src/components/Avatar.tsx +5 -2
  15. package/src/components/BaseToolbar.tsx +61 -0
  16. package/src/components/Button.tsx +1 -1
  17. package/src/components/Checkbox.tsx +6 -5
  18. package/src/components/Combobox.tsx +220 -324
  19. package/src/components/ComboboxMenu.tsx +88 -0
  20. package/src/components/Dialog.tsx +10 -6
  21. package/src/components/DimensionInput.tsx +4 -4
  22. package/src/components/FillPreviewBackground.tsx +51 -44
  23. package/src/components/FloatingWindow.tsx +5 -2
  24. package/src/components/GradientPicker.tsx +14 -14
  25. package/src/components/IconButton.tsx +6 -12
  26. package/src/components/Icons.tsx +3 -3
  27. package/src/components/InputField.tsx +145 -160
  28. package/src/components/Label.tsx +4 -4
  29. package/src/components/LabeledElementView.tsx +35 -41
  30. package/src/components/ListView.tsx +49 -39
  31. package/src/components/Message.tsx +75 -0
  32. package/src/components/Progress.tsx +2 -2
  33. package/src/components/ScrollArea.tsx +7 -5
  34. package/src/components/SegmentedControl.tsx +171 -0
  35. package/src/components/SelectMenu.tsx +61 -10
  36. package/src/components/Slider.tsx +5 -2
  37. package/src/components/Sortable.tsx +17 -27
  38. package/src/components/Spacer.tsx +28 -9
  39. package/src/components/Switch.tsx +8 -8
  40. package/src/components/TextArea.tsx +23 -13
  41. package/src/components/Toolbar.tsx +134 -0
  42. package/src/components/Tooltip.tsx +10 -7
  43. package/src/components/internal/Menu.tsx +5 -4
  44. package/src/contexts/DesignSystemConfiguration.tsx +15 -24
  45. package/src/contexts/DialogContext.tsx +137 -49
  46. package/src/contexts/ImageDataContext.tsx +6 -12
  47. package/src/index.css +1 -3
  48. package/src/index.tsx +12 -3
  49. package/src/utils/combobox.ts +369 -0
  50. package/tailwind.config.ts +1 -2
  51. package/src/components/RadioGroup.tsx +0 -100
  52. package/src/utils/completions.ts +0 -21
@@ -1,13 +1,5 @@
1
1
  import { getCurrentPlatform, PlatformName } from "@noya-app/noya-keymap";
2
- import React, {
3
- createContext,
4
- memo,
5
- ReactNode,
6
- useContext,
7
- useEffect,
8
- useMemo,
9
- useState,
10
- } from "react";
2
+ import * as React from "react";
11
3
  import { ToastProvider } from "../components/Toast";
12
4
  import { DialogProvider } from "./DialogContext";
13
5
  import { FloatingWindowProvider } from "./FloatingWindowContext";
@@ -17,23 +9,22 @@ export type DesignSystemConfigurationContextValue = {
17
9
  };
18
10
 
19
11
  const DesignSystemConfigurationContext =
20
- createContext<DesignSystemConfigurationContextValue>({
12
+ React.createContext<DesignSystemConfigurationContextValue>({
21
13
  platform: "key",
22
14
  });
23
15
 
24
- export const DesignSystemConfigurationProvider = memo(
16
+ export const DesignSystemConfigurationProvider = React.memo(
25
17
  function DesignSystemConfigurationProvider({
26
18
  children,
27
19
  platform,
28
20
  }: {
29
- children: ReactNode;
21
+ children: React.ReactNode;
30
22
  platform?: PlatformName;
31
23
  }) {
32
- const [internalPlatform, setInternalPlatform] = useState<PlatformName>(
33
- platform ?? "key"
34
- );
24
+ const [internalPlatform, setInternalPlatform] =
25
+ React.useState<PlatformName>(platform ?? "key");
35
26
 
36
- useEffect(() => {
27
+ React.useEffect(() => {
37
28
  if (platform !== undefined) return;
38
29
 
39
30
  if (typeof navigator !== "undefined") {
@@ -41,23 +32,23 @@ export const DesignSystemConfigurationProvider = memo(
41
32
  }
42
33
  }, [platform]);
43
34
 
44
- const contextValue = useMemo(
35
+ const contextValue = React.useMemo(
45
36
  () => ({ platform: platform ?? internalPlatform }),
46
37
  [platform, internalPlatform]
47
38
  );
48
39
 
49
40
  return (
50
41
  <DesignSystemConfigurationContext.Provider value={contextValue}>
51
- <DialogProvider>
52
- <ToastProvider>
53
- <FloatingWindowProvider>{children}</FloatingWindowProvider>
54
- </ToastProvider>
55
- </DialogProvider>
42
+ <DialogProvider>
43
+ <ToastProvider>
44
+ <FloatingWindowProvider>{children}</FloatingWindowProvider>
45
+ </ToastProvider>
46
+ </DialogProvider>
56
47
  </DesignSystemConfigurationContext.Provider>
57
48
  );
58
49
  }
59
50
  );
60
51
 
61
52
  export function useDesignSystemConfiguration(): DesignSystemConfigurationContextValue {
62
- return useContext(DesignSystemConfigurationContext);
63
- }
53
+ return React.useContext(DesignSystemConfigurationContext);
54
+ }
@@ -36,37 +36,60 @@ export type DialogContextValue = {
36
36
  }
37
37
  ): Promise<string | undefined>;
38
38
 
39
+ openConfirmationDialog(
40
+ options:
41
+ | string
42
+ | {
43
+ title: string;
44
+ description?: ReactNode;
45
+ }
46
+ ): Promise<boolean>;
47
+
39
48
  containsElement(element: HTMLElement): boolean;
40
49
  };
41
50
 
42
51
  const DialogContext = createContext<DialogContextValue | undefined>(undefined);
43
52
 
53
+ type InputDialogContents = {
54
+ type: "input";
55
+ title: string;
56
+ description?: ReactNode;
57
+ placeholder?: string;
58
+ inputValue: string;
59
+ resolve: (value: string | undefined) => void;
60
+ };
61
+
62
+ type ConfirmationDialogContents = {
63
+ type: "confirmation";
64
+ title: string;
65
+ description?: ReactNode;
66
+ resolve: (value: boolean) => void;
67
+ };
68
+
69
+ type DialogContents = InputDialogContents | ConfirmationDialogContents;
70
+
44
71
  export const DialogProvider = function DialogProvider({
45
72
  children,
46
73
  }: {
47
74
  children: ReactNode;
48
75
  }) {
49
- const [contents, setContents] = useState<
50
- | {
51
- title: string;
52
- description?: ReactNode;
53
- placeholder?: string;
54
- inputValue: string;
55
- resolve: (value: string | undefined) => void;
56
- }
57
- | undefined
58
- >();
76
+ const [contents, setContents] = useState<DialogContents | undefined>();
59
77
 
60
78
  const isOpen = !!contents;
61
79
 
62
80
  const close = useCallback(() => {
63
81
  if (!contents) return;
64
82
 
65
- contents.resolve(undefined);
83
+ if (contents.type === "input") {
84
+ contents.resolve(undefined);
85
+ } else {
86
+ contents.resolve(false);
87
+ }
88
+ setContents(undefined);
66
89
  }, [contents]);
67
90
 
68
91
  const submit = useCallback(() => {
69
- if (!contents || !contents.inputValue) return;
92
+ if (!contents || contents.type !== "input") return;
70
93
 
71
94
  contents.resolve(contents.inputValue);
72
95
  setContents(undefined);
@@ -79,11 +102,12 @@ export const DialogProvider = function DialogProvider({
79
102
  typeof options === "string" ? { title: options } : options;
80
103
 
81
104
  setContents({
105
+ type: "input",
82
106
  title,
83
107
  description,
84
108
  inputValue: initialValue ?? "",
85
109
  placeholder,
86
- resolve: (value) => {
110
+ resolve: (value: string | undefined) => {
87
111
  resolve(value);
88
112
  setContents(undefined);
89
113
  },
@@ -92,6 +116,26 @@ export const DialogProvider = function DialogProvider({
92
116
  return promise;
93
117
  }, []);
94
118
 
119
+ const openConfirmation: DialogContextValue["openConfirmationDialog"] =
120
+ useCallback((options) => {
121
+ const { promise, resolve } = createDeferredPromise<boolean>();
122
+
123
+ const { title, description } =
124
+ typeof options === "string" ? { title: options } : options;
125
+
126
+ setContents({
127
+ type: "confirmation",
128
+ title,
129
+ description,
130
+ resolve: (value: boolean) => {
131
+ resolve(value);
132
+ setContents(undefined);
133
+ },
134
+ });
135
+
136
+ return promise;
137
+ }, []);
138
+
95
139
  const handleKeyDown = useCallback(
96
140
  (event: React.KeyboardEvent<HTMLInputElement>) => {
97
141
  if (event.key !== "Enter") return;
@@ -117,8 +161,12 @@ export const DialogProvider = function DialogProvider({
117
161
  <>
118
162
  <DialogContext.Provider
119
163
  value={useMemo(
120
- () => ({ openInputDialog: open, containsElement }),
121
- [containsElement, open]
164
+ () => ({
165
+ openInputDialog: open,
166
+ openConfirmationDialog: openConfirmation,
167
+ containsElement,
168
+ }),
169
+ [containsElement, open, openConfirmation]
122
170
  )}
123
171
  >
124
172
  {children}
@@ -136,43 +184,79 @@ export const DialogProvider = function DialogProvider({
136
184
  },
137
185
  [close]
138
186
  )}
139
- onOpenAutoFocus={useCallback((event: Event) => {
140
- event.stopPropagation();
141
- event.preventDefault();
187
+ onOpenAutoFocus={useCallback(
188
+ (event: Event) => {
189
+ event.stopPropagation();
190
+ event.preventDefault();
142
191
 
143
- inputRef.current?.focus();
192
+ if (!contents || contents.type !== "input") return;
144
193
 
145
- // Select the entire input value
146
- inputRef.current?.setSelectionRange(0, inputRef.current.value.length);
147
- }, [])}
194
+ inputRef.current?.focus();
195
+ inputRef.current?.setSelectionRange(
196
+ 0,
197
+ inputRef.current.value.length
198
+ );
199
+ },
200
+ [contents]
201
+ )}
148
202
  >
149
- <InputField.Root>
150
- <InputField.Input
151
- ref={inputRef}
152
- placeholder={contents?.placeholder}
153
- value={contents?.inputValue ?? ""}
154
- onChange={(value) => {
155
- setContents((contents) =>
156
- contents
157
- ? {
158
- ...contents,
159
- inputValue: value,
160
- }
161
- : undefined
162
- );
163
- }}
164
- onKeyDown={handleKeyDown}
165
- />
166
- </InputField.Root>
167
- <Spacer.Vertical size={20} />
168
- <div className="flex flex-1 flex-row items-center">
169
- <Spacer.Horizontal />
170
- <Button onClick={close}>Cancel</Button>
171
- <Spacer.Horizontal size={16} />
172
- <Button disabled={!contents?.inputValue} onClick={submit}>
173
- Submit
174
- </Button>
175
- </div>
203
+ {contents?.type === "confirmation" ? (
204
+ <div className="flex flex-1 flex-row items-center">
205
+ <Spacer.Horizontal />
206
+ <Button
207
+ onClick={() => {
208
+ contents.resolve(false);
209
+ setContents(undefined);
210
+ }}
211
+ >
212
+ Cancel
213
+ </Button>
214
+ <Spacer.Horizontal size={16} />
215
+ <Button
216
+ onClick={() => {
217
+ contents.resolve(true);
218
+ setContents(undefined);
219
+ }}
220
+ >
221
+ OK
222
+ </Button>
223
+ </div>
224
+ ) : (
225
+ <>
226
+ <InputField.Root>
227
+ <InputField.Input
228
+ ref={inputRef}
229
+ placeholder={contents?.placeholder}
230
+ value={contents?.type === "input" ? contents.inputValue : ""}
231
+ onChange={(value: string) => {
232
+ setContents((contents) =>
233
+ contents?.type === "input"
234
+ ? {
235
+ ...contents,
236
+ inputValue: value,
237
+ }
238
+ : undefined
239
+ );
240
+ }}
241
+ onKeyDown={handleKeyDown}
242
+ />
243
+ </InputField.Root>
244
+ <Spacer.Vertical size={20} />
245
+ <div className="flex flex-1 flex-row items-center">
246
+ <Spacer.Horizontal />
247
+ <Button onClick={close}>Cancel</Button>
248
+ <Spacer.Horizontal size={16} />
249
+ <Button
250
+ disabled={
251
+ !contents || contents.type !== "input" || !contents.inputValue
252
+ }
253
+ onClick={submit}
254
+ >
255
+ Submit
256
+ </Button>
257
+ </div>
258
+ </>
259
+ )}
176
260
  </Dialog>
177
261
  </>
178
262
  );
@@ -195,3 +279,7 @@ export function useOpenInputDialog() {
195
279
  export function useDialogContainsElement() {
196
280
  return useDialog().containsElement;
197
281
  }
282
+
283
+ export function useOpenConfirmationDialog() {
284
+ return useDialog().openConfirmationDialog;
285
+ }
@@ -1,27 +1,21 @@
1
- import React, {
2
- createContext,
3
- memo,
4
- ReactNode,
5
- useContext,
6
- useMemo,
7
- } from "react";
1
+ import * as React from "react";
8
2
 
9
3
  export type ImageDataContextValue = {
10
4
  getImageData: (ref: string) => ArrayBuffer | undefined;
11
5
  };
12
6
 
13
- const ImageDataContext = createContext<ImageDataContextValue | undefined>(
7
+ const ImageDataContext = React.createContext<ImageDataContextValue | undefined>(
14
8
  undefined
15
9
  );
16
10
 
17
- export const ImageDataProvider = memo(function ImageDataProvider({
11
+ export const ImageDataProvider = React.memo(function ImageDataProvider({
18
12
  children,
19
13
  getImageData,
20
14
  }: {
21
- children: ReactNode;
15
+ children: React.ReactNode;
22
16
  getImageData?: (ref: string) => ArrayBuffer | undefined;
23
17
  }) {
24
- const contextValue = useMemo(
18
+ const contextValue = React.useMemo(
25
19
  () => ({ getImageData: getImageData ?? (() => undefined) }),
26
20
  [getImageData]
27
21
  );
@@ -34,7 +28,7 @@ export const ImageDataProvider = memo(function ImageDataProvider({
34
28
  });
35
29
 
36
30
  export function useImageData(ref: string): ArrayBuffer | undefined {
37
- const value = useContext(ImageDataContext);
31
+ const value = React.useContext(ImageDataContext);
38
32
 
39
33
  if (!value) {
40
34
  throw new Error("Missing ImageDataProvider");
package/src/index.css CHANGED
@@ -39,7 +39,6 @@
39
39
  --listview-editing-background: #fff;
40
40
  --slider-thumb-background: white;
41
41
  --slider-border: #9698ac;
42
- --radio-group-background: white;
43
42
  --mask: rgb(12, 193, 67);
44
43
  --transparent-checker: rgba(255, 255, 255, 0.8);
45
44
  --scrollbar: rgba(199, 199, 199, 0.8);
@@ -62,7 +61,7 @@
62
61
  --icon: rgb(129, 131, 165);
63
62
  --icon-selected: rgb(220, 220, 220);
64
63
  --warning: rgb(251, 211, 0);
65
- --radio-group-item: rgb(139, 139, 139);
64
+ --segmented-control-item: rgb(139, 139, 139);
66
65
  --dot: rgba(0, 0, 0, 0.25);
67
66
  --row-highlight: #3390ff10;
68
67
  --table-row-background: var(--background);
@@ -106,7 +105,6 @@
106
105
  --listview-raised-background: rgba(181, 178, 255, 0.1);
107
106
  --listview-editing-background: #000;
108
107
  --slider-thumb-background: rgb(248, 248, 250);
109
- --radio-group-background: rgba(181, 178, 255, 0.08);
110
108
  --mask: rgb(102, 187, 106);
111
109
  --transparent-checker: rgba(255, 255, 255, 0.3);
112
110
  --scrollbar: rgba(199, 199, 199, 0.2);
package/src/index.tsx CHANGED
@@ -6,6 +6,7 @@ export * from "./components/Button";
6
6
  export * from "./components/Checkbox";
7
7
  export * from "./components/Chip";
8
8
  export * from "./components/Combobox";
9
+ export * from "./components/ComboboxMenu";
9
10
  export * from "./components/ContextMenu";
10
11
  export * from "./components/Dialog";
11
12
  export * from "./components/Divider";
@@ -20,7 +21,11 @@ export * from "./components/IconButton";
20
21
  export * from "./components/Icons";
21
22
  export * from "./components/InputField";
22
23
  export * from "./components/InspectorContainer";
23
- export { KeyboardShortcut, SEPARATOR_ITEM } from "./components/internal/Menu";
24
+ export {
25
+ KeyboardShortcut,
26
+ SEPARATOR_ITEM,
27
+ getKeyboardShortcutsForMenuItems,
28
+ } from "./components/internal/Menu";
24
29
  export type {
25
30
  ExtractMenuItemType,
26
31
  MenuItem,
@@ -29,10 +34,11 @@ export type {
29
34
  export * from "./components/Label";
30
35
  export * from "./components/LabeledElementView";
31
36
  export * from "./components/ListView";
37
+ export * from "./components/Message";
32
38
  export * from "./components/Popover";
33
39
  export * from "./components/Progress";
34
- export * from "./components/RadioGroup";
35
40
  export * from "./components/ScrollArea";
41
+ export * from "./components/SegmentedControl";
36
42
  export * from "./components/SelectMenu";
37
43
  export * from "./components/Slider";
38
44
  export * from "./components/Sortable";
@@ -64,7 +70,7 @@ export * from "./utils/createSectionedMenu";
64
70
  export * from "./utils/getGradientBackground";
65
71
  // Utils
66
72
  export * from "./utils/colorFromString";
67
- export * from "./utils/completions";
73
+ export * from "./utils/combobox";
68
74
  export * from "./utils/fuzzyScorer";
69
75
  export * from "./utils/sketchColor";
70
76
  export * from "./utils/sketchPattern";
@@ -73,3 +79,6 @@ export { default as withSeparatorElements } from "./utils/withSeparatorElements"
73
79
  // Avoid dependency cycles
74
80
  export * from "./components/DimensionInput";
75
81
  export * as InspectorPrimitives from "./components/InspectorPrimitives";
82
+
83
+ export * from "./components/BaseToolbar";
84
+ export * from "./components/Toolbar";