@noya-app/noya-designsystem 0.1.64 → 0.1.65

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (115) hide show
  1. package/.turbo/turbo-build.log +13 -10
  2. package/CHANGELOG.md +12 -0
  3. package/dist/index.css +1 -1
  4. package/dist/index.d.mts +501 -228
  5. package/dist/index.d.ts +501 -228
  6. package/dist/index.js +6738 -2571
  7. package/dist/index.js.map +1 -1
  8. package/dist/index.mjs +6697 -2499
  9. package/dist/index.mjs.map +1 -1
  10. package/package.json +6 -6
  11. package/src/components/ActivityIndicator.tsx +9 -4
  12. package/src/components/ActivityLog.tsx +197 -0
  13. package/src/components/AnimatePresence.tsx +37 -22
  14. package/src/components/Avatar.tsx +42 -25
  15. package/src/components/Banner.tsx +57 -11
  16. package/src/components/BaseToolbar.tsx +29 -7
  17. package/src/components/Breadcrumbs.tsx +2 -2
  18. package/src/components/Button.tsx +23 -24
  19. package/src/components/Checkbox.tsx +6 -35
  20. package/src/components/Chip.tsx +45 -33
  21. package/src/components/Collection.tsx +16 -11
  22. package/src/components/ColorSwatch.tsx +3 -3
  23. package/src/components/ColorSwatchControl.tsx +2 -2
  24. package/src/components/Combobox.tsx +6 -6
  25. package/src/components/ComboboxMenu.tsx +6 -5
  26. package/src/components/CommandPalette.tsx +1 -1
  27. package/src/components/Dialog.tsx +6 -12
  28. package/src/components/Divider.tsx +8 -8
  29. package/src/components/DraggableMenuButton.tsx +9 -5
  30. package/src/components/Drawer.tsx +5 -5
  31. package/src/components/DropdownMenu.tsx +1 -1
  32. package/src/components/EditableText.tsx +2 -2
  33. package/src/components/Fade.tsx +13 -13
  34. package/src/components/FileUploadIndicator.tsx +11 -11
  35. package/src/components/FloatingWindow.tsx +2 -2
  36. package/src/components/Grid.tsx +18 -11
  37. package/src/components/GridView.tsx +32 -25
  38. package/src/components/IVirtualizedList.tsx +5 -0
  39. package/src/components/Icons.tsx +24 -4
  40. package/src/components/InputField.tsx +19 -19
  41. package/src/components/InspectorContainer.tsx +2 -2
  42. package/src/components/InspectorPrimitives.tsx +15 -9
  43. package/src/components/Label.tsx +4 -3
  44. package/src/components/LabeledElementView.tsx +3 -3
  45. package/src/components/LabeledField.tsx +6 -6
  46. package/src/components/List.tsx +103 -45
  47. package/src/components/ListMenu.tsx +91 -0
  48. package/src/components/ListNavigator.tsx +106 -0
  49. package/src/components/ListView.tsx +20 -1158
  50. package/src/components/MediaThumbnail.tsx +56 -15
  51. package/src/components/Message.tsx +5 -5
  52. package/src/components/Navigator.tsx +461 -0
  53. package/src/components/NoyaLogo.tsx +1 -1
  54. package/src/components/Popover.tsx +9 -5
  55. package/src/components/Progress.tsx +19 -9
  56. package/src/components/ResizableContainer.tsx +187 -0
  57. package/src/components/RingProgress.tsx +128 -0
  58. package/src/components/ScrollArea.tsx +4 -4
  59. package/src/components/ScrollableSidebar.tsx +10 -0
  60. package/src/components/SearchCompletionMenu.tsx +3 -3
  61. package/src/components/Section.tsx +107 -57
  62. package/src/components/SegmentedControl.tsx +29 -29
  63. package/src/components/SelectMenu.tsx +13 -20
  64. package/src/components/SelectionToolbar.tsx +4 -3
  65. package/src/components/Slider.tsx +9 -9
  66. package/src/components/Spacer.tsx +2 -2
  67. package/src/components/StackNavigator.tsx +378 -0
  68. package/src/components/Stepper.tsx +88 -0
  69. package/src/components/Switch.tsx +4 -4
  70. package/src/components/Tabs.tsx +3 -3
  71. package/src/components/Text.tsx +23 -13
  72. package/src/components/TextArea.tsx +3 -3
  73. package/src/components/Toast.tsx +4 -4
  74. package/src/components/Toolbar.tsx +50 -9
  75. package/src/components/ToolbarDrawer.tsx +2 -2
  76. package/src/components/Tooltip.tsx +1 -1
  77. package/src/components/TreeView.tsx +11 -4
  78. package/src/components/UserPointer.tsx +4 -4
  79. package/src/components/ai-assistant/AIAssistantLayout.tsx +8 -8
  80. package/src/components/blocks/ImageBlockComponent.tsx +13 -7
  81. package/src/components/connected-users-menu/ConnectedUsersMenuLayout.tsx +4 -4
  82. package/src/components/file-explorer/FileExplorerLayout.tsx +20 -8
  83. package/src/components/internal/Checkmark.tsx +1 -1
  84. package/src/components/internal/Menu.tsx +22 -18
  85. package/src/components/internal/MenuViewport.tsx +13 -6
  86. package/src/components/internal/SelectItem.tsx +3 -3
  87. package/src/components/internal/TextInput.tsx +5 -1
  88. package/src/components/listView/ListViewContexts.tsx +68 -0
  89. package/src/components/listView/ListViewEditableRowTitle.tsx +55 -0
  90. package/src/components/listView/ListViewRoot.tsx +521 -0
  91. package/src/components/listView/ListViewRow.tsx +475 -0
  92. package/src/components/listView/ListViewRowTitle.tsx +21 -0
  93. package/src/components/listView/types.ts +11 -0
  94. package/src/components/pipeline/PipelineResultLayout.tsx +6 -6
  95. package/src/components/sorting/DragRegistration.tsx +2 -1
  96. package/src/components/sorting/SharedDragProvider.tsx +91 -9
  97. package/src/components/sorting/Sortable.tsx +71 -17
  98. package/src/components/workspace/DrawerWorkspaceLayout.tsx +5 -5
  99. package/src/components/workspace/PanelWorkspaceLayout.tsx +65 -49
  100. package/src/components/workspace/VerticalTabMenu.tsx +25 -15
  101. package/src/components/workspace/WorkspaceLayout.tsx +30 -21
  102. package/src/contexts/DialogContext.tsx +17 -5
  103. package/src/hooks/useIndent.ts +3 -8
  104. package/src/index.css +24 -10
  105. package/src/index.tsx +10 -5
  106. package/src/utils/classNames.ts +52 -6
  107. package/src/utils/editableBlockStyles.ts +2 -2
  108. package/src/utils/formatByteSize.ts +1 -0
  109. package/src/utils/inputs.ts +8 -6
  110. package/src/utils/sketchColor.ts +0 -34
  111. package/tailwind.config.ts +1 -0
  112. package/src/components/FillInputField.tsx +0 -37
  113. package/src/components/FillPreviewBackground.tsx +0 -134
  114. package/src/components/GradientPicker.tsx +0 -90
  115. package/src/utils/getGradientBackground.tsx +0 -31
