@noya-app/noya-designsystem 0.1.49 → 0.1.51

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 (49) hide show
  1. package/.turbo/turbo-build.log +10 -10
  2. package/CHANGELOG.md +19 -0
  3. package/dist/index.css +1 -1
  4. package/dist/index.d.mts +169 -205
  5. package/dist/index.d.ts +169 -205
  6. package/dist/index.js +5092 -13463
  7. package/dist/index.js.map +1 -1
  8. package/dist/index.mjs +3501 -11912
  9. package/dist/index.mjs.map +1 -1
  10. package/package.json +9 -9
  11. package/src/components/ActionMenu.tsx +4 -2
  12. package/src/components/Avatar.tsx +2 -2
  13. package/src/components/Banner.tsx +29 -0
  14. package/src/components/Button.tsx +2 -2
  15. package/src/components/Chip.tsx +8 -8
  16. package/src/components/Collection.tsx +18 -4
  17. package/src/components/ComboboxMenu.tsx +10 -1
  18. package/src/components/Dialog.tsx +2 -2
  19. package/src/components/FillInputField.tsx +1 -1
  20. package/src/components/Grid.tsx +12 -11
  21. package/src/components/Label.tsx +11 -6
  22. package/src/components/LabeledField.tsx +6 -1
  23. package/src/components/List.tsx +30 -24
  24. package/src/components/ListView.tsx +10 -5
  25. package/src/components/MediaThumbnail.tsx +31 -9
  26. package/src/components/Popover.tsx +9 -6
  27. package/src/components/SearchCompletionMenu.tsx +12 -12
  28. package/src/components/Section.tsx +172 -0
  29. package/src/components/SegmentedControl.tsx +1 -1
  30. package/src/components/SelectMenu.tsx +1 -1
  31. package/src/components/Slider.tsx +1 -1
  32. package/src/components/Sortable.tsx +124 -22
  33. package/src/components/Toolbar.tsx +22 -10
  34. package/src/components/UserPointer.tsx +1 -1
  35. package/src/components/ai-assistant/AIAssistantLayout.tsx +102 -0
  36. package/src/components/connected-users-menu/ConnectedUsersMenuLayout.tsx +71 -0
  37. package/src/components/file-explorer/FileExplorerLayout.tsx +81 -0
  38. package/src/components/internal/Menu.tsx +12 -7
  39. package/src/components/internal/TextInput.tsx +7 -2
  40. package/src/components/pipeline/PipelineResultLayout.tsx +32 -0
  41. package/src/index.css +125 -118
  42. package/src/index.tsx +7 -0
  43. package/src/theme/index.ts +1 -1
  44. package/src/utils/classNames.ts +28 -3
  45. package/src/utils/combobox.ts +18 -13
  46. package/src/utils/moveTreeItem.ts +99 -0
  47. package/tailwind.config.ts +2 -239
  48. package/tailwind.d.ts +1 -1
  49. package/tsup.config.ts +0 -3
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@noya-app/noya-designsystem",
3
- "version": "0.1.49",
3
+ "version": "0.1.51",
4
4
  "main": "./dist/index.js",
5
5
  "module": "./dist/index.mjs",
6
6
  "types": "./dist/index.d.ts",
@@ -22,11 +22,12 @@
22
22
  "dependencies": {
23
23
  "@dnd-kit/core": "6.3.1",
24
24
  "@dnd-kit/sortable": "10.0.0",
25
- "@noya-app/noya-colorpicker": "0.1.18",
26
- "@noya-app/noya-utils": "0.1.4",
27
- "@noya-app/noya-geometry": "0.1.9",
28
- "@noya-app/noya-icons": "0.1.6",
25
+ "@noya-app/noya-colorpicker": "0.1.19",
26
+ "@noya-app/noya-utils": "0.1.5",
27
+ "@noya-app/noya-geometry": "0.1.10",
28
+ "@noya-app/noya-icons": "0.1.7",
29
29
  "@noya-app/noya-keymap": "0.1.3",
30
+ "@noya-app/noya-tailwind-config": "0.1.2",
30
31
  "@radix-ui/primitive": "^1.0.0",
31
32
  "@radix-ui/react-avatar": "^1.0.1",
32
33
  "@radix-ui/react-compose-refs": "^1.0.0",
@@ -47,17 +48,16 @@
47
48
  "kiwi.js": "^1.1.3",
48
49
  "react-resizable-panels": "2.0.22",
49
50
  "react-window": "^1.8.8",
50
- "vscode-fuzzy-scorer": "^0.0.4"
51
+ "vscode-fuzzy-scorer": "^0.0.4",
52
+ "react-virtualized": "9.22.6"
51
53
  },
