@kushagradhawan/kookie-ui 0.1.69 → 0.1.71

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 (87) hide show
  1. package/dist/cjs/components/_internal/shell-bottom.d.ts +2 -21
  2. package/dist/cjs/components/_internal/shell-bottom.d.ts.map +1 -1
  3. package/dist/cjs/components/_internal/shell-bottom.js +1 -1
  4. package/dist/cjs/components/_internal/shell-bottom.js.map +3 -3
  5. package/dist/cjs/components/_internal/shell-inspector.d.ts +10 -21
  6. package/dist/cjs/components/_internal/shell-inspector.d.ts.map +1 -1
  7. package/dist/cjs/components/_internal/shell-inspector.js +1 -1
  8. package/dist/cjs/components/_internal/shell-inspector.js.map +3 -3
  9. package/dist/cjs/components/_internal/shell-prop-helpers.d.ts +7 -0
  10. package/dist/cjs/components/_internal/shell-prop-helpers.d.ts.map +1 -0
  11. package/dist/cjs/components/_internal/shell-prop-helpers.js +2 -0
  12. package/dist/cjs/components/_internal/shell-prop-helpers.js.map +7 -0
  13. package/dist/cjs/components/_internal/shell-sidebar.d.ts +4 -21
  14. package/dist/cjs/components/_internal/shell-sidebar.d.ts.map +1 -1
  15. package/dist/cjs/components/_internal/shell-sidebar.js +1 -1
  16. package/dist/cjs/components/_internal/shell-sidebar.js.map +3 -3
  17. package/dist/cjs/components/chatbar.d.ts +12 -3
  18. package/dist/cjs/components/chatbar.d.ts.map +1 -1
  19. package/dist/cjs/components/chatbar.js +1 -1
  20. package/dist/cjs/components/chatbar.js.map +3 -3
  21. package/dist/cjs/components/schemas/shell.schema.d.ts +70 -70
  22. package/dist/cjs/components/shell.context.d.ts +1 -0
  23. package/dist/cjs/components/shell.context.d.ts.map +1 -1
  24. package/dist/cjs/components/shell.context.js.map +2 -2
  25. package/dist/cjs/components/shell.d.ts +6 -26
  26. package/dist/cjs/components/shell.d.ts.map +1 -1
  27. package/dist/cjs/components/shell.hooks.d.ts +19 -2
  28. package/dist/cjs/components/shell.hooks.d.ts.map +1 -1
  29. package/dist/cjs/components/shell.hooks.js +1 -1
  30. package/dist/cjs/components/shell.hooks.js.map +3 -3
  31. package/dist/cjs/components/shell.js +1 -1
  32. package/dist/cjs/components/shell.js.map +3 -3
  33. package/dist/cjs/components/shell.types.d.ts +21 -0
  34. package/dist/cjs/components/shell.types.d.ts.map +1 -1
  35. package/dist/cjs/components/shell.types.js +1 -1
  36. package/dist/cjs/components/shell.types.js.map +2 -2
  37. package/dist/esm/components/_internal/shell-bottom.d.ts +2 -21
  38. package/dist/esm/components/_internal/shell-bottom.d.ts.map +1 -1
  39. package/dist/esm/components/_internal/shell-bottom.js +1 -1
  40. package/dist/esm/components/_internal/shell-bottom.js.map +3 -3
  41. package/dist/esm/components/_internal/shell-inspector.d.ts +10 -21
  42. package/dist/esm/components/_internal/shell-inspector.d.ts.map +1 -1
  43. package/dist/esm/components/_internal/shell-inspector.js +1 -1
  44. package/dist/esm/components/_internal/shell-inspector.js.map +3 -3
  45. package/dist/esm/components/_internal/shell-prop-helpers.d.ts +7 -0
  46. package/dist/esm/components/_internal/shell-prop-helpers.d.ts.map +1 -0
  47. package/dist/esm/components/_internal/shell-prop-helpers.js +2 -0
  48. package/dist/esm/components/_internal/shell-prop-helpers.js.map +7 -0
  49. package/dist/esm/components/_internal/shell-sidebar.d.ts +4 -21
  50. package/dist/esm/components/_internal/shell-sidebar.d.ts.map +1 -1
  51. package/dist/esm/components/_internal/shell-sidebar.js +1 -1
  52. package/dist/esm/components/_internal/shell-sidebar.js.map +3 -3
  53. package/dist/esm/components/chatbar.d.ts +12 -3
  54. package/dist/esm/components/chatbar.d.ts.map +1 -1
  55. package/dist/esm/components/chatbar.js +1 -1
  56. package/dist/esm/components/chatbar.js.map +3 -3
  57. package/dist/esm/components/schemas/shell.schema.d.ts +70 -70
  58. package/dist/esm/components/shell.context.d.ts +1 -0
  59. package/dist/esm/components/shell.context.d.ts.map +1 -1
  60. package/dist/esm/components/shell.context.js.map +2 -2
  61. package/dist/esm/components/shell.d.ts +6 -26
  62. package/dist/esm/components/shell.d.ts.map +1 -1
  63. package/dist/esm/components/shell.hooks.d.ts +19 -2
  64. package/dist/esm/components/shell.hooks.d.ts.map +1 -1
  65. package/dist/esm/components/shell.hooks.js +1 -1
  66. package/dist/esm/components/shell.hooks.js.map +3 -3
  67. package/dist/esm/components/shell.js +1 -1
  68. package/dist/esm/components/shell.js.map +3 -3
  69. package/dist/esm/components/shell.types.d.ts +21 -0
  70. package/dist/esm/components/shell.types.d.ts.map +1 -1
  71. package/dist/esm/components/shell.types.js.map +2 -2
  72. package/package.json +1 -1
  73. package/schemas/base-button.json +1 -1
  74. package/schemas/button.json +1 -1
  75. package/schemas/icon-button.json +1 -1
  76. package/schemas/index.json +6 -6
  77. package/schemas/toggle-button.json +1 -1
  78. package/schemas/toggle-icon-button.json +1 -1
  79. package/src/components/_internal/shell-bottom.tsx +305 -321
  80. package/src/components/_internal/shell-inspector.tsx +310 -320
  81. package/src/components/_internal/shell-prop-helpers.ts +53 -0
  82. package/src/components/_internal/shell-sidebar.tsx +370 -384
  83. package/src/components/chatbar.tsx +22 -6
  84. package/src/components/shell.context.tsx +1 -0
  85. package/src/components/shell.hooks.ts +67 -2
  86. package/src/components/shell.tsx +186 -200
  87. package/src/components/shell.types.ts +23 -0
