@noya-app/noya-designsystem 0.1.77 → 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.
- package/.turbo/turbo-build.log +20 -12
- package/CHANGELOG.md +9 -0
- package/dist/chunk-D57E6H3M.mjs +36 -0
- package/dist/chunk-D57E6H3M.mjs.map +1 -0
- package/dist/emojis.d.mts +1 -0
- package/dist/emojis.d.ts +1 -0
- package/dist/emojis.js +31 -0
- package/dist/emojis.js.map +1 -0
- package/dist/emojis.mjs +8 -0
- package/dist/emojis.mjs.map +1 -0
- package/dist/index.css +1 -1
- package/dist/index.d.mts +142 -90
- package/dist/index.d.ts +142 -90
- package/dist/index.js +3171 -1745
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +3349 -1918
- package/dist/index.mjs.map +1 -1
- package/package.json +11 -7
- package/src/__tests__/__snapshots__/fuzzyScorer.test.ts.snap +1 -1
- package/src/components/Avatar.tsx +10 -10
- package/src/components/Banner.tsx +1 -1
- package/src/components/BaseToolbar.tsx +1 -1
- package/src/components/Button.tsx +2 -1
- package/src/components/Chip.tsx +1 -1
- package/src/components/ColorSwatchControl.tsx +22 -14
- package/src/components/CommandPalette.tsx +10 -6
- package/src/components/ContextMenu.tsx +249 -38
- package/src/components/Dialog.tsx +70 -67
- package/src/components/Drawer.tsx +56 -17
- package/src/components/DropdownMenu.tsx +305 -46
- package/src/components/EditableText.tsx +1 -1
- package/src/components/EmojiPicker.tsx +645 -0
- package/src/components/GridView.tsx +5 -1
- package/src/components/IVirtualizedList.tsx +21 -1
- package/src/components/InputField.tsx +2 -10
- package/src/components/InspectorContainer.tsx +4 -2
- package/src/components/Message.tsx +5 -16
- package/src/components/Popover.tsx +73 -107
- package/src/components/Progress.tsx +18 -18
- package/src/components/ScrollArea.tsx +66 -31
- package/src/components/ScrollableSidebar.tsx +1 -1
- package/src/components/SegmentedControl.tsx +166 -38
- package/src/components/SelectMenu.tsx +193 -101
- package/src/components/Slider.tsx +40 -38
- package/src/components/Switch.tsx +4 -4
- package/src/components/TextArea.tsx +1 -1
- package/src/components/Toast.tsx +99 -26
- package/src/components/Toolbar.tsx +114 -16
- package/src/components/Tooltip.tsx +18 -8
- package/src/components/Virtualized.tsx +193 -14
- package/src/components/VisuallyHidden.tsx +20 -0
- package/src/components/__tests__/Virtualized.math.test.ts +426 -1
- package/src/components/__tests__/Virtualized.test.tsx +129 -1
- package/src/components/ai-assistant/AIAssistantLayout.tsx +11 -6
- package/src/components/internal/Menu.tsx +4 -0
- package/src/components/listView/ListViewEditableRowTitle.tsx +1 -1
- package/src/components/listView/ListViewRoot.tsx +5 -1
- package/src/components/resizablePanels/Panel.tsx +94 -0
- package/src/components/resizablePanels/PanelGroup.tsx +498 -0
- package/src/components/resizablePanels/PanelGroupContext.tsx +14 -0
- package/src/components/resizablePanels/PanelResizeHandle.tsx +61 -0
- package/src/components/resizablePanels/index.ts +7 -0
- package/src/components/resizablePanels/types.ts +65 -0
- package/src/components/workspace/DrawerWorkspaceLayout.tsx +22 -7
- package/src/components/workspace/PanelWorkspaceLayout.tsx +30 -84
- package/src/components/workspace/WorkspaceLayout.tsx +28 -58
- package/src/components/workspace/types.ts +6 -4
- package/src/contexts/DialogContext.tsx +15 -24
- package/src/emojis.ts +1 -0
- package/src/hooks/useTriggerToggle.ts +95 -0
- package/src/index.css +2 -0
- package/src/index.tsx +1 -2
- package/src/theme/proseTheme.ts +2 -0
- package/src/utils/mergeProps.ts +57 -0
- package/src/utils/skinTone.ts +90 -0
- package/tsup.config.ts +1 -1
- package/src/__tests__/workspaceLayout.test.ts +0 -281
- package/src/components/ScrollArea2.tsx +0 -76
- package/src/components/internal/MenuViewport.tsx +0 -178
- package/src/components/internal/SelectItem.tsx +0 -138
- 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 {
|
|
17
|
-
|
|
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 (
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
nextScrollTop =
|
|
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
|
-
|
|
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
|
|
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
|
-
<
|
|
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
|
-
</
|
|
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={
|
|
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
|
+
}
|