@@ -1,1166 +1,28 @@
1
1
  "use client";
2
2
 
3
- import { Size } from "@noya-app/noya-geometry";
4
- import { range } from "@noya-app/noya-utils";
5
- import { forwardRefGeneric, memoGeneric } from "@noya-app/react-utils";
6
- import { composeRefs } from "@radix-ui/react-compose-refs";
7
- import React, {
8
- Children,
9
- createContext,
10
- CSSProperties,
11
- ForwardedRef,
12
- forwardRef,
13
- HTMLAttributes,
14
- isValidElement,
15
- memo,
16
- ReactElement,
17
- ReactNode,
18
- Ref,
19
- useCallback,
20
- useContext,
21
- useImperativeHandle,
22
- useLayoutEffect,
23
- useMemo,
24
- useRef,
25
- } from "react";
26
- import { WindowScroller, WindowScrollerChildProps } from "react-virtualized";
27
- import { ListChildComponentProps, VariableSizeList } from "react-window";
28
- import { mergeEventHandlers } from "../hooks/mergeEventHandlers";
29
- import { useHover } from "../hooks/useHover";
30
- import { cssVars, INPUT_HEIGHT } from "../theme";
31
- import { cx, mergeConflictingClassNames } from "../utils/classNames";
32
- import { ContextMenu } from "./ContextMenu";
33
- import { InputField } from "./InputField";
34
- import { MenuItem } from "./internal/Menu";
35
- import { ScrollArea } from "./ScrollArea";
36
- import { SharedDragProps } from "./sorting/DragRegistration";
37
- import { Sortable } from "./sorting/Sortable";
3
+ import { IVirtualizedList } from "./IVirtualizedList";
4
+ import { ListRowContext } from "./listView/ListViewContexts";
5
+ import { ListViewEditableRowTitle } from "./listView/ListViewEditableRowTitle";
38
6
  import {
39
- DropValidator,
40
- MoveDragItemHandler,
41
- normalizeListTargetIndex,
42
- RelativeDropPosition,
43
- SortableItemContextProps,
44
- } from "./sorting/sorting";
45
- import { Spacer } from "./Spacer";
46
-
47
- export type ListRowMarginType = "none" | "top" | "bottom" | "vertical";
48
- export type ListRowPosition = "only" | "first" | "middle" | "last";
49
-
50
- const ROW_HEIGHT = 31;
51
- const SECTION_HEADER_LABEL_HEIGHT = INPUT_HEIGHT;
52
-
53
- type ListColorScheme = "primary" | "secondary";
54
-
55
- type PressEventName = "onClick" | "onPointerDown";
56
-
57
- // TODO: Some of these could be passed in a single context value that's
58
- // the same for all rows. But doing this seems to cause an infinite loop and
59
- // prevents drag and drop from working.
60
- type ListRowContextValue = {
61
- marginType: ListRowMarginType;
62
- selectedPosition: ListRowPosition;
63
- sortable: boolean;
64
- expandable: boolean;
65
- divider: boolean;
66
- gap: number;
67
- variant: ListViewVariant;
68
- sectionHeaderVariant: ListViewSectionHeaderVariant;
69
- pressEventName: PressEventName;
70
- isSectionHeader: boolean;
71
- colorScheme: ListColorScheme;
72
- };
73
-
74
- // The dragged item isn't passed a context value (doing so causes an infinite loop),
75
- // so we pass this separately. Rows need the indentation to look correct when dragging,
76
- // and the dragged row doesn't have a ListRowContextValue.
77
- type ListViewDraggingContextValue = {
78
- indentation: number;
79
- isDragOverlay?: boolean;
80
- };
81
-
82
- const ListViewDraggingContext = createContext<ListViewDraggingContextValue>({
83
- indentation: 12,
84
- });
85
- (ListViewDraggingContext as any).displayName = "ListViewDraggingContext";
86
-
87
- const ListRowContext = createContext<ListRowContextValue>({
88
- marginType: "none",
89
- selectedPosition: "only",
90
- sortable: false,
91
- expandable: true,
92
- divider: true,
93
- gap: 0,
94
- variant: "normal",
95
- sectionHeaderVariant: "normal",
96
- pressEventName: "onClick",
97
- isSectionHeader: false,
98
- colorScheme: "primary",
99
- });
100
- (ListRowContext as any).displayName = "ListRowContext";
101
-
102
- /* ----------------------------------------------------------------------------
103
- * RowTitle
104
- * ------------------------------------------------------------------------- */
105
-
106
- const ListViewRowTitle = ({
107
- className,
108
- children,
109
- ...props
110
- }: {
111
- className?: string;
112
- children: React.ReactNode;
113
- } & HTMLAttributes<HTMLSpanElement>) => (
114
- <span
115
- className={cx(
116
- `flex-1 overflow-hidden text-ellipsis whitespace-pre `,
117
- className
118
- )}
119
- {...props}
120
- >
121
- {children}
122
- </span>
123
- );
124
-
125
- /* ----------------------------------------------------------------------------
126
- * EditableRowTitle
127
- * ------------------------------------------------------------------------- */
128
-
129
- export interface EditableRowProps {
130
- value: string;
131
- onSubmitEditing: (value: string) => void;
132
- autoFocus: boolean;
133
- placeholder?: string;
134
- className?: string;
135
- onKeyDown?: (e: React.KeyboardEvent) => void;
136
- }
137
-
138
- function ListViewEditableRowTitle({
139
- value,
140
- onSubmitEditing,
141
- autoFocus,
142
- placeholder,
143
- className,
144
- onKeyDown,
145
- }: EditableRowProps) {
146
- const inputRef = useRef<HTMLInputElement | null>(null);
147
-
148
- useLayoutEffect(() => {
149
- const element = inputRef.current;
150
-
151
- if (!element || !autoFocus) return;
152
-
153
- // Calling `focus` is necessary, in addition to `select`, to ensure
154
- // the `onBlur` fires correctly.
155
- element.focus();
156
-
157
- // must be a delay of 1ms or more to ensure any radix ui components have finished any focus behaviors
158
- setTimeout(() => {
159
- element.select();
160
- }, 1);
161
- }, [autoFocus]);
162
-
163
- return (
164
- <InputField.Input
165
- ref={inputRef}
166
- value={value}
167
- placeholder={placeholder}
168
- onSubmit={onSubmitEditing}
169
- allowSubmittingWithSameValue
170
- className={cx("-mx-1.5 -my-1 bg-listview-editing-background", className)}
171
- onKeyDown={onKeyDown}
172
- />
173
- );
174
- }
175
-
176
- /* ----------------------------------------------------------------------------
177
- * Row
178
- * ------------------------------------------------------------------------- */
179
-
180
- const RowContainer = forwardRef<
181
- HTMLDivElement,
182
- React.HTMLAttributes<HTMLDivElement> & {
183
- $marginType: ListRowMarginType;
184
- $selected: boolean;
185
- $selectedPosition: ListRowPosition;
186
- $disabled: boolean;
187
- $hovered: boolean;
188
- $variant: ListViewVariant;
189
- $divider: boolean;
190
- $isSectionHeader: boolean;
191
- $showsActiveState: boolean;
192
- $sectionHeaderVariant: ListViewSectionHeaderVariant;
193
- $colorScheme: ListColorScheme;
194
- $gap: number;
195
- $backgroundColor?: CSSProperties["backgroundColor"];
196
- $clickable?: boolean;
197
- href?: string;
198
- as?: React.ElementType;
199
- }
200
- >(
201
- (
202
- {
203
- as: Component = "div",
204
- href,
205
- className,
206
- $marginType,
207
- $selected,
208
- $selectedPosition,
209
- $disabled,
210
- $hovered,
211
- $variant,
212
- $divider,
213
- $isSectionHeader,
214
- $showsActiveState,
215
- $sectionHeaderVariant,
216
- $colorScheme,
217
- $gap,
218
- $backgroundColor,
219
- style,
220
- $clickable,
221
- "aria-describedby": _, // Causes hydration warning
222
- role,
223
- "aria-label": ariaLabel,
224
- "aria-roledescription": ariaRoleDescription,
225
- "aria-selected": ariaSelected,
226
- ...props
227
- },
228
- ref
229
- ) => {
230
- return (
231
- <Component
232
- ref={ref}
233
- href={href}
234
- role={role}
235
- aria-label={ariaLabel}
236
- aria-roledescription={ariaRoleDescription}
237
- aria-selected={ariaSelected ?? $selected}
238
- className={cx(
239
- $isSectionHeader && $sectionHeaderVariant === "label"
240
- ? "font-sans text-label uppercase leading-3 font-bold text-text-muted"
241
- : "font-sans text-heading5 font-normal",
242
- !$isSectionHeader &&
243
- $selected &&
244
- "text-selected-list-item-text active:bg-selected-list-item-background",
245
- !$isSectionHeader && $disabled && "text-text-disabled",
246
- $clickable ? "cursor-pointer" : "cursor-default",
247
- $isSectionHeader && "bg-listview-raised-background",
248
- "flex flex-[0_0_auto] select-none items-center relative",
249
- className
250
- )}
251
- style={{
252
- gap: $gap,
253
- ...($variant !== "bare" && {
254
- paddingTop: $selected ? "4px" : "6px",
255
- paddingRight: $selected ? "8px" : "12px",
256
- paddingBottom: $selected ? "4px" : "6px",
257
- paddingLeft: $selected ? "8px" : "12px",
258
- borderRadius: $selected ? "4px" : "",
259
- marginLeft: $selected ? "4px" : "",
260
- marginRight: $selected ? "4px" : "",
261
- marginTop: $selected ? "2px" : "",
262
- marginBottom: $selected ? "2px" : "",
263
- }),
264
- ...($selected &&
265
- !$isSectionHeader && {
266
- backgroundColor:
267
- $colorScheme === "primary"
268
- ? cssVars.colors["selectedListItemBackground"]
269
- : cssVars.colors[$colorScheme],
270
- }),
271
- ...($selected &&
272
- !$isSectionHeader &&
273
- ($selectedPosition === "middle" ||
274
- $selectedPosition === "last") && {
275
- borderTopRightRadius: "0px",
276
- borderTopLeftRadius: "0px",
277
- }),
278
- ...($selected &&
279
- !$isSectionHeader &&
280
- ($selectedPosition === "middle" ||
281
- $selectedPosition === "first") && {
282
- borderBottomRightRadius: "0px",
283
- borderBottomLeftRadius: "0px",
284
- }),
285
- ...($hovered &&
286
- !$selected && {
287
- background: cssVars.colors.listViewHoverBackground,
288
- }),
289
- ...($showsActiveState && {
290
- "&:active": {
291
- backgroundColor: $selected
292
- ? $colorScheme === "secondary"
293
- ? cssVars.colors.secondaryLight
294
- : cssVars.colors.selectedListItemBackground
295
- : cssVars.colors.activeBackground,
296
- },
297
- }),
298
- ...($divider && {
299
- borderBottom: `1px solid ${cssVars.colors.dividerSubtle}`,
300
- }),
301
- ...($backgroundColor && {
302
- backgroundColor: $backgroundColor,
303
- "&:hover": {
304
- backgroundColor: $backgroundColor,
305
- },
306
- "&:active": {
307
- backgroundColor: $backgroundColor,
308
- },
309
- }),
310
- ...style,
311
- }}
312
- {...props}
313
- />
314
- );
315
- }
316
- );
317
-
318
- const ListViewDragIndicatorElement = forwardRef<
319
- HTMLDivElement,
320
- React.HTMLAttributes<HTMLDivElement> & {
321
- $relativeDropPosition?: RelativeDropPosition;
322
- $gap: number;
323
- $offsetLeft: number;
324
- $colorScheme: ListColorScheme;
325
- }
326
- >(
327
- (
328
- {
329
- className,
330
- $relativeDropPosition,
331
- $offsetLeft,
332
- $colorScheme,
333
- $gap,
334
- style,
335
- ...props
336
- },
337
- ref
338
- ) => {
339
- return (
340
- <div
341
- ref={ref}
342
- {...props}
343
- className={className}
344
- style={{
345
- zIndex: 1000,
346
- position: "absolute",
347
- borderRadius: "3px",
348
- pointerEvents: "none",
349
- ...($relativeDropPosition === "inside"
350
- ? {
351
- inset: 2,
352
- boxShadow: `0 0 0 1px ${cssVars.colors.sidebarBackground}, 0 0 0 3px ${
353
- $colorScheme === "secondary"
354
- ? cssVars.colors.secondary
355
- : cssVars.colors.dragOutline
356
- }`,
357
- }
358
- : {
359
- top:
360
- $relativeDropPosition === "above"
361
- ? -(3 + $gap / 2)
362
- : undefined,
363
- bottom:
364
- $relativeDropPosition === "below"
365
- ? -(3 + $gap / 2)
366
- : undefined,
367
- left: $offsetLeft,
368
- right: 0,
369
- height: 6,
370
- backgroundColor:
371
- $colorScheme === "secondary"
372
- ? cssVars.colors.secondary
373
- : cssVars.colors.dragOutline,
374
- border: `2px solid white`,
375
- boxShadow: "0 0 2px rgba(0,0,0,0.5)",
376
- }),
377
- ...style,
378
- }}
379
- />
380
- );
381
- }
382
- );
383
-
384
- interface ListViewClickInfo {
385
- shiftKey: boolean;
386
- altKey: boolean;
387
- metaKey: boolean;
388
- ctrlKey: boolean;
389
- }
390
-
391
- export interface ListViewRowProps<MenuItemType extends string = string> {
392
- id?: string;
393
- tabIndex?: number;
394
- role?: string;
395
- selected?: boolean;
396
- /** @default 0 */
397
- depth?: number;
398
- disabled?: boolean;
399
- // draggable?: boolean;
400
- hovered?: boolean;
401
- sortable?: boolean;
402
- gap?: number;
403
- backgroundColor?: CSSProperties["backgroundColor"];
404
- onPress?: (info: ListViewClickInfo) => void;
405
- onDoubleClick?: () => void;
406
- onHoverChange?: (isHovering: boolean) => void;
407
- children?: ReactNode;
408
- isSectionHeader?: boolean;
409
- menuItems?: MenuItem<MenuItemType>[];
410
- onSelectMenuItem?: (value: MenuItemType) => void;
411
- onContextMenu?: () => void;
412
- onMenuOpenChange?: (isOpen: boolean) => void;
413
- onKeyDown?: (event: React.KeyboardEvent) => void;
414
- style?: CSSProperties;
415
- className?: string;
416
- dragIndicatorStyle?:
417
- | CSSProperties
418
- | ((props: DragIndicatorStyleProps) => CSSProperties);
419
- testRelativeDropPosition?: RelativeDropPosition;
420
- href?: string;
421
- as?: React.ElementType;
422
- }
423
-
424
- export type DragIndicatorStyleProps = {
425
- depth: number;
426
- indentation: number;
427
- position: RelativeDropPosition;
428
- };
429
-
430
- const ListViewRow = forwardRefGeneric(function ListViewRow<
431
- MenuItemType extends string,
432
- >(
433
- {
434
- id,
435
- tabIndex = 0,
436
- role,
437
- gap,
438
- backgroundColor,
439
- selected = false,
440
- depth = 0,
441
- disabled = false,
442
- hovered = false,
443
- isSectionHeader = false,
444
- sortable: overrideSortable,
445
- onPress,
446
- onDoubleClick,
447
- onHoverChange,
448
- children,
449
- menuItems,
450
- onContextMenu,
451
- onSelectMenuItem,
452
- onMenuOpenChange,
453
- onKeyDown,
454
- style,
455
- dragIndicatorStyle,
456
- className,
457
- testRelativeDropPosition,
458
- href,
459
- as,
460
- }: ListViewRowProps<MenuItemType>,
461
- forwardedRef: ForwardedRef<HTMLElement>
462
- ) {
463
- depth = Math.max(0, depth);
464
-
465
- const {
466
- marginType,
467
- selectedPosition,
468
- sortable,
469
- pressEventName,
470
- variant,
471
- sectionHeaderVariant,
472
- divider,
473
- gap: listGap,
474
- colorScheme,
475
- } = useContext(ListRowContext);
476
- const { hoverProps } = useHover({
477
- onHoverChange,
478
- });
479
- const { indentation, isDragOverlay } = useContext(ListViewDraggingContext);
480
-
481
- const handlePress = useCallback(
482
- (event: React.MouseEvent) => {
483
- // We use preventDefault as a hack to mark this event as handled. We check for
484
- // this in the ListView.Root. We can't stopPropagation here or existing ContextMenus
485
- // won't close (onPointerDownOutside won't fire).
486
- // event.preventDefault();
487
-
488
- // We only want to handle left clicks
489
- if (event.button !== 0) return;
490
-
491
- onPress?.(event);
492
- },
493
- [onPress]
494
- );
495
-
496
- const handleDoubleClick = useCallback(
497
- (event: React.MouseEvent) => {
498
- event.stopPropagation();
499
-
500
- onDoubleClick?.();
501
- },
502
- [onDoubleClick]
503
- );
504
-
505
- const mergedStyle = useMemo(() => {
506
- return isDragOverlay
507
- ? {
508
- // TODO: Where do these offsets actually come from?
509
- transform: `translate3d(-36px, -6px, 0)`,
510
- width: `calc(100% + 48px)`,
511
- ...style,
512
- }
513
- : style;
514
- }, [isDragOverlay, style]);
515
-
516
- const renderContent = (
517
- {
518
- relativeDropPosition: relativeDropPositionProp,
519
- ...renderProps
520
- }: React.ComponentProps<typeof RowContainer> & {
521
- relativeDropPosition?: RelativeDropPosition;
522
- },
523
- ref: Ref<HTMLElement>
524
- ) => {
525
- const Component = RowContainer;
526
- const relativeDropPosition =
527
- testRelativeDropPosition ?? relativeDropPositionProp;
528
-
529
- const element = (
530
- <Component
531
- as={as}
532
- href={href}
533
- ref={ref as any}
534
- $colorScheme={colorScheme}
535
- onContextMenu={onContextMenu}
536
- $isSectionHeader={isSectionHeader}
537
- id={id}
538
- role={role}
539
- $gap={gap ?? 0}
540
- $backgroundColor={backgroundColor}
541
- {...hoverProps}
542
- onDoubleClick={handleDoubleClick}
543
- $marginType={marginType ?? "none"}
544
- $disabled={disabled}
545
- $hovered={hovered}
546
- $selected={selected}
547
- $variant={variant}
548
- $sectionHeaderVariant={sectionHeaderVariant}
549
- $selectedPosition={selectedPosition}
550
- $showsActiveState={pressEventName === "onClick"}
551
- aria-selected={selected}
552
- $divider={!isDragOverlay && divider}
553
- onKeyDown={onKeyDown}
554
- style={mergedStyle}
555
- {...(renderProps as Partial<React.ComponentProps<typeof RowContainer>>)}
556
- {...mergeEventHandlers(
557
- { onPointerDown: renderProps.onPointerDown! },
558
- { [pressEventName]: handlePress }
559
- )}
560
- tabIndex={tabIndex}
561
- className={className}
562
- $clickable={!!onPress}
563
- >
564
- {relativeDropPosition && (
565
- <ListViewDragIndicatorElement
566
- key={relativeDropPosition}
567
- $colorScheme={colorScheme}
568
- $relativeDropPosition={relativeDropPosition}
569
- $offsetLeft={depth * indentation}
570
- $gap={listGap + (divider ? 1 : 0)}
571
- style={
572
- typeof dragIndicatorStyle === "function"
573
- ? dragIndicatorStyle({
574
- depth,
575
- indentation,
576
- position: relativeDropPosition,
577
- })
578
- : dragIndicatorStyle
579
- }
580
- />
581
- )}
582
- {depth > 0 && <Spacer.Horizontal size={depth * indentation} />}
583
- {children}
584
- </Component>
585
- );
586
-
587
- if (menuItems && onSelectMenuItem) {
588
- return (
589
- <ContextMenu<MenuItemType>
590
- items={menuItems}
591
- onSelect={onSelectMenuItem}
592
- onOpenChange={onMenuOpenChange}
593
- >
594
- {element}
595
- </ContextMenu>
596
- );
597
- }
598
-
599
- return element;
600
- };
601
-
602
- if (sortable && id) {
603
- return (
604
- <Sortable.Item<HTMLElement> id={id} disabled={overrideSortable === false}>
605
- {({ ref: sortableRef, ...sortableProps }) =>
606
- renderContent(
607
- sortableProps as any,
608
- composeRefs(sortableRef, forwardedRef)
609
- )
610
- }
611
- </Sortable.Item>
612
- );
613
- }
614
-
615
- return renderContent({} as any, forwardedRef);
616
- });
617
-
618
- /* ----------------------------------------------------------------------------
619
- * VirtualizedListRow
620
- * ------------------------------------------------------------------------- */
621
-
622
- const RenderItemContext = createContext<(index: number) => ReactNode>(
623
- () => null
624
- );
625
- (RenderItemContext as any).displayName = "RenderItemContext";
626
-
627
- const VirtualizedListRow = memo(function VirtualizedListRow({
628
- index,
629
- style,
630
- }: ListChildComponentProps) {
631
- const renderItem = useContext(RenderItemContext);
632
-
633
- return (
634
- <div key={index} style={style}>
635
- {renderItem(index)}
636
- </div>
637
- );
638
- });
639
-
640
- /* ----------------------------------------------------------------------------
641
- * VirtualizedList
642
- * ------------------------------------------------------------------------- */
643
-
644
- interface VirtualizedListProps<T> {
645
- size: Size;
646
- scrollElement: HTMLDivElement;
647
- items: T[];
648
- getItemHeight: (index: number) => number;
649
- keyExtractor: (index: number) => string;
650
- renderItem: (index: number) => ReactNode;
651
- }
652
-
653
- export interface IVirtualizedList {
654
- scrollToIndex(index: number): void;
655
- }
656
-
657
- const VirtualizedListInner = forwardRefGeneric(function VirtualizedListInner<T>(
658
- {
659
- size,
660
- scrollElement,
661
- items,
662
- getItemHeight,
663
- keyExtractor,
664
- renderItem,
665
- }: VirtualizedListProps<T>,
666
- ref: ForwardedRef<IVirtualizedList>
667
- ) {
668
- const listRef = useRef<VariableSizeList<T> | null>(null);
669
-
670
- useImperativeHandle(ref, () => ({
671
- scrollToIndex(index) {
672
- listRef.current?.scrollToItem(index);
673
- },
674
- }));
675
-
676
- useLayoutEffect(() => {
677
- listRef.current?.resetAfterIndex(0);
678
- }, [
679
- // When items change, we need to re-render the virtualized list,
680
- // since it doesn't currently support row height changes
681
- items,
682
- ]);
683
-
684
- // Internally, react-virtualized updates these properties. We always want
685
- // to use our custom scroll element, so we override them. It may update
686
- // overflowX/Y individually in addition to `overflow`, so we include all 3.
687
- const listStyle = useMemo(
688
- (): CSSProperties => ({
689
- overflowX: "initial",
690
- overflowY: "initial",
691
- overflow: "initial",
692
- }),
693
- []
694
- );
695
-
696
- return (
697
- <RenderItemContext.Provider value={renderItem}>
698
- <WindowScroller
699
- scrollElement={scrollElement}
700
- style={useMemo(() => ({ flex: "1 1 auto" }), [])}
701
- >
702
- {useCallback(
703
- ({
704
- registerChild,
705
- onChildScroll,
706
- scrollTop,
707
- }: WindowScrollerChildProps & {
708
- // Added when noya-designsystem moved to a separate package.
709
- // I think this is a hack to get around the fact that react-virtualized
710
- // doesn't update on scroll unless we force it to by changing ref.
711
- // Either we're not using this the intended way or the types are wrong.
712
- registerChild: (element: any) => void;
713
- }) => (
714
- <div ref={registerChild}>
715
- <VariableSizeList<T>
716
- ref={listRef}
717
- // The list won't update on scroll unless we force it to by changing key
718
- key={scrollTop}
719
- style={listStyle}
720
- itemKey={keyExtractor}
721
- onScroll={({ scrollOffset }: { scrollOffset: number }) => {
722
- onChildScroll({ scrollTop: scrollOffset });
723
- }}
724
- initialScrollOffset={scrollTop}
725
- width={size.width}
726
- height={size.height}
727
- itemCount={items.length}
728
- itemSize={getItemHeight}
729
- estimatedItemSize={ROW_HEIGHT}
730
- >
731
- {VirtualizedListRow as any}
732
- </VariableSizeList>
733
- </div>
734
- ),
735
- [
736
- listStyle,
737
- keyExtractor,
738
- size.width,
739
- size.height,
740
- items.length,
741
- getItemHeight,
742
- ]
743
- )}
744
- </WindowScroller>
745
- </RenderItemContext.Provider>
746
- );
747
- });
748
-
749
- const VirtualizedList = memo(
750
- VirtualizedListInner
751
- ) as typeof VirtualizedListInner;
752
-
753
- /* ----------------------------------------------------------------------------
754
- * Root
755
- * ------------------------------------------------------------------------- */
756
-
757
- export type ListViewItemInfo = {
758
- isDragOverlay: boolean;
759
- activeDragIndex?: number;
760
- };
761
-
762
- type ChildrenProps = {
763
- children: ReactNode;
764
- };
765
-
766
- type RenderProps<T> = {
767
- data: T[];
768
- renderItem: (item: T, index: number, info: ListViewItemInfo) => ReactNode;
769
- keyExtractor: (item: T, index: number) => string;
770
- /**
771
- * Each item must have an `id` in order to be sortable.
772
- *
773
- * Additionally, the key extracted with `keyExtractor` must match the ListItem.Row `id`.
774
- */
775
- sortable?: boolean;
776
- virtualized?: Size;
777
- };
778
-
779
- type ListViewVariant = "normal" | "bare";
780
-
781
- type ListViewSectionHeaderVariant = "normal" | "label";
782
-
783
- export type ListViewRootProps = {
784
- id?: string;
785
- className?: string;
786
- style?: CSSProperties;
787
- role?: string;
788
- "aria-labelledby"?: string;
789
- "aria-orientation"?: "vertical" | "horizontal";
790
- "aria-multiselectable"?: boolean;
791
- onPress?: () => void;
792
- scrollable?: boolean;
793
- expandable?: boolean;
794
- onMoveItem?: MoveDragItemHandler;
795
- indentation?: number;
796
- acceptsDrop?: DropValidator;
797
- pressEventName?: PressEventName;
798
- variant?: ListViewVariant;
799
- sectionHeaderVariant?: ListViewSectionHeaderVariant;
800
- divider?: boolean;
801
- gap?: number;
802
- colorScheme?: ListColorScheme;
803
- containerRef?: React.RefObject<HTMLDivElement | null>;
804
- onDragOver?: React.DragEventHandler<HTMLDivElement>;
805
- onDragEnter?: React.DragEventHandler<HTMLDivElement>;
806
- onDragLeave?: React.DragEventHandler<HTMLDivElement>;
807
- onDrop?: React.DragEventHandler<HTMLDivElement>;
808
- renderEmptyState?: () => ReactNode;
809
- getDropTargetParentIndex?: SortableItemContextProps["getDropTargetParentIndex"];
810
- sharedDragProps?: SharedDragProps;
811
- sortableId?: string;
812
- };
813
-
814
- const ListViewRootInner = forwardRefGeneric(function ListViewRootInner<T>(
815
- {
816
- id,
817
- className,
818
- style,
819
- role,
820
- "aria-labelledby": ariaLabelledby,
821
- "aria-orientation": ariaOrientation,
822
- "aria-multiselectable": ariaMultiselectable,
823
- onPress,
824
- scrollable = false,
825
- expandable = true,
826
- sortable = false,
827
- divider = false,
828
- onMoveItem,
829
- indentation = 12,
830
- acceptsDrop,
831
- data,
832
- renderItem,
833
- keyExtractor,
834
- virtualized,
835
- variant = "normal",
836
- sectionHeaderVariant = "normal",
837
- pressEventName = "onClick",
838
- colorScheme = "primary",
839
- gap = 0,
840
- containerRef,
841
- onDragOver,
842
- onDragEnter,
843
- onDragLeave,
844
- onDrop,
845
- renderEmptyState,
846
- getDropTargetParentIndex,
847
- sharedDragProps,
848
- sortableId,
849
- }: RenderProps<T> & ListViewRootProps,
850
- forwardedRef: ForwardedRef<IVirtualizedList>
851
- ) {
852
- const handleClick = useCallback(
853
- (event: React.MouseEvent) => {
854
- if (
855
- event.target instanceof HTMLElement &&
856
- event.target.classList.contains("scroll-component")
857
- )
858
- return;
859
-
860
- // As a hack, we call preventDefault in a row if the event was handled.
861
- // If the event wasn't handled already, we call onPress here.
862
- if (!event.isDefaultPrevented()) {
863
- onPress?.();
864
- }
865
- },
866
- [onPress]
867
- );
868
-
869
- const renderChild = useCallback(
870
- (index: number) => renderItem(data[index], index, { isDragOverlay: false }),
871
- [data, renderItem]
872
- );
873
-
874
- // TODO: Do we still use section header properties anywhere?
875
- const defaultContextValue = useMemo(
876
- (): ListRowContextValue => ({
877
- colorScheme,
878
- marginType: "none",
879
- selectedPosition: "only",
880
- sortable,
881
- expandable,
882
- divider,
883
- pressEventName,
884
- variant,
885
- gap,
886
- sectionHeaderVariant,
887
- isSectionHeader: false,
888
- }),
889
- [
890
- colorScheme,
891
- sortable,
892
- expandable,
893
- divider,
894
- pressEventName,
895
- variant,
896
- gap,
897
- sectionHeaderVariant,
898
- ]
899
- );
900
-
901
- const getItemContextValue = useCallback(
902
- (i: number): ListRowContextValue | undefined => {
903
- if (variant === "bare" || variant === "normal")
904
- return defaultContextValue;
905
-
906
- const current = renderChild(i);
907
-
908
- if (!isValidElement(current)) return;
909
-
910
- const prevChild = i - 1 >= 0 && renderChild(i - 1);
911
- const nextChild = i + 1 < data.length && renderChild(i + 1);
912
-
913
- const next: ReactElement<any> | undefined = isValidElement(nextChild)
914
- ? nextChild
915
- : undefined;
916
- const prev: ReactElement<any> | undefined = isValidElement(prevChild)
917
- ? prevChild
918
- : undefined;
919
-
920
- const hasMarginTop = !prev;
921
- const hasMarginBottom =
922
- !next ||
923
- (current as any).props.isSectionHeader ||
924
- (next && (next as any).props.isSectionHeader);
925
-
926
- let marginType: ListRowMarginType;
927
-
928
- if (hasMarginTop && hasMarginBottom) {
929
- marginType = "vertical";
930
- } else if (hasMarginBottom) {
931
- marginType = "bottom";
932
- } else if (hasMarginTop) {
933
- marginType = "top";
934
- } else {
935
- marginType = "none";
936
- }
937
-
938
- let selectedPosition: ListRowPosition = "only";
939
-
940
- if ((current as any).props.selected) {
941
- const nextSelected =
942
- next && !next.props.isSectionHeader && (next as any).props.selected;
943
- const prevSelected =
944
- prev && !prev.props.isSectionHeader && (prev as any).props.selected;
945
-
946
- if (nextSelected && prevSelected) {
947
- selectedPosition = "middle";
948
- } else if (nextSelected && !prevSelected) {
949
- selectedPosition = "first";
950
- } else if (!nextSelected && prevSelected) {
951
- selectedPosition = "last";
952
- }
953
- }
954
-
955
- return {
956
- colorScheme,
957
- marginType,
958
- selectedPosition,
959
- sortable,
960
- expandable,
961
- divider,
962
- pressEventName,
963
- variant,
964
- sectionHeaderVariant,
965
- isSectionHeader: (current as any).props.isSectionHeader,
966
- gap,
967
- };
968
- },
969
- [
970
- variant,
971
- defaultContextValue,
972
- renderChild,
973
- data.length,
974
- colorScheme,
975
- sortable,
976
- expandable,
977
- divider,
978
- pressEventName,
979
- sectionHeaderVariant,
980
- gap,
981
- ]
982
- );
983
-
984
- const draggingContextOverlayValue = useMemo(
985
- (): ListViewDraggingContextValue => ({ indentation, isDragOverlay: true }),
986
- [indentation]
987
- );
988
-
989
- const renderOverlay = useCallback(
990
- (index: number) => (
991
- <div style={{ opacity: 0.25 }}>
992
- <ListViewDraggingContext.Provider value={draggingContextOverlayValue}>
993
- {renderItem(data[index], index, { isDragOverlay: true })}
994
- </ListViewDraggingContext.Provider>
995
- </div>
996
- ),
997
- [draggingContextOverlayValue, renderItem, data]
998
- );
999
-
1000
- const renderWrappedChild = useCallback(
1001
- (index: number) => {
1002
- const contextValue = getItemContextValue(index);
1003
- const current = renderChild(index);
1004
-
1005
- if (!contextValue || !isValidElement(current)) return null;
1006
-
1007
- return (
1008
- <ListRowContext.Provider key={current.key} value={contextValue}>
1009
- {current}
1010
- </ListRowContext.Provider>
1011
- );
1012
- },
1013
- [getItemContextValue, renderChild]
1014
- );
1015
-
1016
- const ids = useMemo(() => data.map(keyExtractor), [keyExtractor, data]);
1017
-
1018
- const withSortable = (children: ReactNode) =>
1019
- sortable ? (
1020
- <Sortable.Root
1021
- id={sortableId}
1022
- onMoveItem={onMoveItem}
1023
- keys={ids}
1024
- renderOverlay={renderOverlay}
1025
- acceptsDrop={acceptsDrop}
1026
- getDropTargetParentIndex={getDropTargetParentIndex}
1027
- sharedDragProps={sharedDragProps}
1028
- >
1029
- {children}
1030
- </Sortable.Root>
1031
- ) : (
1032
- children
1033
- );
1034
-
1035
- const withScrollable = (
1036
- children: (scrollElementRef: HTMLDivElement | null) => ReactNode
1037
- ) => (scrollable ? <ScrollArea>{children}</ScrollArea> : children(null));
1038
-
1039
- const getItemHeight = useCallback(
1040
- (index: number) => {
1041
- const child = getItemContextValue(index);
1042
- const height =
1043
- child?.isSectionHeader && child.sectionHeaderVariant === "label"
1044
- ? SECTION_HEADER_LABEL_HEIGHT
1045
- : ROW_HEIGHT;
1046
- return height;
1047
- },
1048
- [getItemContextValue]
1049
- );
1050
-
1051
- const getKey = useCallback(
1052
- (index: number) => keyExtractor(data[index], index),
1053
- [data, keyExtractor]
1054
- );
1055
-
1056
- const draggingContextValue = useMemo(() => ({ indentation }), [indentation]);
1057
- const gapStyle = useMemo(() => ({ gap: `${gap}px` }), [gap]);
1058
-
1059
- return (
1060
- <ListViewDraggingContext.Provider value={draggingContextValue}>
1061
- <div
1062
- id={id}
1063
- role={role}
1064
- aria-labelledby={ariaLabelledby}
1065
- aria-orientation={ariaOrientation}
1066
- aria-multiselectable={ariaMultiselectable}
1067
- className={mergeConflictingClassNames(
1068
- [
1069
- `flex flex-col text-text-muted ${scrollable ? "flex-1" : "flex-none"} `,
1070
- className,
1071
- ],
1072
- {
1073
- categories: ["flex"],
1074
- }
1075
- )}
1076
- style={{ ...gapStyle, ...style }}
1077
- {...{
1078
- [pressEventName]: handleClick,
1079
- }}
1080
- ref={containerRef}
1081
- onDragOver={onDragOver}
1082
- onDragEnter={onDragEnter}
1083
- onDragLeave={onDragLeave}
1084
- onDrop={onDrop}
1085
- >
1086
- {data.length === 0 && renderEmptyState
1087
- ? renderEmptyState()
1088
- : withScrollable((scrollElementRef: HTMLDivElement | null) =>
1089
- withSortable(
1090
- virtualized ? (
1091
- <VirtualizedList<T>
1092
- ref={forwardedRef}
1093
- scrollElement={scrollElementRef!}
1094
- items={data}
1095
- size={virtualized}
1096
- getItemHeight={getItemHeight}
1097
- keyExtractor={getKey}
1098
- renderItem={renderWrappedChild}
1099
- />
1100
- ) : (
1101
- range(0, data.length).map(renderWrappedChild)
1102
- )
1103
- )
1104
- )}
1105
- </div>
1106
- </ListViewDraggingContext.Provider>
1107
- );
1108
- });
1109
-
1110
- const ListViewRoot = memoGeneric(ListViewRootInner) as typeof ListViewRootInner;
1111
-
1112
- const ChildrenListViewInner = forwardRef(function ChildrenListViewInner(
1113
- { children, ...rest }: ChildrenProps & ListViewRootProps,
1114
- forwardedRef: ForwardedRef<IVirtualizedList>
1115
- ) {
1116
- const items: ReactElement<any>[] = useMemo(
1117
- () =>
1118
- Children.toArray(children).flatMap((child) =>
1119
- isValidElement(child) ? [child] : []
1120
- ),
1121
- [children]
1122
- );
1123
-
1124
- return (
1125
- <ListViewRoot
1126
- ref={forwardedRef}
1127
- {...rest}
1128
- data={items}
1129
- keyExtractor={useCallback(
1130
- ({ key }: { key: string | number | null }, index: number) =>
1131
- typeof key === "string" ? key : (key ?? index).toString(),
1132
- []
1133
- )}
1134
- renderItem={useCallback((item: ReactElement<any>) => item, [])}
1135
- />
1136
- );
1137
- });
1138
-
1139
- const ChildrenListView = memo(ChildrenListViewInner);
1140
-
1141
- const SimpleListViewInner = forwardRefGeneric(function SimpleListViewInner<
1142
- T = any,
1143
- >(
1144
- props: (ChildrenProps | RenderProps<T>) & ListViewRootProps,
1145
- forwardedRef: ForwardedRef<IVirtualizedList>
1146
- ) {
1147
- if ("children" in props) {
1148
- return <ChildrenListView ref={forwardedRef} {...props} />;
1149
- } else {
1150
- return <ListViewRoot ref={forwardedRef} {...props} />;
1151
- }
1152
- });
1153
-
1154
- /**
1155
- * A ListView can be created either with `children` or render props
1156
- */
1157
- const SimpleListView = memoGeneric(SimpleListViewInner);
7
+ ListViewItemInfo,
8
+ ListViewRoot,
9
+ ROW_HEIGHT,
10
+ SECTION_HEADER_LABEL_HEIGHT,
11
+ } from "./listView/ListViewRoot";
12
+ import {
13
+ ListViewClickInfo,
14
+ ListViewDragIndicatorElement,
15
+ ListViewRow,
16
+ ListViewRowProps,
17
+ } from "./listView/ListViewRow";
18
+ import { ListViewRowTitle } from "./listView/ListViewRowTitle";
19
+ import { normalizeListTargetIndex } from "./sorting/sorting";
1158
20
 
1159
21
  export namespace ListView {
1160
- export const RowTitle = memo(ListViewRowTitle);
1161
- export const EditableRowTitle = memo(ListViewEditableRowTitle);
1162
- export const Row = memoGeneric(ListViewRow);
1163
- export const Root = SimpleListView;
22
+ export const RowTitle = ListViewRowTitle;
23
+ export const EditableRowTitle = ListViewEditableRowTitle;
24
+ export const Row = ListViewRow;
25
+ export const Root = ListViewRoot;
1164
26
  export const RowContext = ListRowContext;
1165
27
  export type ClickInfo = ListViewClickInfo;
1166
28
  export type ItemInfo = ListViewItemInfo;