@kushagradhawan/kookie-ui 0.1.72 → 0.1.74

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 (63) hide show
  1. package/components.css +6 -2
  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-handles.d.ts.map +1 -1
  6. package/dist/cjs/components/_internal/shell-handles.js +1 -1
  7. package/dist/cjs/components/_internal/shell-handles.js.map +3 -3
  8. package/dist/cjs/components/_internal/shell-inspector.d.ts.map +1 -1
  9. package/dist/cjs/components/_internal/shell-inspector.js +1 -1
  10. package/dist/cjs/components/_internal/shell-inspector.js.map +3 -3
  11. package/dist/cjs/components/_internal/shell-sidebar.d.ts.map +1 -1
  12. package/dist/cjs/components/_internal/shell-sidebar.js +1 -1
  13. package/dist/cjs/components/_internal/shell-sidebar.js.map +3 -3
  14. package/dist/cjs/components/shell.d.ts.map +1 -1
  15. package/dist/cjs/components/shell.js +1 -1
  16. package/dist/cjs/components/shell.js.map +3 -3
  17. package/dist/cjs/helpers/index.d.ts +1 -0
  18. package/dist/cjs/helpers/index.d.ts.map +1 -1
  19. package/dist/cjs/helpers/index.js +1 -1
  20. package/dist/cjs/helpers/index.js.map +2 -2
  21. package/dist/cjs/helpers/normalize-to-px.d.ts +10 -0
  22. package/dist/cjs/helpers/normalize-to-px.d.ts.map +1 -0
  23. package/dist/cjs/helpers/normalize-to-px.js +2 -0
  24. package/dist/cjs/helpers/normalize-to-px.js.map +7 -0
  25. package/dist/esm/components/_internal/shell-bottom.d.ts.map +1 -1
  26. package/dist/esm/components/_internal/shell-bottom.js +1 -1
  27. package/dist/esm/components/_internal/shell-bottom.js.map +3 -3
  28. package/dist/esm/components/_internal/shell-handles.d.ts.map +1 -1
  29. package/dist/esm/components/_internal/shell-handles.js +1 -1
  30. package/dist/esm/components/_internal/shell-handles.js.map +3 -3
  31. package/dist/esm/components/_internal/shell-inspector.d.ts.map +1 -1
  32. package/dist/esm/components/_internal/shell-inspector.js +1 -1
  33. package/dist/esm/components/_internal/shell-inspector.js.map +3 -3
  34. package/dist/esm/components/_internal/shell-sidebar.d.ts.map +1 -1
  35. package/dist/esm/components/_internal/shell-sidebar.js +1 -1
  36. package/dist/esm/components/_internal/shell-sidebar.js.map +3 -3
  37. package/dist/esm/components/shell.d.ts.map +1 -1
  38. package/dist/esm/components/shell.js +1 -1
  39. package/dist/esm/components/shell.js.map +3 -3
  40. package/dist/esm/helpers/index.d.ts +1 -0
  41. package/dist/esm/helpers/index.d.ts.map +1 -1
  42. package/dist/esm/helpers/index.js +1 -1
  43. package/dist/esm/helpers/index.js.map +2 -2
  44. package/dist/esm/helpers/normalize-to-px.d.ts +10 -0
  45. package/dist/esm/helpers/normalize-to-px.d.ts.map +1 -0
  46. package/dist/esm/helpers/normalize-to-px.js +2 -0
  47. package/dist/esm/helpers/normalize-to-px.js.map +7 -0
  48. package/package.json +1 -1
  49. package/schemas/base-button.json +1 -1
  50. package/schemas/button.json +1 -1
  51. package/schemas/icon-button.json +1 -1
  52. package/schemas/index.json +6 -6
  53. package/schemas/toggle-button.json +1 -1
  54. package/schemas/toggle-icon-button.json +1 -1
  55. package/src/components/_internal/shell-bottom.tsx +60 -30
  56. package/src/components/_internal/shell-handles.tsx +6 -1
  57. package/src/components/_internal/shell-inspector.tsx +60 -30
  58. package/src/components/_internal/shell-sidebar.tsx +62 -31
  59. package/src/components/shell.css +10 -11
  60. package/src/components/shell.tsx +83 -32
  61. package/src/helpers/index.ts +1 -0
  62. package/src/helpers/normalize-to-px.ts +42 -0
  63. package/styles.css +6 -2
@@ -38,6 +38,7 @@ import { Bottom } from './_internal/shell-bottom.js';
38
38
  import { Inspector } from './_internal/shell-inspector.js';
39
39
  import type { PresentationValue, ResponsivePresentation, PaneMode, SidebarMode, PaneSizePersistence, Breakpoint, PaneTarget, Responsive, PaneBaseProps } from './shell.types.js';
