@kushagradhawan/kookie-ui 0.1.50 → 0.1.52

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 (94) hide show
  1. package/components.css +582 -116
  2. package/dist/cjs/components/_internal/shell-bottom.d.ts +31 -5
  3. package/dist/cjs/components/_internal/shell-bottom.d.ts.map +1 -1
  4. package/dist/cjs/components/_internal/shell-bottom.js +1 -1
  5. package/dist/cjs/components/_internal/shell-bottom.js.map +3 -3
  6. package/dist/cjs/components/_internal/shell-handles.d.ts.map +1 -1
  7. package/dist/cjs/components/_internal/shell-handles.js +1 -1
  8. package/dist/cjs/components/_internal/shell-handles.js.map +3 -3
  9. package/dist/cjs/components/_internal/shell-inspector.d.ts +23 -5
  10. package/dist/cjs/components/_internal/shell-inspector.d.ts.map +1 -1
  11. package/dist/cjs/components/_internal/shell-inspector.js +1 -1
  12. package/dist/cjs/components/_internal/shell-inspector.js.map +3 -3
  13. package/dist/cjs/components/_internal/shell-sidebar.d.ts +24 -6
  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 +21 -2
  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/shell.context.d.ts +88 -1
  22. package/dist/cjs/components/shell.context.d.ts.map +1 -1
  23. package/dist/cjs/components/shell.context.js +1 -1
  24. package/dist/cjs/components/shell.context.js.map +3 -3
  25. package/dist/cjs/components/shell.d.ts +51 -13
  26. package/dist/cjs/components/shell.d.ts.map +1 -1
  27. package/dist/cjs/components/shell.hooks.d.ts +7 -1
  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 +1 -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 +31 -5
  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-handles.d.ts.map +1 -1
  42. package/dist/esm/components/_internal/shell-handles.js +1 -1
  43. package/dist/esm/components/_internal/shell-handles.js.map +3 -3
  44. package/dist/esm/components/_internal/shell-inspector.d.ts +23 -5
  45. package/dist/esm/components/_internal/shell-inspector.d.ts.map +1 -1
  46. package/dist/esm/components/_internal/shell-inspector.js +1 -1
  47. package/dist/esm/components/_internal/shell-inspector.js.map +3 -3
  48. package/dist/esm/components/_internal/shell-sidebar.d.ts +24 -6
  49. package/dist/esm/components/_internal/shell-sidebar.d.ts.map +1 -1
  50. package/dist/esm/components/_internal/shell-sidebar.js +1 -1
  51. package/dist/esm/components/_internal/shell-sidebar.js.map +3 -3
  52. package/dist/esm/components/chatbar.d.ts +21 -2
  53. package/dist/esm/components/chatbar.d.ts.map +1 -1
  54. package/dist/esm/components/chatbar.js +1 -1
  55. package/dist/esm/components/chatbar.js.map +3 -3
  56. package/dist/esm/components/shell.context.d.ts +88 -1
  57. package/dist/esm/components/shell.context.d.ts.map +1 -1
  58. package/dist/esm/components/shell.context.js +1 -1
  59. package/dist/esm/components/shell.context.js.map +3 -3
  60. package/dist/esm/components/shell.d.ts +51 -13
  61. package/dist/esm/components/shell.d.ts.map +1 -1
  62. package/dist/esm/components/shell.hooks.d.ts +7 -1
  63. package/dist/esm/components/shell.hooks.d.ts.map +1 -1
  64. package/dist/esm/components/shell.hooks.js +1 -1
  65. package/dist/esm/components/shell.hooks.js.map +3 -3
  66. package/dist/esm/components/shell.js +1 -1
  67. package/dist/esm/components/shell.js.map +3 -3
  68. package/dist/esm/components/shell.types.d.ts +1 -0
  69. package/dist/esm/components/shell.types.d.ts.map +1 -1
  70. package/dist/esm/components/shell.types.js.map +2 -2
  71. package/package.json +14 -3
  72. package/schemas/base-button.json +1 -1
  73. package/schemas/button.json +1 -1
  74. package/schemas/icon-button.json +1 -1
  75. package/schemas/index.json +6 -6
  76. package/schemas/toggle-button.json +1 -1
  77. package/schemas/toggle-icon-button.json +1 -1
  78. package/src/components/_internal/base-menu.css +16 -16
  79. package/src/components/_internal/base-sidebar-menu.css +23 -20
  80. package/src/components/_internal/base-sidebar.css +13 -0
  81. package/src/components/_internal/shell-bottom.tsx +176 -49
  82. package/src/components/_internal/shell-handles.tsx +29 -4
  83. package/src/components/_internal/shell-inspector.tsx +175 -43
  84. package/src/components/_internal/shell-sidebar.tsx +177 -93
  85. package/src/components/chatbar.css +240 -21
  86. package/src/components/chatbar.tsx +280 -291
  87. package/src/components/sheet.css +8 -16
  88. package/src/components/shell.context.tsx +79 -3
  89. package/src/components/shell.css +0 -1
  90. package/src/components/shell.hooks.ts +35 -0
  91. package/src/components/shell.tsx +574 -235
  92. package/src/components/shell.types.ts +2 -0
  93. package/src/components/sidebar.css +2 -2
  94. package/styles.css +582 -116
