@kushagradhawan/kookie-ui 0.1.70 → 0.1.72
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/README.md +4 -0
- package/components.css +63 -380
- package/dist/cjs/components/_internal/base-button.d.ts.map +1 -1
- package/dist/cjs/components/_internal/base-button.js +1 -1
- package/dist/cjs/components/_internal/base-button.js.map +3 -3
- package/dist/cjs/components/_internal/shell-bottom.d.ts +2 -21
- package/dist/cjs/components/_internal/shell-bottom.d.ts.map +1 -1
- package/dist/cjs/components/_internal/shell-bottom.js +1 -1
- package/dist/cjs/components/_internal/shell-bottom.js.map +3 -3
- package/dist/cjs/components/_internal/shell-inspector.d.ts +10 -21
- package/dist/cjs/components/_internal/shell-inspector.d.ts.map +1 -1
- package/dist/cjs/components/_internal/shell-inspector.js +1 -1
- package/dist/cjs/components/_internal/shell-inspector.js.map +3 -3
- package/dist/cjs/components/_internal/shell-prop-helpers.d.ts +7 -0
- package/dist/cjs/components/_internal/shell-prop-helpers.d.ts.map +1 -0
- package/dist/cjs/components/_internal/shell-prop-helpers.js +2 -0
- package/dist/cjs/components/_internal/shell-prop-helpers.js.map +7 -0
- package/dist/cjs/components/_internal/shell-sidebar.d.ts +4 -21
- package/dist/cjs/components/_internal/shell-sidebar.d.ts.map +1 -1
- package/dist/cjs/components/_internal/shell-sidebar.js +1 -1
- package/dist/cjs/components/_internal/shell-sidebar.js.map +3 -3
- package/dist/cjs/components/button.d.ts.map +1 -1
- package/dist/cjs/components/button.js +1 -1
- package/dist/cjs/components/button.js.map +3 -3
- package/dist/cjs/components/chatbar.d.ts +11 -2
- package/dist/cjs/components/chatbar.d.ts.map +1 -1
- package/dist/cjs/components/chatbar.js +1 -1
- package/dist/cjs/components/chatbar.js.map +3 -3
- package/dist/cjs/components/icon-button.d.ts.map +1 -1
- package/dist/cjs/components/icon-button.js +2 -2
- package/dist/cjs/components/icon-button.js.map +3 -3
- package/dist/cjs/components/schemas/shell.schema.d.ts +70 -70
- package/dist/cjs/components/shell.context.d.ts +1 -0
- package/dist/cjs/components/shell.context.d.ts.map +1 -1
- package/dist/cjs/components/shell.context.js.map +2 -2
- package/dist/cjs/components/shell.d.ts +6 -26
- package/dist/cjs/components/shell.d.ts.map +1 -1
- package/dist/cjs/components/shell.hooks.d.ts +19 -2
- package/dist/cjs/components/shell.hooks.d.ts.map +1 -1
- package/dist/cjs/components/shell.hooks.js +1 -1
- package/dist/cjs/components/shell.hooks.js.map +3 -3
- package/dist/cjs/components/shell.js +1 -1
- package/dist/cjs/components/shell.js.map +3 -3
- package/dist/cjs/components/shell.types.d.ts +21 -0
- package/dist/cjs/components/shell.types.d.ts.map +1 -1
- package/dist/cjs/components/shell.types.js +1 -1
- package/dist/cjs/components/shell.types.js.map +2 -2
- package/dist/cjs/components/toggle-button.d.ts.map +1 -1
- package/dist/cjs/components/toggle-button.js +1 -1
- package/dist/cjs/components/toggle-button.js.map +3 -3
- package/dist/cjs/components/toggle-icon-button.d.ts.map +1 -1
- package/dist/cjs/components/toggle-icon-button.js +1 -1
- package/dist/cjs/components/toggle-icon-button.js.map +3 -3
- package/dist/cjs/hooks/index.d.ts +2 -0
- package/dist/cjs/hooks/index.d.ts.map +1 -1
- package/dist/cjs/hooks/index.js +1 -1
- package/dist/cjs/hooks/index.js.map +3 -3
- package/dist/cjs/hooks/use-live-announcer.d.ts.map +1 -1
- package/dist/cjs/hooks/use-live-announcer.js +2 -2
- package/dist/cjs/hooks/use-live-announcer.js.map +3 -3
- package/dist/cjs/hooks/use-toggle-state.d.ts +37 -0
- package/dist/cjs/hooks/use-toggle-state.d.ts.map +1 -0
- package/dist/cjs/hooks/use-toggle-state.js +2 -0
- package/dist/cjs/hooks/use-toggle-state.js.map +7 -0
- package/dist/cjs/hooks/use-tooltip-wrapper.d.ts +29 -0
- package/dist/cjs/hooks/use-tooltip-wrapper.d.ts.map +1 -0
- package/dist/cjs/hooks/use-tooltip-wrapper.js +2 -0
- package/dist/cjs/hooks/use-tooltip-wrapper.js.map +7 -0
- package/dist/esm/components/_internal/base-button.d.ts.map +1 -1
- package/dist/esm/components/_internal/base-button.js +1 -1
- package/dist/esm/components/_internal/base-button.js.map +3 -3
- package/dist/esm/components/_internal/shell-bottom.d.ts +2 -21
- package/dist/esm/components/_internal/shell-bottom.d.ts.map +1 -1
- package/dist/esm/components/_internal/shell-bottom.js +1 -1
- package/dist/esm/components/_internal/shell-bottom.js.map +3 -3
- package/dist/esm/components/_internal/shell-inspector.d.ts +10 -21
- package/dist/esm/components/_internal/shell-inspector.d.ts.map +1 -1
- package/dist/esm/components/_internal/shell-inspector.js +1 -1
- package/dist/esm/components/_internal/shell-inspector.js.map +3 -3
- package/dist/esm/components/_internal/shell-prop-helpers.d.ts +7 -0
- package/dist/esm/components/_internal/shell-prop-helpers.d.ts.map +1 -0
- package/dist/esm/components/_internal/shell-prop-helpers.js +2 -0
- package/dist/esm/components/_internal/shell-prop-helpers.js.map +7 -0
- package/dist/esm/components/_internal/shell-sidebar.d.ts +4 -21
- package/dist/esm/components/_internal/shell-sidebar.d.ts.map +1 -1
- package/dist/esm/components/_internal/shell-sidebar.js +1 -1
- package/dist/esm/components/_internal/shell-sidebar.js.map +3 -3
- package/dist/esm/components/button.d.ts.map +1 -1
- package/dist/esm/components/button.js +1 -1
- package/dist/esm/components/button.js.map +3 -3
- package/dist/esm/components/chatbar.d.ts +11 -2
- package/dist/esm/components/chatbar.d.ts.map +1 -1
- package/dist/esm/components/chatbar.js +1 -1
- package/dist/esm/components/chatbar.js.map +3 -3
- package/dist/esm/components/icon-button.d.ts.map +1 -1
- package/dist/esm/components/icon-button.js +2 -2
- package/dist/esm/components/icon-button.js.map +3 -3
- package/dist/esm/components/schemas/shell.schema.d.ts +70 -70
- package/dist/esm/components/shell.context.d.ts +1 -0
- package/dist/esm/components/shell.context.d.ts.map +1 -1
- package/dist/esm/components/shell.context.js.map +2 -2
- package/dist/esm/components/shell.d.ts +6 -26
- package/dist/esm/components/shell.d.ts.map +1 -1
- package/dist/esm/components/shell.hooks.d.ts +19 -2
- package/dist/esm/components/shell.hooks.d.ts.map +1 -1
- package/dist/esm/components/shell.hooks.js +1 -1
- package/dist/esm/components/shell.hooks.js.map +3 -3
- package/dist/esm/components/shell.js +1 -1
- package/dist/esm/components/shell.js.map +3 -3
- package/dist/esm/components/shell.types.d.ts +21 -0
- package/dist/esm/components/shell.types.d.ts.map +1 -1
- package/dist/esm/components/shell.types.js.map +2 -2
- package/dist/esm/components/toggle-button.d.ts.map +1 -1
- package/dist/esm/components/toggle-button.js +1 -1
- package/dist/esm/components/toggle-button.js.map +3 -3
- package/dist/esm/components/toggle-icon-button.d.ts.map +1 -1
- package/dist/esm/components/toggle-icon-button.js +1 -1
- package/dist/esm/components/toggle-icon-button.js.map +3 -3
- package/dist/esm/hooks/index.d.ts +2 -0
- package/dist/esm/hooks/index.d.ts.map +1 -1
- package/dist/esm/hooks/index.js +1 -1
- package/dist/esm/hooks/index.js.map +3 -3
- package/dist/esm/hooks/use-live-announcer.d.ts.map +1 -1
- package/dist/esm/hooks/use-live-announcer.js +2 -2
- package/dist/esm/hooks/use-live-announcer.js.map +3 -3
- package/dist/esm/hooks/use-toggle-state.d.ts +37 -0
- package/dist/esm/hooks/use-toggle-state.d.ts.map +1 -0
- package/dist/esm/hooks/use-toggle-state.js +2 -0
- package/dist/esm/hooks/use-toggle-state.js.map +7 -0
- package/dist/esm/hooks/use-tooltip-wrapper.d.ts +29 -0
- package/dist/esm/hooks/use-tooltip-wrapper.d.ts.map +1 -0
- package/dist/esm/hooks/use-tooltip-wrapper.js +2 -0
- package/dist/esm/hooks/use-tooltip-wrapper.js.map +7 -0
- package/package.json +4 -4
- package/schemas/base-button.json +1 -1
- package/schemas/button.json +1 -1
- package/schemas/icon-button.json +1 -1
- package/schemas/index.json +6 -6
- package/schemas/toggle-button.json +1 -1
- package/schemas/toggle-icon-button.json +1 -1
- package/src/components/_internal/base-button.css +136 -614
- package/src/components/_internal/base-button.tsx +15 -13
- package/src/components/_internal/shell-bottom.tsx +305 -321
- package/src/components/_internal/shell-inspector.tsx +310 -320
- package/src/components/_internal/shell-prop-helpers.ts +53 -0
- package/src/components/_internal/shell-sidebar.tsx +370 -384
- package/src/components/button.tsx +13 -42
- package/src/components/chatbar.tsx +7 -3
- package/src/components/icon-button.tsx +20 -44
- package/src/components/image.css +10 -8
- package/src/components/shell.context.tsx +1 -0
- package/src/components/shell.hooks.ts +67 -2
- package/src/components/shell.tsx +199 -209
- package/src/components/shell.types.ts +23 -0
- package/src/components/toggle-button.tsx +30 -59
- package/src/components/toggle-icon-button.tsx +29 -51
- package/src/hooks/index.ts +2 -0
- package/src/hooks/use-live-announcer.ts +34 -7
- package/src/hooks/use-toggle-state.ts +72 -0
- package/src/hooks/use-tooltip-wrapper.ts +28 -0
- package/src/styles/tokens/color.css +11 -1
- package/styles.css +70 -381
- package/tokens/base.css +7 -1
- package/tokens.css +7 -1
package/src/components/shell.tsx
CHANGED
|
@@ -29,13 +29,14 @@ import * as React from 'react';
|
|
|
29
29
|
import classNames from 'classnames';
|
|
30
30
|
import * as Sheet from './sheet.js';
|
|
31
31
|
import { VisuallyHidden } from './visually-hidden.js';
|
|
32
|
-
import { useResponsivePresentation,
|
|
32
|
+
import { useResponsivePresentation, useResponsiveInitialState } from './shell.hooks.js';
|
|
33
33
|
import { PaneResizeContext } from './_internal/shell-resize.js';
|
|
34
34
|
import { PaneHandle, PanelHandle } from './_internal/shell-handles.js';
|
|
35
|
+
import { omitPaneProps, extractPaneDomProps, mapResponsiveBooleanToPaneMode } from './_internal/shell-prop-helpers.js';
|
|
35
36
|
import { Sidebar } from './_internal/shell-sidebar.js';
|
|
36
37
|
import { Bottom } from './_internal/shell-bottom.js';
|
|
37
38
|
import { Inspector } from './_internal/shell-inspector.js';
|
|
38
|
-
import type { PresentationValue, ResponsivePresentation, PaneMode, SidebarMode, PaneSizePersistence, Breakpoint, PaneTarget, Responsive } from './shell.types.js';
|
|
39
|
+
import type { PresentationValue, ResponsivePresentation, PaneMode, SidebarMode, PaneSizePersistence, Breakpoint, PaneTarget, Responsive, PaneBaseProps } from './shell.types.js';
|
|
39
40
|
import { _BREAKPOINTS } from './shell.types.js';
|
|
40
41
|
import {
|
|
41
42
|
ShellProvider,
|
|
@@ -135,6 +136,26 @@ type PaneAction =
|
|
|
135
136
|
| { type: 'EXPAND_PANE'; target: PaneTarget }
|
|
136
137
|
| { type: 'COLLAPSE_PANE'; target: PaneTarget };
|
|
137
138
|
|
|
139
|
+
const SHELL_SLOT = Symbol('rtShellSlot');
|
|
140
|
+
|
|
141
|
+
function assignShellSlot<T extends React.ComponentType<any>>(component: T, slot: string): T {
|
|
142
|
+
(component as any)[SHELL_SLOT] = slot;
|
|
143
|
+
return component;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function isShellComponent(element: React.ReactElement, component: any): boolean {
|
|
147
|
+
if (!React.isValidElement(element)) return false;
|
|
148
|
+
const type: any = element.type;
|
|
149
|
+
if (type === component) return true;
|
|
150
|
+
const targetSlot = (component as any)?.[SHELL_SLOT];
|
|
151
|
+
return Boolean(type?.[SHELL_SLOT] && targetSlot && type[SHELL_SLOT] === targetSlot);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Tag imported slot components so isType remains stable after minification
|
|
155
|
+
assignShellSlot(Sidebar as any, 'Shell.Sidebar');
|
|
156
|
+
assignShellSlot(Inspector as any, 'Shell.Inspector');
|
|
157
|
+
assignShellSlot(Bottom as any, 'Shell.Bottom');
|
|
158
|
+
|
|
138
159
|
function paneReducer(state: PaneState, action: PaneAction): PaneState {
|
|
139
160
|
switch (action.type) {
|
|
140
161
|
case 'SET_LEFT_MODE': {
|
|
@@ -239,13 +260,17 @@ const Root = React.forwardRef<HTMLDivElement, ShellRootProps>(({ className, chil
|
|
|
239
260
|
const initialChildren = React.Children.toArray(children) as React.ReactElement[];
|
|
240
261
|
const hasPanelDefaultOpen = initialChildren.some((el) => React.isValidElement(el) && (el as any).type?.displayName === 'Shell.Panel' && Boolean((el as any).props?.defaultOpen));
|
|
241
262
|
const hasRailDefaultOpen = initialChildren.some((el) => React.isValidElement(el) && (el as any).type?.displayName === 'Shell.Rail' && Boolean((el as any).props?.defaultOpen));
|
|
263
|
+
const hasInspectorDefaultOpen = initialChildren.some((el) => React.isValidElement(el) && (el as any).type?.displayName === 'Shell.Inspector' && Boolean((el as any).props?.defaultOpen));
|
|
264
|
+
const hasInspectorOpenControlled = initialChildren.some(
|
|
265
|
+
(el) => React.isValidElement(el) && (el as any).type?.displayName === 'Shell.Inspector' && typeof (el as any).props?.open !== 'undefined' && Boolean((el as any).props?.open),
|
|
266
|
+
);
|
|
242
267
|
|
|
243
268
|
// Pane state management via reducer
|
|
244
269
|
const [paneState, dispatchPane] = React.useReducer(paneReducer, {
|
|
245
270
|
leftMode: hasPanelDefaultOpen || hasRailDefaultOpen ? 'expanded' : 'collapsed',
|
|
246
271
|
panelMode: hasPanelDefaultOpen ? 'expanded' : 'collapsed',
|
|
247
272
|
sidebarMode: 'expanded',
|
|
248
|
-
inspectorMode: 'collapsed',
|
|
273
|
+
inspectorMode: hasInspectorDefaultOpen || hasInspectorOpenControlled ? 'expanded' : 'collapsed',
|
|
249
274
|
bottomMode: 'collapsed',
|
|
250
275
|
});
|
|
251
276
|
const setLeftMode = React.useCallback((mode: PaneMode) => dispatchPane({ type: 'SET_LEFT_MODE', mode }), []);
|
|
@@ -425,17 +450,21 @@ const Root = React.forwardRef<HTMLDivElement, ShellRootProps>(({ className, chil
|
|
|
425
450
|
const peekCtxValue = React.useMemo(() => ({ peekTarget, setPeekTarget, peekPane, clearPeek }), [peekTarget, setPeekTarget, peekPane, clearPeek]);
|
|
426
451
|
const actionsCtxValue = React.useMemo(() => ({ togglePane, expandPane, collapsePane, setSidebarToggleComputer }), [togglePane, expandPane, collapsePane, setSidebarToggleComputer]);
|
|
427
452
|
|
|
453
|
+
// Memoized full context value for ShellProvider to prevent unnecessary effect re-runs
|
|
454
|
+
const shellContextValue = React.useMemo(
|
|
455
|
+
() => ({
|
|
456
|
+
...baseContextValue,
|
|
457
|
+
peekTarget,
|
|
458
|
+
setPeekTarget,
|
|
459
|
+
peekPane,
|
|
460
|
+
clearPeek,
|
|
461
|
+
}),
|
|
462
|
+
[baseContextValue, peekTarget, setPeekTarget, peekPane, clearPeek],
|
|
463
|
+
);
|
|
464
|
+
|
|
428
465
|
return (
|
|
429
466
|
<div {...props} ref={ref} className={classNames('rt-ShellRoot', className)} style={{ ...heightStyle, ...props.style }}>
|
|
430
|
-
<ShellProvider
|
|
431
|
-
value={{
|
|
432
|
-
...baseContextValue,
|
|
433
|
-
peekTarget,
|
|
434
|
-
setPeekTarget,
|
|
435
|
-
peekPane,
|
|
436
|
-
clearPeek,
|
|
437
|
-
}}
|
|
438
|
-
>
|
|
467
|
+
<ShellProvider value={shellContextValue}>
|
|
439
468
|
<PresentationContext.Provider value={presentationCtxValue}>
|
|
440
469
|
<LeftModeContext.Provider value={leftModeCtxValue}>
|
|
441
470
|
<PanelModeContext.Provider value={panelModeCtxValue}>
|
|
@@ -496,7 +525,7 @@ const Root = React.forwardRef<HTMLDivElement, ShellRootProps>(({ className, chil
|
|
|
496
525
|
</ShellProvider>
|
|
497
526
|
</div>
|
|
498
527
|
);
|
|
499
|
-
});
|
|
528
|
+
}) as PanelComponent;
|
|
500
529
|
Root.displayName = 'Shell.Root';
|
|
501
530
|
|
|
502
531
|
// Header
|
|
@@ -518,26 +547,7 @@ const Header = React.forwardRef<HTMLElement, ShellHeaderProps>(({ className, hei
|
|
|
518
547
|
Header.displayName = 'Shell.Header';
|
|
519
548
|
|
|
520
549
|
// Pane Props Interface (shared by Panel, Sidebar, Inspector, Bottom)
|
|
521
|
-
|
|
522
|
-
presentation?: ResponsivePresentation;
|
|
523
|
-
expandedSize?: number;
|
|
524
|
-
minSize?: number;
|
|
525
|
-
maxSize?: number;
|
|
526
|
-
resizable?: boolean;
|
|
527
|
-
collapsible?: boolean;
|
|
528
|
-
onExpand?: () => void;
|
|
529
|
-
onCollapse?: () => void;
|
|
530
|
-
onResize?: (size: number) => void;
|
|
531
|
-
/** Optional custom content inside the resizer handle (kept unstyled). */
|
|
532
|
-
resizer?: React.ReactNode;
|
|
533
|
-
onResizeStart?: (size: number) => void;
|
|
534
|
-
onResizeEnd?: (size: number) => void;
|
|
535
|
-
snapPoints?: number[];
|
|
536
|
-
snapTolerance?: number;
|
|
537
|
-
collapseThreshold?: number;
|
|
538
|
-
paneId?: string;
|
|
539
|
-
persistence?: PaneSizePersistence;
|
|
540
|
-
}
|
|
550
|
+
type PaneProps = PaneBaseProps;
|
|
541
551
|
|
|
542
552
|
// Left container (auto-created for Rail+Panel)
|
|
543
553
|
interface LeftProps extends React.ComponentPropsWithoutRef<'div'> {
|
|
@@ -549,6 +559,9 @@ interface LeftProps extends React.ComponentPropsWithoutRef<'div'> {
|
|
|
549
559
|
collapsible?: boolean;
|
|
550
560
|
onExpand?: () => void;
|
|
551
561
|
onCollapse?: () => void;
|
|
562
|
+
mode?: never;
|
|
563
|
+
defaultMode?: never;
|
|
564
|
+
onModeChange?: never;
|
|
552
565
|
}
|
|
553
566
|
|
|
554
567
|
// Rail (special case)
|
|
@@ -566,166 +579,120 @@ type RailProps = React.ComponentPropsWithoutRef<'div'> & {
|
|
|
566
579
|
} & (RailControlledProps | RailUncontrolledProps);
|
|
567
580
|
|
|
568
581
|
// Left container - behaves like Inspector but contains Rail+Panel
|
|
569
|
-
const
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
)
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
582
|
+
const LEFT_DOM_OMIT_PROPS = ['open', 'defaultOpen', 'onOpenChange', 'mode', 'defaultMode', 'onModeChange'] as const;
|
|
583
|
+
|
|
584
|
+
const Left = React.forwardRef<HTMLDivElement, LeftProps>((initialProps, ref) => {
|
|
585
|
+
const { className, presentation = { initial: 'fixed', sm: 'fixed' }, collapsible: _collapsible = true, onExpand, onCollapse, children, style, ...restProps } = initialProps;
|
|
586
|
+
const propsOpen = restProps.open;
|
|
587
|
+
const propsDefaultOpen = restProps.defaultOpen;
|
|
588
|
+
const propsOnOpenChange = restProps.onOpenChange;
|
|
589
|
+
const domProps = omitPaneProps(restProps, LEFT_DOM_OMIT_PROPS);
|
|
590
|
+
const shell = useShell();
|
|
591
|
+
const resolvedPresentation = useResponsivePresentation(presentation);
|
|
592
|
+
const isOverlay = resolvedPresentation === 'overlay';
|
|
593
|
+
const isStacked = resolvedPresentation === 'stacked';
|
|
594
|
+
const localRef = React.useRef<HTMLDivElement | null>(null);
|
|
595
|
+
// Publish resolved presentation so Root can gate peeking in overlay
|
|
596
|
+
React.useEffect(() => {
|
|
597
|
+
(shell as any).onLeftPres?.(resolvedPresentation);
|
|
598
|
+
}, [shell, resolvedPresentation]);
|
|
599
|
+
const setRef = React.useCallback(
|
|
600
|
+
(node: HTMLDivElement | null) => {
|
|
601
|
+
localRef.current = node;
|
|
602
|
+
if (typeof ref === 'function') ref(node);
|
|
603
|
+
else if (ref) (ref as React.MutableRefObject<HTMLDivElement | null>).current = node;
|
|
604
|
+
},
|
|
605
|
+
[ref],
|
|
606
|
+
);
|
|
594
607
|
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
608
|
+
// Register with shell
|
|
609
|
+
React.useEffect(() => {
|
|
610
|
+
shell.setHasLeft(true);
|
|
611
|
+
return () => shell.setHasLeft(false);
|
|
612
|
+
}, [shell]);
|
|
613
|
+
|
|
614
|
+
const lastLeftModeRef = React.useRef<PaneMode | null>(null);
|
|
615
|
+
const initNotifiedRef = React.useRef(false);
|
|
616
|
+
const normalizedLeftControlled = React.useMemo(() => {
|
|
617
|
+
if (typeof propsOpen === 'undefined') return undefined;
|
|
618
|
+
return propsOpen ? 'expanded' : 'collapsed';
|
|
619
|
+
}, [propsOpen]);
|
|
620
|
+
const normalizedLeftDefault = React.useMemo(() => mapResponsiveBooleanToPaneMode(propsDefaultOpen), [propsDefaultOpen]);
|
|
621
|
+
useResponsiveInitialState<PaneMode>({
|
|
622
|
+
controlledValue: normalizedLeftControlled,
|
|
623
|
+
defaultValue: normalizedLeftDefault,
|
|
624
|
+
currentValue: shell.leftMode,
|
|
625
|
+
setValue: shell.setLeftMode,
|
|
626
|
+
breakpointReady: shell.currentBreakpointReady,
|
|
627
|
+
onInit: (initial) => propsOnOpenChange?.(initial === 'expanded', { reason: 'init' }),
|
|
628
|
+
});
|
|
599
629
|
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
if (typeof propsDefaultOpen === 'undefined') return;
|
|
609
|
-
didInitFromDefaultOpenRef.current = true;
|
|
610
|
-
const initial = Boolean(resolvedDefaultOpen);
|
|
611
|
-
shell.setLeftMode(initial ? 'expanded' : 'collapsed');
|
|
612
|
-
(props as any).onOpenChange?.(initial, { reason: 'init' });
|
|
613
|
-
}, [shell, propsOpen, propsDefaultOpen, resolvedDefaultOpen, props]);
|
|
614
|
-
React.useEffect(() => {
|
|
615
|
-
// Controlled Left via Rail.open
|
|
616
|
-
if (typeof propsOpen !== 'undefined') {
|
|
617
|
-
const shouldOpen = Boolean(propsOpen);
|
|
618
|
-
shell.setLeftMode(shouldOpen ? 'expanded' : 'collapsed');
|
|
619
|
-
return;
|
|
620
|
-
}
|
|
621
|
-
// defaultOpen is applied in Rail; Left no longer follows responsive defaults
|
|
622
|
-
}, [shell, propsOpen]);
|
|
630
|
+
// Emit mode changes (uncontrolled toggles + init)
|
|
631
|
+
React.useEffect(() => {
|
|
632
|
+
if (typeof propsOpen !== 'undefined') return; // controlled, notifications only via parent changes
|
|
633
|
+
if (lastLeftModeRef.current !== null && lastLeftModeRef.current !== shell.leftMode) {
|
|
634
|
+
propsOnOpenChange?.(shell.leftMode === 'expanded', { reason: 'toggle' });
|
|
635
|
+
}
|
|
636
|
+
lastLeftModeRef.current = shell.leftMode;
|
|
637
|
+
}, [shell, propsOnOpenChange, propsOpen]);
|
|
623
638
|
|
|
624
|
-
|
|
625
|
-
|
|
639
|
+
// Emit expand/collapse events
|
|
640
|
+
React.useEffect(() => {
|
|
641
|
+
if (shell.leftMode === 'expanded') {
|
|
642
|
+
onExpand?.();
|
|
643
|
+
} else {
|
|
644
|
+
onCollapse?.();
|
|
645
|
+
}
|
|
646
|
+
}, [shell.leftMode, onExpand, onCollapse]);
|
|
626
647
|
|
|
627
|
-
|
|
628
|
-
React.useEffect(() => {
|
|
629
|
-
if (typeof (props as any).open !== 'undefined') return; // controlled, notifications only via parent changes
|
|
630
|
-
if (!initNotifiedRef.current && Boolean(resolvedDefaultOpen) && shell.leftMode === 'expanded') {
|
|
631
|
-
(props as any).onOpenChange?.(true, { reason: 'init' });
|
|
632
|
-
initNotifiedRef.current = true;
|
|
633
|
-
}
|
|
634
|
-
if (lastLeftModeRef.current !== null && lastLeftModeRef.current !== shell.leftMode) {
|
|
635
|
-
(props as any).onOpenChange?.(shell.leftMode === 'expanded', { reason: 'toggle' });
|
|
636
|
-
}
|
|
637
|
-
lastLeftModeRef.current = shell.leftMode;
|
|
638
|
-
}, [shell, resolvedDefaultOpen, props]);
|
|
648
|
+
const _isExpanded = shell.leftMode === 'expanded';
|
|
639
649
|
|
|
640
|
-
|
|
641
|
-
React.useEffect(() => {
|
|
642
|
-
if (shell.leftMode === 'expanded') {
|
|
643
|
-
onExpand?.();
|
|
644
|
-
} else {
|
|
645
|
-
onCollapse?.();
|
|
646
|
-
}
|
|
647
|
-
}, [shell.leftMode, onExpand, onCollapse]);
|
|
648
|
-
|
|
649
|
-
const _isExpanded = shell.leftMode === 'expanded';
|
|
650
|
-
|
|
651
|
-
// Left is not resizable; width derives from Rail/Panel.
|
|
652
|
-
|
|
653
|
-
if (isOverlay) {
|
|
654
|
-
const open = shell.leftMode === 'expanded';
|
|
655
|
-
// Compute overlay width from child Rail/Panel expanded sizes
|
|
656
|
-
const childArray = React.Children.toArray(children) as React.ReactElement[];
|
|
657
|
-
const isType = (el: React.ReactElement, comp: any) => React.isValidElement(el) && el.type === comp;
|
|
658
|
-
const railEl = childArray.find((el) => isType(el, Rail));
|
|
659
|
-
const panelEl = childArray.find((el) => isType(el, Panel));
|
|
660
|
-
const railSize = typeof (railEl as any)?.props?.expandedSize === 'number' ? (railEl as any).props.expandedSize : 64;
|
|
661
|
-
const panelSize = typeof (panelEl as any)?.props?.expandedSize === 'number' ? (panelEl as any).props.expandedSize : 288;
|
|
662
|
-
const hasRail = Boolean(railEl);
|
|
663
|
-
const hasPanel = Boolean(panelEl);
|
|
664
|
-
const overlayPx = (hasRail ? railSize : 0) + (shell.panelMode === 'expanded' && hasPanel ? panelSize : 0);
|
|
665
|
-
return (
|
|
666
|
-
<Sheet.Root open={open} onOpenChange={(o) => shell.setLeftMode(o ? 'expanded' : 'collapsed')}>
|
|
667
|
-
<Sheet.Content
|
|
668
|
-
side="start"
|
|
669
|
-
style={{ padding: 0 }}
|
|
670
|
-
width={{
|
|
671
|
-
initial: `${overlayPx}px`,
|
|
672
|
-
}}
|
|
673
|
-
>
|
|
674
|
-
<VisuallyHidden>
|
|
675
|
-
<Sheet.Title>Navigation</Sheet.Title>
|
|
676
|
-
</VisuallyHidden>
|
|
677
|
-
<div className="rt-ShellLeft">{children}</div>
|
|
678
|
-
</Sheet.Content>
|
|
679
|
-
</Sheet.Root>
|
|
680
|
-
);
|
|
681
|
-
}
|
|
650
|
+
// Left is not resizable; width derives from Rail/Panel.
|
|
682
651
|
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
ref={setRef}
|
|
703
|
-
className={classNames('rt-ShellLeft', className)}
|
|
704
|
-
data-mode={shell.leftMode}
|
|
705
|
-
data-peek={shell.peekTarget === 'left' || shell.peekTarget === 'rail' || shell.peekTarget === 'panel' || undefined}
|
|
706
|
-
data-presentation={resolvedPresentation}
|
|
707
|
-
style={{
|
|
708
|
-
...style,
|
|
652
|
+
if (isOverlay) {
|
|
653
|
+
const open = shell.leftMode === 'expanded';
|
|
654
|
+
// Compute overlay width from child Rail/Panel expanded sizes
|
|
655
|
+
const childArray = React.Children.toArray(children) as React.ReactElement[];
|
|
656
|
+
const isType = (el: React.ReactElement, comp: any) => React.isValidElement(el) && el.type === comp;
|
|
657
|
+
const railEl = childArray.find((el) => isType(el, Rail));
|
|
658
|
+
const panelEl = childArray.find((el) => isType(el, Panel));
|
|
659
|
+
const railSize = typeof (railEl as any)?.props?.expandedSize === 'number' ? (railEl as any).props.expandedSize : 64;
|
|
660
|
+
const panelSize = typeof (panelEl as any)?.props?.expandedSize === 'number' ? (panelEl as any).props.expandedSize : 288;
|
|
661
|
+
const hasRail = Boolean(railEl);
|
|
662
|
+
const hasPanel = Boolean(panelEl);
|
|
663
|
+
const overlayPx = (hasRail ? railSize : 0) + (shell.panelMode === 'expanded' && hasPanel ? panelSize : 0);
|
|
664
|
+
return (
|
|
665
|
+
<Sheet.Root open={open} onOpenChange={(o) => shell.setLeftMode(o ? 'expanded' : 'collapsed')}>
|
|
666
|
+
<Sheet.Content
|
|
667
|
+
side="start"
|
|
668
|
+
style={{ padding: 0 }}
|
|
669
|
+
width={{
|
|
670
|
+
initial: `${overlayPx}px`,
|
|
709
671
|
}}
|
|
710
|
-
data-open={open || undefined}
|
|
711
672
|
>
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
673
|
+
<VisuallyHidden>
|
|
674
|
+
<Sheet.Title>Navigation</Sheet.Title>
|
|
675
|
+
</VisuallyHidden>
|
|
676
|
+
<div className="rt-ShellLeft">{children}</div>
|
|
677
|
+
</Sheet.Content>
|
|
678
|
+
</Sheet.Root>
|
|
679
|
+
);
|
|
680
|
+
}
|
|
716
681
|
|
|
717
|
-
|
|
718
|
-
const
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
682
|
+
if (isStacked) {
|
|
683
|
+
const open = shell.leftMode === 'expanded';
|
|
684
|
+
// Compute floating width from child Rail/Panel expanded sizes (like overlay)
|
|
685
|
+
const childArray = React.Children.toArray(children) as React.ReactElement[];
|
|
686
|
+
const isType = (el: React.ReactElement, comp: any) => React.isValidElement(el) && el.type === comp;
|
|
687
|
+
const railEl = childArray.find((el) => isType(el, Rail));
|
|
688
|
+
const panelEl = childArray.find((el) => isType(el, Panel));
|
|
689
|
+
const _railSize = typeof (railEl as any)?.props?.expandedSize === 'number' ? (railEl as any).props.expandedSize : 64;
|
|
690
|
+
const _panelSize = typeof (panelEl as any)?.props?.expandedSize === 'number' ? (panelEl as any).props.expandedSize : 288;
|
|
691
|
+
const _hasRail = Boolean(railEl);
|
|
692
|
+
const _hasPanel = Boolean(panelEl);
|
|
693
|
+
const _includePanel = _hasPanel && (shell.panelMode === 'expanded' || shell.peekTarget === 'panel');
|
|
728
694
|
|
|
695
|
+
// Strip control props from DOM spread
|
|
729
696
|
return (
|
|
730
697
|
<div
|
|
731
698
|
{...domProps}
|
|
@@ -737,28 +704,48 @@ const Left = React.forwardRef<HTMLDivElement, LeftProps>(
|
|
|
737
704
|
style={{
|
|
738
705
|
...style,
|
|
739
706
|
}}
|
|
707
|
+
data-open={open || undefined}
|
|
740
708
|
>
|
|
741
709
|
{children}
|
|
742
710
|
</div>
|
|
743
711
|
);
|
|
744
|
-
}
|
|
745
|
-
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
// Strip control/legacy props from DOM spread
|
|
715
|
+
return (
|
|
716
|
+
<div
|
|
717
|
+
{...domProps}
|
|
718
|
+
ref={setRef}
|
|
719
|
+
className={classNames('rt-ShellLeft', className)}
|
|
720
|
+
data-mode={shell.leftMode}
|
|
721
|
+
data-peek={shell.peekTarget === 'left' || shell.peekTarget === 'rail' || shell.peekTarget === 'panel' || undefined}
|
|
722
|
+
data-presentation={resolvedPresentation}
|
|
723
|
+
style={{
|
|
724
|
+
...style,
|
|
725
|
+
}}
|
|
726
|
+
>
|
|
727
|
+
{children}
|
|
728
|
+
</div>
|
|
729
|
+
);
|
|
730
|
+
});
|
|
746
731
|
Left.displayName = 'Shell.Left';
|
|
732
|
+
assignShellSlot(Left as any, 'Shell.Left');
|
|
747
733
|
|
|
748
|
-
const Rail = React.forwardRef<HTMLDivElement, RailProps>((
|
|
734
|
+
const Rail = React.forwardRef<HTMLDivElement, RailProps>((initialProps, ref) => {
|
|
735
|
+
const { className, presentation, expandedSize = 64, collapsible, onExpand, onCollapse, children, style, open, defaultOpen, onOpenChange, ...domProps } = initialProps;
|
|
749
736
|
const shell = useShell();
|
|
750
737
|
|
|
751
738
|
// Dev guards
|
|
752
739
|
const wasControlledRef = React.useRef<boolean | null>(null);
|
|
753
740
|
if (process.env.NODE_ENV !== 'production') {
|
|
754
|
-
if (typeof
|
|
741
|
+
if (typeof open !== 'undefined' && typeof defaultOpen !== 'undefined') {
|
|
755
742
|
console.error('Shell.Rail: Do not pass both `open` and `defaultOpen`. Choose one.');
|
|
756
743
|
}
|
|
757
744
|
}
|
|
758
745
|
|
|
759
746
|
// Warn on controlled/uncontrolled mode switch
|
|
760
747
|
React.useEffect(() => {
|
|
761
|
-
const isControlled = typeof
|
|
748
|
+
const isControlled = typeof open !== 'undefined';
|
|
762
749
|
if (wasControlledRef.current === null) {
|
|
763
750
|
wasControlledRef.current = isControlled;
|
|
764
751
|
return;
|
|
@@ -767,7 +754,7 @@ const Rail = React.forwardRef<HTMLDivElement, RailProps>(({ className, presentat
|
|
|
767
754
|
console.warn('Shell.Rail: Switching between controlled and uncontrolled `open` is not supported.');
|
|
768
755
|
wasControlledRef.current = isControlled;
|
|
769
756
|
}
|
|
770
|
-
}, [
|
|
757
|
+
}, [open]);
|
|
771
758
|
|
|
772
759
|
// Register expanded size with Left container
|
|
773
760
|
React.useEffect(() => {
|
|
@@ -777,8 +764,6 @@ const Rail = React.forwardRef<HTMLDivElement, RailProps>(({ className, presentat
|
|
|
777
764
|
const isExpanded = shell.leftMode === 'expanded';
|
|
778
765
|
|
|
779
766
|
// Strip unknown open/defaultOpen props from DOM by not spreading them
|
|
780
|
-
const { defaultOpen: _defaultOpenIgnored, open: _openIgnored, onOpenChange: _onOpenChangeIgnored, ...domProps } = props as any;
|
|
781
|
-
|
|
782
767
|
return (
|
|
783
768
|
<div
|
|
784
769
|
{...domProps}
|
|
@@ -798,6 +783,7 @@ const Rail = React.forwardRef<HTMLDivElement, RailProps>(({ className, presentat
|
|
|
798
783
|
);
|
|
799
784
|
});
|
|
800
785
|
Rail.displayName = 'Shell.Rail';
|
|
786
|
+
assignShellSlot(Rail as any, 'Shell.Rail');
|
|
801
787
|
|
|
802
788
|
// Panel
|
|
803
789
|
type HandleComponent = React.ForwardRefExoticComponent<React.ComponentPropsWithoutRef<'div'> & React.RefAttributes<HTMLDivElement>>;
|
|
@@ -836,9 +822,23 @@ type _InspectorComponent = React.ForwardRefExoticComponent<PaneProps & React.Ref
|
|
|
836
822
|
|
|
837
823
|
type _BottomComponent = React.ForwardRefExoticComponent<PaneProps & React.RefAttributes<HTMLDivElement>> & { Handle: HandleComponent };
|
|
838
824
|
|
|
839
|
-
const
|
|
840
|
-
|
|
841
|
-
|
|
825
|
+
const PANEL_DOM_PROP_KEYS = [
|
|
826
|
+
'className',
|
|
827
|
+
'children',
|
|
828
|
+
'defaultOpen',
|
|
829
|
+
'open',
|
|
830
|
+
'onOpenChange',
|
|
831
|
+
'size',
|
|
832
|
+
'defaultSize',
|
|
833
|
+
'onSizeChange',
|
|
834
|
+
'sizeUpdate',
|
|
835
|
+
'sizeUpdateMs',
|
|
836
|
+
'style',
|
|
837
|
+
] as const satisfies readonly (keyof PanelPublicProps)[];
|
|
838
|
+
|
|
839
|
+
const Panel = assignShellSlot(
|
|
840
|
+
React.forwardRef<HTMLDivElement, PanelPublicProps>((initialProps, ref) => {
|
|
841
|
+
const {
|
|
842
842
|
className,
|
|
843
843
|
defaultOpen,
|
|
844
844
|
open,
|
|
@@ -865,10 +865,8 @@ const Panel = React.forwardRef<HTMLDivElement, PanelPublicProps>(
|
|
|
865
865
|
onSizeChange,
|
|
866
866
|
sizeUpdate,
|
|
867
867
|
sizeUpdateMs = 50,
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
ref,
|
|
871
|
-
) => {
|
|
868
|
+
} = initialProps;
|
|
869
|
+
const panelDomProps = extractPaneDomProps(initialProps, PANEL_DOM_PROP_KEYS);
|
|
872
870
|
// Throttled/debounced emitter for onSizeChange
|
|
873
871
|
const emitSizeChange = React.useMemo(() => {
|
|
874
872
|
if (!onSizeChange) return () => {};
|
|
@@ -1141,16 +1139,6 @@ const Panel = React.forwardRef<HTMLDivElement, PanelPublicProps>(
|
|
|
1141
1139
|
</PaneResizeContext.Provider>
|
|
1142
1140
|
) : null;
|
|
1143
1141
|
|
|
1144
|
-
// Strip control props from DOM spread
|
|
1145
|
-
const {
|
|
1146
|
-
defaultOpen: _panelDefaultOpenIgnored,
|
|
1147
|
-
open: _panelOpenIgnored,
|
|
1148
|
-
onOpenChange: _panelOnOpenChangeIgnored,
|
|
1149
|
-
size: _panelSizeIgnored,
|
|
1150
|
-
defaultSize: _panelDefaultSizeIgnored,
|
|
1151
|
-
...panelDomProps
|
|
1152
|
-
} = props as any;
|
|
1153
|
-
|
|
1154
1142
|
return (
|
|
1155
1143
|
<div
|
|
1156
1144
|
{...panelDomProps}
|
|
@@ -1170,7 +1158,8 @@ const Panel = React.forwardRef<HTMLDivElement, PanelPublicProps>(
|
|
|
1170
1158
|
{handleEl}
|
|
1171
1159
|
</div>
|
|
1172
1160
|
);
|
|
1173
|
-
},
|
|
1161
|
+
}),
|
|
1162
|
+
'Shell.Panel',
|
|
1174
1163
|
) as PanelComponent;
|
|
1175
1164
|
Panel.displayName = 'Shell.Panel';
|
|
1176
1165
|
Panel.Handle = PanelHandle;
|
|
@@ -1182,6 +1171,7 @@ interface ShellContentProps extends React.ComponentPropsWithoutRef<'main'> {}
|
|
|
1182
1171
|
|
|
1183
1172
|
const Content = React.forwardRef<HTMLElement, ShellContentProps>(({ className, ...props }, ref) => <main {...props} ref={ref} className={classNames('rt-ShellContent', className)} />);
|
|
1184
1173
|
Content.displayName = 'Shell.Content';
|
|
1174
|
+
assignShellSlot(Content as any, 'Shell.Content');
|
|
1185
1175
|
|
|
1186
1176
|
// Inspector moved to ./_internal/shell-inspector
|
|
1187
1177
|
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type * as React from 'react';
|
|
2
|
+
|
|
1
3
|
export type PresentationValue = 'fixed' | 'overlay' | 'stacked';
|
|
2
4
|
|
|
3
5
|
export type ResponsivePresentation = PresentationValue | Partial<Record<'initial' | 'xs' | 'sm' | 'md' | 'lg' | 'xl', PresentationValue>>;
|
|
@@ -16,6 +18,27 @@ export type PaneSizePersistence = {
|
|
|
16
18
|
save?: (size: number) => void | Promise<void>;
|
|
17
19
|
};
|
|
18
20
|
|
|
21
|
+
export interface PaneBaseProps extends React.ComponentPropsWithoutRef<'div'> {
|
|
22
|
+
presentation?: ResponsivePresentation;
|
|
23
|
+
expandedSize?: number;
|
|
24
|
+
minSize?: number;
|
|
25
|
+
maxSize?: number;
|
|
26
|
+
height?: string | number;
|
|
27
|
+
resizable?: boolean;
|
|
28
|
+
collapsible?: boolean;
|
|
29
|
+
onExpand?: () => void;
|
|
30
|
+
onCollapse?: () => void;
|
|
31
|
+
onResize?: (size: number) => void;
|
|
32
|
+
resizer?: React.ReactNode;
|
|
33
|
+
onResizeStart?: (size: number) => void;
|
|
34
|
+
onResizeEnd?: (size: number) => void;
|
|
35
|
+
snapPoints?: number[];
|
|
36
|
+
snapTolerance?: number;
|
|
37
|
+
collapseThreshold?: number;
|
|
38
|
+
paneId?: string;
|
|
39
|
+
persistence?: PaneSizePersistence;
|
|
40
|
+
}
|
|
41
|
+
|
|
19
42
|
export const _BREAKPOINTS = {
|
|
20
43
|
xs: '(min-width: 520px)',
|
|
21
44
|
sm: '(min-width: 768px)',
|