@noya-app/noya-designsystem 0.1.64 → 0.1.65

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 (115) hide show
  1. package/.turbo/turbo-build.log +13 -10
  2. package/CHANGELOG.md +12 -0
  3. package/dist/index.css +1 -1
  4. package/dist/index.d.mts +501 -228
  5. package/dist/index.d.ts +501 -228
  6. package/dist/index.js +6738 -2571
  7. package/dist/index.js.map +1 -1
  8. package/dist/index.mjs +6697 -2499
  9. package/dist/index.mjs.map +1 -1
  10. package/package.json +6 -6
  11. package/src/components/ActivityIndicator.tsx +9 -4
  12. package/src/components/ActivityLog.tsx +197 -0
  13. package/src/components/AnimatePresence.tsx +37 -22
  14. package/src/components/Avatar.tsx +42 -25
  15. package/src/components/Banner.tsx +57 -11
  16. package/src/components/BaseToolbar.tsx +29 -7
  17. package/src/components/Breadcrumbs.tsx +2 -2
  18. package/src/components/Button.tsx +23 -24
  19. package/src/components/Checkbox.tsx +6 -35
  20. package/src/components/Chip.tsx +45 -33
  21. package/src/components/Collection.tsx +16 -11
  22. package/src/components/ColorSwatch.tsx +3 -3
  23. package/src/components/ColorSwatchControl.tsx +2 -2
  24. package/src/components/Combobox.tsx +6 -6
  25. package/src/components/ComboboxMenu.tsx +6 -5
  26. package/src/components/CommandPalette.tsx +1 -1
  27. package/src/components/Dialog.tsx +6 -12
  28. package/src/components/Divider.tsx +8 -8
  29. package/src/components/DraggableMenuButton.tsx +9 -5
  30. package/src/components/Drawer.tsx +5 -5
  31. package/src/components/DropdownMenu.tsx +1 -1
  32. package/src/components/EditableText.tsx +2 -2
  33. package/src/components/Fade.tsx +13 -13
  34. package/src/components/FileUploadIndicator.tsx +11 -11
  35. package/src/components/FloatingWindow.tsx +2 -2
  36. package/src/components/Grid.tsx +18 -11
  37. package/src/components/GridView.tsx +32 -25
  38. package/src/components/IVirtualizedList.tsx +5 -0
  39. package/src/components/Icons.tsx +24 -4
  40. package/src/components/InputField.tsx +19 -19
  41. package/src/components/InspectorContainer.tsx +2 -2
  42. package/src/components/InspectorPrimitives.tsx +15 -9
  43. package/src/components/Label.tsx +4 -3
  44. package/src/components/LabeledElementView.tsx +3 -3
  45. package/src/components/LabeledField.tsx +6 -6
  46. package/src/components/List.tsx +103 -45
  47. package/src/components/ListMenu.tsx +91 -0
  48. package/src/components/ListNavigator.tsx +106 -0
  49. package/src/components/ListView.tsx +20 -1158
  50. package/src/components/MediaThumbnail.tsx +56 -15
  51. package/src/components/Message.tsx +5 -5
  52. package/src/components/Navigator.tsx +461 -0
  53. package/src/components/NoyaLogo.tsx +1 -1
  54. package/src/components/Popover.tsx +9 -5
  55. package/src/components/Progress.tsx +19 -9
  56. package/src/components/ResizableContainer.tsx +187 -0
  57. package/src/components/RingProgress.tsx +128 -0
  58. package/src/components/ScrollArea.tsx +4 -4
  59. package/src/components/ScrollableSidebar.tsx +10 -0
  60. package/src/components/SearchCompletionMenu.tsx +3 -3
  61. package/src/components/Section.tsx +107 -57
  62. package/src/components/SegmentedControl.tsx +29 -29
  63. package/src/components/SelectMenu.tsx +13 -20
  64. package/src/components/SelectionToolbar.tsx +4 -3
  65. package/src/components/Slider.tsx +9 -9
  66. package/src/components/Spacer.tsx +2 -2
  67. package/src/components/StackNavigator.tsx +378 -0
  68. package/src/components/Stepper.tsx +88 -0
  69. package/src/components/Switch.tsx +4 -4
  70. package/src/components/Tabs.tsx +3 -3
  71. package/src/components/Text.tsx +23 -13
  72. package/src/components/TextArea.tsx +3 -3
  73. package/src/components/Toast.tsx +4 -4
  74. package/src/components/Toolbar.tsx +50 -9
  75. package/src/components/ToolbarDrawer.tsx +2 -2
  76. package/src/components/Tooltip.tsx +1 -1
  77. package/src/components/TreeView.tsx +11 -4
  78. package/src/components/UserPointer.tsx +4 -4
  79. package/src/components/ai-assistant/AIAssistantLayout.tsx +8 -8
  80. package/src/components/blocks/ImageBlockComponent.tsx +13 -7
  81. package/src/components/connected-users-menu/ConnectedUsersMenuLayout.tsx +4 -4
  82. package/src/components/file-explorer/FileExplorerLayout.tsx +20 -8
  83. package/src/components/internal/Checkmark.tsx +1 -1
  84. package/src/components/internal/Menu.tsx +22 -18
  85. package/src/components/internal/MenuViewport.tsx +13 -6
  86. package/src/components/internal/SelectItem.tsx +3 -3
  87. package/src/components/internal/TextInput.tsx +5 -1
  88. package/src/components/listView/ListViewContexts.tsx +68 -0
  89. package/src/components/listView/ListViewEditableRowTitle.tsx +55 -0
  90. package/src/components/listView/ListViewRoot.tsx +521 -0
  91. package/src/components/listView/ListViewRow.tsx +475 -0
  92. package/src/components/listView/ListViewRowTitle.tsx +21 -0
  93. package/src/components/listView/types.ts +11 -0
  94. package/src/components/pipeline/PipelineResultLayout.tsx +6 -6
  95. package/src/components/sorting/DragRegistration.tsx +2 -1
  96. package/src/components/sorting/SharedDragProvider.tsx +91 -9
  97. package/src/components/sorting/Sortable.tsx +71 -17
  98. package/src/components/workspace/DrawerWorkspaceLayout.tsx +5 -5
  99. package/src/components/workspace/PanelWorkspaceLayout.tsx +65 -49
  100. package/src/components/workspace/VerticalTabMenu.tsx +25 -15
  101. package/src/components/workspace/WorkspaceLayout.tsx +30 -21
  102. package/src/contexts/DialogContext.tsx +17 -5
  103. package/src/hooks/useIndent.ts +3 -8
  104. package/src/index.css +24 -10
  105. package/src/index.tsx +10 -5
  106. package/src/utils/classNames.ts +52 -6
  107. package/src/utils/editableBlockStyles.ts +2 -2
  108. package/src/utils/formatByteSize.ts +1 -0
  109. package/src/utils/inputs.ts +8 -6
  110. package/src/utils/sketchColor.ts +0 -34
  111. package/tailwind.config.ts +1 -0
  112. package/src/components/FillInputField.tsx +0 -37
  113. package/src/components/FillPreviewBackground.tsx +0 -134
  114. package/src/components/GradientPicker.tsx +0 -90
  115. package/src/utils/getGradientBackground.tsx +0 -31