@@ -3,10 +3,10 @@ import classNames from 'classnames';
3
3
  import * as Sheet from '../sheet.js';
4
4
  import { VisuallyHidden } from '../visually-hidden.js';
5
5
  import { useShell } from '../shell.context.js';
6
- import { useResponsivePresentation } from '../shell.hooks.js';
6
+ import { useResponsivePresentation, useResponsiveValue } from '../shell.hooks.js';
7
7
  import { PaneResizeContext } from './shell-resize.js';
8
8
  import { SidebarHandle, PaneHandle } from './shell-handles.js';
9
- import type { Breakpoint, PaneMode, PaneSizePersistence, PresentationValue, ResponsivePresentation, ResponsiveSidebarMode, SidebarMode } from '../shell.types.js';
9
+ import type { Breakpoint, PaneMode, PaneSizePersistence, PresentationValue, ResponsivePresentation, ResponsiveSidebarMode, SidebarMode, Responsive } from '../shell.types.js';
10
10
  import { BREAKPOINTS } from '../shell.types.js';
11
11
 
12
12
  interface PaneProps extends React.ComponentPropsWithoutRef<'div'> {
@@ -32,33 +32,29 @@ interface PaneProps extends React.ComponentPropsWithoutRef<'div'> {
32
32
  persistence?: PaneSizePersistence;
33
33
  }
34
34
 
35
- type SidebarComponent = React.ForwardRefExoticComponent<
36
- Omit<PaneProps, 'mode' | 'defaultMode' | 'onModeChange'> & {
37
- mode?: SidebarMode;
38
- defaultMode?: ResponsiveSidebarMode;
39
- onModeChange?: (mode: SidebarMode) => void;
40
- thinSize?: number;
41
- toggleModes?: 'both' | 'single';
42
- } & React.RefAttributes<HTMLDivElement>
43
- > & { Handle: typeof SidebarHandle };
35
+ type SidebarStateChangeMeta = { reason: 'init' | 'toggle' | 'responsive' };
36
+ type SidebarControlledProps = { state: Responsive<SidebarMode>; onStateChange?: (state: SidebarMode, meta: SidebarStateChangeMeta) => void; defaultState?: never };
37
+ type SidebarUncontrolledProps = { defaultState?: SidebarMode | Partial<Record<Breakpoint, SidebarMode>>; onStateChange?: (state: SidebarMode, meta: SidebarStateChangeMeta) => void; state?: never };
38
+ type SidebarPublicProps = Omit<PaneProps, 'mode' | 'defaultMode' | 'onModeChange'> & {
39
+ // removed legacy mode props
40
+ thinSize?: number;
41
+ toggleModes?: 'both' | 'single';
42
+ // size API (width when expanded)
43
+ size?: number | string;
44
+ defaultSize?: number | string;
45
+ onSizeChange?: (size: number, meta: { reason: 'init' | 'resize' | 'controlled' }) => void;
46
+ sizeUpdate?: 'throttle' | 'debounce';
47
+ sizeUpdateMs?: number;
48
+ } & (SidebarControlledProps | SidebarUncontrolledProps);
44
49
 
45
- export const Sidebar = React.forwardRef<
46
- HTMLDivElement,
47
- Omit<PaneProps, 'mode' | 'defaultMode' | 'onModeChange'> & {
48
- mode?: SidebarMode;
49
- defaultMode?: ResponsiveSidebarMode;
50
- onModeChange?: (mode: SidebarMode) => void;
51
- thinSize?: number;
52
- toggleModes?: 'both' | 'single';
53
- }
54
- >(
50
+ type SidebarComponent = React.ForwardRefExoticComponent<SidebarPublicProps & React.RefAttributes<HTMLDivElement>> & { Handle: typeof SidebarHandle };
51
+
52
+ export const Sidebar = React.forwardRef<HTMLDivElement, SidebarPublicProps>(
55
53
  (
56
54
  {
57
55
  className,
58
56
  presentation = { initial: 'overlay', md: 'fixed' },
59
- mode,
60
- defaultMode = 'expanded',
61
- onModeChange,
57
+ // removed legacy props
62
58
  expandedSize = 288,
63
59
  minSize = 200,
64
60
  maxSize = 400,
@@ -78,6 +74,10 @@ export const Sidebar = React.forwardRef<
78
74
  style,
79
75
  thinSize = 64,
80
76
  toggleModes,
77
+ // new state props (XOR)
78
+ state,
79
+ defaultState,
80
+ onStateChange,
81
81
  ...props
82
82
  },
83
83
  ref,
@@ -86,29 +86,7 @@ export const Sidebar = React.forwardRef<
86
86
  const resolvedPresentation = useResponsivePresentation(presentation);
87
87
  const isOverlay = resolvedPresentation === 'overlay';
88
88
  const isStacked = resolvedPresentation === 'stacked';
89
- // Use library-managed phase for thin expanded sequencing
90
- const transitionPhase = (shell as any).sidebarPhase as 'idle' | 'hiding' | 'resizing' | 'showing' | undefined;
91
- React.useLayoutEffect(() => {
92
- if (isOverlay) return;
93
- const containerEl = localRef.current;
94
- if (!containerEl) return;
95
- if (transitionPhase === 'hiding') {
96
- // Freeze width while we fade out
97
- try {
98
- const rect = containerEl.getBoundingClientRect();
99
- containerEl.style.width = `${Math.round(rect.width)}px`;
100
- } catch {}
101
- } else if (transitionPhase === 'resizing') {
102
- // Release width so the CSS width transition happens
103
- try {
104
- containerEl.style.removeProperty('width');
105
- } catch {}
106
- } else if (transitionPhase === 'idle') {
107
- try {
108
- containerEl.style.removeProperty('width');
109
- } catch {}
110
- }
111
- }, [transitionPhase, isOverlay]);
89
+ // Phase sequencing is now CSS-driven; no JS-managed phase
112
90
  const localRef = React.useRef<HTMLDivElement | null>(null);
113
91
  const setRef = React.useCallback(
114
92
  (node: HTMLDivElement | null) => {
@@ -122,6 +100,34 @@ export const Sidebar = React.forwardRef<
122
100
  const handleChildren = childArray.filter((el: React.ReactElement) => React.isValidElement(el) && el.type === SidebarHandle);
123
101
  const contentChildren = childArray.filter((el: React.ReactElement) => !(React.isValidElement(el) && el.type === SidebarHandle));
124
102
 
103
+ // Throttled/debounced emitter for onSizeChange
104
+ const emitSizeChange = React.useMemo(() => {
105
+ const cb = (props as any).onSizeChange as undefined | ((s: number, meta: { reason: 'init' | 'resize' | 'controlled' }) => void);
106
+ const strategy = (props as any).sizeUpdate as undefined | 'throttle' | 'debounce';
107
+ const ms = (props as any).sizeUpdateMs ?? 50;
108
+ if (!cb) return () => {};
109
+ if (strategy === 'debounce') {
110
+ let t: any = null;
111
+ return (s: number, meta: { reason: 'init' | 'resize' | 'controlled' }) => {
112
+ if (t) clearTimeout(t);
113
+ t = setTimeout(() => {
114
+ cb(s, meta);
115
+ }, ms);
116
+ };
117
+ }
118
+ if (strategy === 'throttle') {
119
+ let last = 0;
120
+ return (s: number, meta: { reason: 'init' | 'resize' | 'controlled' }) => {
121
+ const now = Date.now();
122
+ if (now - last >= ms) {
123
+ last = now;
124
+ cb(s, meta);
125
+ }
126
+ };
127
+ }
128
+ return (s: number, meta: { reason: 'init' | 'resize' | 'controlled' }) => cb(s, meta);
129
+ }, [(props as any).onSizeChange, (props as any).sizeUpdate, (props as any).sizeUpdateMs]);
130
+
125
131
  // Register with shell
126
132
  const sidebarId = React.useId();
127
133
  React.useEffect(() => {
@@ -131,30 +137,78 @@ export const Sidebar = React.forwardRef<
131
137
  };
132
138
  }, [shell, sidebarId]);
133
139
 
134
- // Honor defaultMode on mount when uncontrolled
140
+ // Dev guards
141
+ const wasControlledRef = React.useRef<boolean | null>(null);
142
+ if (process.env.NODE_ENV !== 'production') {
143
+ if (typeof state !== 'undefined' && typeof defaultState !== 'undefined') {
144
+ // eslint-disable-next-line no-console
145
+ console.error('Shell.Sidebar: Do not pass both `state` and `defaultState`. Choose one.');
146
+ }
147
+ if (typeof (props as any).size !== 'undefined' && typeof (props as any).defaultSize !== 'undefined') {
148
+ // eslint-disable-next-line no-console
149
+ console.error('Shell.Sidebar: Do not pass both `size` and `defaultSize`. Choose one.');
150
+ }
151
+ }
152
+
153
+ // Warn on mode switch between controlled/uncontrolled
154
+ React.useEffect(() => {
155
+ const isControlled = typeof state !== 'undefined';
156
+ if (wasControlledRef.current === null) {
157
+ wasControlledRef.current = isControlled;
158
+ return;
159
+ }
160
+ if (wasControlledRef.current !== isControlled) {
161
+ // eslint-disable-next-line no-console
162
+ console.warn('Shell.Sidebar: Switching between controlled and uncontrolled `state` is not supported.');
163
+ wasControlledRef.current = isControlled;
164
+ }
165
+ }, [state]);
166
+
167
+ // Resolve responsive controlled state at top level
168
+ const resolvedState = useResponsiveValue(state);
169
+ const resolvedDefaultState = useResponsiveValue(defaultState as any);
170
+
171
+ // Honor state/defaultState on mount when uncontrolled
135
172
  const didInitRef = React.useRef(false);
136
173
  React.useEffect(() => {
137
174
  if (didInitRef.current) return;
175
+ if (!shell.currentBreakpointReady) return;
138
176
  didInitRef.current = true;
139
- if (mode === undefined && shell.sidebarMode !== (defaultMode as SidebarMode)) {
140
- shell.setSidebarMode(defaultMode as SidebarMode);
177
+ // Controlled state may be responsive; use resolved value
178
+ if (typeof state !== 'undefined' && resolvedState) {
179
+ if (shell.sidebarMode !== resolvedState) shell.setSidebarMode(resolvedState);
180
+ return;
181
+ }
182
+ if (typeof defaultState !== 'undefined') {
183
+ const initialState = (resolvedDefaultState ?? defaultState) as SidebarMode;
184
+ if (shell.sidebarMode !== initialState) {
185
+ shell.setSidebarMode(initialState);
186
+ }
187
+ onStateChange?.(initialState, { reason: 'init' });
188
+ return;
141
189
  }
142
190
  // eslint-disable-next-line react-hooks/exhaustive-deps
143
- }, []);
191
+ }, [shell.currentBreakpointReady, resolvedDefaultState, resolvedState, state, defaultState]);
144
192
 
145
- // Sync controlled mode
193
+ // Sync controlled state (responsive-aware)
146
194
  React.useEffect(() => {
147
- if (mode !== undefined && shell.sidebarMode !== mode) {
148
- shell.setSidebarMode(mode);
149
- }
150
- }, [mode, shell]);
195
+ if (resolvedState === undefined) return;
196
+ if (shell.sidebarMode !== resolvedState) shell.setSidebarMode(resolvedState);
197
+ }, [resolvedState, shell.sidebarMode]);
151
198
 
152
199
  // Emit mode changes
200
+ const lastNotifyModeRef = React.useRef<SidebarMode | null>(null);
153
201
  React.useEffect(() => {
154
- if (mode === undefined) {
155
- onModeChange?.(shell.sidebarMode);
202
+ // notify new API when uncontrolled; skip first run to avoid masking init
203
+ if (typeof state === 'undefined') {
204
+ if (lastNotifyModeRef.current === null) {
205
+ lastNotifyModeRef.current = shell.sidebarMode as SidebarMode;
206
+ } else if (lastNotifyModeRef.current !== shell.sidebarMode) {
207
+ lastNotifyModeRef.current = shell.sidebarMode as SidebarMode;
208
+ onStateChange?.(shell.sidebarMode as SidebarMode, { reason: 'toggle' });
209
+ }
156
210
  }
157
- }, [shell.sidebarMode, mode, onModeChange]);
211
+ }, [shell.sidebarMode, state, onStateChange]);
158
212
 
159
213
  // Emit expand/collapse events
160
214
  React.useEffect(() => {
@@ -201,29 +255,12 @@ export const Sidebar = React.forwardRef<
201
255
  };
202
256
  }, [resizable, persistenceAdapter, onResize, isOverlay]);