52
54
  "devDependencies": {
53
55
  "@tailwindcss/container-queries": "^0.1.1",
54
56
  "@types/react": "*",
55
57
  "@types/react-dom": "*",
56
- "@types/react-virtualized": "^9.21.21",
58
+ "@types/react-virtualized": "^9.21.30",
57
59
  "@types/react-window": "^1.8.5",
58
- "esbuild-plugin-react-virtualized": "1.0.4",
59
60
  "esbuild-plugin-tailwindcss": "^1.2.1",
60
- "react-virtualized": "9.22.5",
61
61
  "tailwind-merge": "^2.5.4",
62
62
  "tailwindcss": "^3.4.14"
63
63
  },
@@ -12,6 +12,7 @@ type ActionMenuProps<TMenu extends string> = {
12
12
  onOpenChange: (open: boolean) => void;
13
13
  className?: string;
14
14
  style?: React.CSSProperties;
15
+ variant?: "normal" | "bare";
15
16
  };
16
17
 
17
18
  export const ActionMenu = memoGeneric(function ActionMenu<
@@ -23,16 +24,17 @@ export const ActionMenu = memoGeneric(function ActionMenu<
23
24
  onOpenChange,
24
25
  className,
25
26
  style,
27
+ variant = "normal",
26
28
  }: ActionMenuProps<TMenu>) {
27
29
  const internalStyle = useMemo(() => {
28
30
  return {
29
31
  backgroundColor: selected
30
32
  ? cssVars.colors.primaryPastel
31
33
  : cssVars.colors.inputBackground,
32
- padding: "4px",
34
+ padding: variant === "bare" ? 0 : 4,
33
35
  ...style,
34
36
  };
35
- }, [selected, style]);
37
+ }, [selected, style, variant]);
36
38
 
