@noya-app/noya-designsystem 0.1.63 → 0.1.64

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 (112) hide show
  1. package/.turbo/turbo-build.log +10 -10
  2. package/CHANGELOG.md +13 -0
  3. package/dist/index.css +1 -1
  4. package/dist/index.d.mts +169 -127
  5. package/dist/index.d.ts +169 -127
  6. package/dist/index.js +3131 -2642
  7. package/dist/index.js.map +1 -1
  8. package/dist/index.mjs +2941 -2459
  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 +5 -3
  15. package/src/components/AnimatePresence.tsx +6 -4
  16. package/src/components/Avatar.tsx +4 -2
  17. package/src/components/BaseToolbar.tsx +45 -34
  18. package/src/components/Breadcrumbs.tsx +2 -0
  19. package/src/components/Button.tsx +9 -3
  20. package/src/components/Chip.tsx +2 -0
  21. package/src/components/Collection.tsx +4 -1
  22. package/src/components/ColorSwatch.tsx +42 -22
  23. package/src/components/ColorSwatchControl.tsx +5 -3
  24. package/src/components/Combobox.tsx +2 -0
  25. package/src/components/CommandPalette.tsx +2 -0
  26. package/src/components/ContextMenu.tsx +3 -1
  27. package/src/components/Dialog.tsx +3 -1
  28. package/src/components/DimensionInput.tsx +2 -0
  29. package/src/components/Divider.tsx +10 -6
  30. package/src/components/DraggableMenuButton.tsx +2 -0
  31. package/src/components/Drawer.tsx +11 -3
  32. package/src/components/DropdownMenu.tsx +3 -1
  33. package/src/components/EditableText.tsx +2 -0
  34. package/src/components/FileUploadIndicator.tsx +108 -0
  35. package/src/components/FillPreviewBackground.tsx +2 -0
  36. package/src/components/FloatingWindow.tsx +2 -0
  37. package/src/components/GradientPicker.tsx +2 -0
  38. package/src/components/Grid.tsx +47 -38
  39. package/src/components/GridView.tsx +33 -11
  40. package/src/components/IconButton.tsx +2 -0
  41. package/src/components/Icons.tsx +1 -1
  42. package/src/components/InputField.tsx +2 -0
  43. package/src/components/Label.tsx +1 -1
  44. package/src/components/LabeledElementView.tsx +6 -2
  45. package/src/components/LabeledField.tsx +2 -0
  46. package/src/components/List.tsx +10 -2
  47. package/src/components/ListView.tsx +27 -14
  48. package/src/components/MediaThumbnail.tsx +31 -2
  49. package/src/components/Message.tsx +8 -9
  50. package/src/components/Popover.tsx +3 -1
  51. package/src/components/Progress.tsx +20 -7
  52. package/src/components/ScrollArea.tsx +3 -1
  53. package/src/components/SearchCompletionMenu.tsx +2 -0
  54. package/src/components/Section.tsx +2 -0
  55. package/src/components/SegmentedControl.tsx +42 -16
  56. package/src/components/SelectMenu.tsx +3 -1
  57. package/src/components/SelectionToolbar.tsx +3 -1
  58. package/src/components/Slider.tsx +3 -1
  59. package/src/components/Switch.tsx +3 -1
  60. package/src/components/Tabs.tsx +3 -1
  61. package/src/components/Text.tsx +6 -5
  62. package/src/components/TextArea.tsx +2 -0
  63. package/src/components/Toast.tsx +1 -1
  64. package/src/components/Toolbar.tsx +8 -2
  65. package/src/components/ToolbarDrawer.tsx +29 -0
  66. package/src/components/Tooltip.tsx +1 -1
  67. package/src/components/TreeView.tsx +2 -0
  68. package/src/components/UserPointer.tsx +2 -0
  69. package/src/components/ai-assistant/AIAssistantLayout.tsx +2 -0
  70. package/src/components/blocks/ImageBlockComponent.tsx +156 -0
  71. package/src/components/connected-users-menu/ConnectedUsersMenuLayout.tsx +2 -0
  72. package/src/components/file-explorer/FileExplorerLayout.tsx +2 -0
  73. package/src/components/internal/Checkmark.tsx +24 -0
  74. package/src/components/internal/Menu.tsx +2 -1
  75. package/src/components/internal/MenuViewport.tsx +4 -2
  76. package/src/components/internal/SelectItem.tsx +4 -23
  77. package/src/components/internal/TextInput.tsx +2 -0
  78. package/src/components/sorting/DragRegistration.tsx +8 -4
  79. package/src/components/sorting/SharedDragProvider.tsx +43 -18
  80. package/src/components/sorting/Sortable.tsx +19 -10
  81. package/src/components/sorting/createSharedDrag.tsx +7 -6
  82. package/src/components/sorting/sorting.ts +6 -9
  83. package/src/components/workspace/DrawerWorkspaceLayout.tsx +107 -26
  84. package/src/components/workspace/PanelWorkspaceLayout.tsx +98 -65
  85. package/src/components/workspace/VerticalTabMenu.tsx +36 -0
  86. package/src/components/workspace/WorkspaceLayout.tsx +46 -42
  87. package/src/components/workspace/constants.ts +4 -0
  88. package/src/components/workspace/panelStorage.ts +216 -0
  89. package/src/components/workspace/types.ts +11 -4
  90. package/src/contexts/DesignSystemConfiguration.tsx +12 -5
  91. package/src/contexts/DialogContext.tsx +2 -0
  92. package/src/contexts/FloatingWindowContext.tsx +2 -0
  93. package/src/contexts/GlobalInputBlurContext.tsx +2 -0
  94. package/src/contexts/ImageDataContext.tsx +2 -0
  95. package/src/contexts/LinkComponentContext.tsx +17 -0
  96. package/src/contexts/OpenPortalsContext.tsx +2 -0
  97. package/src/contexts/PortalScopeContext.tsx +2 -0
  98. package/src/hooks/mergeEventHandlers.ts +1 -1
  99. package/src/hooks/useHover.ts +30 -17
  100. package/src/hooks/useIndent.ts +2 -0
  101. package/src/hooks/useLabel.ts +2 -0
  102. package/src/hooks/useObjectURL.ts +5 -3
  103. package/src/hooks/usePlatform.ts +5 -3
  104. package/src/hooks/useTheme.ts +3 -1
  105. package/src/index.css +19 -0
  106. package/src/index.tsx +5 -2
  107. package/src/utils/classNames.ts +4 -3
  108. package/src/utils/combobox.ts +2 -0
  109. package/src/utils/formatByteSize.ts +8 -0
  110. package/src/utils/withSeparatorElements.ts +1 -1
  111. package/src/hooks/usePreservePanelSize.tsx +0 -183
  112. package/src/mediaQuery.ts +0 -13
