@noya-app/noya-designsystem 0.1.76 → 0.1.78

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 (86) hide show
  1. package/.turbo/turbo-build.log +20 -12
  2. package/CHANGELOG.md +19 -0
  3. package/dist/chunk-D57E6H3M.mjs +36 -0
  4. package/dist/chunk-D57E6H3M.mjs.map +1 -0
  5. package/dist/emojis.d.mts +1 -0
  6. package/dist/emojis.d.ts +1 -0
  7. package/dist/emojis.js +31 -0
  8. package/dist/emojis.js.map +1 -0
  9. package/dist/emojis.mjs +8 -0
  10. package/dist/emojis.mjs.map +1 -0
  11. package/dist/index.css +1 -1
  12. package/dist/index.d.mts +192 -93
  13. package/dist/index.d.ts +192 -93
  14. package/dist/index.js +3355 -1763
  15. package/dist/index.js.map +1 -1
  16. package/dist/index.mjs +3622 -2030
  17. package/dist/index.mjs.map +1 -1
  18. package/package.json +13 -9
  19. package/src/__tests__/__snapshots__/fuzzyScorer.test.ts.snap +1 -1
  20. package/src/components/Avatar.tsx +10 -10
  21. package/src/components/Banner.tsx +1 -1
  22. package/src/components/BaseToolbar.tsx +20 -6
  23. package/src/components/Button.tsx +2 -1
  24. package/src/components/Chip.tsx +1 -1
  25. package/src/components/ColorSwatchControl.tsx +22 -14
  26. package/src/components/CommandPalette.tsx +10 -6
  27. package/src/components/ContextMenu.tsx +249 -38
  28. package/src/components/Dialog.tsx +70 -67
  29. package/src/components/Drawer.tsx +56 -17
  30. package/src/components/DropdownMenu.tsx +307 -46
  31. package/src/components/EditableText.tsx +1 -1
  32. package/src/components/EmojiPicker.tsx +645 -0
  33. package/src/components/GridBackground.tsx +40 -0
  34. package/src/components/GridView.tsx +5 -1
  35. package/src/components/IVirtualizedList.tsx +21 -1
  36. package/src/components/InputField.tsx +2 -10
  37. package/src/components/InspectorContainer.tsx +4 -2
  38. package/src/components/Message.tsx +5 -16
  39. package/src/components/OverlayToolbar.tsx +97 -0
  40. package/src/components/Popover.tsx +73 -107
  41. package/src/components/Progress.tsx +18 -18
  42. package/src/components/ScrollArea.tsx +66 -31
  43. package/src/components/ScrollableSidebar.tsx +1 -1
  44. package/src/components/SegmentedControl.tsx +166 -38
  45. package/src/components/SelectMenu.tsx +193 -101
  46. package/src/components/Slider.tsx +40 -38
  47. package/src/components/StackNavigator.tsx +1 -0
  48. package/src/components/Switch.tsx +4 -4
  49. package/src/components/TextArea.tsx +1 -1
  50. package/src/components/Toast.tsx +99 -26
  51. package/src/components/Toolbar.tsx +133 -24
  52. package/src/components/Tooltip.tsx +18 -8
  53. package/src/components/UserPointer.tsx +5 -3
  54. package/src/components/Virtualized.tsx +193 -14
  55. package/src/components/VisuallyHidden.tsx +20 -0
  56. package/src/components/__tests__/Virtualized.math.test.ts +426 -1
  57. package/src/components/__tests__/Virtualized.test.tsx +129 -1
  58. package/src/components/ai-assistant/AIAssistantLayout.tsx +11 -6
  59. package/src/components/internal/Menu.tsx +4 -0
  60. package/src/components/listView/ListViewEditableRowTitle.tsx +1 -1
  61. package/src/components/listView/ListViewRoot.tsx +5 -1
  62. package/src/components/resizablePanels/Panel.tsx +94 -0
  63. package/src/components/resizablePanels/PanelGroup.tsx +498 -0
  64. package/src/components/resizablePanels/PanelGroupContext.tsx +14 -0
  65. package/src/components/resizablePanels/PanelResizeHandle.tsx +61 -0
  66. package/src/components/resizablePanels/index.ts +7 -0
  67. package/src/components/resizablePanels/types.ts +65 -0
  68. package/src/components/workspace/DrawerWorkspaceLayout.tsx +22 -7
  69. package/src/components/workspace/PanelWorkspaceLayout.tsx +30 -84
  70. package/src/components/workspace/WorkspaceLayout.tsx +82 -76
  71. package/src/components/workspace/types.ts +6 -4
  72. package/src/contexts/DialogContext.tsx +15 -24
  73. package/src/emojis.ts +1 -0
  74. package/src/hooks/useTriggerToggle.ts +95 -0
  75. package/src/index.css +2 -0
  76. package/src/index.tsx +3 -2
  77. package/src/theme/proseTheme.ts +22 -0
  78. package/src/utils/mergeProps.ts +57 -0
  79. package/src/utils/skinTone.ts +90 -0
  80. package/tailwind.config.ts +6 -1
  81. package/tsup.config.ts +1 -1
  82. package/src/__tests__/workspaceLayout.test.ts +0 -281
  83. package/src/components/ScrollArea2.tsx +0 -76
  84. package/src/components/internal/MenuViewport.tsx +0 -178
  85. package/src/components/internal/SelectItem.tsx +0 -138
  86. package/src/components/workspace/panelStorage.ts +0 -216