37
39
  return (
38
40
  <DropdownMenu
@@ -181,8 +181,8 @@ const OverflowAvatar = React.memo(function OverflowAvatar({
181
181
  count: number;
182
182
  }) {
183
183
  return (
184
- <AvatarRoot size={size} backgroundColor="var(--text-disabled)" disabled>
185
- <AvatarFallback backgroundColor="var(--text-disabled)" size={size}>
184
+ <AvatarRoot size={size} backgroundColor="var(--n-text-disabled)" disabled>
185
+ <AvatarFallback backgroundColor="var(--n-text-disabled)" size={size}>
186
186
  +{count}
187
187
  </AvatarFallback>
188
188
  </AvatarRoot>
@@ -0,0 +1,29 @@
1
+ import React, { CSSProperties } from "react";
2
+ import { cssVars } from "../theme";
3
+ import { cx } from "../utils/classNames";
4
+ import { Small } from "./Text";
5
+
6
+ const bannerStyles = {
7
+ backgroundColor: cssVars.colors.inputBackground,
8
+ color: cssVars.colors.textDisabled,
9
+ };
10
+
11
+ export type BannerProps = {
12
+ label?: string;
13
+ className?: string;
14
+ style?: CSSProperties;
15
+ };
16
+
17
+ export function Banner({ label = "No items", className, style }: BannerProps) {
18
+ return (
19
+ <div
20
+ className={cx(
21
+ "flex p-1 items-center justify-center select-none rounded",
22
+ className
23
+ )}
24
+ style={{ ...bannerStyles, ...style }}
25
+ >
26
+ <Small color="textDisabled">{label}</Small>
27
+ </div>
28
+ );
29
+ }
@@ -5,8 +5,8 @@ import React, {
5
5
  ReactNode,
6
6
  useCallback,
7
7
  } from "react";
8
- import { Tooltip } from "./Tooltip";
9
8
  import { cx } from "../utils/classNames";
9
+ import { Tooltip } from "./Tooltip";
10
10
 
11
11
  type ButtonVariant =
12
12
  | "normal"
@@ -87,7 +87,7 @@ export const Button = forwardRef(function Button(
87
87
  ref={forwardedRef}
88
88
  id={id}
89
89
  className={cx(
90
- "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(--popover-background)_inset] transition-all",
90
+ "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",
91
91
  disabled ? "opacity-25" : "",
92
92
  !className?.includes("flex") ? "flex-none" : "",
93
93
  active
@@ -19,32 +19,32 @@ const SIZE_STYLES = {
19
19
  } as const;
20
20
 
21
21
  const SHADOW_STYLES = {
22
- primary: "shadow-[0_0_0_1px_var(--chip-primary-shadow)_inset]",
23
- secondary: "shadow-[0_0_0_1px_var(--chip-secondary-shadow)_inset]",
24
- error: "shadow-[0_0_0_1px_var(--chip-error-shadow)_inset]",
25
- default: "shadow-[0_0_0_1px_var(--chip-default-shadow)_inset]",
22
+ primary: "shadow-[0_0_0_1px_var(--n-chip-primary-shadow)_inset]",
23
+ secondary: "shadow-[0_0_0_1px_var(--n-chip-secondary-shadow)_inset]",
24
+ error: "shadow-[0_0_0_1px_var(--n-chip-error-shadow)_inset]",
25
+ default: "shadow-[0_0_0_1px_var(--n-chip-default-shadow)_inset]",
26
26
  } as const;
27
27
 
28
28
  const COLOR_STYLES = {
29
29
  primary: {
30
30
  text: "text-primary",
31
31
  bg: "bg-primary-pastel",
32
- subtle: "bg-[var(--chip-primary-bg)]",
32
+ subtle: "bg-[var(--n-chip-primary-bg)]",
33
33
  },
34
34
  secondary: {
35
35
  text: "text-secondary",
36
36
  bg: "bg-secondary-pastel",
37
- subtle: "bg-[var(--chip-secondary-bg)]",
37
+ subtle: "bg-[var(--n-chip-secondary-bg)]",
38
38
  },
39
39
  error: {
40
40
  text: "text-text",
41
41
  bg: "bg-[rgb(255,219,219)]",
42
- subtle: "bg-[var(--chip-error-bg)]",
42
+ subtle: "bg-[var(--n-chip-error-bg)]",
43
43
  },
44
44
  default: {
45
45
  text: "text-text",
46
46
  bg: "bg-input-background",
47
- subtle: "bg-[var(--chip-default-bg)]",
47
+ subtle: "bg-[var(--n-chip-default-bg)]",
48
48
  },
49
49
  } as const;
50
50
 
@@ -1,8 +1,12 @@
1
+ import { forwardRefGeneric } from "@noya-app/react-utils";
1
2
  import React from "react";
2
- import { forwardRefGeneric } from "../../../noya-react-utils/src/utils/reactGenerics";
3
- import { Grid, GridViewSize } from "../components/Grid";
3
+ import { Grid } from "../components/Grid";
4
4
  import { MenuItem } from "../components/internal/Menu";
5
- import { ListView, ListViewRootProps } from "../components/ListView";
5
+ import {
6
+ ListView,
7
+ ListViewRootProps,
8
+ ListViewRowProps,
9
+ } from "../components/ListView";
6
10
  import { List } from "./List";
7
11
 
8
12
  export type CollectionViewType = "grid" | "list";
@@ -16,6 +20,7 @@ export type CollectionRef = {
16
20
  export type CollectionThumbnailProps<T> = {
17
21
  item: T;
18
22
  selected: boolean;
23
+ size?: CollectionItemSize;
19
24
  };
20
25
 
21
26
  export type CollectionRenderActionProps<T> = {
@@ -47,6 +52,7 @@ export interface CollectionProps<T, M extends string = string> {
47
52
  renderThumbnail?: ({
48
53
  item,
49
54
  selected,
55
+ size,
50
56
  }: CollectionThumbnailProps<T>) => React.ReactNode;
51
57
  renderAction?:
52
58
  | "menu"
@@ -62,7 +68,7 @@ export interface CollectionProps<T, M extends string = string> {
62
68
  /** Position of the detail content. Defaults to 'end'. */
63
69
  detailPosition?: "end" | "below";
64
70
  /** Size of the list items. Defaults to 'medium'. */
65
- size?: GridViewSize;
71
+ size?: CollectionItemSize;
66
72
  /** Size of the thumbnail. Defaults to 'auto', which will be based on the size prop. */
67
73
  thumbnailSize?: CollectionThumbnailSize;
68
74
  /** For testing: Override the hover state with a specific item ID */
@@ -86,6 +92,7 @@ export interface CollectionProps<T, M extends string = string> {
86
92
  renderEmptyState?: () => React.ReactElement;
87
93
  /** @default false */
88
94
  sortable?: boolean;
95
+ dragIndicatorStyle?: ListViewRowProps<M>["dragIndicatorStyle"];
89
96
  }
90
97
 
91
98
  export const Collection = forwardRefGeneric(function Collection<
@@ -100,3 +107,10 @@ export const Collection = forwardRefGeneric(function Collection<
100
107
  <List ref={ref} {...rest} />
101
108
  );
102
109
  });
110
+ export type CollectionItemSize =
111
+ | "xxs"
112
+ | "xs"
113
+ | "small"
114
+ | "medium"
115
+ | "large"
116
+ | "xl";
@@ -25,6 +25,8 @@ interface ComboboxMenuProps<T extends string> {
25
25
  listSize: Size;
26
26
  style?: React.CSSProperties;
27
27
  indented?: boolean;
28
+ /** @default right */
29
+ iconPosition?: "left" | "right";
28
30
  }
29
31
 
30
32
  export const ComboboxMenu = memoGeneric(
@@ -37,6 +39,7 @@ export const ComboboxMenu = memoGeneric(
37
39
  listSize,
38
40
  style,
39
41
  indented,
42
+ iconPosition = "right",
40
43
  }: ComboboxMenuProps<T>,
41
44
  forwardedRef: ForwardedRef<IVirtualizedList>
42
45
  ) {
@@ -100,6 +103,12 @@ export const ComboboxMenu = memoGeneric(
100
103
  size={CHECKBOX_WIDTH - CHECKBOX_RIGHT_INSET}
101
104
  />
102
105
  ) : null}
106
+ {iconPosition === "left" && item.icon && (
107
+ <>
108
+ {renderIcon(item.icon)}
109
+ <Spacer.Horizontal size={8} />
110
+ </>
111
+ )}
103
112
  {tokens.map((token, j) => (
104
113
  <span
105
114
  key={`${item.value}-token-${j}`}
@@ -112,7 +121,7 @@ export const ComboboxMenu = memoGeneric(
112
121
  </span>
113
122
  ))}
114
123
  </div>
115
- {(item.shortcut || item.icon) && (
124
+ {(item.shortcut || (item.icon && iconPosition === "right")) && (
116
125
  <>
117
126
  {item.shortcut ? (
118
127
  <KeyboardShortcut shortcut={item.shortcut} />
@@ -138,8 +138,8 @@ export const Dialog = forwardRef(function Dialog(
138
138
  className="z-[1]"
139
139
  style={{
140
140
  position: "absolute",
141
- top: "var(--dialog-padding)",
142
- right: "var(--dialog-padding)",
141
+ top: "var(--n-dialog-padding)",
142
+ right: "var(--n-dialog-padding)",
143
143
  }}
144
144
  />
145
145
  </DialogPrimitive.Close>
@@ -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-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)] `,
14
+ `outline-none p-0 w-[50px] h-input-height rounded overflow-hidden border-none shadow-[0_0_0_1px_var(--n-divider)_inset] bg-transparent relative focus:shadow-[0_0_0_1px_var(--n-sidebar-background),0_0_0_3px_var(--n-primary)] `,
15
15
  className
16
16
  )}
17
17
  {...props}
@@ -1,23 +1,25 @@
1
- import { useFileDropTarget } from "@noya-app/react-utils";
2
- import React, { useImperativeHandle, useRef, useState } from "react";
3
1
  import {
4
2
  forwardRefGeneric,
5
3
  memoGeneric,
6
- } from "../../../noya-react-utils/src/utils/reactGenerics";
4
+ useFileDropTarget,
5
+ } from "@noya-app/react-utils";
6
+ import React, { useImperativeHandle, useRef, useState } from "react";
7
7
  import { cx } from "../utils/classNames";
8
8
  import { updateSelection } from "../utils/selection";
9
9
  import { ActionMenu } from "./ActionMenu";
10
- import { CollectionProps, CollectionRef } from "./Collection";
10
+ import {
11
+ CollectionItemSize,
12
+ CollectionProps,
13
+ CollectionRef,
14
+ } from "./Collection";
11
15
  import { EditableText } from "./EditableText";
12
16
  import { GridView } from "./GridView";
13
17
  import { ListView } from "./ListView";
14
18
 
15
- export type GridViewSize = "xxs" | "xs" | "small" | "medium" | "large" | "xl";
16
-
17
- export const getGridSize = (size: GridViewSize) => {
19
+ export const getGridSize = (size: CollectionItemSize) => {
18
20
  // Define size ranges that allow items to grow/shrink within bounds
19
21
  const sizes: Record<
20
- Exclude<GridViewSize, number>,
22
+ Exclude<CollectionItemSize, number>,
21
23
  {
22
24
  minColumnWidth: string;
23
25
  gap: number;
@@ -173,6 +175,7 @@ export const Grid = memoGeneric(
173
175
  const thumbnail = renderThumbnail?.({
174
176
  item,
175
177
  selected: isSelected,
178
+ size,
176
179
  });
177
180
  const onOpenChange = (open: boolean) => {
178
181
  setDropdownOpenId(open ? id : undefined);
@@ -216,9 +219,7 @@ export const Grid = memoGeneric(
216
219
  testFocused={isTestingRenaming}
217
220
  readOnly={!itemIsRenamable || !onRename}
218
221
  onSubmit={(value) => {
219
- if (value !== getName(item)) {
220
- onRename?.(item, value);
221
- }
222
+ onRename?.(item, value);
222
223
  setRenamingId(undefined);
223
224
  }}
224
225
  onBlur={() => {
@@ -1,5 +1,5 @@
1
1
  import React, { LabelHTMLAttributes, forwardRef, memo } from "react";
2
- import { cx } from "../utils/classNames";
2
+ import { mergeConflictingClassNames } from "../utils/classNames";
3
3
 
4
4
  export type LabelPosition = "inset" | "start" | "end" | "above";
5
5
 
@@ -17,11 +17,16 @@ export const Label = memo(
17
17
  return (
18
18
  <label
19
19
  ref={ref}
20
- className={cx(
21
- labelStyles,
22
- labelTextStyles,
23
- "text-left min-h-input-height",
24
- className
20
+ className={mergeConflictingClassNames(
21
+ [
22
+ labelStyles,
23
+ labelTextStyles,
24
+ "text-left min-h-input-height",
25
+ className,
26
+ ],
27
+ {
28
+ categories: ["color", "font"],
29
+ }
25
30
  )}
26
31
  {...props}
27
32
  >
@@ -4,6 +4,7 @@ import {
4
4
  LabelContext,
5
5
  LabelPositionContext,
6
6
  LabelPositionContextValue,
7
+ useLabelPosition,
7
8
  useLabelWidth,
8
9
  } from "../hooks/useLabel";
9
10
  import { cx } from "../utils/classNames";
@@ -23,7 +24,7 @@ const labelPositionInset: LabelPositionContextValue = {
23
24
  export const LabeledField = memo(function LabeledField({
24
25
  children,
25
26
  label,
26
- labelPosition = "start",
27
+ labelPosition: labelPositionProp,
27
28
  fieldId,
28
29
  minWidth = 100,
29
30
  className,
@@ -44,6 +45,10 @@ export const LabeledField = memo(function LabeledField({
44
45
  const labelWidthFromContext = useLabelWidth();
45
46
  const labelWidth = labelWidthFromContext ?? minWidth;
46
47
 
48
+ const labelPositionFromContext = useLabelPosition();
49
+ const labelPosition =
50
+ labelPositionProp ?? labelPositionFromContext ?? "start";
51
+
47
52
  const labeledFieldClasses = useMemo(
48
53
  () => labeledFieldStyles(labelPosition),
49
54
  [labelPosition]
@@ -8,13 +8,16 @@ import { cssVars } from "../theme";
8
8
  import { cx } from "../utils/classNames";
9
9
  import { updateSelection } from "../utils/selection";
10
10
  import { ActionMenu } from "./ActionMenu";
11
- import { CollectionProps, CollectionRef } from "./Collection";
12
- import { GridViewSize } from "./Grid";
11
+ import {
12
+ CollectionItemSize,
13
+ CollectionProps,
14
+ CollectionRef,
15
+ } from "./Collection";
13
16
  import { ListView, ListViewItemInfo } from "./ListView";
14
17
  import { TreeView } from "./TreeView";
15
18
 
16
19
  const cssVarOverrides = {
17
- "--icon-selected": cssVars.colors.primary,
20
+ "--n-icon-selected": cssVars.colors.primary,
18
21
  };
19
22
 
20
23
  const emptyArray: string[] = [];
@@ -60,6 +63,7 @@ export const List = memoGeneric(
60
63
  getDropTargetParentIndex,
61
64
  getPlaceholder,
62
65
  onClickItem,
66
+ dragIndicatorStyle,
63
67
  } = props;
64
68
 
65
69
  const [internalHoveredId, setHoveredId] = useState<string>();
@@ -148,6 +152,7 @@ export const List = memoGeneric(
148
152
  const thumbnail = renderThumbnail?.({
149
153
  item,
150
154
  selected: isSelected,
155
+ size,
151
156
  });
152
157
  const onOpenChange = (open: boolean) => {
153
158
  setDropdownOpenId(open ? id : undefined);
@@ -168,6 +173,7 @@ export const List = memoGeneric(
168
173
  onSelect={(action) => handleMenuAction(action, item)}
169
174
  selected={isSelected}
170
175
  onOpenChange={onOpenChange}
176
+ variant="bare"
171
177
  />
172
178
  ));
173
179
 
@@ -229,6 +235,7 @@ export const List = memoGeneric(
229
235
  if (!expandable) return;
230
236
  setExpanded?.(item, !expanded);
231
237
  }}
238
+ dragIndicatorStyle={dragIndicatorStyle}
232
239
  >
233
240
  <div
234
241
  className={cx("flex flex-1 items-center px-2", rowGapMap[size])}
@@ -236,9 +243,10 @@ export const List = memoGeneric(
236
243
  {thumbnail && (
237
244
  <div
238
245
  className={cx(
239
- "rounded overflow-hidden flex-none flex items-center justify-center",
246
+ "overflow-hidden flex-none flex items-center justify-center",
240
247
  thumbnailSize === "auto" && thumbnailSizeMap[size],
241
- isSelected && "text-primary"
248
+ isSelected && "text-primary",
249
+ size === "small" ? "rounded-sm" : "rounded"
242
250
  )}
243
251
  tabIndex={-1}
244
252
  >
@@ -255,9 +263,7 @@ export const List = memoGeneric(
255
263
  className={fontStyleMap[size]}
256
264
  autoFocus={!isTestingRenaming}
257
265
  onSubmitEditing={(value: string) => {
258
- if (value !== getName(item) && value !== "") {
259
- onRename?.(item, value);
260
- }
266
+ onRename?.(item, value);
261
267
  setRenamingId(undefined);
262
268
  }}
263
269
  onKeyDown={(e: React.KeyboardEvent) => {
@@ -316,37 +322,37 @@ const DetailContainer = memo(function DetailContainer({
316
322
  );
317
323
  });
318
324
 
319
- const pyMap: Record<GridViewSize, string> = {
320
- xxs: "py-1",
321
- xs: "py-1",
322
- small: "py-1",
325
+ const pyMap: Record<CollectionItemSize, string> = {
326
+ xxs: "py-0.5",
327
+ xs: "py-0.5",
328
+ small: "py-0.5",
323
329
  medium: "py-1",
324
330
  large: "py-2",
325
331
  xl: "py-3",
326
332
  };
327
333
 
328
- const thumbnailSizeMap: Record<GridViewSize, string> = {
329
- xxs: "w-4 h-4",
330
- xs: "w-4 h-4",
331
- small: "w-6 h-6",
334
+ const thumbnailSizeMap: Record<CollectionItemSize, string> = {
335
+ xxs: "w-5 h-5",
336
+ xs: "w-5 h-5",
337
+ small: "w-5 h-5",
332
338
  medium: "w-8 h-8",
333
339
  large: "w-16 h-16",
334
340
  xl: "w-24 h-24",
335
341
  };
336
342
 
337
- const rowGapMap: Record<GridViewSize, string> = {
338
- xxs: "gap-3",
339
- xs: "gap-3",
340
- small: "gap-3",
343
+ const rowGapMap: Record<CollectionItemSize, string> = {
344
+ xxs: "gap-2",
345
+ xs: "gap-2",
346
+ small: "gap-2",
341
347
  medium: "gap-3",
342
348
  large: "gap-3",
343
349
  xl: "gap-4",
344
350
  };
345
351
 
346
- const fontStyleMap: Record<GridViewSize, string> = {
347
- xxs: "font-medium",
348
- xs: "font-medium",
349
- small: "font-medium",
352
+ const fontStyleMap: Record<CollectionItemSize, string> = {
353
+ xxs: "font-medium text-list-small",
354
+ xs: "font-medium text-list-small",
355
+ small: "font-medium text-list-small",
350
356
  medium: "font-medium",
351
357
  large: "font-medium",
352
358
  xl: "font-medium text-heading4 text-text",
@@ -26,7 +26,7 @@ import { ListChildComponentProps, VariableSizeList } from "react-window";
26
26
  import { mergeEventHandlers } from "../hooks/mergeEventHandlers";
27
27
  import { useHover } from "../hooks/useHover";
28
28
  import { cssVars, INPUT_HEIGHT } from "../theme";
29
- import { cx } from "../utils/classNames";
29
+ import { cx, mergeConflictingClassNames } from "../utils/classNames";
30
30
  import { ContextMenu } from "./ContextMenu";
31
31
  import { InputField } from "./InputField";
32
32
  import { MenuItem } from "./internal/Menu";
@@ -376,7 +376,7 @@ interface ListViewClickInfo {
376
376
  ctrlKey: boolean;
377
377
  }
378
378
 
379
- interface ListViewRowProps<MenuItemType extends string = string> {
379
+ export interface ListViewRowProps<MenuItemType extends string = string> {
380
380
  id?: string;
381
381
  tabIndex?: number;
382
382
  role?: string;
@@ -1044,9 +1044,14 @@ const ListViewRootInner = forwardRefGeneric(function ListViewRootInner<T>(
1044
1044
  aria-labelledby={ariaLabelledby}
1045
1045
  aria-orientation={ariaOrientation}
1046
1046
  aria-multiselectable={ariaMultiselectable}
1047
- className={cx(
1048
- `flex flex-col text-text-muted ${scrollable ? "flex-1" : "flex-none"} `,
1049
- className
1047
+ className={mergeConflictingClassNames(
1048
+ [
1049
+ `flex flex-col text-text-muted ${scrollable ? "flex-1" : "flex-none"} `,
1050
+ className,
1051
+ ],
1052
+ {
1053
+ categories: ["flex"],
1054
+ }
1050
1055
  )}
1051
1056
  style={{ ...gapStyle, ...style }}
1052
1057
  {...{
@@ -2,9 +2,10 @@ import { ImageIcon, SpeakerLoudIcon, VideoIcon } from "@noya-app/noya-icons";
2
2
  import React, { memo, useMemo } from "react";
3
3
  import { cssVars } from "../theme";
4
4
  import { cx } from "../utils/classNames";
5
+ import { CollectionItemSize } from "./Collection";
5
6
  import { IconName, Icons } from "./Icons";
6
7
 
7
- type ColorScheme = "primary" | "icon" | "warning";
8
+ type ColorScheme = "primary" | "secondary" | "icon" | "warning";
8
9
 
9
10
  type AssetType = "image" | "video" | "audio";
10
11
 
@@ -29,9 +30,13 @@ type MediaThumbnailProps = {
29
30
  * If provided, use this icon instead of the default one for the asset type
30
31
  */
31
32
  iconName?: IconName;
33
+ size?: CollectionItemSize;
32
34
  };
33
35
 
34
- const getColors = (colorScheme: ColorScheme, selected: boolean) => {
36
+ export const getThumbnailColors = (
37
+ colorScheme: ColorScheme,
38
+ selected: boolean
39
+ ) => {
35
40
  if (selected) {
36
41
  return {
37
42
  icon: cssVars.colors.primary,
@@ -45,6 +50,11 @@ const getColors = (colorScheme: ColorScheme, selected: boolean) => {
45
50
  icon: cssVars.colors.primary,
46
51
  background: cssVars.colors.listViewThumbnailBackground,
47
52
  };
53
+ case "secondary":
54
+ return {
55
+ icon: cssVars.colors.secondary,
56
+ background: cssVars.colors.secondaryPastel,
57
+ };
48
58
  case "warning":
49
59
  return {
50
60
  icon: cssVars.colors.warning,
@@ -59,11 +69,6 @@ const getColors = (colorScheme: ColorScheme, selected: boolean) => {
59
69
  }
60
70
  };
61
71
 
62
- const iconStyles = {
63
- width: "max(15px, 20%)",
64
- height: "max(15px, 20%)",
65
- };
66
-
67
72
  export const MediaThumbnail = memo(function MediaThumbnail({
68
73
  contentType: contentTypeProp,
69
74
  selected = false,
@@ -71,12 +76,20 @@ export const MediaThumbnail = memo(function MediaThumbnail({
71
76
  colorScheme = "icon",
72
77
  url,
73
78
  iconName = "FileIcon",
79
+ size = "medium",
74
80
  }: MediaThumbnailProps) {
75
- const { icon: iconColor, background: backgroundColor } = getColors(
81
+ const { icon: iconColor, background: backgroundColor } = getThumbnailColors(
76
82
  colorScheme,
77
83
  selected
78
84
  );
79
85
  const contentType = getAssetType(contentTypeProp);
86
+ const iconStyles = useMemo(
87
+ () => ({
88
+ width: iconSizeMap[size],
89
+ height: iconSizeMap[size],
90
+ }),
91
+ [size]
92
+ );
80
93
 
81
94
  const content = useMemo(() => {
82
95
  switch (contentType) {
@@ -101,7 +114,7 @@ export const MediaThumbnail = memo(function MediaThumbnail({
101
114
  const Icon = Icons[iconName];
102
115
  return <Icon color={iconColor} style={iconStyles} />;
103
116
  }
104
- }, [contentType, url, iconColor, iconName]);
117
+ }, [contentType, url, iconColor, iconStyles, iconName]);
105
118
 
106
119
  return (
107
120
  <div
@@ -115,3 +128,12 @@ export const MediaThumbnail = memo(function MediaThumbnail({
115
128
  </div>
116
129
  );
117
130
  });
131
+
132
+ const iconSizeMap: Record<CollectionItemSize, number | string> = {
133
+ xxs: 12,
134
+ xs: 12,
135
+ small: 12,
136
+ medium: "max(15px, 20%)",
137
+ large: "max(15px, 20%)",
138
+ xl: "max(15px, 20%)",
139
+ };