@@ -1,3 +1,5 @@
1
+ "use client";
2
+
1
3
  import React, {
2
4
  CSSProperties,
3
5
  ForwardedRef,
@@ -12,7 +14,7 @@ import { Spacer } from "./Spacer";
12
14
  import { Tooltip } from "./Tooltip";
13
15
  import type { MenuItemIcon } from "./internal/Menu";
14
16
 
15
- type ButtonVariant = "normal" | "thin" | "floating" | "none";
17
+ type ButtonVariant = "normal" | "thin" | "floating" | "none" | "ghost";
16
18
 
17
19
  type ButtonSize = "small" | "normal" | "large" | "floating";
18
20
 
@@ -32,6 +34,7 @@ const variantStyles: Record<ButtonVariant, string> = {
32
34
  "bg-floating-button text-text shadow-[0_1px_2px_rgba(0,0,0,0.1)] hover:opacity-80 active:opacity-90",
33
35
  thin: "bg-transparent text-text hover:bg-input-background-light",
34
36
  none: "bg-transparent text-text hover:bg-input-background-light",
37
+ ghost: "bg-transparent text-text hover:bg-input-background-light",
35
38
  };
36
39
 
37
40
  const sizeStyles: Record<ButtonSize | "thin" | "none", string> = {
@@ -97,7 +100,8 @@ export const Button = forwardRef(function Button(
97
100
  const colorScheme = colorSchemeProp ?? "default";
98
101
 
99
102
  const baseClassName = cx(
100
- "no-underline leading-[1] relative border-0 outline-none select-none text-left rounded flex items-center justify-center [&>*]:pointer-events-none [-webkit-app-region:no-drag] focus:ring-2 focus:ring-primary focus:shadow-[0_0_0_1px_var(--n-popover-background)_inset] transition-all",
103
+ "no-underline leading-[1] relative outline-none select-none text-left rounded flex items-center justify-center [&>*]:pointer-events-none [-webkit-app-region:no-drag] focus:ring-2 focus:ring-primary focus:shadow-[0_0_0_1px_var(--n-popover-background)_inset] transition-all",
104
+ variant === "ghost" ? "border" : "border-0",
101
105
  disabled ? "opacity-25" : "",
102
106
  !className?.includes("flex") ? "flex-none" : "",
103
107
  active
@@ -113,7 +117,9 @@ export const Button = forwardRef(function Button(
113
117
  : size
114
118
  ],
115
119
  variant === "normal" && (children ? "min-w-[31px]" : "min-w-[27px]"),
116
- colorSchemeStyles[colorScheme],
120
+ variant === "ghost"
121
+ ? "border-solid border-input-background hover:bg-input-background-light"
122
+ : colorSchemeStyles[colorScheme],
117
123
  !children && (icon || iconRight) && !(icon && iconRight) && "w-[27px]"
118
124
  );
119
125
 
@@ -1,3 +1,5 @@
1
+ "use client";
2
+
1
3
  import { Cross1Icon, PlusIcon } from "@noya-app/noya-icons";
2
4
  import React, { forwardRef, memo } from "react";
3
5
  import { useHover } from "../hooks/useHover";
@@ -1,3 +1,5 @@
1
+ "use client";
2
+
1
3
  import { forwardRefGeneric } from "@noya-app/react-utils";
2
4
  import React from "react";
3
5
  import { Grid } from "../components/Grid";
@@ -35,6 +37,7 @@ export interface CollectionProps<T, M extends string = string> {
35
37
  items: T[];
36
38
  getId: (item: T) => string;
37
39
  getName: (item: T) => string;
40
+ getHref?: (item: T) => string;
38
41
  getExpanded?: (item: T) => boolean | undefined;
39
42
  getPlaceholder?: (item: T) => string;
40
43
  /**
@@ -90,7 +93,7 @@ export interface CollectionProps<T, M extends string = string> {
90
93
  viewType?: CollectionViewType;
91
94
  onClickItem?: (itemId: string) => void;
92
95
  onDoubleClickItem?: (itemId: string) => void;
93
- renderEmptyState?: () => React.ReactElement;
96
+ renderEmptyState?: () => React.ReactElement<any>;
94
97
  /** @default false */
95
98
  sortable?: boolean;
96
99
  dragIndicatorStyle?: ListViewRowProps<M>["dragIndicatorStyle"];
@@ -1,9 +1,16 @@
1
+ import { getContrastColorForBackground } from "@noya-app/noya-colorpicker";
2
+ import { CheckIcon } from "@noya-app/noya-icons";
1
3
  import React, { forwardRef } from "react";
2
- import { cssVars } from "../theme";
3
4
 
4
- export type ColorSwatchSize = "xsmall" | "small" | "medium" | "large";
5
+ export type ColorSwatchSize =
6
+ | "xxsmall"
7
+ | "xsmall"
8
+ | "small"
9
+ | "medium"
10
+ | "large";
5
11
 
6
- export const colorSwatchSizeMap = {
12
+ export const colorSwatchSizeMap: Record<ColorSwatchSize, number> = {
13
+ xxsmall: 15,
7
14
  xsmall: 21,
8
15
  small: 27,
9
16
  medium: 36,
@@ -14,32 +21,45 @@ type ColorSwatchProps = {
14
21
  color: string;
15
22
  size?: ColorSwatchSize;
16
23
  checked?: boolean;
24
+ as?: React.ElementType;
17
25
  } & React.ButtonHTMLAttributes<HTMLButtonElement>;
18
26
 
19
27
  export const ColorSwatch = forwardRef(function ColorSwatch(
20
- { color, size = "small", checked, style, ...props }: ColorSwatchProps,
28
+ {
29
+ color,
30
+ size = "small",
31
+ checked,
32
+ style,
33
+ as: Component = "button",
34
+ ...props
35
+ }: ColorSwatchProps,
21
36
  ref: React.ForwardedRef<HTMLButtonElement>
22
37
  ) {
23
38
  const sizePx = colorSwatchSizeMap[size];
39
+ const checkedColor = getContrastColorForBackground(color);
24
40
 
25
41
  return (
26
- <button
27
- {...props}
28
- ref={ref}
29
- style={{
30
- background: color,
31
- width: sizePx,
32
- height: sizePx,
33
- borderRadius: "4px",
34
- outline: checked
35
- ? `2px solid ${cssVars.colors.primary}`
36
- : "1px solid rgba(0,0,0,0.1)",
37
- outlineOffset: -1,
38
- boxShadow: checked
39
- ? `inset 0 0 0 2px ${cssVars.colors.background}`
40
- : undefined,
41
- ...style,
42
- }}
43
- />
42
+ <div className="relative" style={{ width: sizePx, height: sizePx }}>
43
+ <Component
44
+ {...props}
45
+ ref={ref}
46
+ style={{
47
+ display: "inline-block",
48
+ background: color,
49
+ width: sizePx,
50
+ height: sizePx,
51
+ borderRadius: "4px",
52
+ outline: "1px solid rgba(0,0,0,0.1)",
53
+ outlineOffset: -1,
54
+ ...style,
55
+ }}
56
+ />
57
+ {checked && (
58
+ <CheckIcon
59
+ color={checkedColor}
60
+ className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2"
61
+ />
62
+ )}
63
+ </div>
44
64
  );
45
65
  });
@@ -1,4 +1,6 @@
1
- import * as ToggleGroupPrimitive from "@radix-ui/react-toggle-group";
1
+ "use client";
2
+
3
+ import { ToggleGroup as ToggleGroupPrimitive } from "radix-ui";
2
4
  import React, { useMemo } from "react";
3
5
  import { cx } from "../utils/classNames";
4
6
  import {
@@ -52,7 +54,7 @@ export function ColorSwatchControl(props: ColorSwatchControlProps) {
52
54
  {options.map((option) => {
53
55
  const item = (
54
56
  <ColorSwatch
55
- key={option as string}
57
+ key={`${option as string}-color-swatch`}
56
58
  color={option as string}
57
59
  size={size}
58
60
  onFocus={onFocus}
@@ -67,7 +69,7 @@ export function ColorSwatchControl(props: ColorSwatchControlProps) {
67
69
  <ToggleGroupPrimitive.Item
68
70
  asChild
69
71
  value={option as string}
70
- key={option as string}
72
+ key={`${option as string}-toggle-group-item`}
71
73
  disabled={readOnly}
72
74
  >
73
75
  {item}
@@ -1,3 +1,5 @@
1
+ "use client";
2
+
1
3
  import { DropdownChevronIcon } from "@noya-app/noya-icons";
2
4
  import { forwardRefGeneric, memoGeneric } from "@noya-app/react-utils";
3
5
  import React, {
@@ -1,3 +1,5 @@
1
+ "use client";
2
+
1
3
  import { Dialog } from "@noya-app/noya-designsystem";
2
4
  import { AutoSizer } from "@noya-app/react-utils";
3
5
  import React, { memo } from "react";
@@ -1,6 +1,8 @@
1
+ "use client";
2
+
1
3
  import { useKeyboardShortcuts } from "@noya-app/noya-keymap";
2
4
  import { memoGeneric } from "@noya-app/react-utils";
3
- import * as RadixContextMenu from "@radix-ui/react-context-menu";
5
+ import { ContextMenu as RadixContextMenu } from "radix-ui";
4
6
  import React, { ReactNode, useCallback, useMemo } from "react";
5
7
  import {
6
8
  portalScopeProps,
@@ -1,4 +1,6 @@
1
- import * as DialogPrimitive from "@radix-ui/react-dialog";
1
+ "use client";
2
+
3
+ import { Dialog as DialogPrimitive } from "radix-ui";
2
4
  import React, {
3
5
  ComponentProps,
4
6
  ForwardedRef,
@@ -1,3 +1,5 @@
1
+ "use client";
2
+
1
3
  import { round } from "@noya-app/noya-utils";
2
4
  import * as React from "react";
3
5
  import { InputField } from "./InputField";
@@ -12,7 +12,7 @@ interface DividerProps {
12
12
 
13
13
  export const Divider = memo(function Divider({
14
14
  variant = "normal",
15
- overflow = 0,
15
+ overflow,
16
16
  className,
17
17
  style,
18
18
  ...props
@@ -29,8 +29,10 @@ export const Divider = memo(function Divider({
29
29
  className
30
30
  )}
31
31
  style={{
32
- marginLeft: `-${overflow}px`,
33
- marginRight: `-${overflow}px`,
32
+ ...(typeof overflow === "number" && {
33
+ marginLeft: `-${overflow}px`,
34
+ marginRight: `-${overflow}px`,
35
+ }),
34
36
  ...style,
35
37
  }}
36
38
  {...props}
@@ -40,7 +42,7 @@ export const Divider = memo(function Divider({
40
42
 
41
43
  export const DividerVertical = memo(function DividerVertical({
42
44
  variant = "normal",
43
- overflow = 0,
45
+ overflow,
44
46
  className,
45
47
  style,
46
48
  ...props
@@ -57,8 +59,10 @@ export const DividerVertical = memo(function DividerVertical({
57
59
  className
58
60
  )}
59
61
  style={{
60
- marginTop: `-${overflow}px`,
61
- marginBottom: `-${overflow}px`,
62
+ ...(typeof overflow === "number" && {
63
+ marginTop: `-${overflow}px`,
64
+ marginBottom: `-${overflow}px`,
65
+ }),
62
66
  ...style,
63
67
  }}
64
68
  {...props}
@@ -1,3 +1,5 @@
1
+ "use client";
2
+
1
3
  import { DragHandleDots2Icon } from "@noya-app/noya-icons";
2
4
  import {
3
5
  memoGeneric,
@@ -1,5 +1,7 @@
1
+ "use client";
2
+
1
3
  import { useControlledOrUncontrolled } from "@noya-app/react-utils";
2
- import * as Dialog from "@radix-ui/react-dialog";
4
+ import { Dialog } from "radix-ui";
3
5
  import * as React from "react";
4
6
  import { useImperativeHandle } from "react";
5
7
  import { cx } from "../utils/classNames";
@@ -14,6 +16,8 @@ export type DrawerProps = {
14
16
  className?: string;
15
17
  id?: string;
16
18
  style?: React.CSSProperties;
19
+ overlayClassName?: string;
20
+ overlayStyle?: React.CSSProperties;
17
21
  positioning?: "fixed" | "absolute";
18
22
  side?: "left" | "right";
19
23
  portalled?: boolean;
@@ -35,6 +39,8 @@ export const Drawer = React.memo(
35
39
  side = "left",
36
40
  portalled = true,
37
41
  portalContainer,
42
+ overlayClassName,
43
+ overlayStyle,
38
44
  }: DrawerProps,
39
45
  forwardedRef: React.ForwardedRef<SidebarRef>
40
46
  ) {
@@ -61,14 +67,16 @@ export const Drawer = React.memo(
61
67
  <Dialog.Overlay
62
68
  className={cx(
63
69
  "inset-0 bg-black/50",
64
- positioning === "absolute" ? "absolute" : "fixed"
70
+ positioning === "absolute" ? "absolute" : "fixed",
71
+ overlayClassName
65
72
  )}
73
+ style={overlayStyle}
66
74
  />
67
75
  <Dialog.Content
68
76
  id={id}
69
77
  style={style}
70
78
  className={cx(
71
- `top-0 bottom-0 z-50 overflow-y-auto bg-sidebar-background rounded-none max-h-none max-w-none sm:max-w-sm w-full border-divider-strong`,
79
+ `top-0 bottom-0 z-50 overflow-y-auto bg-sidebar-background rounded-none max-h-none max-w-none sm:!max-w-sm w-full border-divider-strong`,
72
80
  positioning === "absolute" ? "absolute" : "fixed",
73
81
  side === "left" ? "left-0 border-r" : "right-0 border-l",
74
82
  className
@@ -1,10 +1,12 @@
1
+ "use client";
2
+
1
3
  import { useKeyboardShortcuts } from "@noya-app/noya-keymap";
2
4
  import {
3
5
  forwardRefGeneric,
4
6
  memoGeneric,
5
7
  useControlledOrUncontrolled,
6
8
  } from "@noya-app/react-utils";
7
- import * as RadixDropdownMenu from "@radix-ui/react-dropdown-menu";
9
+ import { DropdownMenu as RadixDropdownMenu } from "radix-ui";
8
10
  import React, {
9
11
  ComponentProps,
10
12
  ForwardedRef,
@@ -1,3 +1,5 @@
1
+ "use client";
2
+
1
3
  import { cx, InputField } from "@noya-app/noya-designsystem";
2
4
  import React, {
3
5
  forwardRef,
@@ -0,0 +1,108 @@
1
+ import React from "react";
2
+ import { formatByteSize } from "../utils/formatByteSize";
3
+ import { ActivityIndicator } from "./ActivityIndicator";
4
+ import { Divider } from "./Divider";
5
+ import { MediaThumbnail } from "./MediaThumbnail";
6
+ import { Progress } from "./Progress";
7
+ import { Heading5, Small } from "./Text";
8
+ import { Checkmark } from "./internal/Checkmark";
9
+
10
+ type FileUploadIndicatorProps = {
11
+ name?: string;
12
+ percent?: number;
13
+ total?: number;
14
+ contentType?: string;
15
+ };
16
+
17
+ export function FileUploadIndicator({
18
+ name,
19
+ percent,
20
+ total,
21
+ contentType,
22
+ }: FileUploadIndicatorProps) {
23
+ const done = percent === 1;
24
+ const value = typeof percent === "number" ? Math.round(percent) : 0;
25
+
26
+ return (
27
+ <div className="flex items-center gap-2">
28
+ <div className="w-12 h-12 bg-background rounded-md overflow-hidden">
29
+ <MediaThumbnail
30
+ size="medium"
31
+ fileName={name}
32
+ contentType={contentType}
33
+ />
34
+ </div>
35
+ <div className="flex flex-col flex-1 justify-center gap-1 min-w-0">
36
+ {name && (
37
+ <Small color="text" className="truncate leading-none">
38
+ {name}
39
+ </Small>
40
+ )}
41
+ {typeof total === "number" && typeof percent === "number" && (
42
+ <Small
43
+ color="textSubtle"
44
+ className="leading-none"
45
+ style={{ fontSize: "11px" }}
46
+ >
47
+ {formatByteSize(Math.round((percent / 100) * total))} /{" "}
48
+ {formatByteSize(total)}
49
+ </Small>
50
+ )}
51
+ {typeof value === "number" && (
52
+ <Progress
53
+ value={value}
54
+ style={{ marginTop: "3px", height: "3px" }}
55
+ className="rounded-sm"
56
+ colorScheme="secondary"
57
+ />
58
+ )}
59
+ </div>
60
+ <div className="flex items-center justify-center gap-2">
61
+ {typeof value === "number" && (
62
+ <Small
63
+ color="textSubtle"
64
+ style={{ lineHeight: "1", fontSize: "13px" }}
65
+ className="tabular-nums text-right w-10"
66
+ >
67
+ {value}%
68
+ </Small>
69
+ )}
70
+ {done ? (
71
+ <div className="w-[15px] h-[15px] bg-secondary rounded-full flex items-center justify-center text-white">
72
+ <Checkmark />
73
+ </div>
74
+ ) : (
75
+ <ActivityIndicator size={15} />
76
+ )}
77
+ </div>
78
+ </div>
79
+ );
80
+ }
81
+
82
+ export function FileUploadList({
83
+ uploads,
84
+ }: {
85
+ uploads: (FileUploadIndicatorProps & { id: string })[];
86
+ }) {
87
+ return (
88
+ <>
89
+ <div className="flex items-center gap-2 py-2 px-4">
90
+ <Heading5>
91
+ Uploading {uploads.length} {uploads.length === 1 ? "asset" : "assets"}
92
+ </Heading5>
93
+ </div>
94
+ <Divider />
95
+ <div className="flex flex-col gap-3 max-h-[500px] overflow-y-auto overflow-x-hidden py-4 px-4">
96
+ {uploads.map((upload) => (
97
+ <FileUploadIndicator
98
+ key={upload.id}
99
+ name={upload?.name}
100
+ percent={upload?.percent}
101
+ total={upload?.total}
102
+ contentType={upload?.contentType}
103
+ />
104
+ ))}
105
+ </div>
106
+ </>
107
+ );
108
+ }
@@ -1,3 +1,5 @@
1
+ "use client";
2
+
1
3
  import { Sketch } from "@noya-app/noya-file-format";
2
4
  // import { useSketchImage } from 'noya-renderer';
3
5
  import * as React from "react";
@@ -1,3 +1,5 @@
1
+ "use client";
2
+
1
3
  import {
2
4
  cssVars,
3
5
  Divider,
@@ -1,3 +1,5 @@
1
+ "use client";
2
+
1
3
  import {
2
4
  Alpha,
3
5
  ColorModel,
@@ -1,3 +1,5 @@
1
+ "use client";
2
+
1
3
  import {
2
4
  forwardRefGeneric,
3
5
  memoGeneric,
@@ -70,6 +72,7 @@ export const Grid = memoGeneric(
70
72
  items,
71
73
  getId,
72
74
  getName,
75
+ getHref,
73
76
  // expandable,
74
77
  // getExpanded,
75
78
  getRenamable,
@@ -84,7 +87,7 @@ export const Grid = memoGeneric(
84
87
  onSelectionChange,
85
88
  selectedIds = emptyArray,
86
89
  // itemRoleDescription = "clickable item",
87
- // detailPosition = "end",
90
+ detailPosition = "below",
88
91
  size = "medium",
89
92
  testHoveredId,
90
93
  testRenamingId,
@@ -169,6 +172,7 @@ export const Grid = memoGeneric(
169
172
  <GridView.Section>
170
173
  {items.map((item) => {
171
174
  const id = getId(item);
175
+ const href = getHref?.(item);
172
176
  const isDropdownOpen = dropdownOpenId === id;
173
177
  const isHovered =
174
178
  testHoveredId === id ||
@@ -206,9 +210,18 @@ export const Grid = memoGeneric(
206
210
  />
207
211
  ));
208
212
 
213
+ const subtitle = (
214
+ <span
215
+ className={cx(isSelected && "text-selected-list-item-text")}
216
+ >
217
+ {renderDetail?.(item, isSelected)}
218
+ </span>
219
+ );
220
+
209
221
  return (
210
222
  <GridView.Item<M>
211
223
  key={id}
224
+ href={href}
212
225
  id={id}
213
226
  role="option"
214
227
  aria-selected={isSelected}
@@ -219,44 +232,40 @@ export const Grid = memoGeneric(
219
232
  setHoveredId(isHovering ? id : undefined);
220
233
  }}
221
234
  title={
222
- <EditableText
223
- tabIndex={isRenaming ? undefined : -1}
224
- value={getName(item)}
225
- focused={isRenaming}
226
- testFocused={isTestingRenaming}
227
- readOnly={!itemIsRenamable || !onRename}
228
- onSubmit={(value) => {
229
- onRename?.(item, value);
230
- setRenamingId(undefined);
231
- }}
232
- onBlur={() => {
233
- setRenamingId(undefined);
234
- }}
235
- // max height ensures preview text and input are one line
236
- textClassName="bg-listview-editing-background max-h-input-height"
237
- >
238
- {(props) => (
239
- <span
240
- {...props}
241
- className={cx(
242
- isSelected
243
- ? "text-selected-list-item-text"
244
- : "text-text",
245
- "truncate select-none"
246
- )}
247
- />
248
- )}
249
- </EditableText>
250
- }
251
- subtitle={
252
- <span
253
- className={cx(
254
- isSelected && "text-selected-list-item-text"
255
- )}
256
- >
257
- {renderDetail?.(item, isSelected)}
258
- </span>
235
+ <div className="flex items-center gap-2 flex-row">
236
+ <EditableText
237
+ className={cx(detailPosition === "end" && "flex-1")}
238
+ tabIndex={isRenaming ? undefined : -1}
239
+ value={getName(item)}
240
+ focused={isRenaming}
241
+ testFocused={isTestingRenaming}
242
+ readOnly={!itemIsRenamable || !onRename}
243
+ onSubmit={(value) => {
244
+ onRename?.(item, value);
245
+ setRenamingId(undefined);
246
+ }}
247
+ onBlur={() => {
248
+ setRenamingId(undefined);
249
+ }}
250
+ // max height ensures preview text and input are one line
251
+ textClassName="bg-listview-editing-background max-h-input-height"
252
+ >
253
+ {(props) => (
254
+ <span
255
+ {...props}
256
+ className={cx(
257
+ isSelected
258
+ ? "text-selected-list-item-text"
259
+ : "text-text",
260
+ "truncate select-none"
261
+ )}
262
+ />
263
+ )}
264
+ </EditableText>
265
+ {detailPosition === "end" && subtitle}
266
+ </div>
259
267
  }
268
+ subtitle={detailPosition === "below" && subtitle}
260
269
  loading={false}
261
270
  selected={isSelected}
262
271
  onClick={() => {