@@ -0,0 +1,88 @@
1
+ "use client";
2
+
3
+ import { clamp } from "@noya-app/noya-utils";
4
+ import React, { FocusEventHandler, memo, useCallback, useMemo } from "react";
5
+ import { useLabel } from "../hooks/useLabel";
6
+ import { cx } from "../utils/classNames";
7
+ import { Button } from "./Button";
8
+ import { InputField } from "./InputField";
9
+
10
+ type ColorScheme = "primary" | "secondary";
11
+
12
+ interface Props {
13
+ id?: string;
14
+ style?: React.CSSProperties;
15
+ className?: string;
16
+ value: number;
17
+ onValueChange: (value: number) => void;
18
+ min: number;
19
+ max: number;
20
+ step?: number;
21
+ colorScheme?: ColorScheme;
22
+ onFocus?: FocusEventHandler;
23
+ onBlur?: FocusEventHandler;
24
+ readOnly?: boolean;
25
+ }
26
+
27
+ export const Stepper = memo(function Stepper({
28
+ style,
29
+ className,
30
+ value: valueProp,
31
+ onValueChange,
32
+ min,
33
+ max,
34
+ step = 1,
35
+ colorScheme,
36
+ onFocus,
37
+ onBlur,
38
+ readOnly = false,
39
+ ...props
40
+ }: Props) {
41
+ const { fieldId: id } = useLabel({
42
+ fieldId: props.id,
43
+ });
44
+
45
+ const value = useMemo(() => {
46
+ return clamp(valueProp, min, max);
47
+ }, [valueProp, min, max]);
48
+
49
+ const handleValueChange = useCallback(
50
+ (value: number) => {
51
+ const newValue = clamp(value, min, max);
52
+ onValueChange(newValue);
53
+ },
54
+ [onValueChange, min, max]
55
+ );
56
+
57
+ return (
58
+ <div
59
+ id={id}
60
+ className={cx(
61
+ "n-flex n-relative n-items-center n-select-none n-touch-none n-h-input-height n-rounded n-flex-grow n-max-h-input-height n-gap-1.5",
62
+ className
63
+ )}
64
+ style={style}
65
+ onFocus={onFocus}
66
+ onBlur={onBlur}
67
+ >
68
+ <Button
69
+ icon="MinusIcon"
70
+ disabled={readOnly}
71
+ onClick={() => handleValueChange(value - step)}
72
+ />
73
+ <InputField.Root className="n-flex-1">
74
+ <InputField.NumberInput
75
+ value={value}
76
+ onChange={handleValueChange}
77
+ onNudge={(amount) => handleValueChange(value + amount)}
78
+ readOnly={readOnly}
79
+ />
80
+ </InputField.Root>
81
+ <Button
82
+ icon="PlusIcon"
83
+ disabled={readOnly}
84
+ onClick={() => handleValueChange(value + step)}
85
+ />
86
+ </div>
87
+ );
88
+ });
@@ -40,15 +40,15 @@ export const Switch = function Switch({
40
40
  onChange(newValue);
41
41
  }}
