@noya-app/noya-designsystem 0.1.63 → 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 (143) hide show
  1. package/.turbo/turbo-build.log +13 -10
  2. package/CHANGELOG.md +25 -0
  3. package/dist/index.css +1 -1
  4. package/dist/index.d.mts +648 -333
  5. package/dist/index.d.ts +648 -333
  6. package/dist/index.js +13813 -9157
  7. package/dist/index.js.map +1 -1
  8. package/dist/index.mjs +13949 -9269
  9. package/dist/index.mjs.map +1 -1
  10. package/package.json +10 -22
  11. package/src/__tests__/validateDropIndicator.test.ts +26 -41
  12. package/src/__tests__/workspaceLayout.test.ts +281 -0
  13. package/src/components/ActionMenu.tsx +2 -0
  14. package/src/components/ActivityIndicator.tsx +13 -6
  15. package/src/components/ActivityLog.tsx +197 -0
  16. package/src/components/AnimatePresence.tsx +43 -26
  17. package/src/components/Avatar.tsx +45 -26
  18. package/src/components/Banner.tsx +57 -11
  19. package/src/components/BaseToolbar.tsx +67 -34
  20. package/src/components/Breadcrumbs.tsx +4 -2
  21. package/src/components/Button.tsx +28 -23
  22. package/src/components/Checkbox.tsx +6 -35
  23. package/src/components/Chip.tsx +47 -33
  24. package/src/components/Collection.tsx +20 -12
  25. package/src/components/ColorSwatch.tsx +42 -22
  26. package/src/components/ColorSwatchControl.tsx +7 -5
  27. package/src/components/Combobox.tsx +8 -6
  28. package/src/components/ComboboxMenu.tsx +6 -5
  29. package/src/components/CommandPalette.tsx +3 -1
  30. package/src/components/ContextMenu.tsx +3 -1
  31. package/src/components/Dialog.tsx +9 -13
  32. package/src/components/DimensionInput.tsx +2 -0
  33. package/src/components/Divider.tsx +18 -14
  34. package/src/components/DraggableMenuButton.tsx +11 -5
  35. package/src/components/Drawer.tsx +14 -6
  36. package/src/components/DropdownMenu.tsx +4 -2
  37. package/src/components/EditableText.tsx +4 -2
  38. package/src/components/Fade.tsx +13 -13
  39. package/src/components/FileUploadIndicator.tsx +108 -0
  40. package/src/components/FloatingWindow.tsx +4 -2
  41. package/src/components/Grid.tsx +58 -42
  42. package/src/components/GridView.tsx +65 -36
  43. package/src/components/IVirtualizedList.tsx +5 -0
  44. package/src/components/IconButton.tsx +2 -0
  45. package/src/components/Icons.tsx +24 -4
  46. package/src/components/InputField.tsx +21 -19
  47. package/src/components/InspectorContainer.tsx +2 -2
  48. package/src/components/InspectorPrimitives.tsx +15 -9
  49. package/src/components/Label.tsx +4 -3
  50. package/src/components/LabeledElementView.tsx +9 -5
  51. package/src/components/LabeledField.tsx +8 -6
  52. package/src/components/List.tsx +112 -46
  53. package/src/components/ListMenu.tsx +91 -0
  54. package/src/components/ListNavigator.tsx +106 -0
  55. package/src/components/ListView.tsx +22 -1147
  56. package/src/components/MediaThumbnail.tsx +84 -14
  57. package/src/components/Message.tsx +11 -12
  58. package/src/components/Navigator.tsx +461 -0
  59. package/src/components/NoyaLogo.tsx +1 -1
  60. package/src/components/Popover.tsx +12 -6
  61. package/src/components/Progress.tsx +31 -8
  62. package/src/components/ResizableContainer.tsx +187 -0
  63. package/src/components/RingProgress.tsx +128 -0
  64. package/src/components/ScrollArea.tsx +7 -5
  65. package/src/components/ScrollableSidebar.tsx +10 -0
  66. package/src/components/SearchCompletionMenu.tsx +5 -3
  67. package/src/components/Section.tsx +109 -57
  68. package/src/components/SegmentedControl.tsx +51 -25
  69. package/src/components/SelectMenu.tsx +16 -21
  70. package/src/components/SelectionToolbar.tsx +7 -4
  71. package/src/components/Slider.tsx +12 -10
  72. package/src/components/Spacer.tsx +2 -2
  73. package/src/components/StackNavigator.tsx +378 -0
  74. package/src/components/Stepper.tsx +88 -0
  75. package/src/components/Switch.tsx +7 -5
  76. package/src/components/Tabs.tsx +6 -4
  77. package/src/components/Text.tsx +29 -18
  78. package/src/components/TextArea.tsx +5 -3
  79. package/src/components/Toast.tsx +5 -5
  80. package/src/components/Toolbar.tsx +57 -10
  81. package/src/components/ToolbarDrawer.tsx +29 -0
  82. package/src/components/Tooltip.tsx +2 -2
  83. package/src/components/TreeView.tsx +13 -4
  84. package/src/components/UserPointer.tsx +6 -4
  85. package/src/components/ai-assistant/AIAssistantLayout.tsx +10 -8
  86. package/src/components/blocks/ImageBlockComponent.tsx +162 -0
  87. package/src/components/connected-users-menu/ConnectedUsersMenuLayout.tsx +6 -4
  88. package/src/components/file-explorer/FileExplorerLayout.tsx +22 -8
  89. package/src/components/internal/Checkmark.tsx +24 -0
  90. package/src/components/internal/Menu.tsx +24 -19
  91. package/src/components/internal/MenuViewport.tsx +15 -6
  92. package/src/components/internal/SelectItem.tsx +7 -26
  93. package/src/components/internal/TextInput.tsx +7 -1
  94. package/src/components/listView/ListViewContexts.tsx +68 -0
  95. package/src/components/listView/ListViewEditableRowTitle.tsx +55 -0
  96. package/src/components/listView/ListViewRoot.tsx +521 -0
  97. package/src/components/listView/ListViewRow.tsx +475 -0
  98. package/src/components/listView/ListViewRowTitle.tsx +21 -0
  99. package/src/components/listView/types.ts +11 -0
  100. package/src/components/pipeline/PipelineResultLayout.tsx +6 -6
  101. package/src/components/sorting/DragRegistration.tsx +10 -5
  102. package/src/components/sorting/SharedDragProvider.tsx +134 -27
  103. package/src/components/sorting/Sortable.tsx +90 -27
  104. package/src/components/sorting/createSharedDrag.tsx +7 -6
  105. package/src/components/sorting/sorting.ts +6 -9
  106. package/src/components/workspace/DrawerWorkspaceLayout.tsx +110 -29
  107. package/src/components/workspace/PanelWorkspaceLayout.tsx +148 -99
  108. package/src/components/workspace/VerticalTabMenu.tsx +46 -0
  109. package/src/components/workspace/WorkspaceLayout.tsx +76 -63
  110. package/src/components/workspace/constants.ts +4 -0
  111. package/src/components/workspace/panelStorage.ts +216 -0
  112. package/src/components/workspace/types.ts +11 -4
  113. package/src/contexts/DesignSystemConfiguration.tsx +12 -5
  114. package/src/contexts/DialogContext.tsx +19 -5
  115. package/src/contexts/FloatingWindowContext.tsx +2 -0
  116. package/src/contexts/GlobalInputBlurContext.tsx +2 -0
  117. package/src/contexts/ImageDataContext.tsx +2 -0
  118. package/src/contexts/LinkComponentContext.tsx +17 -0
  119. package/src/contexts/OpenPortalsContext.tsx +2 -0
  120. package/src/contexts/PortalScopeContext.tsx +2 -0
  121. package/src/hooks/mergeEventHandlers.ts +1 -1
  122. package/src/hooks/useHover.ts +30 -17
  123. package/src/hooks/useIndent.ts +5 -8
  124. package/src/hooks/useLabel.ts +2 -0
  125. package/src/hooks/useObjectURL.ts +5 -3
  126. package/src/hooks/usePlatform.ts +5 -3
  127. package/src/hooks/useTheme.ts +3 -1
  128. package/src/index.css +43 -10
  129. package/src/index.tsx +15 -7
  130. package/src/utils/classNames.ts +56 -9
  131. package/src/utils/combobox.ts +2 -0
  132. package/src/utils/editableBlockStyles.ts +2 -2
  133. package/src/utils/formatByteSize.ts +9 -0
  134. package/src/utils/inputs.ts +8 -6
  135. package/src/utils/sketchColor.ts +0 -34
  136. package/src/utils/withSeparatorElements.ts +1 -1
  137. package/tailwind.config.ts +1 -0
  138. package/src/components/FillInputField.tsx +0 -37
  139. package/src/components/FillPreviewBackground.tsx +0 -132
  140. package/src/components/GradientPicker.tsx +0 -88
  141. package/src/hooks/usePreservePanelSize.tsx +0 -183
  142. package/src/mediaQuery.ts +0 -13
  143. package/src/utils/getGradientBackground.tsx +0 -31
