@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.
Files changed (44) hide show
  1. package/.turbo/turbo-build.log +13 -10
  2. package/CHANGELOG.md +9 -0
  3. package/dist/index.css +1 -1
  4. package/dist/index.d.mts +320 -78
  5. package/dist/index.d.ts +320 -78
  6. package/dist/index.js +2113 -1374
  7. package/dist/index.js.map +1 -1
  8. package/dist/index.mjs +2000 -1274
  9. package/dist/index.mjs.map +1 -1
  10. package/package.json +3 -3
  11. package/src/components/AnimatePresence.tsx +10 -1
  12. package/src/components/Avatar.tsx +2 -0
  13. package/src/components/Checkbox.tsx +6 -1
  14. package/src/components/Combobox.tsx +6 -4
  15. package/src/components/ComboboxMenu.tsx +30 -15
  16. package/src/components/CommandPalette.tsx +69 -0
  17. package/src/components/ContextMenu.tsx +33 -134
  18. package/src/components/DropdownMenu.tsx +47 -155
  19. package/src/components/Fade.tsx +62 -0
  20. package/src/components/InputField.tsx +109 -133
  21. package/src/components/Label.tsx +81 -7
  22. package/src/components/LabeledField.tsx +112 -0
  23. package/src/components/ListView.tsx +55 -52
  24. package/src/components/Popover.tsx +12 -1
  25. package/src/components/SearchCompletionMenu.tsx +206 -0
  26. package/src/components/SegmentedControl.tsx +5 -2
  27. package/src/components/SelectMenu.tsx +104 -124
  28. package/src/components/SidebarList.tsx +252 -0
  29. package/src/components/Slider.tsx +18 -26
  30. package/src/components/Text.tsx +1 -0
  31. package/src/components/TextArea.tsx +4 -1
  32. package/src/components/Toolbar.tsx +6 -2
  33. package/src/components/TreeView.tsx +3 -0
  34. package/src/components/WorkspaceLayout.tsx +38 -18
  35. package/src/components/internal/Menu.tsx +58 -10
  36. package/src/components/internal/MenuViewport.tsx +151 -0
  37. package/src/components/internal/SelectItem.tsx +111 -0
  38. package/src/hooks/useIndent.ts +12 -0
  39. package/src/hooks/useLabel.ts +51 -0
  40. package/src/hooks/usePreservePanelSize.tsx +50 -19
  41. package/src/index.tsx +12 -5
  42. package/src/utils/combobox.ts +4 -1
  43. package/src/utils/createSectionedMenu.ts +11 -8
  44. package/src/utils/selection.ts +36 -0
@@ -1,4 +1,3 @@
1
- import { CheckIcon, ChevronRightIcon } from "@noya-app/noya-icons";
2
1
  import { useKeyboardShortcuts } from "@noya-app/noya-keymap";
3
2
  import { forwardRefGeneric, memoGeneric } from "@noya-app/react-utils";
4
3
  import * as RadixDropdownMenu from "@radix-ui/react-dropdown-menu";
@@ -6,132 +5,46 @@ import React, {
6
5
  ComponentProps,
7
6
  ForwardedRef,
8
7
  SyntheticEvent,
9
- useCallback,
10
8
  useMemo,
11
9
  } from "react";
