@noya-app/noya-designsystem 0.1.82 → 0.1.83
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 +37 -38
- package/.turbo/turbo-lint.log +1 -15
- package/CHANGELOG.md +8 -0
- package/dist/index.css +1 -1
- package/dist/index.d.mts +36 -9
- package/dist/index.d.ts +36 -9
- package/dist/index.js +422 -278
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +411 -267
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
- package/src/components/SegmentedControl.tsx +227 -48
- package/src/components/resizablePanels/Panel.tsx +22 -2
- package/src/components/resizablePanels/PanelGroup.tsx +15 -8
- package/src/components/workspace/DrawerWorkspaceLayout.tsx +131 -152
- package/src/components/workspace/HorizontalTabBar.tsx +43 -0
- package/src/components/workspace/PanelWorkspaceLayout.tsx +112 -54
- package/src/components/workspace/WorkspaceLayout.tsx +19 -5
- package/src/components/workspace/types.ts +17 -5
- package/src/index.css +12 -2
- package/dist/chunk-D57E6H3M.mjs +0 -36
- package/dist/chunk-D57E6H3M.mjs.map +0 -1
- package/dist/chunk-FJ6ZGZIA.mjs +0 -43
- package/dist/chunk-FJ6ZGZIA.mjs.map +0 -1
- package/dist/chunk-QDV7OQMJ.mjs +0 -37
- package/dist/chunk-QDV7OQMJ.mjs.map +0 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@noya-app/noya-designsystem",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.83",
|
|
4
4
|
"main": "./dist/index.js",
|
|
5
5
|
"module": "./dist/index.mjs",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
"@noya-app/noya-colorpicker": "0.1.34",
|
|
33
33
|
"@noya-app/noya-utils": "0.1.9",
|
|
34
34
|
"@noya-app/noya-geometry": "0.1.17",
|
|
35
|
-
"@noya-app/noya-icons": "0.1.
|
|
35
|
+
"@noya-app/noya-icons": "0.1.19",
|
|
36
36
|
"@noya-app/noya-keymap": "0.1.4",
|
|
37
37
|
"@noya-app/noya-tailwind-config": "0.1.10",
|
|
38
38
|
"@radix-ui/react-compose-refs": "^1.0.0",
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { Toggle } from "@base-ui/react/toggle";
|
|
4
4
|
import { ToggleGroup } from "@base-ui/react/toggle-group";
|
|
5
|
-
import { memoGeneric } from "@noya-app/react-utils";
|
|
5
|
+
import { memoGeneric, useSize } from "@noya-app/react-utils";
|
|
6
6
|
import React, {
|
|
7
7
|
createContext,
|
|
8
8
|
forwardRef,
|
|
@@ -20,6 +20,16 @@ import { renderIcon } from "./Icons";
|
|
|
20
20
|
import { SelectableMenuItem } from "./internal/Menu";
|
|
21
21
|
import { Tooltip } from "./Tooltip";
|
|
22
22
|
|
|
23
|
+
// Layout constants — the Tailwind classes and px values must stay in sync.
|
|
24
|
+
const CONTAINER_PADDING_X = "n-px-0.5";
|
|
25
|
+
const CONTAINER_PADDING_X_PX = 4; // 0.5 * 4px = 2px each side = 4px total
|
|
26
|
+
const ITEM_PADDING = "n-px-input-padding-x";
|
|
27
|
+
const ITEM_PADDING_PX = 16; // n-px-input-padding-x * 2
|
|
28
|
+
// Extra width required before switching from compact back to expanded mode
|
|
29
|
+
const EXPANSION_THRESHOLD_PX = 20;
|
|
30
|
+
// Debounce delay (ms) before re-enabling indicator transition after resize stops
|
|
31
|
+
const RESIZE_SETTLE_MS = 150;
|
|
32
|
+
|
|
23
33
|
type SegmentedControlColorScheme = "primary" | "secondary";
|
|
24
34
|
type SegmentedControlVariant = "default" | "tabs" | "buttons";
|
|
25
35
|
|
|
@@ -28,6 +38,8 @@ type SegmentedControlContextValue = {
|
|
|
28
38
|
colorScheme?: SegmentedControlColorScheme;
|
|
29
39
|
onSelect?: (value: string) => void;
|
|
30
40
|
itemWidth?: SegmentedControlItemWidth;
|
|
41
|
+
/** When true, items with icons hide their titles */
|
|
42
|
+
isCompact?: boolean;
|
|
31
43
|
};
|
|
32
44
|
|
|
33
45
|
const SegmentedControlContext = createContext<SegmentedControlContextValue>({
|
|
@@ -55,6 +67,13 @@ export interface SegmentedControlProps<T extends string> {
|
|
|
55
67
|
* @default "auto"
|
|
56
68
|
*/
|
|
57
69
|
itemWidth?: SegmentedControlItemWidth;
|
|
70
|
+
/**
|
|
71
|
+
* When enabled, automatically hides item titles and shows only icons
|
|
72
|
+
* when the control doesn't fit in its parent container.
|
|
73
|
+
* Requires all items to have icons.
|
|
74
|
+
* @default false
|
|
75
|
+
*/
|
|
76
|
+
enableCompactMode?: boolean;
|
|
58
77
|
}
|
|
59
78
|
|
|
60
79
|
export type SegmentedControlItemProps<T extends string> = {
|
|
@@ -83,8 +102,17 @@ const SegmentedControlItem = forwardRef(function SegmentedControlItem<
|
|
|
83
102
|
}: SegmentedControlItemProps<T>,
|
|
84
103
|
forwardedRef: React.ForwardedRef<HTMLButtonElement>
|
|
85
104
|
) {
|
|
86
|
-
const {
|
|
87
|
-
|
|
105
|
+
const {
|
|
106
|
+
colorScheme,
|
|
107
|
+
onSelect,
|
|
108
|
+
itemWidth = "auto",
|
|
109
|
+
isCompact,
|
|
110
|
+
} = useContext(SegmentedControlContext);
|
|
111
|
+
|
|
112
|
+
// In compact mode, hide title if we have an icon
|
|
113
|
+
const showTitle = !(isCompact && icon);
|
|
114
|
+
// Use title as tooltip when hidden (if no explicit tooltip)
|
|
115
|
+
const effectiveTooltip = !showTitle && !tooltip ? title : tooltip;
|
|
88
116
|
|
|
89
117
|
const handlePointerDown = useCallback(
|
|
90
118
|
(event: React.PointerEvent) => {
|
|
@@ -133,11 +161,9 @@ const SegmentedControlItem = forwardRef(function SegmentedControlItem<
|
|
|
133
161
|
? "active:n-text-button-text-active active:n-bg-button-background-active data-[pressed]:n-text-button-text-active data-[pressed]:n-bg-button-background-active"
|
|
134
162
|
: variant === "default"
|
|
135
163
|
? // default variant: only text color, background handled by sliding indicator
|
|
136
|
-
colorScheme === "secondary"
|
|
164
|
+
colorScheme === "secondary" || colorScheme === "primary"
|
|
137
165
|
? "data-[pressed]:n-text-white"
|
|
138
|
-
:
|
|
139
|
-
? "data-[pressed]:n-text-white"
|
|
140
|
-
: "data-[pressed]:n-text-segmented-control-item-active-text"
|
|
166
|
+
: "data-[pressed]:n-text-segmented-control-item-active-text"
|
|
141
167
|
: colorScheme === "secondary"
|
|
142
168
|
? "data-[pressed]:n-bg-secondary data-[pressed]:n-text-white"
|
|
143
169
|
: colorScheme === "primary"
|
|
@@ -150,14 +176,16 @@ const SegmentedControlItem = forwardRef(function SegmentedControlItem<
|
|
|
150
176
|
// hover
|
|
151
177
|
variant === "buttons" && "hover:n-bg-button-background-hover",
|
|
152
178
|
// spacing and borders
|
|
153
|
-
|
|
179
|
+
ITEM_PADDING,
|
|
154
180
|
variant === "default"
|
|
155
181
|
? "n-rounded after:n-content-[''] after:n-absolute after:n-right-0 after:n-top-0.5 after:n-bottom-0.5 after:n-w-px after:n-bg-divider last:after:n-hidden"
|
|
156
182
|
: variant === "buttons"
|
|
157
183
|
? "n-rounded-sm n-max-w-fit"
|
|
158
184
|
: "n-border-y-2 n-border-y-transparent data-[pressed]:n-border-b-primary",
|
|
159
|
-
// icon only buttons
|
|
185
|
+
// icon only buttons (explicit or due to compact mode)
|
|
160
186
|
variant === "buttons" && title === "" && icon && "n-min-w-input-height",
|
|
187
|
+
// icon only in compact mode
|
|
188
|
+
isCompact && icon && "n-min-w-input-height",
|
|
161
189
|
className
|
|
162
190
|
)}
|
|
163
191
|
{...props}
|
|
@@ -170,54 +198,48 @@ const SegmentedControlItem = forwardRef(function SegmentedControlItem<
|
|
|
170
198
|
)}
|
|
171
199
|
>
|
|
172
200
|
{icon && renderIcon(icon)}
|
|
173
|
-
{title}
|
|
201
|
+
{showTitle && title}
|
|
174
202
|
</span>
|
|
175
203
|
</Toggle>
|
|
176
204
|
);
|
|
177
205
|
|
|
178
|
-
return
|
|
179
|
-
<Tooltip content={
|
|
206
|
+
return effectiveTooltip ? (
|
|
207
|
+
<Tooltip content={effectiveTooltip}>{itemElement}</Tooltip>
|
|
180
208
|
) : (
|
|
181
209
|
itemElement
|
|
182
210
|
);
|
|
183
211
|
});
|
|
184
212
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
// Track if we should animate (skip initial mount)
|
|
213
|
+
function useIndicatorPosition({
|
|
214
|
+
containerRef,
|
|
215
|
+
itemRefs,
|
|
216
|
+
selectedIndex,
|
|
217
|
+
variant,
|
|
218
|
+
itemCount,
|
|
219
|
+
isCompact,
|
|
220
|
+
}: {
|
|
221
|
+
containerRef: React.RefObject<HTMLDivElement | null>;
|
|
222
|
+
itemRefs: React.RefObject<(HTMLButtonElement | null)[]>;
|
|
223
|
+
selectedIndex: number;
|
|
224
|
+
variant: SegmentedControlVariant;
|
|
225
|
+
itemCount: number;
|
|
226
|
+
/** Triggers re-measurement when compact mode changes item widths */
|
|
227
|
+
isCompact: boolean;
|
|
228
|
+
}) {
|
|
202
229
|
const hasInitialized = useRef(false);
|
|
203
|
-
const
|
|
204
|
-
const
|
|
230
|
+
const isResizingRef = useRef(false);
|
|
231
|
+
const resizeTimeoutRef = useRef<ReturnType<typeof setTimeout>>(undefined);
|
|
205
232
|
|
|
206
|
-
// Indicator position based on measured item dimensions
|
|
207
233
|
const [indicatorStyle, setIndicatorStyle] = useState<{
|
|
208
234
|
left: number;
|
|
209
235
|
width: number;
|
|
210
236
|
} | null>(null);
|
|
211
237
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
if (!value) return -1;
|
|
217
|
-
return items.findIndex((item) => item.value === value);
|
|
218
|
-
}, [value, items]);
|
|
238
|
+
const measureIndicator = useCallback(() => {
|
|
239
|
+
// Reference these to trigger re-measurement when items change size
|
|
240
|
+
void itemCount;
|
|
241
|
+
void isCompact;
|
|
219
242
|
|
|
220
|
-
useLayoutEffect(() => {
|
|
221
243
|
if (variant !== "default" || selectedIndex < 0) {
|
|
222
244
|
setIndicatorStyle(null);
|
|
223
245
|
return;
|
|
@@ -239,9 +261,10 @@ export const SegmentedControl = memoGeneric(function SegmentedControl<
|
|
|
239
261
|
width: itemRect.width,
|
|
240
262
|
});
|
|
241
263
|
|
|
242
|
-
// Enable animation after first measurement
|
|
243
264
|
hasInitialized.current = true;
|
|
244
|
-
}, [selectedIndex, variant,
|
|
265
|
+
}, [containerRef, itemRefs, selectedIndex, variant, itemCount, isCompact]);
|
|
266
|
+
|
|
267
|
+
useLayoutEffect(measureIndicator, [measureIndicator]);
|
|
245
268
|
|
|
246
269
|
// Watch for container resize to recalculate indicator position
|
|
247
270
|
useLayoutEffect(() => {
|
|
@@ -249,12 +272,167 @@ export const SegmentedControl = memoGeneric(function SegmentedControl<
|
|
|
249
272
|
if (!container || variant !== "default") return;
|
|
250
273
|
|
|
251
274
|
const observer = new ResizeObserver(() => {
|
|
252
|
-
|
|
275
|
+
isResizingRef.current = true;
|
|
276
|
+
clearTimeout(resizeTimeoutRef.current);
|
|
277
|
+
resizeTimeoutRef.current = setTimeout(() => {
|
|
278
|
+
isResizingRef.current = false;
|
|
279
|
+
}, RESIZE_SETTLE_MS);
|
|
280
|
+
measureIndicator();
|
|
253
281
|
});
|
|
254
282
|
observer.observe(container);
|
|
255
283
|
|
|
256
|
-
return () =>
|
|
257
|
-
|
|
284
|
+
return () => {
|
|
285
|
+
observer.disconnect();
|
|
286
|
+
clearTimeout(resizeTimeoutRef.current);
|
|
287
|
+
};
|
|
288
|
+
}, [containerRef, variant, measureIndicator]);
|
|
289
|
+
|
|
290
|
+
return { indicatorStyle, hasInitialized, isResizingRef };
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
function useCompactMode({
|
|
294
|
+
containerRef,
|
|
295
|
+
itemRefs,
|
|
296
|
+
variant,
|
|
297
|
+
items,
|
|
298
|
+
enabled,
|
|
299
|
+
}: {
|
|
300
|
+
containerRef: React.RefObject<HTMLDivElement | null>;
|
|
301
|
+
itemRefs: React.RefObject<(HTMLButtonElement | null)[]>;
|
|
302
|
+
variant: SegmentedControlVariant;
|
|
303
|
+
items: SegmentedControlItemProps<string>[];
|
|
304
|
+
enabled: boolean;
|
|
305
|
+
}) {
|
|
306
|
+
const containerSize = useSize(containerRef, "width");
|
|
307
|
+
const expandedWidthRef = useRef<number | null>(null);
|
|
308
|
+
const isCompactRef = useRef(false);
|
|
309
|
+
const [isCompact, setIsCompact] = useState(false);
|
|
310
|
+
|
|
311
|
+
const allItemsHaveIcons = useMemo(
|
|
312
|
+
() => items.every((item) => item.icon),
|
|
313
|
+
[items]
|
|
314
|
+
);
|
|
315
|
+
|
|
316
|
+
const itemsKey = useMemo(
|
|
317
|
+
() =>
|
|
318
|
+
items.map((item) => `${item.value}-${item.icon}-${item.title}`).join("|"),
|
|
319
|
+
[items]
|
|
320
|
+
);
|
|
321
|
+
|
|
322
|
+
// Reset expanded width measurement when items change
|
|
323
|
+
useLayoutEffect(() => {
|
|
324
|
+
expandedWidthRef.current = null;
|
|
325
|
+
isCompactRef.current = false;
|
|
326
|
+
setIsCompact(false);
|
|
327
|
+
}, [itemsKey]);
|
|
328
|
+
|
|
329
|
+
// Calculate compact mode based on whether items fit in parent
|
|
330
|
+
useLayoutEffect(() => {
|
|
331
|
+
if (!enabled) return;
|
|
332
|
+
|
|
333
|
+
const container = containerRef.current;
|
|
334
|
+
if (!container || variant !== "default" || !allItemsHaveIcons) {
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
const validItems = itemRefs.current.filter(Boolean);
|
|
339
|
+
if (validItems.length !== items.length) return;
|
|
340
|
+
|
|
341
|
+
const availableWidth =
|
|
342
|
+
(containerSize?.width ?? container.clientWidth) - CONTAINER_PADDING_X_PX;
|
|
343
|
+
|
|
344
|
+
// Use ref to check current compact state to avoid re-triggering on state change
|
|
345
|
+
if (!isCompactRef.current) {
|
|
346
|
+
// Check if any item's content is being truncated
|
|
347
|
+
// Compare inner span width (content) to button clientWidth (available space)
|
|
348
|
+
// Note: Grid with minmax(min-content, 1fr) prevents container overflow,
|
|
349
|
+
// so we must check individual items for truncation instead
|
|
350
|
+
const isOverflowing = validItems.some((item) => {
|
|
351
|
+
const span = item!.querySelector("span");
|
|
352
|
+
if (!span) return false;
|
|
353
|
+
const contentWidth = span.scrollWidth;
|
|
354
|
+
const availableInButton = item!.clientWidth - ITEM_PADDING_PX;
|
|
355
|
+
return contentWidth >= availableInButton - 1; // -1 for rounding tolerance
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
if (isOverflowing) {
|
|
359
|
+
// Store the width needed for expanded mode (for hysteresis calculation)
|
|
360
|
+
const totalItemsWidth = validItems.reduce((sum, item) => {
|
|
361
|
+
const span = item!.querySelector("span");
|
|
362
|
+
const contentWidth = span ? span.scrollWidth : 0;
|
|
363
|
+
return sum + contentWidth + ITEM_PADDING_PX;
|
|
364
|
+
}, 0);
|
|
365
|
+
expandedWidthRef.current = totalItemsWidth;
|
|
366
|
+
isCompactRef.current = true;
|
|
367
|
+
setIsCompact(true);
|
|
368
|
+
}
|
|
369
|
+
} else {
|
|
370
|
+
// Currently compact - check if we can go back to expanded
|
|
371
|
+
const expandedWidth = expandedWidthRef.current;
|
|
372
|
+
if (!expandedWidth) return;
|
|
373
|
+
|
|
374
|
+
if (availableWidth >= expandedWidth + EXPANSION_THRESHOLD_PX) {
|
|
375
|
+
isCompactRef.current = false;
|
|
376
|
+
expandedWidthRef.current = null; // Reset to re-measure next time
|
|
377
|
+
setIsCompact(false);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
}, [
|
|
381
|
+
enabled,
|
|
382
|
+
containerRef,
|
|
383
|
+
itemRefs,
|
|
384
|
+
variant,
|
|
385
|
+
allItemsHaveIcons,
|
|
386
|
+
containerSize?.width,
|
|
387
|
+
itemsKey,
|
|
388
|
+
items.length,
|
|
389
|
+
]);
|
|
390
|
+
|
|
391
|
+
return isCompact;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
export const SegmentedControl = memoGeneric(function SegmentedControl<
|
|
395
|
+
T extends string,
|
|
396
|
+
>({
|
|
397
|
+
id: idProp,
|
|
398
|
+
value,
|
|
399
|
+
onValueChange,
|
|
400
|
+
colorScheme,
|
|
401
|
+
allowEmpty,
|
|
402
|
+
items,
|
|
403
|
+
variant = "default",
|
|
404
|
+
className,
|
|
405
|
+
style,
|
|
406
|
+
itemWidth = "auto",
|
|
407
|
+
enableCompactMode = false,
|
|
408
|
+
}: SegmentedControlProps<T>) {
|
|
409
|
+
const { fieldId: id } = useLabel({ fieldId: idProp });
|
|
410
|
+
|
|
411
|
+
const containerRef = useRef<HTMLDivElement>(null);
|
|
412
|
+
const itemRefs = useRef<(HTMLButtonElement | null)[]>([]);
|
|
413
|
+
|
|
414
|
+
const selectedIndex = useMemo(() => {
|
|
415
|
+
if (!value) return -1;
|
|
416
|
+
return items.findIndex((item) => item.value === value);
|
|
417
|
+
}, [value, items]);
|
|
418
|
+
|
|
419
|
+
const isCompact = useCompactMode({
|
|
420
|
+
containerRef,
|
|
421
|
+
itemRefs,
|
|
422
|
+
variant,
|
|
423
|
+
items,
|
|
424
|
+
enabled: enableCompactMode,
|
|
425
|
+
});
|
|
426
|
+
|
|
427
|
+
const { indicatorStyle, hasInitialized, isResizingRef } =
|
|
428
|
+
useIndicatorPosition({
|
|
429
|
+
containerRef,
|
|
430
|
+
itemRefs,
|
|
431
|
+
selectedIndex,
|
|
432
|
+
variant,
|
|
433
|
+
itemCount: items.length,
|
|
434
|
+
isCompact,
|
|
435
|
+
});
|
|
258
436
|
|
|
259
437
|
const handleSelect = useCallback(
|
|
260
438
|
(newValue: string) => {
|
|
@@ -270,8 +448,8 @@ export const SegmentedControl = memoGeneric(function SegmentedControl<
|
|
|
270
448
|
);
|
|
271
449
|
|
|
272
450
|
const contextValue = useMemo(
|
|
273
|
-
() => ({ colorScheme, onSelect: handleSelect, itemWidth }),
|
|
274
|
-
[colorScheme, handleSelect, itemWidth]
|
|
451
|
+
() => ({ colorScheme, onSelect: handleSelect, itemWidth, isCompact }),
|
|
452
|
+
[colorScheme, handleSelect, itemWidth, isCompact]
|
|
275
453
|
);
|
|
276
454
|
|
|
277
455
|
const indicatorBgClass =
|
|
@@ -293,7 +471,7 @@ export const SegmentedControl = memoGeneric(function SegmentedControl<
|
|
|
293
471
|
variant === "default" &&
|
|
294
472
|
(itemWidth === "fit" ? "n-flex n-w-fit" : "n-grid"),
|
|
295
473
|
variant === "default" &&
|
|
296
|
-
|
|
474
|
+
`n-min-h-input-height n-bg-input-background n-rounded n-py-0.5 ${CONTAINER_PADDING_X}`,
|
|
297
475
|
variant !== "default" && "n-flex",
|
|
298
476
|
variant === "tabs" && "n-gap-1.5 n-min-h-[33px]",
|
|
299
477
|
variant === "buttons" && "n-gap-1.5 n-min-h-input-height",
|
|
@@ -317,6 +495,7 @@ export const SegmentedControl = memoGeneric(function SegmentedControl<
|
|
|
317
495
|
"n-absolute n-top-0.5 n-bottom-0.5 n-rounded n-shadow-segment n-pointer-events-none",
|
|
318
496
|
indicatorBgClass,
|
|
319
497
|
hasInitialized.current &&
|
|
498
|
+
!isResizingRef.current &&
|
|
320
499
|
"n-transition-[left,width] n-duration-200 n-ease-out"
|
|
321
500
|
)}
|
|
322
501
|
style={{
|
|
@@ -25,7 +25,17 @@ interface PanelProps {
|
|
|
25
25
|
|
|
26
26
|
export const Panel = forwardRef<ImperativePanelHandle, PanelProps>(
|
|
27
27
|
function Panel(
|
|
28
|
-
{
|
|
28
|
+
{
|
|
29
|
+
id: propId,
|
|
30
|
+
order,
|
|
31
|
+
className,
|
|
32
|
+
defaultSize,
|
|
33
|
+
minSize,
|
|
34
|
+
maxSize,
|
|
35
|
+
collapsible,
|
|
36
|
+
defaultCollapsed,
|
|
37
|
+
children,
|
|
38
|
+
},
|
|
29
39
|
ref
|
|
30
40
|
) {
|
|
31
41
|
const generatedId = useId();
|
|
@@ -56,7 +66,17 @@ export const Panel = forwardRef<ImperativePanelHandle, PanelProps>(
|
|
|
56
66
|
return () => {
|
|
57
67
|
unregisterPanel(id);
|
|
58
68
|
};
|
|
59
|
-
}, [
|
|
69
|
+
}, [
|
|
70
|
+
id,
|
|
71
|
+
order,
|
|
72
|
+
defaultSize,
|
|
73
|
+
minSize,
|
|
74
|
+
maxSize,
|
|
75
|
+
collapsible,
|
|
76
|
+
defaultCollapsed,
|
|
77
|
+
registerPanel,
|
|
78
|
+
unregisterPanel,
|
|
79
|
+
]);
|
|
60
80
|
|
|
61
81
|
const size = getPanelSize(id);
|
|
62
82
|
const isFlexPanel = defaultSize === undefined;
|
|
@@ -144,17 +144,24 @@ export const PanelGroup = forwardRef<ImperativePanelGroupHandle, PanelGroupProps
|
|
|
144
144
|
const registerPanel = useCallback(
|
|
145
145
|
(panel: PanelRegistration) => {
|
|
146
146
|
setPanels((prev) => {
|
|
147
|
+
// Check saved sizes first, then use defaultCollapsed
|
|
147
148
|
const savedSizes = loadSavedSizes(autoSaveId);
|
|
148
149
|
const savedSize = savedSizes?.[panel.id];
|
|
149
|
-
// Use saved size if available, otherwise check defaultCollapsed, then use defaultSize
|
|
150
150
|
const hasSavedSize = savedSize !== undefined;
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
151
|
+
|
|
152
|
+
let collapsed: boolean;
|
|
153
|
+
if (hasSavedSize) {
|
|
154
|
+
// Use saved state
|
|
155
|
+
collapsed = savedSize === 0 && !!panel.collapsible;
|
|
156
|
+
} else {
|
|
157
|
+
// Use defaultCollapsed
|
|
158
|
+
collapsed = !!panel.defaultCollapsed && !!panel.collapsible;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const initialSize = collapsed
|
|
162
|
+
? 0
|
|
163
|
+
: hasSavedSize
|
|
164
|
+
? savedSize
|
|
158
165
|
: panel.defaultSize ?? 0;
|
|
159
166
|
|
|
160
167
|
const newPanels = new Map(prev);
|