203
257
 
204
- // Always-follow responsive defaultMode for uncontrolled Sidebar (on breakpoint change only)
205
- const resolveResponsiveMode = React.useCallback((): SidebarMode => {
206
- if (typeof defaultMode === 'string') return defaultMode as SidebarMode;
207
- const dm = defaultMode as Partial<Record<Breakpoint, SidebarMode>> | undefined;
208
- if (dm && dm[shell.currentBreakpoint as Breakpoint]) {
209
- return dm[shell.currentBreakpoint as Breakpoint] as SidebarMode;
210
- }
211
- const bpKeys = Object.keys(BREAKPOINTS) as Array<keyof typeof BREAKPOINTS>;
212
- const order: Breakpoint[] = ([...bpKeys].reverse() as Breakpoint[]).concat('initial' as Breakpoint);
213
- const startIdx = order.indexOf(shell.currentBreakpoint as Breakpoint);
214
- for (let i = startIdx + 1; i < order.length; i++) {
215
- const bp = order[i];
216
- if (dm && dm[bp]) return dm[bp] as SidebarMode;
217
- }
218
- return 'collapsed';
219
- }, [defaultMode, shell.currentBreakpoint]);
220
-
221
258
  // Register custom toggle behavior based on toggleModes (both|single)
222
259
  const shellForToggle = useShell();