12
- import { MenuItemProps, MenuProps } from "./ContextMenu";
13
- import { renderIcon } from "./Icons";
14
- import { Spacer } from "./Spacer";
15
- import {
16
- CHECKBOX_RIGHT_INSET,
17
- CHECKBOX_WIDTH,
18
- KeyboardShortcut,
19
- SEPARATOR_ITEM,
20
- getKeyboardShortcutsForMenuItems,
21
- styles,
22
- } from "./internal/Menu";
23
-
24
- const DropdownMenuItem = memoGeneric(function ContextMenuItem<
25
- T extends string,
26
- >({
27
- value,
28
- children,
29
- onSelect,
30
- checked,
31
- disabled,
32
- indented,
33
- icon,
34
- items,
35
- shortcut,
36
- }: MenuItemProps<T>) {
37
- const handleSelectItem = useCallback(() => {
38
- if (!value) return;
39
-
40
- onSelect(value);
41
- }, [onSelect, value]);
42
-
43
- if (checked) {
44
- return (
45
- <RadixDropdownMenu.CheckboxItem
46
- checked={checked}
47
- disabled={disabled}
48
- onSelect={handleSelectItem}
49
- className={styles.itemStyle({ disabled })}
50
- >
51
- <RadixDropdownMenu.ItemIndicator className={styles.itemIndicatorStyle}>
52
- <CheckIcon />
53
- </RadixDropdownMenu.ItemIndicator>
54
- {icon && (
55
- <>
56
- {renderIcon(icon)}
57
- <Spacer.Horizontal size={8} />
58
- </>
59
- )}
60
- {children}
61
- </RadixDropdownMenu.CheckboxItem>
62
- );
63
- }
64
-
65
- const element = (
66
- <RadixDropdownMenu.Item
67
- disabled={disabled}
68
- onSelect={handleSelectItem}
69
- className={styles.itemStyle({ disabled })}
70
- >
71
- {indented && (
72
- <Spacer.Horizontal size={CHECKBOX_WIDTH - CHECKBOX_RIGHT_INSET} />
73
- )}
74
- {icon && (
75
- <>
76
- {renderIcon(icon)}
77
- <Spacer.Horizontal size={8} />
78
- </>
79
- )}
80
- {children}
81
- {shortcut && (
82
- <>
83
- <Spacer.Horizontal />
84
- <Spacer.Horizontal size={24} />
85
- <KeyboardShortcut shortcut={shortcut} />
86
- </>
87
- )}
88
- {items && items.length > 0 && (
89
- <>
90
- <Spacer.Horizontal />
91
- <Spacer.Horizontal size={16} />
92
- <ChevronRightIcon />
93
- </>
94
- )}
95
- </RadixDropdownMenu.Item>
96
- );
97
-
98
- if (items && items.length > 0) {
99
- return (
100
- <DropdownMenuRoot
101
- isNested
102
- items={items}
103
- onSelect={onSelect}
104
- alignOffset={-5}
105
- >
106
- {element}
107
- </DropdownMenuRoot>
108
- );
109
- } else {
110
- return element;
111
- }
112
- });
10
+ import { MenuProps } from "./ContextMenu";
11
+ import { getKeyboardShortcutsForMenuItems, styles } from "./internal/Menu";
12
+ import { MenuViewport } from "./internal/MenuViewport";
113
13
 
114
14
  /* ----------------------------------------------------------------------------
115
15
  * Root
116
16
  * ------------------------------------------------------------------------- */
117
17
 
