@noya-app/noya-designsystem 0.1.48 → 0.1.49

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 (43) hide show
  1. package/.turbo/turbo-build.log +10 -10
  2. package/CHANGELOG.md +8 -0
  3. package/dist/index.css +1 -1
  4. package/dist/index.d.mts +213 -164
  5. package/dist/index.d.ts +213 -164
  6. package/dist/index.js +3583 -3290
  7. package/dist/index.js.map +1 -1
  8. package/dist/index.mjs +3617 -3329
  9. package/dist/index.mjs.map +1 -1
  10. package/package.json +3 -2
  11. package/src/__tests__/validateDropIndicator.test.ts +4 -4
  12. package/src/components/ActionMenu.tsx +15 -4
  13. package/src/components/BaseToolbar.tsx +2 -2
  14. package/src/components/Checkbox.tsx +2 -2
  15. package/src/components/Chip.tsx +7 -6
  16. package/src/components/Collection.tsx +19 -1
  17. package/src/components/Combobox.tsx +22 -23
  18. package/src/components/ComboboxMenu.tsx +1 -1
  19. package/src/components/DimensionInput.tsx +14 -12
  20. package/src/components/EditableText.tsx +3 -1
  21. package/src/components/FillInputField.tsx +2 -2
  22. package/src/components/Grid.tsx +133 -113
  23. package/src/components/GridView.tsx +36 -18
  24. package/src/components/InputField.tsx +116 -171
  25. package/src/components/Label.tsx +4 -68
  26. package/src/components/LabeledField.tsx +7 -1
  27. package/src/components/List.tsx +103 -44
  28. package/src/components/ListView.tsx +42 -26
  29. package/src/components/MediaThumbnail.tsx +3 -3
  30. package/src/components/Message.tsx +8 -8
  31. package/src/components/NoyaLogo.tsx +41 -0
  32. package/src/components/SegmentedControl.tsx +1 -1
  33. package/src/components/SelectMenu.tsx +9 -4
  34. package/src/components/Slider.tsx +16 -7
  35. package/src/components/Sortable.tsx +62 -25
  36. package/src/components/internal/Menu.tsx +8 -3
  37. package/src/components/internal/SelectItem.tsx +3 -2
  38. package/src/index.css +7 -1
  39. package/src/index.tsx +2 -0
  40. package/src/theme/index.ts +2 -0
  41. package/src/utils/classNames.ts +51 -3
  42. package/src/utils/inputs.ts +21 -0
  43. package/tailwind.config.ts +7 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@noya-app/noya-designsystem",
3
- "version": "0.1.48",
3
+ "version": "0.1.49",
4
4
  "main": "./dist/index.js",
5
5
  "module": "./dist/index.mjs",
6
6
  "types": "./dist/index.d.ts",
@@ -24,7 +24,7 @@
24
24
  "@dnd-kit/sortable": "10.0.0",
25
25
  "@noya-app/noya-colorpicker": "0.1.18",
26
26
  "@noya-app/noya-utils": "0.1.4",
27
- "@noya-app/noya-geometry": "0.1.8",
27
+ "@noya-app/noya-geometry": "0.1.9",
28
28
  "@noya-app/noya-icons": "0.1.6",
29
29
  "@noya-app/noya-keymap": "0.1.3",
30
30
  "@radix-ui/primitive": "^1.0.0",
@@ -50,6 +50,7 @@
50
50
  "vscode-fuzzy-scorer": "^0.0.4"
51
51
  },