223
260
  const resolveDefaultSidebarMode = React.useCallback((): SidebarMode => {
224
- const resolved = resolveResponsiveMode();
261
+ const resolved = defaultState ?? 'expanded';
225
262
  return resolved === 'thin' || resolved === 'expanded' ? resolved : 'expanded';
226
- }, [resolveResponsiveMode]);
263
+ }, [defaultState]);
227
264
 
228
265
  React.useEffect(() => {
229
266
  if (!shellForToggle.setSidebarToggleComputer) return;
@@ -254,15 +291,7 @@ export const Sidebar = React.forwardRef<
254
291
  }
255
292
  }, [shell.sidebarMode, thinSize, expandedSize]);
256
293
 
257
- const lastSidebarBpRef = React.useRef<Breakpoint | null>(null);
258
- React.useEffect(() => {
259
- if (mode !== undefined) return;
260
- if (!shell.currentBreakpointReady) return;
261
- if (lastSidebarBpRef.current === shell.currentBreakpoint) return;
262
- lastSidebarBpRef.current = shell.currentBreakpoint as Breakpoint;
263
- const next = resolveResponsiveMode();
264
- if (next !== shell.sidebarMode) shell.setSidebarMode(next);
265
- }, [mode, shell.currentBreakpoint, shell.currentBreakpointReady, resolveResponsiveMode, shell.sidebarMode, shell.setSidebarMode]);
294
+ // Remove responsive default mode behavior entirely
266
295
 