42
42
  className={cx(
43
- "all-unset w-8 h-[19px] bg-active-background rounded-full relative cursor-pointer ring-offset-background [-webkit-tap-highlight-color:rgba(0,0,0,0)] focus:ring-2 focus:ring-primary focus:ring-offset-[1px] outline-none data-[state=checked]:bg-primary transition-all",
44
- colorScheme === "secondary" && "data-[state=checked]:bg-secondary",
45
- "focus:z-interactable",
43
+ "n-all-unset n-w-8 n-h-[19px] n-bg-active-background n-rounded-full n-relative n-cursor-pointer n-ring-offset-background n-[-webkit-tap-highlight-color:rgba(0,0,0,0)] focus:n-ring-2 focus:n-ring-primary focus:n-ring-offset-[1px] n-outline-none data-[state=checked]:n-bg-primary n-transition-all",
44
+ colorScheme === "secondary" && "data-[state=checked]:n-bg-secondary",
45
+ "focus:n-z-interactable",
46
46
  className
47
47
  )}
48
48
  onFocus={onFocus}
49
49
  onBlur={onBlur}
50
50
  >
51
- <SwitchPrimitive.Thumb className="block w-[15px] h-[15px] bg-background rounded-full transition-transform translate-x-[2px] data-[state=checked]:translate-x-[15px]" />
51
+ <SwitchPrimitive.Thumb className="n-block n-w-[15px] n-h-[15px] n-bg-background n-rounded-full n-transition-transform n-translate-x-[2px] data-[state=checked]:n-translate-x-[15px]" />
52
52
  </SwitchPrimitive.Root>
53
53
  );
54
54
  };
@@ -31,10 +31,10 @@ export function Tabs<T extends string = string>({
31
31
  });
32
32
 