@@ -13,8 +13,13 @@ import React, {
13
13
  useState,
14
14
  } from "react";
15
15
  import { flushSync } from "react-dom";
16
- import { IVirtualizedList } from "./IVirtualizedList";
17
- import { ScrollArea2 } from "./ScrollArea2";
16
+ import {
17
+ IVirtualizedList,
18
+ ScrollToIndexAlign,
19
+ ScrollToIndexOptions,
20
+ ScrollToIndexPadding,
21
+ } from "./IVirtualizedList";
22
+ import { ScrollArea } from "./ScrollArea";
18
23
 
19
24
  export type ItemMeasurement = {
20
25
  index: number;
@@ -47,18 +52,35 @@ type VisibleRange = {
47
52
  end: number;
48
53
  };
49
54
 
55
+ function resolveScrollPadding(padding?: ScrollToIndexPadding): {
56
+ top: number;
57
+ bottom: number;
58
+ } {
59
+ if (typeof padding === "number") {
60
+ return { top: padding, bottom: padding };
61
+ }
62
+ return {
63
+ top: padding?.top ?? 0,
64
+ bottom: padding?.bottom ?? 0,
65
+ };
66
+ }
67
+
50
68
  export function calculateScrollOffsetForIndex({
51
69
  measurements,
52
70
  height,
53
71
  totalHeight,
54
72
  targetIndex,
55
73
  currentScrollTop,
74
+ align = "auto",
75
+ padding,
56
76
  }: {
57
77
  measurements: ItemMeasurement[];
58
78
  height: number;
59
79
  totalHeight: number;
60
80
  targetIndex: number;
61
81
  currentScrollTop: number;
82
+ align?: ScrollToIndexAlign;
83
+ padding?: ScrollToIndexPadding;
62
84
  }): number | undefined {
63
85
  if (!measurements.length) return;
64
86
 
@@ -71,17 +93,32 @@ export function calculateScrollOffsetForIndex({
71
93
  const viewportBottom = viewportTop + height;
72
94
  const itemTop = measurement.start;
73
95
  const itemBottom = measurement.end;
96
+ const { top: paddingTop, bottom: paddingBottom } =
97
+ resolveScrollPadding(padding);
74
98
 
75
99
  let nextScrollTop = viewportTop;
76
100
 
77
- if (itemTop < viewportTop) {
78
- nextScrollTop = itemTop;
79
- } else if (itemBottom > viewportBottom) {
80
- const canAlignTop = height >= measurement.size;
81
-
82
- nextScrollTop = canAlignTop ? itemTop : itemBottom - height;
101
+ if (align === "start") {
102
+ // Always align item to top of viewport, with padding above
103
+ nextScrollTop = itemTop - paddingTop;
104
+ } else if (align === "end") {
105
+ // Always align item to bottom of viewport, with padding below
106
+ nextScrollTop = itemBottom - height + paddingBottom;
107
+ } else if (align === "center") {
108
+ // Always center item in viewport (padding not applied for center)
109
+ nextScrollTop = itemTop - (height - measurement.size) / 2;
83
110
  } else {
84
- return;
111
+ // "auto": scroll minimum distance to make item visible
112
+ if (itemTop < viewportTop) {
113
+ // Item is above viewport, scroll up to show it at top with padding
114
+ nextScrollTop = itemTop - paddingTop;
115
+ } else if (itemBottom > viewportBottom) {
116
+ // Item is below viewport, scroll down to show it at bottom with padding
117
+ nextScrollTop = itemBottom - height + paddingBottom;
118
+ } else {
119
+ // Item is already fully visible, no scroll needed
120
+ return;
121
+ }
85
122
  }
86
123
 
87
124
  const maxScrollTop = Math.max(0, totalHeight - height);
@@ -203,6 +240,78 @@ export function calculateVisibleRange({
203
240
  };
204
241
  }
205
242
 
243
+ export type StickyRowPosition = {
244
+ index: number;
245
+ stickyTop: number;
246
+ };
247
+
248
+ export function calculateStickyRows({
249
+ measurements,
250
+ scrollTop,
251
+ isStickyRow,
252
+ }: {
253
+ measurements: ItemMeasurement[];
254
+ scrollTop: number;
255
+ isStickyRow: (index: number) => boolean;
256
+ }): StickyRowPosition[] {
257
+ // Find all sticky row indices
258
+ const stickyIndices: number[] = [];
259
+ for (const measurement of measurements) {
260
+ if (isStickyRow(measurement.index)) {
261
+ stickyIndices.push(measurement.index);
262
+ }
263
+ }
264
+
265
+ if (stickyIndices.length === 0) return [];
266
+
267
+ // Find the current sticky row (the last one that has scrolled past the top)
268
+ let currentStickyIdx = -1;
269
+ for (let i = 0; i < stickyIndices.length; i++) {
270
+ const measurement = measurements[stickyIndices[i]];
271
+ if (measurement.start <= scrollTop) {
272
+ currentStickyIdx = i;
273
+ } else {
274
+ break;
275
+ }
276
+ }
277
+
278
+ if (currentStickyIdx === -1) return [];
279
+
280
+ const currentSticky = measurements[stickyIndices[currentStickyIdx]];
281
+ const result: StickyRowPosition[] = [];
282
+
283
+ // Check if there's a next sticky row approaching
284
+ const nextStickyIdx = currentStickyIdx + 1;
285
+ if (nextStickyIdx < stickyIndices.length) {
286
+ const nextSticky = measurements[stickyIndices[nextStickyIdx]];
287
+ // Calculate how much the current sticky should be pushed up
288
+ // When nextSticky.start reaches scrollTop + currentSticky.size, push starts
289
+ const pushDistance = scrollTop + currentSticky.size - nextSticky.start;
290
+
291
+ if (pushDistance > 0) {
292
+ // Current sticky is being pushed up
293
+ result.push({
294
+ index: currentSticky.index,
295
+ stickyTop: -pushDistance,
296
+ });
297
+ } else {
298
+ // Current sticky stays at top
299
+ result.push({
300
+ index: currentSticky.index,
301
+ stickyTop: 0,
302
+ });
303
+ }
304
+ } else {
305
+ // No next sticky, current stays at top
306
+ result.push({
307
+ index: currentSticky.index,
308
+ stickyTop: 0,
309
+ });
310
+ }
311
+
312
+ return result;
313
+ }
314
+
206
315
  export type VirtualizedProps = {
207
316
  height: number;
208
317
  itemCount: number;
@@ -230,13 +339,19 @@ export type VirtualizedProps = {
230
339
  */
231
340
  gap?: number;
232
341
  /**
233
- * Render content inside ScrollArea2 instead of a plain div.
342
+ * Render content inside ScrollArea instead of a plain div.
234
343
  */
235
344
  useScrollArea?: boolean;
236
345
  /**
237
346
  * Optional class applied to the ScrollArea viewport when `useScrollArea` is true.
238
347
  */
239
348
  scrollAreaViewportClassName?: string;
349
+ /**
350
+ * Callback to determine if a row should be sticky.
351
+ * Only one sticky row is shown at a time. When the next sticky row approaches,
352
+ * it pushes the current one up and out of view.
353
+ */
354
+ isStickyRow?: (index: number) => boolean;
240
355
  };
241
356
 
242
357
  const DEFAULT_OVERSCAN = 2;
@@ -260,6 +375,7 @@ const VirtualizedInner = React.forwardRef(function Virtualized(
260
375
  gap = 0,
261
376
  useScrollArea = false,
262
377
  scrollAreaViewportClassName,
378
+ isStickyRow,
263
379
  }: VirtualizedProps,
264
380
  forwardedRef: ForwardedRef<IVirtualizedList>
265
381
  ) {
@@ -312,6 +428,15 @@ const VirtualizedInner = React.forwardRef(function Virtualized(
312
428
  [height, measurements, overscan, scrollTop]
313
429
  );
314
430
 
431
+ const stickyRows = useMemo(() => {
432
+ if (!isStickyRow) return [];
433
+ return calculateStickyRows({
434
+ measurements,
435
+ scrollTop,
436
+ isStickyRow,
437
+ });
438
+ }, [measurements, scrollTop, isStickyRow]);
439
+
315
440
  const handleScroll = useCallback<UIEventHandler<HTMLDivElement>>(
316
441
  (event) => {
317
442
  const nextOffset = event.currentTarget.scrollTop;
@@ -333,7 +458,7 @@ const VirtualizedInner = React.forwardRef(function Virtualized(
333
458
  useImperativeHandle(
334
459
  forwardedRef,
335
460
  () => ({
336
- scrollToIndex(index: number) {
461
+ scrollToIndex(index: number, options?: ScrollToIndexOptions) {
337
462
  const container = containerRef.current;
338
463
  if (!container) return;
339
464
 
@@ -343,6 +468,8 @@ const VirtualizedInner = React.forwardRef(function Virtualized(
343
468
  totalHeight,
344
469
  targetIndex: index,
345
470
  currentScrollTop: container.scrollTop,
471
+ align: options?.align,
472
+ padding: options?.padding,
346
473
  });
347
474
 
348
475
  if (nextScrollTop == null) return;
@@ -408,19 +535,66 @@ const VirtualizedInner = React.forwardRef(function Virtualized(
408
535
  </div>
409
536
  );
410
537
 
538
+ const stickyOverlay =
539
+ stickyRows.length > 0 ? (
540
+ <div
541
+ style={{
542
+ position: "sticky",
543
+ top: 0,
544
+ height: 0,
545
+ overflow: "visible",
546
+ pointerEvents: "none",
547
+ zIndex: 1,
548
+ }}
549
+ >
550
+ <div
551
+ style={{
552
+ position: "absolute",
553
+ top: 0,
554
+ width: "100%",
555
+ overflow: "hidden",
556
+ }}
557
+ >
558
+ {stickyRows.map(({ index, stickyTop }) => {
559
+ const measurement = measurements[index];
560
+ const key = itemKey
561
+ ? `sticky-${itemKey(index)}`
562
+ : `sticky-${index}`;
563
+
564
+ return (
565
+ <div
566
+ key={key}
567
+ style={{
568
+ position: "relative",
569
+ top: stickyTop,
570
+ width: "100%",
571
+ height: measurement.size,
572
+ pointerEvents: "auto",
573
+ }}
574
+ >
575
+ {renderItem(index)}
576
+ </div>
577
+ );
578
+ })}
579
+ </div>
580
+ </div>
581
+ ) : null;
582
+
411
583
  if (useScrollArea) {
412
584
  return (
413
- <ScrollArea2
585
+ <ScrollArea
414
586
  className={className}
415
587
  style={scrollAreaStyle}
416
588
  viewportClassName={scrollAreaViewportClassName}
417
589
  viewportProps={{
418
590
  ref: setScrollElement,
419
591
  onScroll: handleScroll,
592
+ style: isStickyRow ? { isolation: "isolate" } : undefined,
420
593
  }}
421
594
  >
595
+ {stickyOverlay}
422
596
  {totalContent}
423
- </ScrollArea2>
597
+ </ScrollArea>
424
598
  );
425
599
  }
426
600
 
@@ -428,9 +602,14 @@ const VirtualizedInner = React.forwardRef(function Virtualized(
428
602
  <div
429
603
  ref={setScrollElement}
430
604
  className={className}
431
- style={containerStyle}
605
+ style={
606
+ isStickyRow
607
+ ? { ...containerStyle, isolation: "isolate" }
608
+ : containerStyle
609
+ }
432
610
  onScroll={handleScroll}
433
611
  >
612
+ {stickyOverlay}
434
613
  {totalContent}
435
614
  </div>
436
615
  );
@@ -0,0 +1,20 @@
1
+ "use client";
2
+
3
+ import React from "react";
4
+
5
+ const visuallyHiddenStyle: React.CSSProperties = {
6
+ border: 0,
7
+ clip: "rect(0 0 0 0)",
8
+ clipPath: "inset(50%)",
9
+ height: "1px",
10
+ margin: "-1px",
11
+ overflow: "hidden",
12
+ padding: 0,
13
+ position: "absolute",
14
+ whiteSpace: "nowrap",
15
+ width: "1px",
16
+ };
17
+
18
+ export function VisuallyHidden({ children }: { children: React.ReactNode }) {
19
+ return <span style={visuallyHiddenStyle}>{children}</span>;
20
+ }