267
296
  const handleEl =
268
297
  resizable && !isOverlay && shell.sidebarMode === 'expanded' ? (
@@ -284,6 +313,7 @@ export const Sidebar = React.forwardRef<
284
313
  onResizeStart,
285
314
  onResizeEnd: (size) => {
286
315
  onResizeEnd?.(size);
316
+ emitSizeChange(size, { reason: 'resize' });
287
317
  persistenceAdapter?.save?.(size);
288
318
  },
289
319
  target: 'sidebar',
@@ -299,6 +329,61 @@ export const Sidebar = React.forwardRef<
299
329
  </PaneResizeContext.Provider>
300
330
  ) : null;
301
331
 
332
+ // Strip new API props from DOM
333
+ const { state: _s, defaultState: _ds, onStateChange: _osc, size: _sz, defaultSize: _dsz, onSizeChange: _onsc, sizeUpdate: _szu, sizeUpdateMs: _szums, ...domProps } = props as any;
334
+
335
+ // Normalize CSS lengths to px
336
+ const normalizeToPx = React.useCallback((value: number | string | undefined): number | undefined => {
337
+ if (value == null) return undefined;
338
+ if (typeof value === 'number' && Number.isFinite(value)) return value;
339
+ const str = String(value).trim();
340
+ if (!str) return undefined;
341
+ if (str.endsWith('px')) return Number.parseFloat(str);
342
+ if (str.endsWith('rem')) {
343
+ const rem = Number.parseFloat(getComputedStyle(document.documentElement).fontSize || '16') || 16;
344
+ return Number.parseFloat(str) * rem;
345
+ }
346
+ if (str.endsWith('%')) {
347
+ const pct = Number.parseFloat(str);
348
+ const base = document.documentElement.clientWidth || window.innerWidth || 0;
349
+ return (pct / 100) * base;
350
+ }
351
+ const n = Number.parseFloat(str);
352
+ return Number.isFinite(n) ? n : undefined;
353
+ }, []);
354
+
355
+ // Apply defaultSize on mount when uncontrolled
356
+ React.useEffect(() => {
357
+ if (!localRef.current) return;
358
+ const { size, defaultSize } = props as any;
359
+ if (typeof size === 'undefined' && typeof defaultSize !== 'undefined') {
360
+ const px = normalizeToPx(defaultSize);
361
+ if (typeof px === 'number' && Number.isFinite(px)) {
362
+ const minPx = typeof minSize === 'number' ? minSize : undefined;
363
+ const maxPx = typeof maxSize === 'number' ? maxSize : undefined;
364
+ const clamped = Math.min(maxPx ?? px, Math.max(minPx ?? px, px));
365
+ localRef.current.style.setProperty('--sidebar-size', `${clamped}px`);
366
+ emitSizeChange(clamped, { reason: 'init' });
367
+ }
368
+ }
369
+ // eslint-disable-next-line react-hooks/exhaustive-deps
370
+ }, []);
371
+
372
+ // Controlled size sync
373
+ React.useEffect(() => {
374
+ if (!localRef.current) return;
375
+ const { size } = props as any;
376
+ if (typeof size === 'undefined') return;
377
+ const px = normalizeToPx(size);
378
+ if (typeof px === 'number' && Number.isFinite(px)) {
379
+ const minPx = typeof minSize === 'number' ? minSize : undefined;
380
+ const maxPx = typeof maxSize === 'number' ? maxSize : undefined;
381
+ const clamped = Math.min(maxPx ?? px, Math.max(minPx ?? px, px));
382
+ localRef.current.style.setProperty('--sidebar-size', `${clamped}px`);
383
+ emitSizeChange(clamped, { reason: 'controlled' });
384
+ }
385
+ }, [(props as any).size, minSize, maxSize, normalizeToPx, emitSizeChange]);
386
+
302
387
  if (isOverlay) {
303
388
  const open = shell.sidebarMode !== 'collapsed';
304
389
  return (
@@ -311,23 +396,22 @@ export const Sidebar = React.forwardRef<
311
396
  }}