@@ -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, useResponsiveValue } from './shell.hooks.js';
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 }), []);
@@ -496,7 +521,7 @@ const Root = React.forwardRef<HTMLDivElement, ShellRootProps>(({ className, chil
496
521
  </ShellProvider>
497
522
  </div>
498
523
  );
499
- });
524
+ }) as PanelComponent;
500
525
  Root.displayName = 'Shell.Root';
501
526
 
502
527
  // Header
@@ -518,26 +543,7 @@ const Header = React.forwardRef<HTMLElement, ShellHeaderProps>(({ className, hei
518
543
  Header.displayName = 'Shell.Header';
519
544
 
520
545
  // Pane Props Interface (shared by Panel, Sidebar, Inspector, Bottom)
521
- interface PaneProps extends React.ComponentPropsWithoutRef<'div'> {
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
- }
546
+ type PaneProps = PaneBaseProps;
541
547
 
542
548
  // Left container (auto-created for Rail+Panel)
543
549
  interface LeftProps extends React.ComponentPropsWithoutRef<'div'> {
@@ -549,6 +555,9 @@ interface LeftProps extends React.ComponentPropsWithoutRef<'div'> {
549
555
  collapsible?: boolean;
550
556
  onExpand?: () => void;
551
557
  onCollapse?: () => void;
558
+ mode?: never;
559
+ defaultMode?: never;
560
+ onModeChange?: never;
552
561
  }
553
562
 
554
563
  // Rail (special case)
@@ -566,166 +575,120 @@ type RailProps = React.ComponentPropsWithoutRef<'div'> & {
566
575
  } & (RailControlledProps | RailUncontrolledProps);
567
576
 
568
577
  // Left container - behaves like Inspector but contains Rail+Panel
569
- const Left = React.forwardRef<HTMLDivElement, LeftProps>(
570
- ({ className, presentation = { initial: 'fixed', sm: 'fixed' }, collapsible: _collapsible = true, onExpand, onCollapse, children, style, ...props }, ref) => {
571
- const shell = useShell();
572
- const resolvedPresentation = useResponsivePresentation(presentation);
573
- const isOverlay = resolvedPresentation === 'overlay';
574
- const isStacked = resolvedPresentation === 'stacked';
575
- const localRef = React.useRef<HTMLDivElement | null>(null);
576
- // Publish resolved presentation so Root can gate peeking in overlay
577
- React.useEffect(() => {
578
- (shell as any).onLeftPres?.(resolvedPresentation);
579
- }, [shell, resolvedPresentation]);
580
- const setRef = React.useCallback(
581
- (node: HTMLDivElement | null) => {
582
- localRef.current = node;
583
- if (typeof ref === 'function') ref(node);
584
- else if (ref) (ref as React.MutableRefObject<HTMLDivElement | null>).current = node;
585
- },
586
- [ref],
587
- );
588
-
589
- // Register with shell
590
- React.useEffect(() => {
591
- shell.setHasLeft(true);
592
- return () => shell.setHasLeft(false);
593
- }, [shell]);
578
+ const LEFT_DOM_OMIT_PROPS = ['open', 'defaultOpen', 'onOpenChange', 'mode', 'defaultMode', 'onModeChange'] as const;
579
+
580
+ const Left = React.forwardRef<HTMLDivElement, LeftProps>((initialProps, ref) => {
581
+ const { className, presentation = { initial: 'fixed', sm: 'fixed' }, collapsible: _collapsible = true, onExpand, onCollapse, children, style, ...restProps } = initialProps;
582
+ const propsOpen = restProps.open;
583
+ const propsDefaultOpen = restProps.defaultOpen;
584
+ const propsOnOpenChange = restProps.onOpenChange;
585
+ const domProps = omitPaneProps(restProps, LEFT_DOM_OMIT_PROPS);
586
+ const shell = useShell();
587
+ const resolvedPresentation = useResponsivePresentation(presentation);
588
+ const isOverlay = resolvedPresentation === 'overlay';
589
+ const isStacked = resolvedPresentation === 'stacked';
590
+ const localRef = React.useRef<HTMLDivElement | null>(null);
591
+ // Publish resolved presentation so Root can gate peeking in overlay
592
+ React.useEffect(() => {
593
+ (shell as any).onLeftPres?.(resolvedPresentation);
594
+ }, [shell, resolvedPresentation]);
595
+ const setRef = React.useCallback(
596
+ (node: HTMLDivElement | null) => {
597
+ localRef.current = node;
598
+ if (typeof ref === 'function') ref(node);
599
+ else if (ref) (ref as React.MutableRefObject<HTMLDivElement | null>).current = node;
600
+ },
601
+ [ref],
602
+ );
594
603
 
595
- const _lastBpRef = React.useRef<Breakpoint | null>(null);
596
- const lastLeftModeRef = React.useRef<PaneMode | null>(null);
597
- const initNotifiedRef = React.useRef(false);
598
- const resolvedDefaultOpen = useResponsiveValue((props as any).defaultOpen as any);
604
+ // Register with shell
605
+ React.useEffect(() => {
606
+ shell.setHasLeft(true);
607
+ return () => shell.setHasLeft(false);
608
+ }, [shell]);
609
+
610
+ const lastLeftModeRef = React.useRef<PaneMode | null>(null);
611
+ const initNotifiedRef = React.useRef(false);
612
+ const normalizedLeftControlled = React.useMemo(() => {
613
+ if (typeof propsOpen === 'undefined') return undefined;
614
+ return propsOpen ? 'expanded' : 'collapsed';
615
+ }, [propsOpen]);
616
+ const normalizedLeftDefault = React.useMemo(() => mapResponsiveBooleanToPaneMode(propsDefaultOpen), [propsDefaultOpen]);
617
+ useResponsiveInitialState<PaneMode>({
618
+ controlledValue: normalizedLeftControlled,
619
+ defaultValue: normalizedLeftDefault,
620
+ currentValue: shell.leftMode,
621
+ setValue: shell.setLeftMode,
622
+ breakpointReady: shell.currentBreakpointReady,
623
+ onInit: (initial) => propsOnOpenChange?.(initial === 'expanded', { reason: 'init' }),
624
+ });
599
625
 
600
- // Initialize from responsive defaultOpen once when uncontrolled and breakpoint ready
601
- const didInitFromDefaultOpenRef = React.useRef(false);
602
- const propsOpen = (props as any).open;
603
- const propsDefaultOpen = (props as any).defaultOpen;
604
- React.useEffect(() => {
605
- if (didInitFromDefaultOpenRef.current) return;
606
- if (!shell.currentBreakpointReady) return;
607
- if (typeof propsOpen !== 'undefined') return; // controlled
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]);
626
+ // Emit mode changes (uncontrolled toggles + init)
627
+ React.useEffect(() => {
628
+ if (typeof propsOpen !== 'undefined') return; // controlled, notifications only via parent changes
629
+ if (lastLeftModeRef.current !== null && lastLeftModeRef.current !== shell.leftMode) {
630
+ propsOnOpenChange?.(shell.leftMode === 'expanded', { reason: 'toggle' });
631
+ }
632
+ lastLeftModeRef.current = shell.leftMode;
633
+ }, [shell, propsOnOpenChange, propsOpen]);
623
634
 
624
- // Sync controlled mode
625
- // removed mode sync
635
+ // Emit expand/collapse events
636
+ React.useEffect(() => {
637
+ if (shell.leftMode === 'expanded') {
638
+ onExpand?.();
639
+ } else {
640
+ onCollapse?.();
641
+ }
642
+ }, [shell.leftMode, onExpand, onCollapse]);
626
643
 
627
- // Emit mode changes (uncontrolled toggles + init)
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]);
644
+ const _isExpanded = shell.leftMode === 'expanded';
639
645
 
640
- // Emit expand/collapse events
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
- }
646
+ // Left is not resizable; width derives from Rail/Panel.
682
647
 
683
- if (isStacked) {
684
- const open = shell.leftMode === 'expanded';
685
- // Compute floating width from child Rail/Panel expanded sizes (like overlay)
686
- const childArray = React.Children.toArray(children) as React.ReactElement[];
687
- const isType = (el: React.ReactElement, comp: any) => React.isValidElement(el) && el.type === comp;
688
- const railEl = childArray.find((el) => isType(el, Rail));
689
- const panelEl = childArray.find((el) => isType(el, Panel));
690
- const _railSize = typeof (railEl as any)?.props?.expandedSize === 'number' ? (railEl as any).props.expandedSize : 64;
691
- const _panelSize = typeof (panelEl as any)?.props?.expandedSize === 'number' ? (panelEl as any).props.expandedSize : 288;
692
- const _hasRail = Boolean(railEl);
693
- const _hasPanel = Boolean(panelEl);
694
- const _includePanel = _hasPanel && (shell.panelMode === 'expanded' || shell.peekTarget === 'panel');
695
-
696
- // Strip control props from DOM spread
697
- const { open: _openIgnored, defaultOpen: _defaultOpenIgnored, onOpenChange: _onOpenChangeIgnored, ...stackDomProps } = props as any;
698
-
699
- return (
700
- <div
701
- {...stackDomProps}
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,
648
+ if (isOverlay) {
649
+ const open = shell.leftMode === 'expanded';
650
+ // Compute overlay width from child Rail/Panel expanded sizes
651
+ const childArray = React.Children.toArray(children) as React.ReactElement[];
652
+ const isType = (el: React.ReactElement, comp: any) => React.isValidElement(el) && el.type === comp;
653
+ const railEl = childArray.find((el) => isType(el, Rail));
654
+ const panelEl = childArray.find((el) => isType(el, Panel));
655
+ const railSize = typeof (railEl as any)?.props?.expandedSize === 'number' ? (railEl as any).props.expandedSize : 64;
656
+ const panelSize = typeof (panelEl as any)?.props?.expandedSize === 'number' ? (panelEl as any).props.expandedSize : 288;
657
+ const hasRail = Boolean(railEl);
658
+ const hasPanel = Boolean(panelEl);
659
+ const overlayPx = (hasRail ? railSize : 0) + (shell.panelMode === 'expanded' && hasPanel ? panelSize : 0);
660
+ return (
661
+ <Sheet.Root open={open} onOpenChange={(o) => shell.setLeftMode(o ? 'expanded' : 'collapsed')}>
662
+ <Sheet.Content
663
+ side="start"
664
+ style={{ padding: 0 }}
665
+ width={{
666
+ initial: `${overlayPx}px`,
709
667
  }}
710
- data-open={open || undefined}
711
668
  >
712
- {children}
713
- </div>
714
- );
715
- }
669
+ <VisuallyHidden>
670
+ <Sheet.Title>Navigation</Sheet.Title>
671
+ </VisuallyHidden>
672
+ <div className="rt-ShellLeft">{children}</div>
673
+ </Sheet.Content>
674
+ </Sheet.Root>
675
+ );
676
+ }
716
677
 
717
- // Strip control/legacy props from DOM spread
718
- const {
719
- open: _openIgnored,
720
- defaultOpen: _defaultOpenIgnored,
721
- onOpenChange: _onOpenChangeIgnored,
722
- // legacy
723
- mode: _legacyModeIgnored,
724
- defaultMode: _legacyDefaultModeIgnored,
725
- onModeChange: _legacyOnModeChangeIgnored,
726
- ...domProps
727
- } = props as any;
678
+ if (isStacked) {
679
+ const open = shell.leftMode === 'expanded';
680
+ // Compute floating width from child Rail/Panel expanded sizes (like overlay)
681
+ const childArray = React.Children.toArray(children) as React.ReactElement[];
682
+ const isType = (el: React.ReactElement, comp: any) => React.isValidElement(el) && el.type === comp;
683
+ const railEl = childArray.find((el) => isType(el, Rail));
684
+ const panelEl = childArray.find((el) => isType(el, Panel));
685
+ const _railSize = typeof (railEl as any)?.props?.expandedSize === 'number' ? (railEl as any).props.expandedSize : 64;
686
+ const _panelSize = typeof (panelEl as any)?.props?.expandedSize === 'number' ? (panelEl as any).props.expandedSize : 288;
687
+ const _hasRail = Boolean(railEl);
688
+ const _hasPanel = Boolean(panelEl);
689
+ const _includePanel = _hasPanel && (shell.panelMode === 'expanded' || shell.peekTarget === 'panel');
728
690
 
691
+ // Strip control props from DOM spread
729
692
  return (
730
693
  <div
731
694
  {...domProps}
@@ -737,28 +700,48 @@ const Left = React.forwardRef<HTMLDivElement, LeftProps>(
737
700
  style={{
738
701
  ...style,
739
702
  }}
703
+ data-open={open || undefined}
740
704
  >
741
705
  {children}
742
706
  </div>
743
707
  );
744
- },
745
- );
708
+ }
709
+
710
+ // Strip control/legacy props from DOM spread
711
+ return (
712
+ <div
713
+ {...domProps}
714
+ ref={setRef}
715
+ className={classNames('rt-ShellLeft', className)}
716
+ data-mode={shell.leftMode}
717
+ data-peek={shell.peekTarget === 'left' || shell.peekTarget === 'rail' || shell.peekTarget === 'panel' || undefined}
718
+ data-presentation={resolvedPresentation}
719
+ style={{
720
+ ...style,
721
+ }}
722
+ >
723
+ {children}
724
+ </div>
725
+ );
726
+ });
746
727
  Left.displayName = 'Shell.Left';
728
+ assignShellSlot(Left as any, 'Shell.Left');
747
729
 
748
- const Rail = React.forwardRef<HTMLDivElement, RailProps>(({ className, presentation, expandedSize = 64, collapsible, onExpand, onCollapse, children, style, ...props }, ref) => {
730
+ const Rail = React.forwardRef<HTMLDivElement, RailProps>((initialProps, ref) => {
731
+ const { className, presentation, expandedSize = 64, collapsible, onExpand, onCollapse, children, style, open, defaultOpen, onOpenChange, ...domProps } = initialProps;
749
732
  const shell = useShell();
750
733
 
751
734
  // Dev guards
752
735
  const wasControlledRef = React.useRef<boolean | null>(null);
753
736
  if (process.env.NODE_ENV !== 'production') {
754
- if (typeof props.open !== 'undefined' && typeof props.defaultOpen !== 'undefined') {
737
+ if (typeof open !== 'undefined' && typeof defaultOpen !== 'undefined') {
755
738
  console.error('Shell.Rail: Do not pass both `open` and `defaultOpen`. Choose one.');
756
739
  }
757
740
  }
758
741
 
759
742
  // Warn on controlled/uncontrolled mode switch
760
743
  React.useEffect(() => {
761
- const isControlled = typeof props.open !== 'undefined';
744
+ const isControlled = typeof open !== 'undefined';
762
745
  if (wasControlledRef.current === null) {
763
746
  wasControlledRef.current = isControlled;
764
747
  return;
@@ -767,7 +750,7 @@ const Rail = React.forwardRef<HTMLDivElement, RailProps>(({ className, presentat
767
750
  console.warn('Shell.Rail: Switching between controlled and uncontrolled `open` is not supported.');
768
751
  wasControlledRef.current = isControlled;
769
752
  }
770
- }, [props.open]);
753
+ }, [open]);
771
754
 
772
755
  // Register expanded size with Left container
773
756
  React.useEffect(() => {
@@ -777,8 +760,6 @@ const Rail = React.forwardRef<HTMLDivElement, RailProps>(({ className, presentat
777
760
  const isExpanded = shell.leftMode === 'expanded';
778
761
 
779
762
  // 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
763
  return (
783
764
  <div
784
765
  {...domProps}
@@ -798,6 +779,7 @@ const Rail = React.forwardRef<HTMLDivElement, RailProps>(({ className, presentat
798
779
  );
799
780
  });
800
781
  Rail.displayName = 'Shell.Rail';
782
+ assignShellSlot(Rail as any, 'Shell.Rail');
801
783
 
802
784
  // Panel
803
785
  type HandleComponent = React.ForwardRefExoticComponent<React.ComponentPropsWithoutRef<'div'> & React.RefAttributes<HTMLDivElement>>;
@@ -836,9 +818,23 @@ type _InspectorComponent = React.ForwardRefExoticComponent<PaneProps & React.Ref
836
818
 
837
819
  type _BottomComponent = React.ForwardRefExoticComponent<PaneProps & React.RefAttributes<HTMLDivElement>> & { Handle: HandleComponent };
838
820
 
839
- const Panel = React.forwardRef<HTMLDivElement, PanelPublicProps>(
840
- (
841
- {
821
+ const PANEL_DOM_PROP_KEYS = [
822
+ 'className',
823
+ 'children',
824
+ 'defaultOpen',
825
+ 'open',
826
+ 'onOpenChange',
827
+ 'size',
828
+ 'defaultSize',
829
+ 'onSizeChange',
830
+ 'sizeUpdate',
831
+ 'sizeUpdateMs',
832
+ 'style',
833
+ ] as const satisfies readonly (keyof PanelPublicProps)[];
834
+
835
+ const Panel = assignShellSlot(
836
+ React.forwardRef<HTMLDivElement, PanelPublicProps>((initialProps, ref) => {
837
+ const {
842
838
  className,
843
839
  defaultOpen,
844
840
  open,
@@ -865,10 +861,8 @@ const Panel = React.forwardRef<HTMLDivElement, PanelPublicProps>(
865
861
  onSizeChange,
866
862
  sizeUpdate,
867
863
  sizeUpdateMs = 50,
868
- ...props
869
- },
870
- ref,
871
- ) => {
864
+ } = initialProps;
865
+ const panelDomProps = extractPaneDomProps(initialProps, PANEL_DOM_PROP_KEYS);
872
866
  // Throttled/debounced emitter for onSizeChange
873
867
  const emitSizeChange = React.useMemo(() => {
874
868
  if (!onSizeChange) return () => {};
@@ -1141,16 +1135,6 @@ const Panel = React.forwardRef<HTMLDivElement, PanelPublicProps>(
1141
1135
  </PaneResizeContext.Provider>
1142
1136
  ) : null;
1143
1137
 
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
1138
  return (
1155
1139
  <div
1156
1140
  {...panelDomProps}
@@ -1170,7 +1154,8 @@ const Panel = React.forwardRef<HTMLDivElement, PanelPublicProps>(
1170
1154
  {handleEl}
1171
1155
  </div>
1172
1156
  );
1173
- },
1157
+ }),
1158
+ 'Shell.Panel',
1174
1159
  ) as PanelComponent;
1175
1160
  Panel.displayName = 'Shell.Panel';
1176
1161
  Panel.Handle = PanelHandle;
@@ -1182,6 +1167,7 @@ interface ShellContentProps extends React.ComponentPropsWithoutRef<'main'> {}
1182
1167
 
1183
1168
  const Content = React.forwardRef<HTMLElement, ShellContentProps>(({ className, ...props }, ref) => <main {...props} ref={ref} className={classNames('rt-ShellContent', className)} />);
1184
1169
  Content.displayName = 'Shell.Content';
1170
+ assignShellSlot(Content as any, 'Shell.Content');
1185
1171
 
1186
1172
  // Inspector moved to ./_internal/shell-inspector
1187
1173
 
@@ -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)',