@@ -1,5 +1,12 @@
1
+ "use client";
2
+
1
3
  import { useKeyboardShortcuts } from "@noya-app/noya-keymap";
2
- import React, { ComponentProps, ReactNode } from "react";
4
+ import React, {
5
+ ComponentProps,
6
+ createContext,
7
+ ReactNode,
8
+ useContext,
9
+ } from "react";
3
10
 
4
11
  import { memoGeneric } from "@noya-app/react-utils";
5
12
  import { BaseToolbar } from "./BaseToolbar";
@@ -17,10 +24,15 @@ import {
17
24
  } from "./internal/Menu";
18
25
  import { Popover } from "./Popover";
19
26
  import { Spacer } from "./Spacer";
27
+ import { ToolbarDrawer } from "./ToolbarDrawer";
20
28
  import { Tooltip } from "./Tooltip";
21
29
 
22
30
  const SIDE_OFFSET = 6;
23
31
 
32
+ export const ToolbarMenuContext = createContext<{
33
+ dividerOverflow?: number;
34
+ }>({});
35
+
24
36
  export const ToolbarMenuDropdown = memoGeneric(function ToolbarMenuDropdown<
25
37
  T extends string,
26
38
  >({
@@ -63,7 +75,7 @@ export const ToolbarShortcut = ({
63
75
  label?: ReactNode;
64
76
  }) => {
65
77
  return (
66
- <div className="flex items-center">
78
+ <div className="n-flex n-items-center">
67
79
  {label}
68
80
  <Spacer.Horizontal size={6} />
69
81
  <KeyboardShortcut shortcut={shortcut} />
@@ -137,19 +149,23 @@ export const ToolbarMenuButton = memoGeneric(function ToolbarMenuButton<
137
149
  as,
138
150
  activeValue,
139
151
  tooltipSide,
152
+ displayFilter,
140
153
  }: {
141
154
  item: SelectableMenuItem<T>;
142
155
  onSelectMenuItem?: (value: T) => void;
143
156
  as?: ComponentProps<typeof Button>["as"];
144
157
  activeValue?: T;
145
158
  tooltipSide?: ComponentProps<typeof Tooltip>["side"];
159
+ displayFilter?: DisplayFilter;
146
160
  }) {
161
+ const isActive = item.checked || activeValue === item.value;
162
+
147
163
  const content = (
148
164
  <Button
149
165
  as={as}
150
166
  disabled={item.disabled}
151
- active={item.checked || activeValue === item.value}
152
- icon={item.icon}
167
+ active={isActive}
168
+ icon={displayFilter === "textOnly" ? undefined : item.icon}
153
169
  {...(as === "a" && { href: item.value })}
154
170
  onClick={() => {
155
171
  if (onSelectMenuItem && item.value) {
@@ -157,12 +173,18 @@ export const ToolbarMenuButton = memoGeneric(function ToolbarMenuButton<
157
173
  }
158
174
  }}
159
175
  >
160
- {item.title}
176
+ {displayFilter === "iconOnly" ? undefined : item.title}
161
177
  </Button>
162
178
  );
163
179
 
164
- return item.tooltip ? (
165
- <Tooltip sideOffset={SIDE_OFFSET} content={item.tooltip} side={tooltipSide}>
180
+ const tooltip =
181
+ item.tooltip ??
182
+ (displayFilter === "iconOnly" && item.title ? item.title : undefined);
183
+
184
+ return item.drawer && isActive ? (
185
+ <ToolbarDrawer trigger={content}>{item.drawer}</ToolbarDrawer>
186
+ ) : tooltip ? (
187
+ <Tooltip sideOffset={SIDE_OFFSET} content={tooltip} side={tooltipSide}>
166
188
  {content}
167
189
  </Tooltip>
168
190
  ) : (
@@ -179,6 +201,8 @@ export const ToolbarMenuItem = memoGeneric(function ToolbarMenuItem<
179
201
  activeValue,
180
202
  tooltipSide,
181
203
  portalContainer,
204
+ displayFilter,
205
+ dividerOverflow: dividerOverflowProp,
182
206
  }: {
183
207
  item: MenuItem<T>;
184
208
  onSelectMenuItem?: (value: T) => void;
@@ -186,14 +210,24 @@ export const ToolbarMenuItem = memoGeneric(function ToolbarMenuItem<
186
210
  activeValue?: T;
187
211
  tooltipSide?: ComponentProps<typeof Tooltip>["side"];
188
212
  portalContainer?: HTMLElement | null;
213
+ displayFilter?: DisplayFilter;
214
+ dividerOverflow?: number;
189
215
  }) {
216
+ const { dividerOverflow: contextDividerOverflow } =
217
+ useContext(ToolbarMenuContext);
218
+
219
+ const dividerOverflow = dividerOverflowProp ?? contextDividerOverflow ?? 4;
220
+
190
221
  if (item.type === "sectionHeader") return null;
222
+
191
223
  if (item.type === "separator") {
192
- return <DividerVertical overflow={4} />;
224
+ return <DividerVertical overflow={dividerOverflow} />;
193
225
  }
226
+
194
227
  if (item.type === "popover") {
195
228
  return <ToolbarMenuPopover item={item} portalContainer={portalContainer} />;
196
229
  }
230
+
197
231
  if (isSelectableMenuItem(item)) {
198
232
  return (
199
233
  <ToolbarMenuButton
@@ -202,6 +236,7 @@ export const ToolbarMenuItem = memoGeneric(function ToolbarMenuItem<
202
236
  as={buttonAs}
203
237
  activeValue={activeValue}
204
238
  tooltipSide={tooltipSide}
239
+ displayFilter={displayFilter}
205
240
  />
206
241
  );
207
242
  }
@@ -211,6 +246,8 @@ export const ToolbarMenuItem = memoGeneric(function ToolbarMenuItem<
211
246
  );
212
247
  });
213
248
 
249
+ type DisplayFilter = "iconOnly" | "iconAndText" | "textOnly";
250
+
214
251
  export const ToolbarMenu = memoGeneric(function ToolbarMenu<T extends string>({
215
252
  items,
216
253
  onSelectMenuItem,
@@ -218,6 +255,8 @@ export const ToolbarMenu = memoGeneric(function ToolbarMenu<T extends string>({
218
255
  activeValue,
219
256
  tooltipSide,
220
257
  portalContainer,
258
+ displayFilter = "iconAndText",
259
+ dividerOverflow,
221
260
  }: {
222
261
  items: MenuItem<T>[];
223
262
  onSelectMenuItem?: (value: T) => void;
@@ -225,6 +264,8 @@ export const ToolbarMenu = memoGeneric(function ToolbarMenu<T extends string>({
225
264
  activeValue?: T;
226
265
  tooltipSide?: ComponentProps<typeof Tooltip>["side"];
227
266
  portalContainer?: HTMLElement | null;
267
+ displayFilter?: DisplayFilter;
268
+ dividerOverflow?: number;
228
269
  }) {
229
270
  return (
230
271
  <>
@@ -238,6 +279,8 @@ export const ToolbarMenu = memoGeneric(function ToolbarMenu<T extends string>({
238
279
  activeValue={activeValue}
239
280
  tooltipSide={tooltipSide}
240
281
  portalContainer={portalContainer}
282
+ displayFilter={displayFilter}
283
+ dividerOverflow={dividerOverflow}
241
284
  />
242
285
  );
243
286
  })}
@@ -253,6 +296,7 @@ export interface ToolbarProps<T extends string = string> {
253
296
  onSelectMenuItem?: (value: T) => void;
254
297
  /** Whether to bind keyboard shortcuts for menu items. @default true */
255
298
  shouldBindKeyboardShortcuts?: boolean;
299
+ dividerOverflow?: number;
256
300
  }
257
301
 
258
302
  export function Toolbar<T extends string = string>({
@@ -262,6 +306,7 @@ export function Toolbar<T extends string = string>({
262
306
  rightMenuItems = [],
263
307
  onSelectMenuItem,
264
308
  shouldBindKeyboardShortcuts = true,
309
+ dividerOverflow,
265
310
  }: ToolbarProps<T>) {
266
311
  // Register keyboard shortcuts for all menu items
267
312
  const allMenuItems = React.useMemo(
@@ -284,20 +329,22 @@ export function Toolbar<T extends string = string>({
284
329
  logo={logo}
285
330
  left={
286
331
  leftMenuItems.length > 0 && (
287
- <div className="flex gap-2">
332
+ <div className="n-flex n-gap-2">
288
333
  <ToolbarMenu
289
334
  items={leftMenuItems}
290
335
  onSelectMenuItem={onSelectMenuItem}
336
+ dividerOverflow={dividerOverflow}
291
337
  />
292
338
  </div>
293
339
  )
294
340
  }
295
341
  right={
296
342
  rightMenuItems.length > 0 && (
297
- <div className="flex gap-2">
343
+ <div className="n-flex n-gap-2">
298
344
  <ToolbarMenu
299
345
  items={rightMenuItems}
300
346
  onSelectMenuItem={onSelectMenuItem}
347
+ dividerOverflow={dividerOverflow}
301
348
  />
302
349
  </div>
303
350
  )
@@ -0,0 +1,29 @@
1
+ import React, { ForwardedRef, forwardRef, memo, ReactNode } from "react";
2
+
3
+ type ToolbarDrawerProps = {
4
+ children: ReactNode;
5
+ trigger: ReactNode;
6
+ sideOffset?: number;
7
+ };
8
+
9
+ export const ToolbarDrawer = memo(
10
+ forwardRef(function ToolbarDrawer(
11
+ { children, trigger, sideOffset = 8 }: ToolbarDrawerProps,
12
+ ref: ForwardedRef<HTMLDivElement>
13
+ ) {
14
+ return (
15
+ <div className="n-relative" ref={ref}>
16
+ <div
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
+ style={{
19
+ marginBottom: `${sideOffset}px`,
20
+ boxShadow: "0px -2px 12px 0px var(--n-toolbar-drawer-shadow)",
21
+ }}
22
+ >
23
+ {children}
24
+ </div>
25
+ {trigger}
26
+ </div>
27
+ );
28
+ })
29
+ );
@@ -1,4 +1,4 @@
1
- import * as TooltipPrimitive from "@radix-ui/react-tooltip";
1
+ import { Tooltip as TooltipPrimitive } from "radix-ui";
2
2
  import * as React from "react";
3
3
  import { ComponentProps } from "react";
4
4
  import {
@@ -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,9 +1,16 @@
1
- import { forwardRefGeneric, memoGeneric } from "@noya-app/react-utils";
1
+ "use client";
2
+
3
+ import {
4
+ forwardRefGeneric,
5
+ memoGeneric,
6
+ useStableCallback,
7
+ } from "@noya-app/react-utils";
2
8
  import React, { ForwardedRef, ReactNode, useCallback, useContext } from "react";
3
9
  import { IconButton } from "./IconButton";
4
10
  import { IconName, renderIcon } from "./Icons";
5
11
  import { ListView } from "./ListView";
6
12
  import { Spacer } from "./Spacer";
13
+ import { ListViewContext } from "./listView/ListViewContexts";
7
14
 
8
15
  /* ----------------------------------------------------------------------------
9
16
  * Row
@@ -31,14 +38,16 @@ const TreeRow = forwardRefGeneric(function TreeRow<MenuItemType extends string>(
31
38
  }: TreeViewRowProps<MenuItemType>,
32
39
  forwardedRef: ForwardedRef<HTMLLIElement>
33
40
  ) {
34
- const { expandable } = useContext(ListView.RowContext);
41
+ const { expandable } = useContext(ListViewContext);
42
+
43
+ const stableOnClickChevron = useStableCallback(onClickChevron);
35
44
 
36
45
  const handleClickChevron = useCallback(
37
46
  (event: React.MouseEvent) => {
38
47
  event.stopPropagation();
39
- onClickChevron?.({ altKey: event.altKey });
48
+ stableOnClickChevron?.({ altKey: event.altKey });
40
49
  },
41
- [onClickChevron]
50
+ [stableOnClickChevron]
42
51
  );
43
52
 
44
53
  return (
@@ -1,3 +1,5 @@
1
+ "use client";
2
+
1
3
  import React, { memo, useMemo } from "react";
2
4
  import { cx } from "../utils/classNames";
3
5
  import { colorFromString } from "../utils/colorFromString";
@@ -25,8 +27,8 @@ const UserPointerContainer = memo(function UserPointerContainer({
25
27
  ...style,
26
28
  }}
27
29
  className={cx(
28
- "pointer-events-none",
29
- visible ? "opacity-100" : "opacity-0",
30
+ "n-pointer-events-none",
31
+ visible ? "n-opacity-100" : "n-opacity-0",
30
32
  className
31
33
  )}
32
34
  >
@@ -49,7 +51,7 @@ const UserNameTag = memo(function UserNameTag({
49
51
  return (
50
52
  <span
51
53
  className={cx(
52
- "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",
53
55
  className
54
56
  )}
55
57
  style={{
@@ -151,7 +153,7 @@ export const UserPointer = memo(function UserPointer({
151
153
  style={style}
152
154
  >
153
155
  {name && (
154
- <div className="relative">
156
+ <div className="n-relative">
155
157
  <UserPointerIcon
156
158
  size={POINTER_SIZE}
157
159
  color={userBackgroundColor}
@@ -1,3 +1,5 @@
1
+ "use client";
2
+
1
3
  import { IconButton } from "../IconButton";
2
4
 
3
5
  import { cssVars } from "../../theme";
@@ -12,7 +14,7 @@ import { TextAreaRow } from "../TextArea";
12
14
 
13
15
  export const AIAssistantLoadingIndicator = () => {
14
16
  return (
15
- <div className="flex gap-2">
17
+ <div className="n-flex n-gap-2">
16
18
  <Avatar
17
19
  name="Assistant"
18
20
  size={INPUT_HEIGHT}
@@ -20,7 +22,7 @@ export const AIAssistantLoadingIndicator = () => {
20
22
  >
21
23
  <Logo style={{ width: 15 }} fill={cssVars.colors.primary} />
22
24
  </Avatar>
23
- <div className="animate-pulse">▋</div>
25
+ <div className="n-animate-pulse">▋</div>
24
26
  </div>
25
27
  );
26
28
  };
@@ -43,7 +45,7 @@ export const AIAssistantInput = forwardRef<
43
45
  value={value}
44
46
  onChange={onChange}
45
47
  ref={ref}
46
- 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"
47
49
  placeholder="Tell me what you'd like to do..."
48
50
  onKeyDown={onKeyDown}
49
51
  disabled={disabled}
@@ -52,7 +54,7 @@ export const AIAssistantInput = forwardRef<
52
54
  iconName="ArrowUpIcon"
53
55
  onClick={onSubmitClick}
54
56
  disabled={!value.trim() || disabled}
55
- className="h-8 w-8 z-20 rounded-full"
57
+ className="n-h-8 n-w-8 n-z-20 n-rounded-full"
56
58
  size={20}
57
59
  color={cssVars.colors.text}
58
60
  style={{
@@ -88,14 +90,14 @@ export const AIAssistantLayout = forwardRef<
88
90
  ref
89
91
  ) => {
90
92
  return (
91
- <div className={cx("flex flex-col flex-1", className)} style={style}>
92
- <div className="flex-1 min-h-0 overflow-auto" ref={ref}>
93
- <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">
94
96
  {renderMessages()}
95
97
  {isLoading ? renderLoadingIndicator?.() : null}
96
98
  </div>
97
99
  </div>
98
- <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>
99
101
  </div>
100
102
  );
101
103
  }
@@ -0,0 +1,162 @@
1
+ import { ActivityIndicator, cx } from "@noya-app/noya-designsystem";
2
+ import { ImageIcon } from "@noya-app/noya-icons";
3
+ import React, { ForwardedRef, forwardRef } from "react";
4
+
5
+ type ImageState = "loading" | "error" | "loaded";
6
+
7
+ const placeholderClassName =
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
+
10
+ export function decodeImageSrc(src?: string): string | undefined {
11
+ if (!src) return undefined;
12
+
13
+ try {
14
+ return decodeURIComponent(src);
15
+ } catch (error) {
16
+ return undefined;
17
+ }
18
+ }
19
+
20
+ const ImageLoadingComponent = forwardRef(function ImageLoadingComponent(
21
+ props: React.HTMLAttributes<HTMLSpanElement>,
22
+ forwardedRef: ForwardedRef<HTMLSpanElement>
23
+ ) {
24
+ return (
25
+ <span
26
+ {...props}
27
+ ref={forwardedRef}
28
+ contentEditable={false}
29
+ className={cx(placeholderClassName, props.className)}
30
+ >
31
+ <ActivityIndicator />
32
+ </span>
33
+ );
34
+ });
35
+
36
+ const ImagePlaceholderComponent = forwardRef(function ImagePlaceholderComponent(
37
+ props: React.HTMLAttributes<HTMLSpanElement>,
38
+ forwardedRef: ForwardedRef<HTMLSpanElement>
39
+ ) {
40
+ return (
41
+ <span
42
+ {...props}
43
+ ref={forwardedRef}
44
+ contentEditable={false}
45
+ className={cx(placeholderClassName, props.className)}
46
+ >
47
+ <ImageIcon className="n-w-[30px] n-h-[30px] n-text-indigo-900 n-pointer-events-none" />
48
+ </span>
49
+ );
50
+ });
51
+
52
+ const ImageErrorComponent = forwardRef(function ImageErrorComponent(
53
+ { src, ...props }: React.HTMLAttributes<HTMLSpanElement> & { src?: string },
54
+ forwardedRef: ForwardedRef<HTMLSpanElement>
55
+ ) {
56
+ return (
57
+ <span
58
+ {...props}
59
+ ref={forwardedRef}
60
+ contentEditable={false}
61
+ className={cx(placeholderClassName, props.className)}
62
+ >
63
+ <span className="n-text-cm-error n-font-mono n-text-sm">
64
+ Invalid image URL: {src ?? "No URL provided"}
65
+ </span>
66
+ </span>
67
+ );
68
+ });
69
+
70
+ const ImageDisplayComponent = forwardRef(function ImageDisplayComponent(
71
+ {
72
+ src,
73
+ alt,
74
+ title,
75
+ ...props
76
+ }: React.DetailedHTMLProps<
77
+ React.ImgHTMLAttributes<HTMLImageElement>,
78
+ HTMLImageElement
79
+ >,
80
+ forwardedRef: ForwardedRef<HTMLImageElement>
81
+ ) {
82
+ return (
83
+ <img
84
+ {...props}
85
+ ref={forwardedRef}
86
+ src={src}
87
+ alt={alt ?? ""}
88
+ title={title ?? undefined}
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)}
99
+ />
100
+ );
101
+ });
102
+
103
+ const ImageBlockComponent_ = forwardRef(function ImageBlockComponent(
104
+ {
105
+ src,
106
+ alt,
107
+ title,
108
+ imageState,
109
+ ...props
110
+ }: React.DetailedHTMLProps<
111
+ React.ImgHTMLAttributes<HTMLImageElement>,
112
+ HTMLImageElement
113
+ > & { imageState?: ImageState },
114
+ forwardedRef: ForwardedRef<HTMLImageElement>
115
+ ) {
116
+ if (imageState === "loading") {
117
+ return (
118
+ <ImageLoadingComponent
119
+ {...props}
120
+ ref={forwardedRef as ForwardedRef<HTMLSpanElement>}
121
+ />
122
+ );
123
+ }
124
+
125
+ if (!src) {
126
+ return (
127
+ <ImagePlaceholderComponent
128
+ {...props}
129
+ ref={forwardedRef as ForwardedRef<HTMLSpanElement>}
130
+ />
131
+ );
132
+ }
133
+
134
+ const decodedSrc = decodeImageSrc(src.toString());
135
+
136
+ if (!decodedSrc || imageState === "error") {
137
+ return (
138
+ <ImageErrorComponent
139
+ {...props}
140
+ src={src.toString()}
141
+ ref={forwardedRef as ForwardedRef<HTMLSpanElement>}
142
+ />
143
+ );
144
+ }
145
+
146
+ return (
147
+ <ImageDisplayComponent
148
+ {...props}
149
+ ref={forwardedRef}
150
+ src={src}
151
+ alt={alt}
152
+ title={title}
153
+ />
154
+ );
155
+ });
156
+
157
+ export const ImageBlockComponent = Object.assign(ImageBlockComponent_, {
158
+ Loading: ImageLoadingComponent,
159
+ Placeholder: ImagePlaceholderComponent,
160
+ Error: ImageErrorComponent,
161
+ Display: ImageDisplayComponent,
162
+ });
@@ -1,7 +1,9 @@
1
+ "use client";
2
+
1
3
  import React from "react";
2
4
  import { INPUT_HEIGHT } from "../../theme";
3
5
  import { Avatar, AvatarProps, AvatarStack } from "../Avatar";
4
- import Button from "../Button";
6
+ import { Button } from "../Button";
5
7
  import { Small } from "../Text";
6
8
  import { Tooltip } from "../Tooltip";
7
9
 
@@ -9,7 +11,7 @@ export const UserAvatar = ({ userId, name, image }: AvatarProps) => (
9
11
  <Tooltip
10
12
  key={userId}
11
13
  content={
12
- <div className="flex flex-col">
14
+ <div className="n-flex n-flex-col">
13
15
  <Small>{name}</Small>
14
16
  </div>
15
17
  }
@@ -30,8 +32,8 @@ export const ConnectedUsersMenuLayout = ({
30
32
  isAssistantOpen,
31
33
  }: ConnectedUsersMenuLayoutProps) => {
32
34
  return (
33
- <div className="flex gap-1.5">
34
- <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">
35
37
  {renderUsers()}
36
38
  </AvatarStack>
37
39
  {launchAIAssistant && (
@@ -1,3 +1,5 @@
1
+ "use client";
2
+
1
3
  import {
2
4
  ActivityIndicator,
3
5
  Banner,
@@ -7,7 +9,7 @@ import {
7
9
  SectionProps,
8
10
  } from "@noya-app/noya-designsystem";
9
11
  import { forwardRefGeneric } from "@noya-app/react-utils";
10
- import React from "react";
12
+ import React, { CSSProperties } from "react";
11
13
  import { cx } from "../../utils/classNames";
12
14
  import {
13
15
  Collection,
@@ -21,7 +23,7 @@ export const FileExplorerLayout = ({
21
23
  className,
22
24
  ...props
23
25
  }: SectionProps) => (
24
- <Section className={cx(className, "px-3 flex-1")} {...props}>
26
+ <Section className={cx(className, "n-px-3 n-flex-1")} {...props}>
25
27
  {children}
26
28
  </Section>
27
29
  );
@@ -37,7 +39,7 @@ export const FileExplorerUploadButton = ({
37
39
  isUploading?: boolean;
38
40
  children?: React.ReactNode;
39
41
  }) => (
40
- <div className="flex items-center gap-2">
42
+ <div className="n-flex n-items-center n-gap-2">
41
43
  {showUploadButton && (
42
44
  <Button
43
45
  icon={isUploading ? <ActivityIndicator size={15} /> : "UploadIcon"}
@@ -58,7 +60,9 @@ export const FileExplorerCollection = forwardRefGeneric(
58
60
  { ...props }: CollectionProps<T, M>,
59
61
  ref: React.ForwardedRef<CollectionRef>
60
62
  ) {
61
- return <Collection<T, M> ref={ref} className="-mx-3 flex-1" {...props} />;
63
+ return (
64
+ <Collection<T, M> ref={ref} className="-n-mx-3 n-flex-1" {...props} />
65
+ );
62
66
  }
63
67
  );
64
68
 
@@ -66,22 +70,32 @@ export const FileExplorerDetail = ({
66
70
  selected,
67
71
  size,
68
72
  children,
73
+ className,
74
+ style,
69
75
  }: {
70
76
  selected: boolean;
71
77
  children: React.ReactNode;
72
78
  size?: CollectionItemSize;
79
+ className?: string;
80
+ style?: CSSProperties;
73
81
  }) => (
74
82
  <span
75
83
  className={cx(
76
- size === "small" ? "text-list-small" : "text-sm",
77
- selected ? "text-primary" : "text-text-muted"
84
+ size === "small" ? "n-text-list-small" : "n-text-sm",
85
+ selected ? "n-text-primary" : "n-text-text-muted",
86
+ className
78
87
  )}
88
+ style={style}
79
89
  >
80
90
  {children}
81
91
  </span>
82
92
  );
83
93
 
84
94
  export const FileExplorerEmptyState = ({
85
- label = "No files",
95
+ children: label = "No files",
86
96
  ...props
87
- }: BannerProps) => <Banner label={label} className="mx-3" {...props} />;
97
+ }: BannerProps) => (
98
+ <Banner className="n-mx-3" {...props}>
99
+ {label}
100
+ </Banner>
101
+ );
@@ -0,0 +1,24 @@
1
+ import React from "react";
2
+
3
+ export function Checkmark(props: React.SVGProps<SVGSVGElement>) {
4
+ return (
5
+ <svg
6
+ viewBox="0 0 15 15"
7
+ fill="none"
8
+ xmlns="http://www.w3.org/2000/svg"
9
+ aria-hidden="true"
10
+ width={15}
11
+ height={15}
12
+ {...props}
13
+ >
14
+ <path
15
+ className="n-scale-90 n-origin-center"
16
+ d="M3 8L6 11L12 5"
17
+ strokeWidth="1.1"
18
+ strokeLinecap="round"
19
+ strokeLinejoin="round"
20
+ stroke="currentColor"
21
+ />
22
+ </svg>
23
+ );
24
+ }