33
33
  return (
34
- <div {...props} className={cx("flex flex-col", props.className)}>
35
- <div className="flex flex-row px-3">
34
+ <div {...props} className={cx("n-flex n-flex-col", props.className)}>
35
+ <div className="n-flex n-flex-row n-px-3">
36
36
  <SegmentedControl
37
- className="flex-1"
37
+ className="n-flex-1"
38
38
  value={activeTab}
39
39
  onValueChange={setActiveTab}
40
40
  variant={variant}
@@ -15,6 +15,7 @@ type Variant =
15
15
  | "heading5"
16
16
  | "body"
17
17
  | "small"
18
+ | "xs"
18
19
  | "button"
19
20
  | "code"
20
21
  | "label";
@@ -29,24 +30,26 @@ const elements: Record<Variant, React.HTMLElementType> = {
29
30
  heading5: "h5",
30
31
  body: "p",
31
32
  small: "span",
33
+ xs: "span",
32
34
  button: "span",
33
35
  code: "code",
34
36
  label: "span",
35
37
  };
36
38
 
37
39
  export const textStyles: Record<Variant, string> = {
38
- title: "font-sans text-title font-bold",
39
- subtitle: "font-sans text-subtitle font-medium",
40
- heading1: "font-sans text-heading1 font-semibold",
41
- heading2: "font-sans text-heading2 font-medium",
42
- heading3: "font-sans text-heading3 font-normal",
43
- heading4: "font-sans text-heading4 font-medium",
44
- heading5: "font-sans text-heading5 font-medium",
45
- body: "font-sans text-base font-normal",
46
- small: "font-sans text-heading5 font-normal",
47
- button: "font-sans text-button font-medium",
48
- code: "font-mono text-code",
49
- label: "font-sans text-label font-normal uppercase",
40
+ title: "n-font-sans n-text-title n-font-bold",
41
+ subtitle: "n-font-sans n-text-subtitle n-font-medium",
42
+ heading1: "n-font-sans n-text-heading1 n-font-semibold",
43
+ heading2: "n-font-sans n-text-heading2 n-font-medium",
44
+ heading3: "n-font-sans n-text-heading3 n-font-normal",
45
+ heading4: "n-font-sans n-text-heading4 n-font-medium",
46
+ heading5: "n-font-sans n-text-heading5 n-font-medium",
47
+ body: "n-font-sans n-text-body n-font-normal",
48
+ small: "n-font-sans n-text-heading5 n-font-normal",
49
+ xs: "n-font-sans n-text-xs n-font-normal",
50
+ button: "n-font-sans n-text-button n-font-medium",
51
+ code: "n-font-mono n-text-code",
52
+ label: "n-font-sans n-text-label n-font-normal n-uppercase",
50
53
  } as const;
51
54
 
52
55
  export type TextProps = {
@@ -86,7 +89,8 @@ export const Text = forwardRef(function Text(
86
89
  <Component
87
90
  ref={forwardedRef}
88
91
  className={cx(
89
- `${textStyles[variant]} text-${camelToKebabCase(String(color)) ?? "text"} `,
92
+ textStyles[variant],
93
+ `n-text-${camelToKebabCase(String(color)) ?? "text"}`,
90
94
  className
91
95
  )}
92
96
  style={style}
@@ -145,6 +149,12 @@ export const Small = forwardRef(
145
149
  )
146
150
  );
147
151
 
152
+ export const ExtraSmall = forwardRef(
153
+ (props: PresetProps, ref: ForwardedRef<HTMLElement>) => (
154
+ <Text ref={ref} {...props} variant="xs" />
155
+ )
156
+ );
157
+
148
158
  // export const Label = forwardRef(
149
159
  // (props: PresetProps, ref: ForwardedRef<HTMLElement>) => (
150
160
  // <Text ref={ref} {...props} variant="label" />
@@ -68,7 +68,7 @@ export const TextArea = memo(
68
68
  return (
69
69
  <textarea
70
70
  className={cx(
71
- `font-sans text-heading5 font-normal text-text bg-input-background flex-1 py-1 px-1.5 border-none outline-none min-h-6 w-full rounded resize-none placeholder:text-text-disabled focus:ring-2 focus:ring-primary read-only:text-text-disabled focus:z-interactable transition-all`,
71
+ `n-font-sans n-text-heading5 n-font-normal n-text-text n-bg-input-background n-flex-1 n-py-1 n-px-1.5 n-border-none n-outline-none n-min-h-6 n-w-full n-rounded n-resize-none placeholder:n-text-text-disabled focus:n-ring-2 focus:n-ring-primary read-only:n-text-text-disabled focus:n-z-interactable n-transition-[box-shadow]`,
72
72
  className
73
73
  )}
74
74
  {...rest}
@@ -102,10 +102,10 @@ export const TextAreaRow = memo(
102
102
  return (
103
103
  <div
104
104
  ref={forwardedRef}
105
- className={cx("relative w-full h-full", className)}
105
+ className={cx("n-relative n-w-full n-h-full", className)}
106
106
  >
107
107
  <TextArea className={textAreaClassName} {...rest} ref={textAreaRef} />
108
- <div className={cx("absolute right-1 top-4", endClassName)}>{end}</div>
108
+ <div className={cx("n-absolute n-right-1 n-top-4", endClassName)}>{end}</div>
109
109
  </div>
110
110
  );
111
111
  })
@@ -15,18 +15,18 @@ export const Toast = ({
15
15
  }) => {
16
16
  return (
17
17
  <ToastPrimitive.Root
18
- className="px-2 py-2.5 grid grid-cols-[auto_max-content] gap-x-2.5 items-center rounded text-sm bg-popover-background shadow-popover text-text-muted"
18
+ className="n-px-2 n-py-2.5 n-grid n-grid-cols-[auto_max-content] n-gap-x-2.5 n-items-center n-rounded n-text-sm n-bg-popover-background n-shadow-popover n-text-text-muted"
19
19
  {...props}
20
20
  >
21
21
  {title && (
22
22
  <ToastPrimitive.Title
23
- className={`mb-[5px] ${textStyles.label} font-bold text-text`}
23
+ className={`n-mb-[5px] ${textStyles.label} n-font-bold n-text-text`}
24
24
  >
25
25
  {title}
26
26
  </ToastPrimitive.Title>
27
27
  )}
28
28
  <ToastPrimitive.Description
29
- className={`${textStyles.small} text-text-muted`}
29
+ className={`${textStyles.small} n-text-text-muted`}
30
30
  >
31
31
  {content}
32
32
  </ToastPrimitive.Description>
@@ -45,6 +45,6 @@ export const Toast = ({
45
45
  export const ToastProvider = ({ children }: { children: React.ReactNode }) => (
46
46
  <ToastPrimitive.Provider>
47
47
  {children}
48
- <ToastPrimitive.Viewport className="fixed bottom-0 right-0 flex flex-col p-5 g-2.5 min-w-[200px] max-w-[100vw] m-0 list-none z-[2147483647] outline-none" />
48
+ <ToastPrimitive.Viewport className="n-fixed n-bottom-0 n-right-0 n-flex n-flex-col n-p-5 n-gap-2.5 n-min-w-[200px] n-max-w-[100vw] n-m-0 n-list-none n-z-[2147483647] n-outline-none" />
49
49
  </ToastPrimitive.Provider>
50
50
  );
@@ -1,7 +1,12 @@
1
1
  "use client";
2
2
 
3
3
  import { useKeyboardShortcuts } from "@noya-app/noya-keymap";
4
- import React, { ComponentProps, ReactNode } from "react";
4
+ import React, {
5
+ ComponentProps,
6
+ createContext,
7
+ ReactNode,
8
+ useContext,
9
+ } from "react";
5
10
 
6
11
  import { memoGeneric } from "@noya-app/react-utils";
7
12
  import { BaseToolbar } from "./BaseToolbar";
@@ -24,6 +29,10 @@ import { Tooltip } from "./Tooltip";
24
29
 
25
30
  const SIDE_OFFSET = 6;
26
31
 
32
+ export const ToolbarMenuContext = createContext<{
33
+ dividerOverflow?: number;
34
+ }>({});
35
+
27
36
  export const ToolbarMenuDropdown = memoGeneric(function ToolbarMenuDropdown<
28
37
  T extends string,
29
38
  >({
@@ -66,7 +75,7 @@ export const ToolbarShortcut = ({
66
75
  label?: ReactNode;
67
76
  }) => {
68
77
  return (
69
- <div className="flex items-center">
78
+ <div className="n-flex n-items-center">
70
79
  {label}
71
80
  <Spacer.Horizontal size={6} />
72
81
  <KeyboardShortcut shortcut={shortcut} />
@@ -140,20 +149,23 @@ export const ToolbarMenuButton = memoGeneric(function ToolbarMenuButton<
140
149
  as,
141
150
  activeValue,
142
151
  tooltipSide,
152
+ displayFilter,
143
153
  }: {
144
154
  item: SelectableMenuItem<T>;
145
155
  onSelectMenuItem?: (value: T) => void;
146
156
  as?: ComponentProps<typeof Button>["as"];
147
157
  activeValue?: T;
148
158
  tooltipSide?: ComponentProps<typeof Tooltip>["side"];
159
+ displayFilter?: DisplayFilter;
149
160
  }) {
150
161
  const isActive = item.checked || activeValue === item.value;
162
+
151
163
  const content = (
152
164
  <Button
153
165
  as={as}
154
166
  disabled={item.disabled}
155
167
  active={isActive}
156
- icon={item.icon}
168
+ icon={displayFilter === "textOnly" ? undefined : item.icon}
157
169
  {...(as === "a" && { href: item.value })}
158
170
  onClick={() => {
159
171
  if (onSelectMenuItem && item.value) {
@@ -161,14 +173,18 @@ export const ToolbarMenuButton = memoGeneric(function ToolbarMenuButton<
161
173
  }
162
174
  }}
163
175
  >
164
- {item.title}
176
+ {displayFilter === "iconOnly" ? undefined : item.title}
165
177
  </Button>
166
178
  );
167
179
 
180
+ const tooltip =
181
+ item.tooltip ??
182
+ (displayFilter === "iconOnly" && item.title ? item.title : undefined);
183
+
168
184
  return item.drawer && isActive ? (
169
185
  <ToolbarDrawer trigger={content}>{item.drawer}</ToolbarDrawer>
170
- ) : item.tooltip ? (
171
- <Tooltip sideOffset={SIDE_OFFSET} content={item.tooltip} side={tooltipSide}>
186
+ ) : tooltip ? (
187
+ <Tooltip sideOffset={SIDE_OFFSET} content={tooltip} side={tooltipSide}>
172
188
  {content}
173
189
  </Tooltip>
174
190
  ) : (
@@ -185,6 +201,8 @@ export const ToolbarMenuItem = memoGeneric(function ToolbarMenuItem<
185
201
  activeValue,
186
202
  tooltipSide,
187
203
  portalContainer,
204
+ displayFilter,
205
+ dividerOverflow: dividerOverflowProp,
188
206
  }: {
189
207
  item: MenuItem<T>;
190
208
  onSelectMenuItem?: (value: T) => void;
@@ -192,14 +210,24 @@ export const ToolbarMenuItem = memoGeneric(function ToolbarMenuItem<
192
210
  activeValue?: T;
193
211
  tooltipSide?: ComponentProps<typeof Tooltip>["side"];
194
212
  portalContainer?: HTMLElement | null;
213
+ displayFilter?: DisplayFilter;
214
+ dividerOverflow?: number;
195
215
  }) {
216
+ const { dividerOverflow: contextDividerOverflow } =
217
+ useContext(ToolbarMenuContext);
218
+
219
+ const dividerOverflow = dividerOverflowProp ?? contextDividerOverflow ?? 4;
220
+
196
221
  if (item.type === "sectionHeader") return null;
222
+
197
223
  if (item.type === "separator") {
198
- return <DividerVertical overflow={4} />;
224
+ return <DividerVertical overflow={dividerOverflow} />;
199
225
  }
226
+
200
227
  if (item.type === "popover") {
201
228
  return <ToolbarMenuPopover item={item} portalContainer={portalContainer} />;
202
229
  }
230
+
203
231
  if (isSelectableMenuItem(item)) {
204
232
  return (
205
233
  <ToolbarMenuButton
@@ -208,6 +236,7 @@ export const ToolbarMenuItem = memoGeneric(function ToolbarMenuItem<
208
236
  as={buttonAs}
209
237
  activeValue={activeValue}
210
238
  tooltipSide={tooltipSide}
239
+ displayFilter={displayFilter}
211
240
  />
212
241
  );
213
242
  }
@@ -217,6 +246,8 @@ export const ToolbarMenuItem = memoGeneric(function ToolbarMenuItem<
217
246
  );
218
247
  });
219
248
 
249
+ type DisplayFilter = "iconOnly" | "iconAndText" | "textOnly";
250
+
220
251
  export const ToolbarMenu = memoGeneric(function ToolbarMenu<T extends string>({
221
252
  items,
222
253
  onSelectMenuItem,
@@ -224,6 +255,8 @@ export const ToolbarMenu = memoGeneric(function ToolbarMenu<T extends string>({
224
255
  activeValue,
225
256
  tooltipSide,
226
257
  portalContainer,
258
+ displayFilter = "iconAndText",
259
+ dividerOverflow,
227
260
  }: {
228
261
  items: MenuItem<T>[];
229
262
  onSelectMenuItem?: (value: T) => void;
@@ -231,6 +264,8 @@ export const ToolbarMenu = memoGeneric(function ToolbarMenu<T extends string>({
231
264
  activeValue?: T;
232
265
  tooltipSide?: ComponentProps<typeof Tooltip>["side"];
233
266
  portalContainer?: HTMLElement | null;
267
+ displayFilter?: DisplayFilter;
268
+ dividerOverflow?: number;
234
269
  }) {
235
270
  return (
236
271
  <>
@@ -244,6 +279,8 @@ export const ToolbarMenu = memoGeneric(function ToolbarMenu<T extends string>({
244
279
  activeValue={activeValue}
245
280
  tooltipSide={tooltipSide}
246
281
  portalContainer={portalContainer}
282
+ displayFilter={displayFilter}
283
+ dividerOverflow={dividerOverflow}
247
284
  />
248
285
  );
249
286
  })}
@@ -259,6 +296,7 @@ export interface ToolbarProps<T extends string = string> {
259
296
  onSelectMenuItem?: (value: T) => void;
260
297
  /** Whether to bind keyboard shortcuts for menu items. @default true */
261
298
  shouldBindKeyboardShortcuts?: boolean;
299
+ dividerOverflow?: number;
262
300
  }
263
301
 
264
302
  export function Toolbar<T extends string = string>({
@@ -268,6 +306,7 @@ export function Toolbar<T extends string = string>({
268
306
  rightMenuItems = [],
269
307
  onSelectMenuItem,
270
308
  shouldBindKeyboardShortcuts = true,
309
+ dividerOverflow,
271
310
  }: ToolbarProps<T>) {
272
311
  // Register keyboard shortcuts for all menu items
273
312
  const allMenuItems = React.useMemo(
@@ -290,20 +329,22 @@ export function Toolbar<T extends string = string>({
290
329
  logo={logo}
291
330
  left={
292
331
  leftMenuItems.length > 0 && (
293
- <div className="flex gap-2">
332
+ <div className="n-flex n-gap-2">
294
333
  <ToolbarMenu
295
334
  items={leftMenuItems}
296
335
  onSelectMenuItem={onSelectMenuItem}
336
+ dividerOverflow={dividerOverflow}
297
337
  />
298
338
  </div>
299
339
  )
300
340
  }
301
341
  right={
302
342
  rightMenuItems.length > 0 && (
303
- <div className="flex gap-2">
343
+ <div className="n-flex n-gap-2">
304
344
  <ToolbarMenu
305
345
  items={rightMenuItems}
306
346
  onSelectMenuItem={onSelectMenuItem}
347
+ dividerOverflow={dividerOverflow}
307
348
  />
308
349
  </div>
309
350
  )
@@ -12,9 +12,9 @@ export const ToolbarDrawer = memo(
12
12
  ref: ForwardedRef<HTMLDivElement>
13
13
  ) {
14
14
  return (
15
- <div className="relative" ref={ref}>
15
+ <div className="n-relative" ref={ref}>
16
16
  <div
17
- className="absolute bottom-full left-1/2 -translate-x-1/2 rounded-t-lg border border-toolbar-drawer-border border-b-transparent bg-toolbar-drawer-background -z-10"
17
+ className="n-absolute n-bottom-full n-left-1/2 -n-translate-x-1/2 n-rounded-t-lg n-border n-border-toolbar-drawer-border n-border-b-transparent n-bg-toolbar-drawer-background -n-z-10"
18
18
  style={{
19
19
  marginBottom: `${sideOffset}px`,
20
20
  boxShadow: "0px -2px 12px 0px var(--n-toolbar-drawer-shadow)",
@@ -35,7 +35,7 @@ export const Tooltip = React.memo(function Tooltip({
35
35
  align="center"
36
36
  sideOffset={sideOffset}
37
37
  collisionPadding={8}
38
- className={`${textStyles.small} text-text rounded-sm p-2 bg-popover-background shadow-tooltip z-menu`}
38
+ className={`${textStyles.small} n-text-text n-rounded-sm n-p-2 n-bg-popover-background n-shadow-tooltip n-z-menu`}
39
39
  >
40
40
  {content}
41
41
  </TooltipPrimitive.Content>
@@ -1,11 +1,16 @@
1
1
  "use client";
2
2
 
3
- import { forwardRefGeneric, memoGeneric } from "@noya-app/react-utils";
3
+ import {
4
+ forwardRefGeneric,
5
+ memoGeneric,
6
+ useStableCallback,
7
+ } from "@noya-app/react-utils";
4
8
  import React, { ForwardedRef, ReactNode, useCallback, useContext } from "react";
5
9
  import { IconButton } from "./IconButton";
6
10
  import { IconName, renderIcon } from "./Icons";
7
11
  import { ListView } from "./ListView";
8
12
  import { Spacer } from "./Spacer";
13
+ import { ListViewContext } from "./listView/ListViewContexts";
9
14
 
10
15
  /* ----------------------------------------------------------------------------
11
16
  * Row
@@ -33,14 +38,16 @@ const TreeRow = forwardRefGeneric(function TreeRow<MenuItemType extends string>(
33
38
  }: TreeViewRowProps<MenuItemType>,
34
39
  forwardedRef: ForwardedRef<HTMLLIElement>
35
40
  ) {
36
- const { expandable } = useContext(ListView.RowContext);
41
+ const { expandable } = useContext(ListViewContext);
42
+
43
+ const stableOnClickChevron = useStableCallback(onClickChevron);
37
44
 
38
45
  const handleClickChevron = useCallback(
39
46
  (event: React.MouseEvent) => {
40
47
  event.stopPropagation();
41
- onClickChevron?.({ altKey: event.altKey });
48
+ stableOnClickChevron?.({ altKey: event.altKey });
42
49
  },
43
- [onClickChevron]
50
+ [stableOnClickChevron]
44
51
  );
45
52
 
46
53
  return (
@@ -27,8 +27,8 @@ const UserPointerContainer = memo(function UserPointerContainer({
27
27
  ...style,
28
28
  }}
29
29
  className={cx(
30
- "pointer-events-none",
31
- visible ? "opacity-100" : "opacity-0",
30
+ "n-pointer-events-none",
31
+ visible ? "n-opacity-100" : "n-opacity-0",
32
32
  className
33
33
  )}
34
34
  >
@@ -51,7 +51,7 @@ const UserNameTag = memo(function UserNameTag({
51
51
  return (
52
52
  <span
53
53
  className={cx(
54
- "relative inline-block rounded-full text-xs font-medium leading-none text-white shadow-sm px-2 py-1.5 border border-background",
54
+ "n-relative n-inline-block n-rounded-full n-text-xs n-font-medium n-leading-none n-text-white n-shadow-sm n-px-2 n-py-1.5 n-border n-border-background",
55
55
  className
56
56
  )}
57
57
  style={{
@@ -153,7 +153,7 @@ export const UserPointer = memo(function UserPointer({
153
153
  style={style}
154
154
  >
155
155
  {name && (
156
- <div className="relative">
156
+ <div className="n-relative">
157
157
  <UserPointerIcon
158
158
  size={POINTER_SIZE}
159
159
  color={userBackgroundColor}
@@ -14,7 +14,7 @@ import { TextAreaRow } from "../TextArea";
14
14
 
15
15
  export const AIAssistantLoadingIndicator = () => {
16
16
  return (
17
- <div className="flex gap-2">
17
+ <div className="n-flex n-gap-2">
18
18
  <Avatar
19
19
  name="Assistant"
20
20
  size={INPUT_HEIGHT}
@@ -22,7 +22,7 @@ export const AIAssistantLoadingIndicator = () => {
22
22
  >
23
23
  <Logo style={{ width: 15 }} fill={cssVars.colors.primary} />
24
24
  </Avatar>
25
- <div className="animate-pulse">▋</div>
25
+ <div className="n-animate-pulse">▋</div>
26
26
  </div>
27
27
  );
28
28
  };
@@ -45,7 +45,7 @@ export const AIAssistantInput = forwardRef<
45
45
  value={value}
46
46
  onChange={onChange}
47
47
  ref={ref}
48
- textAreaClassName="py-2 pl-3 !pr-9 w-full mt-3"
48
+ textAreaClassName="n-py-2 n-pl-3 !n-pr-9 n-w-full n-mt-3"
49
49
  placeholder="Tell me what you'd like to do..."
50
50
  onKeyDown={onKeyDown}
51
51
  disabled={disabled}
@@ -54,7 +54,7 @@ export const AIAssistantInput = forwardRef<
54
54
  iconName="ArrowUpIcon"
55
55
  onClick={onSubmitClick}
56
56
  disabled={!value.trim() || disabled}
57
- className="h-8 w-8 z-20 rounded-full"
57
+ className="n-h-8 n-w-8 n-z-20 n-rounded-full"
58
58
  size={20}
59
59
  color={cssVars.colors.text}
60
60
  style={{
@@ -90,14 +90,14 @@ export const AIAssistantLayout = forwardRef<
90
90
  ref
91
91
  ) => {
92
92
  return (
93
- <div className={cx("flex flex-col flex-1", className)} style={style}>
94
- <div className="flex-1 min-h-0 overflow-auto" ref={ref}>
95
- <div className="flex flex-col gap-2 pb-4 pt-1">
93
+ <div className={cx("n-flex n-flex-col n-flex-1", className)} style={style}>
94
+ <div className="n-flex-1 n-min-h-0 n-overflow-auto" ref={ref}>
95
+ <div className="n-flex n-flex-col n-gap-2 n-pb-4 n-pt-1">
96
96
  {renderMessages()}
97
97
  {isLoading ? renderLoadingIndicator?.() : null}
98
98
  </div>
99
99
  </div>
100
- <div className="border-t border-divider pt-1 px-1">{renderInput()}</div>
100
+ <div className="n-border-t n-border-divider n-pt-1 n-px-1">{renderInput()}</div>
101
101
  </div>
102
102
  );
103
103
  }
@@ -5,7 +5,7 @@ import React, { ForwardedRef, forwardRef } from "react";
5
5
  type ImageState = "loading" | "error" | "loaded";
6
6
 
7
7
  const placeholderClassName =
8
- "cursor-pointer flex items-center justify-center p-4 border border-dashed border-indigo-100 rounded-md bg-indigo-50";
8
+ "n-cursor-pointer n-flex n-items-center n-justify-center n-p-4 n-border n-border-dashed n-border-indigo-100 n-rounded-md n-bg-indigo-50";
9
9
 
10
10
  export function decodeImageSrc(src?: string): string | undefined {
11
11
  if (!src) return undefined;
@@ -44,7 +44,7 @@ const ImagePlaceholderComponent = forwardRef(function ImagePlaceholderComponent(
44
44
  contentEditable={false}
45
45
  className={cx(placeholderClassName, props.className)}
46
46
  >
47
- <ImageIcon className="w-[30px] h-[30px] text-indigo-900 pointer-events-none" />
47
+ <ImageIcon className="n-w-[30px] n-h-[30px] n-text-indigo-900 n-pointer-events-none" />
48
48
  </span>
49
49
  );
50
50
  });
@@ -60,7 +60,7 @@ const ImageErrorComponent = forwardRef(function ImageErrorComponent(
60
60
  contentEditable={false}
61
61
  className={cx(placeholderClassName, props.className)}
62
62
  >
63
- <span className="text-cm-error font-mono text-sm">
63
+ <span className="n-text-cm-error n-font-mono n-text-sm">
64
64
  Invalid image URL: {src ?? "No URL provided"}
65
65
  </span>
66
66
  </span>
@@ -86,10 +86,16 @@ const ImageDisplayComponent = forwardRef(function ImageDisplayComponent(
86
86
  src={src}
87
87
  alt={alt ?? ""}
88
88
  title={title ?? undefined}
89
- className={cx(
90
- "cursor-pointer max-w-full max-h-[720px] object-contain mx-auto my-8",
91
- props.className
92
- )}
89
+ // use styles over classname for rendering to static HTML
90
+ style={{
91
+ maxWidth: "100%",
92
+ maxHeight: "720px",
93
+ height: "auto",
94
+ objectFit: "contain",
95
+ margin: "32px auto",
96
+ ...props.style,
97
+ }}
98
+ className={cx("n-cursor-pointer", props.className)}
93
99
  />
94
100
  );
95
101
  });
@@ -3,7 +3,7 @@
3
3
  import React from "react";
4
4
  import { INPUT_HEIGHT } from "../../theme";
5
5
  import { Avatar, AvatarProps, AvatarStack } from "../Avatar";
6
- import Button from "../Button";
6
+ import { Button } from "../Button";
7
7
  import { Small } from "../Text";
8
8
  import { Tooltip } from "../Tooltip";
9
9
 
@@ -11,7 +11,7 @@ export const UserAvatar = ({ userId, name, image }: AvatarProps) => (
11
11
  <Tooltip
12
12
  key={userId}
13
13
  content={
14
- <div className="flex flex-col">
14
+ <div className="n-flex n-flex-col">
15
15
  <Small>{name}</Small>
16
16
  </div>
17
17
  }
@@ -32,8 +32,8 @@ export const ConnectedUsersMenuLayout = ({
32
32
  isAssistantOpen,
33
33
  }: ConnectedUsersMenuLayoutProps) => {
34
34
  return (
35
- <div className="flex gap-1.5">
36
- <AvatarStack size={INPUT_HEIGHT} className="mr-1">
35
+ <div className="n-flex n-gap-1.5">
36
+ <AvatarStack size={INPUT_HEIGHT} className="n-mr-1">
37
37
  {renderUsers()}
38
38
  </AvatarStack>
39
39
  {launchAIAssistant && (