118
- type DropdownRootProps<T extends string> = MenuProps<T> & {
18
+ export type DropdownRootProps<T extends string> = MenuProps<T> & {
119
19
  open?: boolean;
120
20
  onCloseAutoFocus?: React.EventHandler<SyntheticEvent<unknown>>;
121
21
  emptyState?: React.ReactNode;
22
+ contentStyle?: React.CSSProperties;
122
23
  } & Pick<
123
24
  ComponentProps<typeof RadixDropdownMenu.Content>,
124
25
  "side" | "sideOffset" | "align" | "alignOffset" | "collisionPadding"
125
26
  >;
126
27
 
28
+ const Components = {
29
+ Separator: RadixDropdownMenu.Separator,
30
+ ItemText: React.Fragment,
31
+ Item: RadixDropdownMenu.Item,
32
+ CheckboxItem: RadixDropdownMenu.CheckboxItem,
33
+ ItemIndicator: RadixDropdownMenu.ItemIndicator,
34
+ SubTrigger: RadixDropdownMenu.SubTrigger,
35
+ SubContent: RadixDropdownMenu.SubContent,
36
+ Sub: RadixDropdownMenu.Sub,
37
+ Portal: RadixDropdownMenu.Portal,
38
+ };
39
+
127
40
  const DropdownMenuRoot = forwardRefGeneric(function DropdownMenuRoot<
128
41
  T extends string,
129
- >(
130
- {
42
+ >(props: DropdownRootProps<T>, forwardedRef: ForwardedRef<HTMLElement>) {
43
+ const {
131
44
  items,
132
45
  children,
133
46
  onSelect,
134
- isNested,
47
+
135
48
  shouldBindKeyboardShortcuts,
136
49
  onOpenChange,
137
50
  open,
@@ -142,77 +55,56 @@ const DropdownMenuRoot = forwardRefGeneric(function DropdownMenuRoot<
142
55
  alignOffset,
143
56
  collisionPadding = 8,
144
57
  emptyState,
145
- }: DropdownRootProps<T>,
146
- forwardedRef: ForwardedRef<HTMLElement>
147
- ) {
148
- const hasCheckedItem = items.some(
149
- (item) => item !== SEPARATOR_ITEM && item.checked
150
- );
58
+ contentStyle,
59
+ } = props;
151
60
 
152
61
  const keymap = useMemo(
153
62
  () =>
154
- isNested || shouldBindKeyboardShortcuts === false
63
+ shouldBindKeyboardShortcuts === false
155
64
  ? {}
156
65
  : getKeyboardShortcutsForMenuItems(items, onSelect),
157
- [isNested, items, onSelect, shouldBindKeyboardShortcuts]
66
+ [items, onSelect, shouldBindKeyboardShortcuts]
158
67
  );
159
68
 
160
69
  useKeyboardShortcuts(keymap);
161
70
 
162
- const RootComponent = isNested
163
- ? RadixDropdownMenu.Sub
164
- : RadixDropdownMenu.Root;
165
- const TriggerComponent = isNested
166
- ? RadixDropdownMenu.SubTrigger
167
- : RadixDropdownMenu.Trigger;
168
- const ContentComponent = isNested
169
- ? RadixDropdownMenu.SubContent
170
- : RadixDropdownMenu.Content;
171
-
172
- const contentStyle = useMemo(() => ({ zIndex: 1000 }), []);
71
+ const contentStyles = useMemo(
72
+ () => ({
73
+ maxHeight: "500px",
74
+ overflow: "auto",
75
+ ...contentStyle,
76
+ }),
77
+ [contentStyle]
78
+ );
173
79
 
174
80
  return (
175
- <RootComponent onOpenChange={onOpenChange} open={open}>
176
- <TriggerComponent ref={forwardedRef as ForwardedRef<any>} asChild>
81
+ <RadixDropdownMenu.Root onOpenChange={onOpenChange} open={open}>
82
+ <RadixDropdownMenu.Trigger
83
+ ref={forwardedRef as ForwardedRef<any>}
84
+ asChild
85
+ >
177
86
  {children}
178
- </TriggerComponent>
87
+ </RadixDropdownMenu.Trigger>
179
88
  <RadixDropdownMenu.Portal>
180
- <ContentComponent
89
+ <RadixDropdownMenu.Content
181
90
  className={styles.contentStyle}
182
91
  side={side}
183
92
  sideOffset={sideOffset}
184
93
  align={align}
185
94
  alignOffset={alignOffset}
186
- style={contentStyle}
95
+ style={contentStyles}
187
96
  collisionPadding={collisionPadding}
188
97
  {...({ onCloseAutoFocus } as any)}
189
98
  >
190
99
  {items.length === 0 ? emptyState : null}
191
- {items.map((item, index) =>
192
- item === SEPARATOR_ITEM ? (
193
- <RadixDropdownMenu.Separator
194
- key={index}
195
- className={styles.separatorStyle}
196
- />
197
- ) : (
198
- <DropdownMenuItem
199
- key={item.value ?? index}
200
- value={item.value}
201
- indented={hasCheckedItem}
202
- checked={item.checked ?? false}
203
- disabled={item.disabled ?? false}
204
- icon={item.icon}
205
- onSelect={onSelect}
206
- items={item.items}
207
- shortcut={item.shortcut}
208
- >
209
- {item.title}
210
- </DropdownMenuItem>
211
- )
212
- )}
213
- </ContentComponent>
100
+ <MenuViewport
101
+ items={items}
102
+ Components={Components}
103
+ onSelect={onSelect}
104
+ />
105
+ </RadixDropdownMenu.Content>
214
106
  </RadixDropdownMenu.Portal>
215
- </RootComponent>
107
+ </RadixDropdownMenu.Root>
216
108
  );
217
109
  });
218
110
 
@@ -0,0 +1,62 @@
1
+ import React, { type ReactNode } from "react";
2
+ import { cx } from "../utils/classNames";
3
+
4
+ export type FadeDirection = "left" | "right" | "top" | "bottom";
5
+
6
+ export type FadeProps = {
7
+ children: ReactNode;
8
+ direction?: FadeDirection;
9
+ width?: number;
10
+ height?: number;
11
+ className?: string;
12
+ zIndex?: number;
13
+ /**
14
+ * @default "front"
15
+ * If "back", the fade will be positioned behind the children
16
+ */
17
+ position?: "front" | "back";
18
+ };
19
+
20
+ export const Fade = ({
21
+ children,
22
+ direction = "right",
23
+ width,
24
+ height,
25
+ className,
26
+ zIndex = 0,
27
+ position = "front",
28
+ }: FadeProps) => {
29
+ const gradientDirection = {
30
+ left: "to-l",
31
+ right: "to-r",
32
+ top: "to-t",
33
+ bottom: "to-b",
34
+ }[direction];
35
+
36
+ const positionClasses = {
37
+ left: "left-0 top-0 bottom-0",
38
+ right: "right-0 top-0 bottom-0",
39
+ top: "left-0 right-0 top-0",
40
+ bottom: "left-0 right-0 bottom-0",
41
+ }[direction];
42
+
43
+ return (
44
+ <div className={cx("relative", className)} style={{ height }}>
45
+ {position === "front" ? children : null}
46
+ <div
47
+ className={cx(
48
+ "absolute pointer-events-none",
49
+ positionClasses,
50
+ `bg-gradient-${gradientDirection}`,
51
+ "from-transparent to-background",
52
+ zIndex && `z-[${zIndex}]`
53
+ )}
54
+ style={{
55
+ width: width ?? "100%",
56
+ height: height ?? "100%",
57
+ }}
58
+ />
59
+ {position === "back" ? children : null}
60
+ </div>
61
+ );
62
+ };