312
397
  >
313
398
  <VisuallyHidden>
314
- <Sheet.Title>Sidebar</Sheet.Title>
399
+ <Sheet.Title>Navigation</Sheet.Title>
315
400
  </VisuallyHidden>
316
401
  {contentChildren}
317
402
  </Sheet.Content>
318
403
  </Sheet.Root>
319
404
  );
320
405
  }
321
-
322
406
  return (
323
407
  <div
324
- {...props}
408
+ {...domProps}
325
409
  ref={setRef}
326
410
  className={classNames('rt-ShellSidebar', className)}
327
411
  data-mode={shell.sidebarMode}
328
412
  data-peek={shell.peekTarget === 'sidebar' || undefined}
329
- data-presentation={resolvedPresentation}
330
- data-open={(isStacked && isContentVisible) || undefined}
413
+ data-presentation={shell.currentBreakpointReady ? resolvedPresentation : undefined}
414
+ data-open={(shell.currentBreakpointReady && isStacked && isContentVisible) || undefined}
331
415
  style={{
332
416
  ...style,
333
417
  ['--sidebar-size' as any]: `${expandedSize}px`,
@@ -357,7 +441,7 @@ export const Sidebar = React.forwardRef<
357
441
  : {}),
358
442
  }}
359
443
  >
360
- <div className="rt-ShellSidebarContent" data-visible={isContentVisible || undefined} data-phase={transitionPhase && transitionPhase !== 'idle' ? transitionPhase : undefined}>
444
+ <div className="rt-ShellSidebarContent" data-visible={isContentVisible || undefined}>
361
445
  {contentChildren}
362
446
  </div>
363
447
  {handleEl}