52
52
  "devDependencies": {
53
+ "@tailwindcss/container-queries": "^0.1.1",
53
54
  "@types/react": "*",
54
55
  "@types/react-dom": "*",
55
56
  "@types/react-virtualized": "^9.21.21",
@@ -49,7 +49,7 @@ describe("defaultAcceptsDrop", () => {
49
49
  describe("validateDropIndicator", () => {
50
50
  const mockAcceptsDrop = (
51
51
  sourceIndex: number,
52
- destinationIndex: number,
52
+ targetIndex: number,
53
53
  position: "above" | "below" | "inside"
54
54
  ) => {
55
55
  // Mock that only allows dropping inside
@@ -59,7 +59,7 @@ describe("validateDropIndicator", () => {
59
59
 
60
60
  const mockAcceptsDropAboveBelow = (
61
61
  sourceIndex: number,
62
- destinationIndex: number,
62
+ targetIndex: number,
63
63
  position: "above" | "below" | "inside"
64
64
  ) => {
65
65
  // Mock that only allows dropping above/below
@@ -69,7 +69,7 @@ describe("validateDropIndicator", () => {
69
69
 
70
70
  const mockAcceptsAll = (
71
71
  sourceIndex: number,
72
- destinationIndex: number,
72
+ targetIndex: number,
73
73
  position: "above" | "below" | "inside"
74
74
  ) => {
75
75
  // Mock that allows all drop positions
@@ -78,7 +78,7 @@ describe("validateDropIndicator", () => {
78
78
 
79
79
  const mockAcceptsAboveAndInside = (
80
80
  sourceIndex: number,
81
- destinationIndex: number,
81
+ targetIndex: number,
82
82
  position: "above" | "below" | "inside"
83
83
  ) => {
84
84
  // Mock that allows dropping above and inside, but not below
@@ -10,19 +10,29 @@ type ActionMenuProps<TMenu extends string> = {
10
10
  onSelect: (action: TMenu) => void;
11
11
  selected: boolean;
12
12
  onOpenChange: (open: boolean) => void;
13
+ className?: string;
14
+ style?: React.CSSProperties;
13
15
  };
14
16
 
15
17
  export const ActionMenu = memoGeneric(function ActionMenu<
16
18
  TMenu extends string,
17
- >({ menuItems, onSelect, selected, onOpenChange }: ActionMenuProps<TMenu>) {
18
- const style = useMemo(() => {
19
+ >({
20
+ menuItems,
21
+ onSelect,
22
+ selected,
23
+ onOpenChange,
24
+ className,
25
+ style,
26
+ }: ActionMenuProps<TMenu>) {
27
+ const internalStyle = useMemo(() => {
19
28
  return {
20
29
  backgroundColor: selected
21
30
  ? cssVars.colors.primaryPastel
22
31
  : cssVars.colors.inputBackground,
23
32
  padding: "4px",
33
+ ...style,
24
34
  };
25
- }, [selected]);
35
+ }, [selected, style]);
26
36
 
27
37
  return (
28
38
  <DropdownMenu
@@ -31,7 +41,8 @@ export const ActionMenu = memoGeneric(function ActionMenu<
31
41
  onOpenChange={onOpenChange}
32
42
  >
33
43
  <IconButton
34
- style={style}
44
+ style={internalStyle}
45
+ className={className}
35
46
  iconName="DotsVerticalIcon"
36
47
  color={selected ? cssVars.colors.primary : undefined}
37
48
  />
@@ -21,7 +21,7 @@ export function BaseToolbar({
21
21
  return (
22
22
  <div className="flex flex-col">
23
23
  <div
24
- className="flex items-center pr-2.5 bg-sidebar-background flex-none relative"
24
+ className="flex items-center pr-2.5 bg-sidebar-background flex-none relative @container/toolbar"
25
25
  style={{
26
26
  flex: `0 0 ${cssVars.spacing.toolbarHeight}`,
27
27
  }}
@@ -39,7 +39,7 @@ export function BaseToolbar({
39
39
  <Spacer.Horizontal size={10} />
40
40
  </>
41
41
  )}
42
- <div className="flex items-center justify-center text-text-muted pointer-events-none lg:absolute inset-0">
42
+ <div className="flex items-center justify-center text-text-muted pointer-events-none @2xl/toolbar:absolute inset-0">
43
43
  <div
44
44
  style={{
45
45
  display: "flex",
@@ -58,8 +58,8 @@ export const Checkbox = React.memo(
58
58
  className={`
59
59
  absolute
60
60
  inset-0
61
- h-[27px]
62
- w-[27px]
61
+ h-input-height
62
+ w-input-height
63
63
  pointer-events-none
64
64
  stroke-white
65
65
  opacity-0
@@ -52,16 +52,16 @@ const ICON_BASE_STYLES = "relative opacity-50 cursor-pointer hover:opacity-85";
52
52
 
53
53
  const ICON_SIZE_STYLES = {
54
54
  large: {
55
- add: "ml-[-2px] mr-[2px] -top-[1px] scale-75",
56
- delete: "mr-[-2px] ml-[2px] scale-75",
55
+ add: "ml-[-2px] mr-[2px] -top-[1px] scale-[0.75]",
56
+ delete: "mr-[-2px] ml-[2px] scale-[0.75]",
57
57
  },
58
58
  medium: {
59
- add: "ml-[-2px] mr-[-2px] scale-60",
60
- delete: "mr-[-2px] scale-60",
59
+ add: "ml-[-2px] mr-[-2px] scale-[0.60]",
60
+ delete: "mr-[-2px] scale-[0.60]",
61
61
  },
62
62
  small: {
63
- add: "ml-[-2px] mr-[-2px] scale-60",
64
- delete: "mr-[-2px] scale-60",
63
+ add: "ml-[-2px] mr-[-2px] scale-[0.60]",
64
+ delete: "mr-[-2px] scale-[0.60]",
65
65
  },
66
66
  } as const;
67
67
 
@@ -82,6 +82,7 @@ export interface ChipProps {
82
82
  onHoverDeleteChange?: (hovering: boolean) => void;
83
83
  style?: React.CSSProperties;
84
84
  tabIndex?: number;
85
+ role?: string;
85
86
  }
86
87
 
87
88
  export const Chip = memo(
@@ -7,6 +7,8 @@ import { List } from "./List";
7
7
 
8
8
  export type CollectionViewType = "grid" | "list";
9
9
 
10
+ export type CollectionThumbnailSize = "auto" | "custom";
11
+
10
12
  export type CollectionRef = {
11
13
  editName: (id: string) => void;
12
14
  };
@@ -16,12 +18,19 @@ export type CollectionThumbnailProps<T> = {
16
18
  selected: boolean;
17
19
  };
18
20
 
21
+ export type CollectionRenderActionProps<T> = {
22
+ item: T;
23
+ selected: boolean;
24
+ onOpenChange: (open: boolean) => void;
25
+ };
26
+
19
27
  export interface CollectionProps<T, M extends string = string> {
20
28
  className?: string;
21
29
  items: T[];
22
30
  getId: (item: T) => string;
23
31
  getName: (item: T) => string;
24
32
  getExpanded?: (item: T) => boolean | undefined;
33
+ getPlaceholder?: (item: T) => string;
25
34
  /**
26
35
  * Whether directories should be expandable.
27
36
  * @default true
@@ -39,7 +48,9 @@ export interface CollectionProps<T, M extends string = string> {
39
48
  item,
40
49
  selected,
41
50
  }: CollectionThumbnailProps<T>) => React.ReactNode;
42
- renderAction?: "menu" | ((item: T, selected: boolean) => React.ReactNode);
51
+ renderAction?:
52
+ | "menu"
53
+ | ((props: CollectionRenderActionProps<T>) => React.ReactNode);
43
54
  renderDetail?: (item: T, selected: boolean) => React.ReactNode;
44
55
  /** Callback when selection changes. If not provided, selection will be disabled. */
45
56
  onSelectionChange?: (
@@ -52,13 +63,18 @@ export interface CollectionProps<T, M extends string = string> {
52
63
  detailPosition?: "end" | "below";
53
64
  /** Size of the list items. Defaults to 'medium'. */
54
65
  size?: GridViewSize;
66
+ /** Size of the thumbnail. Defaults to 'auto', which will be based on the size prop. */
67
+ thumbnailSize?: CollectionThumbnailSize;
55
68
  /** For testing: Override the hover state with a specific item ID */
56
69
  testHoveredId?: string;
57
70
  /** For testing: Override the renaming state with a specific item ID */
58
71
  testRenamingId?: string;
72
+ /** For testing: Override the drop indicator state with a specific item ID */
73
+ testShowDropIndicatorId?: string;
59
74
  setExpanded?: (item: T, expanded: boolean) => void;
60
75
  acceptsDrop?: ListViewRootProps["acceptsDrop"];
61
76
  onMoveItem?: ListViewRootProps["onMoveItem"];
77
+ getDropTargetParentIndex?: ListViewRootProps["getDropTargetParentIndex"];
62
78
  getDepth?: (item: T) => number;
63
79
  onFilesDrop?: (event: React.DragEvent<Element>) => void;
64
80
  getRenamable?: (item: T) => boolean;
@@ -68,6 +84,8 @@ export interface CollectionProps<T, M extends string = string> {
68
84
  onClickItem?: (itemId: string) => void;
69
85
  onDoubleClickItem?: (itemId: string) => void;
70
86
  renderEmptyState?: () => React.ReactElement;
87
+ /** @default false */
88
+ sortable?: boolean;
71
89
  }
72
90
 
73
91
  export const Collection = forwardRefGeneric(function Collection<
@@ -27,10 +27,6 @@ import {
27
27
  import { ListView } from "./ListView";
28
28
  import { Small } from "./Text";
29
29
 
30
- const comboboxButtonStyle = {
31
- right: "2px",
32
- };
33
-
34
30
  export type BaseComboboxProps<T extends string> = {
35
31
  id?: string;
36
32
  loading?: boolean;
@@ -454,6 +450,28 @@ export const Combobox = memoGeneric(
454
450
  id={id}
455
451
  size={size}
456
452
  sideOffset={6}
453
+ end={
454
+ <>
455
+ {loading && (
456
+ <div className="pr-1.5">
457
+ <ActivityIndicator />
458
+ </div>
459
+ )}
460
+ {!readOnly && (
461
+ <InputField.Button
462
+ variant="floating"
463
+ onClick={handleChevronClick}
464
+ >
465
+ <DropdownChevronIcon
466
+ className={cx(
467
+ "transition-transform duration-100",
468
+ shouldShowMenu ? "rotate-180" : "rotate-0"
469
+ )}
470
+ />
471
+ </InputField.Button>
472
+ )}
473
+ </>
474
+ }
457
475
  renderPopoverContent={({ width }) => {
458
476
  const height = Math.min(
459
477
  ListView.calculateHeight({
@@ -528,25 +546,6 @@ export const Combobox = memoGeneric(
528
546
  />
529
547
  )}
530
548
  {children}
531
- {loading && (
532
- <InputField.Label htmlFor={id}>
533
- <ActivityIndicator />
534
- </InputField.Label>
535
- )}
536
- {!readOnly && (
537
- <InputField.Button
538
- variant="floating"
539
- onClick={handleChevronClick}
540
- style={comboboxButtonStyle}
541
- >
542
- <DropdownChevronIcon
543
- className={cx(
544
- "transition-transform duration-100",
545
- shouldShowMenu ? "rotate-180" : "rotate-0"
546
- )}
547
- />
548
- </InputField.Button>
549
- )}
550
549
  </InputField.Root>
551
550
  );
552
551
  })
@@ -92,7 +92,7 @@ export const ComboboxMenu = memoGeneric(
92
92
  <div className="flex flex-1 min-w-0 items-center">
93
93
  {indented && <Spacer.Horizontal size={CHECKBOX_INDENT_WIDTH} />}
94
94
  {item.checked ? (
95
- <div className={styles.itemIndicatorStyle}>
95
+ <div {...styles.itemIndicator}>
96
96
  <CheckIcon />
97
97
  </div>
98
98
  ) : indented ? (
@@ -1,6 +1,7 @@
1
1
  import { round } from "@noya-app/noya-utils";
2
2
  import * as React from "react";
3
3
  import { InputField } from "./InputField";
4
+ import { LabeledField } from "./LabeledField";
4
5
 
5
6
  export type SetNumberMode = "replace" | "adjust";
6
7
 
@@ -50,17 +51,18 @@ export const DimensionInput = React.memo(function DimensionInput({
50
51
  );
51
52
 
52
53
  return (
53
- <InputField.Root id={id} width={size}>
54
- <InputField.NumberInput
55
- value={value === undefined ? value : round(value, 2)}
56
- placeholder={value === undefined ? placeholder : undefined}
57
- onNudge={handleNudgeValue}
58
- disabled={disabled}
59
- {...(trigger === "change"
60
- ? { onChange: handleSetValue }
61
- : { onSubmit: handleSetValue })}
62
- />
63
- {label && <InputField.Label>{label}</InputField.Label>}
64
- </InputField.Root>
54
+ <LabeledField label={label} labelPosition="inset">
55
+ <InputField.Root id={id} width={size}>
56
+ <InputField.NumberInput
57
+ value={value === undefined ? value : round(value, 2)}
58
+ placeholder={value === undefined ? placeholder : undefined}
59
+ onNudge={handleNudgeValue}
60
+ disabled={disabled}
61
+ {...(trigger === "change"
62
+ ? { onChange: handleSetValue }
63
+ : { onSubmit: handleSetValue })}
64
+ />
65
+ </InputField.Root>
66
+ </LabeledField>
65
67
  );
66
68
  });
@@ -35,6 +35,7 @@ type Props = {
35
35
  textClassName?: string;
36
36
  readOnly?: boolean;
37
37
  onBlur?: () => void;
38
+ tabIndex?: number;
38
39
  /**
39
40
  * If true, will render an unfocused input. This should only be used for visual regression tests.
40
41
  */
@@ -66,6 +67,7 @@ export const EditableText = memo(
66
67
  readOnly,
67
68
  onBlur,
68
69
  testFocused = false,
70
+ tabIndex = 0,
69
71
  ...props
70
72
  }: Props,
71
73
  ref: React.ForwardedRef<EditableTextRef>
@@ -136,7 +138,7 @@ export const EditableText = memo(
136
138
  children: displayValue,
137
139
  onClick: () => setFocused(true),
138
140
  ref: previewRef,
139
- tabIndex: 0,
141
+ tabIndex,
140
142
  onKeyDown: (e: React.KeyboardEvent) => {
141
143
  if (e.key === "Enter" || e.key === " ") {
142
144
  setFocused(true);
@@ -1,8 +1,8 @@
1
1
  import { Sketch } from "@noya-app/noya-file-format";
2
2
  import React, { forwardRef, memo } from "react";
3
+ import { cx } from "../utils/classNames";
3
4
  import { SketchPattern } from "../utils/sketchPattern";
4
5
  import { FillPreviewBackground } from "./FillPreviewBackground";
5
- import { cx } from "../utils/classNames";
6
6
 
7
7
  const FillButton = forwardRef<
8
8
  HTMLButtonElement,
@@ -11,7 +11,7 @@ const FillButton = forwardRef<
11
11
  <button
12
12
  ref={ref}
13
13
  className={cx(
14
- `outline-none p-0 w-[50px] h-[27px] rounded overflow-hidden border-none shadow-[0_0_0_1px_var(--divider)_inset] bg-transparent relative focus:shadow-[0_0_0_1px_var(--sidebar-background),0_0_0_3px_var(--primary)] `,
14
+ `outline-none p-0 w-[50px] h-input-height rounded overflow-hidden border-none shadow-[0_0_0_1px_var(--divider)_inset] bg-transparent relative focus:shadow-[0_0_0_1px_var(--sidebar-background),0_0_0_3px_var(--primary)] `,
15
15
  className
16
16
  )}
17
17
  {...props}
@@ -82,6 +82,7 @@ export const Grid = memoGeneric(
82
82
  size = "medium",
83
83
  testHoveredId,
84
84
  testRenamingId,
85
+ testShowDropIndicatorId,
85
86
  scrollable = false,
86
87
  // acceptsDrop,
87
88
  // onMoveItem,
@@ -90,6 +91,7 @@ export const Grid = memoGeneric(
90
91
  onClickItem,
91
92
  onDoubleClickItem,
92
93
  renderEmptyState,
94
+ // getDropTargetParentIndex,
93
95
  } = props;
94
96
 
95
97
  const [hoveredId, setHoveredId] = useState<string>();
@@ -143,130 +145,148 @@ export const Grid = memoGeneric(
143
145
 
144
146
  return (
145
147
  <GridView.Root
148
+ role="listbox"
149
+ aria-orientation="horizontal"
150
+ aria-multiselectable={true}
146
151
  minColumnWidth={minColumnWidth}
147
- scrollable={scrollable}
152
+ scrollable={items.length > 0 && scrollable}
153
+ contentClassName={cx(items.length === 0 && "flex flex-1")}
148
154
  gap={gap}
149
155
  className={cx(isDropTargetActive && "bg-primary-pastel", className)}
150
156
  {...dropTargetProps}
151
157
  ref={fileDropTargetRef}
152
158
  >
153
- <GridView.Section renderEmptyState={renderEmptyState}>
154
- {items.map((item) => {
155
- const id = getId(item);
156
- const isDropdownOpen = dropdownOpenId === id;
157
- const isHovered =
158
- testHoveredId === id || currentHoveredId === id || isDropdownOpen;
159
- const itemIsRenamable = getRenamable?.(item) ?? renamable;
160
- const isSelected = selectedIds.includes(id);
161
- const isRenaming = itemIsRenamable && onRename && renamingId === id;
162
- const isTestingRenaming = testRenamingId === id;
163
- const thumbnail = renderThumbnail?.({
164
- item,
165
- selected: isSelected,
166
- });
167
- const action =
168
- (isHovered || isSelected) &&
169
- !isRenaming &&
170
- (typeof renderAction === "function"
171
- ? renderAction(item, isSelected)
172
- : renderAction === "menu" &&
173
- menuItems && (
174
- <ActionMenu
175
- menuItems={menuItems}
176
- onSelect={(action) => handleMenuAction(action, item)}
177
- selected={isSelected}
178
- onOpenChange={(open) => {
179
- setDropdownOpenId(open ? id : undefined);
159
+ {items.length > 0 ? (
160
+ <GridView.Section>
161
+ {items.map((item) => {
162
+ const id = getId(item);
163
+ const isDropdownOpen = dropdownOpenId === id;
164
+ const isHovered =
165
+ testHoveredId === id ||
166
+ currentHoveredId === id ||
167
+ isDropdownOpen;
168
+ const itemIsRenamable = getRenamable?.(item) ?? renamable;
169
+ const isSelected = selectedIds.includes(id);
170
+ const isRenaming =
171
+ itemIsRenamable && onRename && renamingId === id;
172
+ const isTestingRenaming = testRenamingId === id;
173
+ const thumbnail = renderThumbnail?.({
174
+ item,
175
+ selected: isSelected,
176
+ });
177
+ const onOpenChange = (open: boolean) => {
178
+ setDropdownOpenId(open ? id : undefined);
180
179
 
181
- if (open && !isSelected) {
182
- handleSelect(id);
180
+ if (open && !isSelected) {
181
+ handleSelect(id);
182
+ }
183
+ };
184
+ const action =
185
+ (isHovered || isSelected) &&
186
+ !isRenaming &&
187
+ (typeof renderAction === "function"
188
+ ? renderAction({ item, selected: isSelected, onOpenChange })
189
+ : renderAction === "menu" &&
190
+ menuItems && (
191
+ <ActionMenu
192
+ menuItems={menuItems}
193
+ onSelect={(action) => handleMenuAction(action, item)}
194
+ selected={isSelected}
195
+ onOpenChange={onOpenChange}
196
+ />
197
+ ));
198
+
199
+ return (
200
+ <GridView.Item<M>
201
+ key={id}
202
+ id={id}
203
+ role="option"
204
+ aria-selected={isSelected}
205
+ aria-label={getName(item)}
206
+ hovered={isHovered}
207
+ testShowInsideDropIndicator={testShowDropIndicatorId === id}
208
+ onHoverChange={(isHovering) => {
209
+ setHoveredId(isHovering ? id : undefined);
210
+ }}
211
+ title={
212
+ <EditableText
213
+ tabIndex={isRenaming ? undefined : -1}
214
+ value={getName(item)}
215
+ focused={isRenaming}
216
+ testFocused={isTestingRenaming}
217
+ readOnly={!itemIsRenamable || !onRename}
218
+ onSubmit={(value) => {
219
+ if (value !== getName(item)) {
220
+ onRename?.(item, value);
183
221
  }
222
+ setRenamingId(undefined);
184
223
  }}
185
- />
186
- ));
187
-
188
- return (
189
- <GridView.Item<M>
190
- key={id}
191
- id={id}
192
- hovered={isHovered}
193
- onHoverChange={(isHovering) => {
194
- setHoveredId(isHovering ? id : undefined);
195
- }}
196
- title={
197
- <EditableText
198
- value={getName(item)}
199
- focused={isRenaming}
200
- testFocused={isTestingRenaming}
201
- readOnly={!itemIsRenamable || !onRename}
202
- onSubmit={(value) => {
203
- if (value !== getName(item)) {
204
- onRename?.(item, value);
205
- }
206
- setRenamingId(undefined);
207
- }}
208
- onBlur={() => {
209
- setRenamingId(undefined);
210
- }}
211
- // max height ensures preview text and input are one line
212
- textClassName="bg-listview-editing-background max-h-[27px]"
213
- >
214
- {(props) => (
215
- <span
216
- {...props}
217
- className={cx(
218
- isSelected && "text-primary",
219
- "truncate select-none"
220
- )}
221
- />
222
- )}
223
- </EditableText>
224
- }
225
- subtitle={
226
- <span className={cx(isSelected && "text-primary")}>
227
- {renderDetail?.(item, isSelected)}
228
- </span>
229
- }
230
- loading={false}
231
- selected={isSelected}
232
- onClick={() => {
233
- onClickItem?.(id);
234
- }}
235
- onSelect={(event) => {
236
- handleSelect(id, event);
237
- }}
238
- onKeyDown={(event) => {
239
- if (event.key === "Enter") {
240
- setRenamingId(id);
224
+ onBlur={() => {
225
+ setRenamingId(undefined);
226
+ }}
227
+ // max height ensures preview text and input are one line
228
+ textClassName="bg-listview-editing-background max-h-input-height"
229
+ >
230
+ {(props) => (
231
+ <span
232
+ {...props}
233
+ className={cx(
234
+ isSelected && "text-primary",
235
+ "truncate select-none"
236
+ )}
237
+ />
238
+ )}
239
+ </EditableText>
241
240
  }
242
- }}
243
- onDoubleClick={() => {
244
- onDoubleClickItem?.(id);
245
- }}
246
- menuItems={menuItems}
247
- onSelectMenuItem={(action) => handleMenuAction(action, item)}
248
- action={action}
249
- onMenuOpenChange={(open: boolean) => {
250
- if (open && !isSelected) {
251
- handleSelect(id);
241
+ subtitle={
242
+ <span className={cx(isSelected && "text-primary")}>
243
+ {renderDetail?.(item, isSelected)}
244
+ </span>
252
245
  }
253
- }}
254
- >
255
- {thumbnail && (
256
- <div
257
- className={cx(
258
- "rounded overflow-hidden flex-none flex w-full",
259
- isSelected && "text-primary"
260
- )}
261
- tabIndex={-1}
262
- >
263
- {thumbnail}
264
- </div>
265
- )}
266
- </GridView.Item>
267
- );
268
- })}
269
- </GridView.Section>
246
+ loading={false}
247
+ selected={isSelected}
248
+ onClick={() => {
249
+ onClickItem?.(id);
250
+ }}
251
+ onSelect={(event) => {
252
+ handleSelect(id, event);
253
+ }}
254
+ onKeyDown={(event) => {
255
+ if (event.key === "Enter") {
256
+ setRenamingId(id);
257
+ }
258
+ }}
259
+ onDoubleClick={() => {
260
+ onDoubleClickItem?.(id);
261
+ }}
262
+ menuItems={menuItems}
263
+ onSelectMenuItem={(action) => handleMenuAction(action, item)}
264
+ action={action}
265
+ onMenuOpenChange={(open: boolean) => {
266
+ if (open && !isSelected) {
267
+ handleSelect(id);
268
+ }
269
+ }}
270
+ // getDropTargetParentIndex={getDropTargetParentIndex}
271
+ >
272
+ {thumbnail && (
273
+ <div
274
+ className={cx(
275
+ "rounded overflow-hidden flex-none flex w-full",
276
+ isSelected && "text-primary"
277
+ )}
278
+ tabIndex={-1}
279
+ >
280
+ {thumbnail}
281
+ </div>
282
+ )}
283
+ </GridView.Item>
284
+ );
285
+ })}
286
+ </GridView.Section>
287
+ ) : (
288
+ renderEmptyState?.()
289
+ )}
270
290
  </GridView.Root>
271
291
  );
272
292
  })