@noya-app/noya-designsystem 0.1.62 → 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 (118) hide show
  1. package/.eslintrc.js +1 -5
  2. package/.turbo/turbo-build.log +10 -10
  3. package/.turbo/turbo-lint.log +0 -1
  4. package/CHANGELOG.md +23 -0
  5. package/dist/index.css +1 -1
  6. package/dist/index.d.mts +299 -179
  7. package/dist/index.d.ts +299 -179
  8. package/dist/index.js +3772 -2999
  9. package/dist/index.js.map +1 -1
  10. package/dist/index.mjs +3485 -2720
  11. package/dist/index.mjs.map +1 -1
  12. package/package.json +10 -22
  13. package/src/__tests__/validateDropIndicator.test.ts +26 -41
  14. package/src/__tests__/workspaceLayout.test.ts +281 -0
  15. package/src/components/ActionMenu.tsx +2 -0
  16. package/src/components/ActivityIndicator.tsx +5 -3
  17. package/src/components/AnimatePresence.tsx +6 -4
  18. package/src/components/Avatar.tsx +4 -2
  19. package/src/components/BaseToolbar.tsx +45 -34
  20. package/src/components/Breadcrumbs.tsx +2 -0
  21. package/src/components/Button.tsx +35 -21
  22. package/src/components/Chip.tsx +2 -0
  23. package/src/components/Collection.tsx +4 -1
  24. package/src/components/ColorSwatch.tsx +42 -22
  25. package/src/components/ColorSwatchControl.tsx +5 -3
  26. package/src/components/Combobox.tsx +2 -0
  27. package/src/components/CommandPalette.tsx +2 -0
  28. package/src/components/ContextMenu.tsx +3 -1
  29. package/src/components/Dialog.tsx +3 -1
  30. package/src/components/DimensionInput.tsx +3 -1
  31. package/src/components/Divider.tsx +10 -6
  32. package/src/components/DraggableMenuButton.tsx +22 -4
  33. package/src/components/Drawer.tsx +11 -3
  34. package/src/components/DropdownMenu.tsx +34 -6
  35. package/src/components/EditableText.tsx +2 -0
  36. package/src/components/FileUploadIndicator.tsx +108 -0
  37. package/src/components/FillPreviewBackground.tsx +2 -0
  38. package/src/components/FloatingWindow.tsx +2 -0
  39. package/src/components/GradientPicker.tsx +2 -0
  40. package/src/components/Grid.tsx +47 -38
  41. package/src/components/GridView.tsx +33 -11
  42. package/src/components/IconButton.tsx +4 -2
  43. package/src/components/Icons.tsx +1 -1
  44. package/src/components/InputField.tsx +7 -5
  45. package/src/components/Label.tsx +1 -3
  46. package/src/components/LabeledElementView.tsx +6 -2
  47. package/src/components/LabeledField.tsx +38 -34
  48. package/src/components/List.tsx +10 -2
  49. package/src/components/ListView.tsx +27 -14
  50. package/src/components/MediaThumbnail.tsx +31 -2
  51. package/src/components/Message.tsx +8 -9
  52. package/src/components/Popover.tsx +15 -4
  53. package/src/components/Progress.tsx +20 -7
  54. package/src/components/ScrollArea.tsx +3 -1
  55. package/src/components/SearchCompletionMenu.tsx +2 -0
  56. package/src/components/Section.tsx +2 -0
  57. package/src/components/SegmentedControl.tsx +43 -17
  58. package/src/components/SelectMenu.tsx +6 -4
  59. package/src/components/SelectionToolbar.tsx +31 -8
  60. package/src/components/Slider.tsx +4 -2
  61. package/src/components/Spacer.tsx +0 -1
  62. package/src/components/Switch.tsx +3 -1
  63. package/src/components/Tabs.tsx +48 -0
  64. package/src/components/Text.tsx +6 -5
  65. package/src/components/TextArea.tsx +2 -0
  66. package/src/components/Toast.tsx +1 -1
  67. package/src/components/Toolbar.tsx +113 -12
  68. package/src/components/ToolbarDrawer.tsx +29 -0
  69. package/src/components/Tooltip.tsx +9 -9
  70. package/src/components/TreeView.tsx +2 -0
  71. package/src/components/UserPointer.tsx +2 -0
  72. package/src/components/ai-assistant/AIAssistantLayout.tsx +2 -0
  73. package/src/components/blocks/ImageBlockComponent.tsx +156 -0
  74. package/src/components/connected-users-menu/ConnectedUsersMenuLayout.tsx +2 -0
  75. package/src/components/file-explorer/FileExplorerLayout.tsx +14 -6
  76. package/src/components/internal/Checkmark.tsx +24 -0
  77. package/src/components/internal/Menu.tsx +21 -3
  78. package/src/components/internal/MenuViewport.tsx +8 -5
  79. package/src/components/internal/SelectItem.tsx +4 -23
  80. package/src/components/internal/TextInput.tsx +2 -0
  81. package/src/components/sorting/DragRegistration.tsx +8 -4
  82. package/src/components/sorting/SharedDragProvider.tsx +43 -18
  83. package/src/components/sorting/Sortable.tsx +19 -10
  84. package/src/components/sorting/createSharedDrag.tsx +7 -6
  85. package/src/components/sorting/sorting.ts +6 -9
  86. package/src/components/workspace/DrawerWorkspaceLayout.tsx +113 -28
  87. package/src/components/workspace/PanelWorkspaceLayout.tsx +149 -16
  88. package/src/components/workspace/VerticalTabMenu.tsx +36 -0
  89. package/src/components/workspace/WorkspaceLayout.tsx +132 -47
  90. package/src/components/workspace/constants.ts +4 -0
  91. package/src/components/workspace/panelStorage.ts +216 -0
  92. package/src/components/workspace/renderPanelChildren.tsx +12 -0
  93. package/src/components/workspace/types.ts +30 -7
  94. package/src/contexts/DesignSystemConfiguration.tsx +12 -5
  95. package/src/contexts/DialogContext.tsx +2 -0
  96. package/src/contexts/FloatingWindowContext.tsx +2 -0
  97. package/src/contexts/GlobalInputBlurContext.tsx +2 -0
  98. package/src/contexts/ImageDataContext.tsx +2 -0
  99. package/src/contexts/LinkComponentContext.tsx +17 -0
  100. package/src/contexts/OpenPortalsContext.tsx +2 -0
  101. package/src/contexts/PortalScopeContext.tsx +2 -0
  102. package/src/hooks/mergeEventHandlers.ts +1 -1
  103. package/src/hooks/useHover.ts +30 -17
  104. package/src/hooks/useIndent.ts +2 -0
  105. package/src/hooks/useLabel.ts +12 -10
  106. package/src/hooks/useObjectURL.ts +5 -3
  107. package/src/hooks/usePlatform.ts +5 -3
  108. package/src/hooks/useTheme.ts +3 -1
  109. package/src/index.css +25 -4
  110. package/src/index.tsx +7 -2
  111. package/src/utils/classNames.ts +4 -3
  112. package/src/utils/combobox.ts +2 -0
  113. package/src/utils/editableBlockStyles.ts +10 -0
  114. package/src/utils/formatByteSize.ts +8 -0
  115. package/src/utils/withSeparatorElements.ts +1 -1
  116. package/src/hooks/usePreservePanelSize.tsx +0 -183
  117. package/src/hooks/useWindowSize.tsx +0 -26
  118. package/src/mediaQuery.ts +0 -13