40
40
  import { _BREAKPOINTS } from './shell.types.js';
41
+ import { normalizeToPx } from '../helpers/normalize-to-px.js';
41
42
  import {
42
43
  ShellProvider,
43
44
  useShell,
@@ -265,13 +266,57 @@ const Root = React.forwardRef<HTMLDivElement, ShellRootProps>(({ className, chil
265
266
  (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
267
  );
267
268
 
269
+ // Detect Sidebar initial state from props
270
+ const getSidebarInitialState = (): SidebarMode => {
271
+ const sidebarEl = initialChildren.find((el) => React.isValidElement(el) && (el as any).type?.displayName === 'Shell.Sidebar');
272
+ if (!sidebarEl) return 'expanded';
273
+ const props = (sidebarEl as any).props;
274
+ // Check controlled state first
275
+ if (typeof props?.state !== 'undefined') {
276
+ if (typeof props.state === 'string') return props.state as SidebarMode;
277
+ // Responsive object - use 'initial' breakpoint or first defined value
278
+ if (typeof props.state === 'object') {
279
+ return (props.state.initial ?? Object.values(props.state)[0] ?? 'expanded') as SidebarMode;
280
+ }
281
+ }
282
+ // Check defaultState
283
+ if (typeof props?.defaultState !== 'undefined') {
284
+ if (typeof props.defaultState === 'string') return props.defaultState as SidebarMode;
285
+ if (typeof props.defaultState === 'object') {
286
+ return (props.defaultState.initial ?? Object.values(props.defaultState)[0] ?? 'expanded') as SidebarMode;
287
+ }
288
+ }
289
+ return 'expanded';
290
+ };
291
+
292
+ // Detect Bottom initial state from props
293
+ const getBottomInitialState = (): PaneMode => {
294
+ const bottomEl = initialChildren.find((el) => React.isValidElement(el) && (el as any).type?.displayName === 'Shell.Bottom');
295
+ if (!bottomEl) return 'collapsed';
296
+ const props = (bottomEl as any).props;
297
+ // Check controlled open first
298
+ if (typeof props?.open !== 'undefined') {
299
+ if (typeof props.open === 'boolean') return props.open ? 'expanded' : 'collapsed';
300
+ // Responsive object - use 'initial' breakpoint or first defined value
301
+ if (typeof props.open === 'object') {
302
+ const val = props.open.initial ?? Object.values(props.open)[0];
303
+ return val ? 'expanded' : 'collapsed';
304
+ }
305
+ }
306
+ // Check defaultOpen
307
+ if (typeof props?.defaultOpen !== 'undefined') {
308
+ return props.defaultOpen ? 'expanded' : 'collapsed';
309
+ }
310
+ return 'collapsed';
311
+ };
312
+
268
313
  // Pane state management via reducer
269
314
  const [paneState, dispatchPane] = React.useReducer(paneReducer, {
270
315
  leftMode: hasPanelDefaultOpen || hasRailDefaultOpen ? 'expanded' : 'collapsed',
271
316
  panelMode: hasPanelDefaultOpen ? 'expanded' : 'collapsed',
272
- sidebarMode: 'expanded',
317
+ sidebarMode: getSidebarInitialState(),
273
318
  inspectorMode: hasInspectorDefaultOpen || hasInspectorOpenControlled ? 'expanded' : 'collapsed',
274
- bottomMode: 'collapsed',
319
+ bottomMode: getBottomInitialState(),
275
320
  });
276
321
  const setLeftMode = React.useCallback((mode: PaneMode) => dispatchPane({ type: 'SET_LEFT_MODE', mode }), []);
277
322
  const setPanelMode = React.useCallback((mode: PaneMode) => dispatchPane({ type: 'SET_PANEL_MODE', mode }), []);
@@ -867,15 +912,26 @@ const Panel = assignShellSlot(
867
912
  sizeUpdateMs = 50,
868
913
  } = initialProps;
869
914
  const panelDomProps = extractPaneDomProps(initialProps, PANEL_DOM_PROP_KEYS);
915
+ // Ref for debounce cleanup
916
+ const debounceTimeoutRef = React.useRef<ReturnType<typeof setTimeout> | null>(null);
917
+ // Cleanup debounce timeout on unmount or when dependencies change
918
+ React.useEffect(() => {
919
+ return () => {
920
+ if (debounceTimeoutRef.current) {
921
+ clearTimeout(debounceTimeoutRef.current);
922
+ debounceTimeoutRef.current = null;
923
+ }
924
+ };
925
+ }, [onSizeChange, sizeUpdate, sizeUpdateMs]);
870
926
  // Throttled/debounced emitter for onSizeChange
871
927
  const emitSizeChange = React.useMemo(() => {
872
928
  if (!onSizeChange) return () => {};
873
929
  if (sizeUpdate === 'debounce') {
874
- let t: any = null;
875
930
  const fn = (s: number, meta: PanelSizeChangeMeta) => {
876
- if (t) clearTimeout(t);
877
- t = setTimeout(() => {
931
+ if (debounceTimeoutRef.current) clearTimeout(debounceTimeoutRef.current);
932
+ debounceTimeoutRef.current = setTimeout(() => {
878
933
  onSizeChange?.(s, meta);
934
+ debounceTimeoutRef.current = null;
879
935
  }, sizeUpdateMs);
880
936
  };
881
937
  return fn;
@@ -934,12 +990,16 @@ const Panel = assignShellSlot(
934
990
  }, [shell, open]);
935
991
 
936
992
  // Dev-only warning if switching controlled/uncontrolled between renders
993
+ const wasControlledRef = React.useRef<boolean | null>(null);
937
994
  React.useEffect(() => {
938
995
  const isControlled = typeof open !== 'undefined';
939
- (Panel as any)._wasControlled = (Panel as any)._wasControlled ?? isControlled;
940
- if ((Panel as any)._wasControlled !== isControlled) {
996
+ if (wasControlledRef.current === null) {
997
+ wasControlledRef.current = isControlled;
998
+ return;
999
+ }
1000
+ if (wasControlledRef.current !== isControlled) {
941
1001
  console.warn('Shell.Panel: Switching between controlled and uncontrolled `open` is not supported.');
942
- (Panel as any)._wasControlled = isControlled;
1002
+ wasControlledRef.current = isControlled;
943
1003
  }
944
1004
  }, [open]);
945
1005
 
@@ -971,26 +1031,8 @@ const Panel = assignShellSlot(
971
1031
 
972
1032
  const isOverlay = shell.leftResolvedPresentation === 'overlay';
973
1033
 
974
- // Normalize CSS lengths to px
975
- const normalizeToPx = React.useCallback((value: number | string | undefined): number | undefined => {
976
- if (value == null) return undefined;
977
- if (typeof value === 'number' && Number.isFinite(value)) return value;
978
- const str = String(value).trim();
979
- if (!str) return undefined;
980
- if (str.endsWith('px')) return Number.parseFloat(str);
981
- if (str.endsWith('rem')) {
982
- const rem = Number.parseFloat(getComputedStyle(document.documentElement).fontSize || '16') || 16;
983
- return Number.parseFloat(str) * rem;
984
- }
985
- if (str.endsWith('%')) {
986
- const pct = Number.parseFloat(str);
987
- const base = document.documentElement.clientWidth || window.innerWidth || 0;
988
- return (pct / 100) * base;
989
- }
990
- // Bare number-like string
991
- const n = Number.parseFloat(str);
992
- return Number.isFinite(n) ? n : undefined;
993
- }, []);
1034
+ // Normalize CSS lengths to px helper
1035
+ const normalizeSizeToPx = React.useCallback((value: number | string | undefined) => normalizeToPx(value, 'horizontal'), []);
994
1036
 
995
1037
  // Derive a default persistence adapter from paneId if none provided
996
1038
  const persistenceAdapter = React.useMemo(() => {
@@ -1038,7 +1080,7 @@ const Panel = assignShellSlot(
1038
1080
  React.useEffect(() => {
1039
1081
  if (!localRef.current) return;
1040
1082
  if (typeof size === 'undefined' && typeof defaultSize !== 'undefined') {
1041
- const px = normalizeToPx(defaultSize);
1083
+ const px = normalizeSizeToPx(defaultSize);
1042
1084
  if (typeof px === 'number' && Number.isFinite(px)) {
1043
1085
  // Clamp to min/max if provided
1044
1086
  const minPx = typeof minSize === 'number' ? minSize : undefined;
@@ -1055,7 +1097,7 @@ const Panel = assignShellSlot(
1055
1097
  React.useEffect(() => {
1056
1098
  if (!localRef.current) return;
1057
1099
  if (typeof size === 'undefined') return;
1058
- const px = normalizeToPx(size);
1100
+ const px = normalizeSizeToPx(size);
1059
1101
  if (typeof px === 'number' && Number.isFinite(px)) {
1060
1102
  const minPx = typeof minSize === 'number' ? minSize : undefined;
1061
1103
  const maxPx = typeof maxSize === 'number' ? maxSize : undefined;
@@ -1063,7 +1105,7 @@ const Panel = assignShellSlot(
1063
1105
  localRef.current.style.setProperty('--panel-size', `${clamped}px`);
1064
1106
  emitSizeChange(clamped, { reason: 'controlled' });
1065
1107
  }
1066
- }, [size, minSize, maxSize, normalizeToPx, emitSizeChange]);
1108
+ }, [size, minSize, maxSize, normalizeSizeToPx, emitSizeChange]);
1067
1109
 
1068
1110
  // Ensure Left container width is auto whenever Panel is expanded in fixed presentation
1069
1111
  React.useEffect(() => {
@@ -1258,7 +1300,16 @@ const Trigger = React.forwardRef<HTMLButtonElement, TriggerProps>(({ target, act
1258
1300
  );
1259
1301
 
1260
1302
  return (
1261
- <button {...props} ref={ref} onClick={handleClick} onMouseEnter={handleMouseEnter} onMouseLeave={handleMouseLeave} data-shell-trigger={target} data-shell-action={action}>
1303
+ <button
1304
+ {...props}
1305
+ ref={ref}
1306
+ onClick={handleClick}
1307
+ onMouseEnter={handleMouseEnter}
1308
+ onMouseLeave={handleMouseLeave}
1309
+ data-shell-trigger={target}
1310
+ data-shell-action={action}
1311
+ aria-expanded={!isCollapsed}
1312
+ >
1262
1313
  {children}
1263
1314
  </button>
1264
1315
  );
@@ -11,4 +11,5 @@ export * from './input-attributes.js';
11
11
  export * from './is-responsive-object.js';
12
12
  export * from './map-prop-values.js';
13
13
  export * from './merge-styles.js';
14
+ export * from './normalize-to-px.js';
14
15
  export * from './require-react-element.js';
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Normalize CSS length values to pixels.
3
+ * Supports: px, rem, %, and bare numbers.
4
+ *
5
+ * @param value - The value to normalize (number, string, or undefined)
6
+ * @param orientation - 'horizontal' for width-based % or 'vertical' for height-based %
7
+ * @returns The value in pixels, or undefined if invalid
8
+ */
9
+ export function normalizeToPx(
10
+ value: number | string | undefined,
11
+ orientation: 'horizontal' | 'vertical' = 'horizontal',
12
+ ): number | undefined {
13
+ if (value == null) return undefined;
14
+ if (typeof value === 'number' && Number.isFinite(value)) return value;
15
+
16
+ const str = String(value).trim();
17
+ if (!str) return undefined;
18
+
19
+ // px: direct parse
20
+ if (str.endsWith('px')) return Number.parseFloat(str);
21
+
22
+ // rem: multiply by root font size
23
+ if (str.endsWith('rem')) {
24
+ const rem = Number.parseFloat(getComputedStyle(document.documentElement).fontSize || '16') || 16;
25
+ return Number.parseFloat(str) * rem;
26
+ }
27
+
28
+ // %: calculate based on viewport dimension
29
+ if (str.endsWith('%')) {
30
+ const pct = Number.parseFloat(str);
31
+ const base =
32
+ orientation === 'horizontal'
33
+ ? document.documentElement.clientWidth || window.innerWidth || 0
34
+ : document.documentElement.clientHeight || window.innerHeight || 0;
35
+ return (pct / 100) * base;
36
+ }
37
+
38
+ // Bare number-like string
39
+ const n = Number.parseFloat(str);
40
+ return Number.isFinite(n) ? n : undefined;
41
+ }
42
+
package/styles.css CHANGED
@@ -22091,10 +22091,12 @@
22091
22091
  }
22092
22092
  .rt-ShellSidebar[data-mode='collapsed']{
22093
22093
  width: 0px;
22094
- transition-delay: var(--motion-duration-small);
22095
22094
  position: absolute;
22096
22095
  inset-block: 0;
22097
22096
  inset-inline-start: 0;
22097
+ flex-shrink: 0;
22098
+ flex-basis: 0;
22099
+ transition-delay: var(--motion-duration-small);
22098
22100
  }
22099
22101
  .rt-ShellSidebarContent{
22100
22102
  display: flex;
@@ -22162,10 +22164,12 @@
22162
22164
  }
22163
22165
  .rt-ShellInspector[data-mode='collapsed']{
22164
22166
  width: 0px;
22165
- transition-delay: var(--motion-duration-small);
22166
22167
  position: absolute;
22167
22168
  inset-block: 0;
22168
22169
  inset-inline-end: 0;
22170
+ flex-shrink: 0;
22171
+ flex-basis: 0;
22172
+ transition-delay: var(--motion-duration-small);
22169
22173
  }
22170
22174
  .rt-ShellInspectorContent{
22171
22175
  display: flex;