@@ -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={() => {
@@ -1,3 +1,5 @@
1
+ "use client";
2
+
1
3
  import { KeyModifiers } from "@noya-app/noya-keymap";
2
4
  import { forwardRefGeneric, memoGeneric } from "@noya-app/react-utils";
3
5
  import React, {
@@ -11,6 +13,7 @@ import React, {
11
13
  useContext,
12
14
  useMemo,
13
15
  } from "react";
16
+ import { useLinkComponent } from "../contexts/LinkComponentContext";
14
17
  import { useHover } from "../hooks/useHover";
15
18
  import { cx, mergeConflictingClassNames } from "../utils/classNames";
16
19
  import withSeparatorElements from "../utils/withSeparatorElements";
@@ -79,6 +82,7 @@ const SectionTitle = ({
79
82
 
80
83
  interface ItemProps<MenuItemType extends string = string> {
81
84
  id: string;
85
+ href?: string;
82
86
  title?: ReactNode;
83
87
  subtitle?: ReactNode;
84
88
  loading?: boolean;
@@ -105,6 +109,7 @@ const GridViewItem = forwardRefGeneric(function GridViewItem<
105
109
  >(
106
110
  {
107
111
  id,
112
+ href,
108
113
  title,
109
114
  subtitle,
110
115
  loading,
@@ -125,23 +130,27 @@ const GridViewItem = forwardRefGeneric(function GridViewItem<
125
130
  testShowInsideDropIndicator: showInsideDropIndicator = false,
126
131
  ...props
127
132
  }: ItemProps<MenuItemType>,
128
- forwardedRef: ForwardedRef<HTMLDivElement>
133
+ forwardedRef: ForwardedRef<HTMLElement>
129
134
  ) {
130
135
  const { hoverProps } = useHover({
131
136
  onHoverChange,
132
137
  });
133
138
 
134
- const { textPosition, bordered, disabled } = useContext(GridViewContext);
139
+ const { textPosition, bordered, disabled, alwaysShowSubtitles } =
140
+ useContext(GridViewContext);
135
141
 
136
142
  const handleClick = useCallback(
137
143
  (event: React.MouseEvent) => {
138
144
  event.stopPropagation();
139
- event.preventDefault();
145
+
146
+ if (!href) {
147
+ event.preventDefault();
148
+ }
140
149
 
141
150
  onClick?.(event);
142
151
  onSelect?.(event);
143
152
  },
144
- [onClick, onSelect]
153
+ [href, onClick, onSelect]
145
154
  );
146
155
 
147
156
  const handleKeyDown = useCallback(
@@ -159,8 +168,11 @@ const GridViewItem = forwardRefGeneric(function GridViewItem<
159
168
  [onSelect, onKeyDown]
160
169
  );
161
170
 
171
+ const LinkComponent = useLinkComponent();
172
+ const Component = typeof href === "string" ? LinkComponent : "div";
173
+
162
174
  let element = (
163
- <div
175
+ <Component
164
176
  {...props}
165
177
  className={cx(
166
178
  "flex flex-col relative rounded p-2 -m-2 text-text",
@@ -173,7 +185,8 @@ const GridViewItem = forwardRefGeneric(function GridViewItem<
173
185
  "focus:ring-2 focus:ring-primary focus:ring-inset focus:outline-none"
174
186
  )}
175
187
  id={id}
176
- ref={forwardedRef}
188
+ href={href}
189
+ ref={forwardedRef as ForwardedRef<HTMLAnchorElement & HTMLDivElement>}
177
190
  {...hoverProps}
178
191
  tabIndex={disabled ? undefined : 0}
179
192
  onKeyDown={handleKeyDown}
@@ -194,8 +207,12 @@ const GridViewItem = forwardRefGeneric(function GridViewItem<
194
207
  <>
195
208
  <Spacer.Vertical size={8} />
196
209
  <ItemTitle>{title || " "}</ItemTitle>
197
- <Spacer.Vertical size={2} />
198
- <ItemDescription>{subtitle || " "}</ItemDescription>
210
+ {(alwaysShowSubtitles || subtitle) && (
211
+ <>
212
+ <Spacer.Vertical size={2} />
213
+ <ItemDescription>{subtitle || " "}</ItemDescription>
214
+ </>
215
+ )}
199
216
  </>
200
217
  )}
201
218
  {textPosition === "overlay" && hovered && (title || subtitle) && (
@@ -216,7 +233,7 @@ const GridViewItem = forwardRefGeneric(function GridViewItem<
216
233
  <ActivityIndicator opacity={0.5} size={13} />
217
234
  </div>
218
235
  )}
219
- </div>
236
+ </Component>
220
237
  );
221
238
 
222
239
  if (menuItems && onSelectMenuItem) {
@@ -255,6 +272,7 @@ type GridViewContextValue = {
255
272
  bordered: boolean;
256
273
  disabled: boolean;
257
274
  gap: number;
275
+ alwaysShowSubtitles: boolean;
258
276
  };
259
277
 
260
278
  const GridViewContext = createContext<GridViewContextValue>({
@@ -263,6 +281,7 @@ const GridViewContext = createContext<GridViewContextValue>({
263
281
  bordered: false,
264
282
  disabled: false,
265
283
  gap: 0,
284
+ alwaysShowSubtitles: false,
266
285
  });
267
286
 
268
287
  interface GridViewRootProps extends Partial<GridViewContextValue> {
@@ -277,6 +296,7 @@ interface GridViewRootProps extends Partial<GridViewContextValue> {
277
296
  role?: string;
278
297
  renderEmptyState?: () => ReactNode;
279
298
  contentClassName?: string;
299
+ alwaysShowSubtitles?: boolean;
280
300
  }
281
301
 
282
302
  const GridViewRoot = forwardRef(function GridViewRoot(
@@ -291,6 +311,7 @@ const GridViewRoot = forwardRef(function GridViewRoot(
291
311
  disabled = false,
292
312
  className,
293
313
  contentClassName,
314
+ alwaysShowSubtitles = false,
294
315
  ...props
295
316
  }: GridViewRootProps,
296
317
  forwardedRef: ForwardedRef<HTMLDivElement>
@@ -306,14 +327,15 @@ const GridViewRoot = forwardRef(function GridViewRoot(
306
327
  );
307
328
 
308
329
  const contextValue = useMemo(
309
- () => ({
330
+ (): GridViewContextValue => ({
310
331
  minColumnWidth,
311
332
  textPosition,
312
333
  bordered,
313
334
  disabled,
314
335
  gap,
336
+ alwaysShowSubtitles,
315
337
  }),
316
- [bordered, disabled, gap, minColumnWidth, textPosition]
338
+ [bordered, disabled, gap, minColumnWidth, textPosition, alwaysShowSubtitles]
317
339
  );
318
340
 
319
341
  const mergedClassName = useMemo(() => {
@@ -1,9 +1,11 @@
1
+ "use client";
2
+
1
3
  import * as React from "react";
2
4
  import { cssVars } from "../theme";
3
- import { Button, ButtonRootProps } from "./Button";
5
+ import { Button, ButtonProps } from "./Button";
4
6
  import { IconName, Icons } from "./Icons";
5
7
 
6
- type Props = Omit<ButtonRootProps, "children" | "variant" | "flex" | "size"> & {
8
+ type Props = Omit<ButtonProps, "children" | "variant" | "flex" | "size"> & {
7
9
  iconName: IconName;
8
10
  className?: string;
9
11
  style?: React.CSSProperties;
@@ -11,7 +11,7 @@ export function renderIcon(
11
11
  if (typeof iconName === "string") {
12
12
  const Icon = Icons[iconName as IconName];
13
13
 
14
- return <Icon />;
14
+ return <Icon className="min-h-[15px]" />;
15
15
  }
16
16
 
17
17
  return iconName;
@@ -1,3 +1,5 @@
1
+ "use client";
2
+
1
3
  import { DropdownChevronIcon } from "@noya-app/noya-icons";
2
4
  import { memoGeneric, useMergeRefs, useSize } from "@noya-app/react-utils";
3
5
  import { Property } from "csstype";
@@ -14,11 +16,11 @@ import React, {
14
16
  useMemo,
15
17
  useState,
16
18
  } from "react";
17
- import { useLabel, useLabelPosition } from "../hooks/useLabel";
19
+ import { useLabel, useLabelType } from "../hooks/useLabel";
18
20
  import { cx, mergeConflictingClassNames } from "../utils/classNames";
19
21
  import handleNudge from "../utils/handleNudge";
20
22
  import { getInsetEndStyles, InputSize, startBaseStyles } from "../utils/inputs";
21
- import { Button, ButtonRootProps } from "./Button";
23
+ import { Button, ButtonProps } from "./Button";
22
24
  import { DropdownMenu } from "./DropdownMenu";
23
25
  import { MenuItem } from "./internal/Menu";
24
26
  import TextInput, { TextInputProps } from "./internal/TextInput";
@@ -101,7 +103,7 @@ const InputFieldDropdownMenu = memoGeneric(function InputFieldDropdownMenu<
101
103
  const InputFieldButton = memo(
102
104
  forwardRef(function InputFieldButton(
103
105
  props: React.ButtonHTMLAttributes<HTMLButtonElement> &
104
- ButtonRootProps & {
106
+ ButtonProps & {
105
107
  variant?: "floating" | "normal";
106
108
  },
107
109
  forwardedRef: ForwardedRef<HTMLButtonElement>
@@ -537,7 +539,7 @@ function InputFieldRoot({
537
539
  const measuredInputSize = useSize(inputRef, "width");
538
540
  const measuredStartSize = useSize(startRef, "width");
539
541
  const measuredEndSize = useSize(endRef, "width");
540
- const labelPosition = useLabelPosition();
542
+ const labelType = useLabelType();
541
543
  const { label } = useLabel({ fieldId: id });
542
544
 
543
545
  const endStyles = getInsetEndStyles(size);
@@ -592,7 +594,7 @@ function InputFieldRoot({
592
594
  >
593
595
  {labelProp
594
596
  ? insetLabel
595
- : label && labelPosition === "inset" && insetLabel}
597
+ : label && labelType === "inset" && insetLabel}
596
598
  {end}
597
599
  </span>
598
600
  )}
@@ -1,13 +1,11 @@
1
1
  import React, { LabelHTMLAttributes, forwardRef, memo } from "react";
2
2
  import { mergeConflictingClassNames } from "../utils/classNames";
3
3
 
4
- export type LabelPosition = "inset" | "start" | "end" | "above";
5
-
6
4
  export interface LabelProps extends LabelHTMLAttributes<HTMLLabelElement> {}
7
5
 
8
6
  const labelStyles = "flex items-center select-none z-label relative";
9
7
  const labelTextStyles =
10
- "font-sans text-label uppercase leading-3 font-bold text-text-muted";
8
+ "font-sans text-label uppercase leading-none font-bold text-text-muted";
11
9
 
12
10
  export const Label = memo(
13
11
  forwardRef<HTMLLabelElement, LabelProps>(function Label(
@@ -1,3 +1,5 @@
1
+ "use client";
2
+
1
3
  import * as kiwi from "kiwi.js";
2
4
  import * as React from "react";
3
5
 
@@ -13,11 +15,13 @@ export const LabeledElementView = React.memo(function LabeledElementView({
13
15
  const elementIds: string[] = React.Children.toArray(children)
14
16
  .flatMap((child) =>
15
17
  React.isValidElement(child) && child.type === React.Fragment
16
- ? (child.props.children as React.ReactNode[])
18
+ ? ((child as any).props.children as React.ReactNode[])
17
19
  : [child]
18
20
  )
19
21
  .map((child) =>
20
- React.isValidElement(child) && "id" in child.props ? child.props.id : null
22
+ React.isValidElement(child) && "id" in (child as any).props
23
+ ? (child as any).props.id
24
+ : null
21
25
  )
22
26
  .filter((id) => !!id);
23
27
  const serializedIds = elementIds.join(",");
@@ -1,30 +1,33 @@
1
+ "use client";
2
+
1
3
  import React, { memo, useMemo } from "react";
2
4
  import { useIndent } from "../hooks/useIndent";
3
5
  import {
4
6
  LabelContext,
5
- LabelPositionContext,
6
- LabelPositionContextValue,
7
- useLabelPosition,
7
+ LabelType,
8
+ LabelTypeContext,
9
+ LabelTypeContextValue,
10
+ useLabelType,
8
11
  useLabelWidth,
9
12
  } from "../hooks/useLabel";
10
13
  import { cx } from "../utils/classNames";
11
- import { Label, LabelPosition } from "./Label";
14
+ import { Label } from "./Label";
12
15
 
13
- export const labeledFieldStyles = (labelPosition?: LabelPosition) =>
16
+ export const labeledFieldStyles = (labelType?: LabelType) =>
14
17
  cx(
15
18
  "flex flex-row flex-1 flex-wrap items-center gap-x-1.5",
16
- labelPosition === "end" && "flex-row-reverse justify-end",
17
- labelPosition === "above" && "flex-col items-stretch justify-start"
19
+ labelType === "end" && "flex-row-reverse justify-end",
20
+ labelType === "above" && "flex-col items-stretch justify-start"
18
21
  );
19
22
 
20
- const labelPositionInset: LabelPositionContextValue = {
21
- position: "inset",
23
+ const labelTypeInset: LabelTypeContextValue = {
24
+ type: "inset",
22
25
  };
23
26
 
24
27
  export const LabeledField = memo(function LabeledField({
25
28
  children,
26
29
  label,
27
- labelPosition: labelPositionProp,
30
+ labelType: labelTypeProp,
28
31
  fieldId,
29
32
  minWidth = 100,
30
33
  className,
@@ -34,7 +37,7 @@ export const LabeledField = memo(function LabeledField({
34
37
  }: {
35
38
  children: React.ReactNode;
36
39
  label: React.ReactNode;
37
- labelPosition?: LabelPosition;
40
+ labelType?: LabelType;
38
41
  labelStyle?: React.CSSProperties;
39
42
  /** Used for both the `id` of the field and the `htmlFor` of the label, unless `id` and `htmlFor` are explicitly provided */
40
43
  fieldId?: string;
@@ -47,13 +50,12 @@ export const LabeledField = memo(function LabeledField({
47
50
  const labelWidthFromContext = useLabelWidth();
48
51
  const labelWidth = labelWidthFromContext ?? minWidth;
49
52
 
50
- const labelPositionFromContext = useLabelPosition();
51
- const labelPosition =
52
- labelPositionProp ?? labelPositionFromContext ?? "start";
53
+ const labelTypeFromContext = useLabelType();
54
+ const labelType = labelTypeProp ?? labelTypeFromContext;
53
55
 
54
56
  const labeledFieldClasses = useMemo(
55
- () => labeledFieldStyles(labelPosition),
56
- [labelPosition]
57
+ () => labeledFieldStyles(labelType),
58
+ [labelType]
57
59
  );
58
60
 
59
61
  const labelContextValue = useMemo(
@@ -89,29 +91,31 @@ export const LabeledField = memo(function LabeledField({
89
91
  return (
90
92
  <LabelContext.Provider value={labelContextValue}>
91
93
  {/* let components individually handle inset labels using info from context*/}
92
- {labelPosition === "inset" ? (
93
- <LabelPositionContext.Provider value={labelPositionInset}>
94
+ {labelType === "inset" ? (
95
+ <LabelTypeContext.Provider value={labelTypeInset}>
94
96
  {children}
95
- </LabelPositionContext.Provider>
97
+ </LabelTypeContext.Provider>
96
98
  ) : (
97
99
  <div className={cx("flex relative flex-1", className)} style={style}>
98
100
  {Boolean(indentLevel) && <div style={indentStyle} />}
99
101
  <div className={labeledFieldClasses}>
100
- <Label
101
- htmlFor={fieldId}
102
- // we do this so presence avatars are aligned to the right when position is above
103
- // and when start/end position with textarea it aligns label to the top of the field
104
- className={
105
- labelPosition === "start" || labelPosition === "end"
106
- ? "self-start"
107
- : undefined
108
- }
109
- style={labelStyle}
110
- {...props}
111
- >
112
- {label}
113
- </Label>
114
- {labelPosition === "start" ? (
102
+ {labelType !== "none" && (
103
+ <Label
104
+ htmlFor={fieldId}
105
+ // we do this so presence avatars are aligned to the right when position is above
106
+ // and when start/end position with textarea it aligns label to the top of the field
107
+ className={
108
+ labelType === "start" || labelType === "end"
109
+ ? "self-start"
110
+ : undefined
111
+ }
112
+ style={labelStyle}
113
+ {...props}
114
+ >
115
+ {label}
116
+ </Label>
117
+ )}
118
+ {labelType === "start" ? (
115
119
  <div className="flex-auto flex" style={fieldStyle}>
116
120
  {children}
117
121
  </div>
@@ -1,9 +1,12 @@
1
+ "use client";
2
+
1
3
  import {
2
4
  forwardRefGeneric,
3
5
  memoGeneric,
4
6
  useFileDropTarget,
5
7
  } from "@noya-app/react-utils";
6
8
  import React, { memo, useImperativeHandle, useRef, useState } from "react";
9
+ import { useLinkComponent } from "../contexts/LinkComponentContext";
7
10
  import { cssVars } from "../theme";
8
11
  import { cx } from "../utils/classNames";
9
12
  import { updateSelection } from "../utils/selection";
@@ -32,6 +35,7 @@ export const List = memoGeneric(
32
35
  items,
33
36
  getId,
34
37
  getName,
38
+ getHref,
35
39
  expandable,
36
40
  getExpanded,
37
41
  getRenamable,
@@ -101,8 +105,9 @@ export const List = memoGeneric(
101
105
  };
102
106
 
103
107
  const ViewComponent = expandable ? TreeView : ListView;
108
+ const LinkComponent = useLinkComponent();
104
109
 
105
- const fileDropTargetRef = useRef<HTMLDivElement>(null);
110
+ const fileDropTargetRef = useRef<HTMLDivElement | null>(null);
106
111
  const { isDropTargetActive, dropTargetProps } = useFileDropTarget(
107
112
  fileDropTargetRef,
108
113
  onFilesDrop
@@ -144,6 +149,7 @@ export const List = memoGeneric(
144
149
  { isDragOverlay }: ListViewItemInfo
145
150
  ) => {
146
151
  const id = getId(item);
152
+ const href = getHref?.(item);
147
153
  const isDropdownOpen = dropdownOpenId === id;
148
154
  const isHovered =
149
155
  (hoveredId === id || isDropdownOpen) && !isDragOverlay;
@@ -197,6 +203,8 @@ export const List = memoGeneric(
197
203
 
198
204
  return (
199
205
  <ViewComponent.Row
206
+ as={typeof href === "string" ? LinkComponent : undefined}
207
+ href={href}
200
208
  id={id}
201
209
  key={id}
202
210
  role="option"
@@ -355,7 +363,7 @@ const rowGapMap: Record<CollectionItemSize, string> = {
355
363
  xxs: "gap-2",
356
364
  xs: "gap-2",
357
365
  small: "gap-2",
358
- medium: "gap-3",
366
+ medium: "gap-2",
359
367
  mediumLarge: "gap-3",
360
368
  large: "gap-3",
361
369
  xl: "gap-4",