@womp/kookie-ui 0.3.23 → 0.3.24
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/components.css +105 -206
- package/dist/cjs/components/index.js +5 -5
- package/dist/cjs/components/index.js.map +3 -3
- package/dist/cjs/components/shell.d.ts.map +1 -1
- package/dist/cjs/components/shell.js +1 -1
- package/dist/cjs/components/shell.js.map +3 -3
- package/dist/cjs/index.js +5 -5
- package/dist/cjs/index.js.map +3 -3
- package/dist/esm/chunk-D2IX5LU6.js +2 -0
- package/dist/esm/chunk-D2IX5LU6.js.map +7 -0
- package/dist/esm/components/index.js +1 -1
- package/dist/esm/components/shell.d.ts.map +1 -1
- package/dist/esm/components/shell.js +1 -1
- package/dist/esm/index.js +1 -1
- package/package.json +1 -1
- 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/shell.tsx +144 -97
- package/styles.css +105 -206
- package/dist/esm/chunk-RMBS6CAO.js +0 -2
- package/dist/esm/chunk-RMBS6CAO.js.map +0 -7
package/src/components/shell.tsx
CHANGED
|
@@ -222,6 +222,113 @@ interface ShellRootProps extends React.ComponentPropsWithoutRef<'div'> {
|
|
|
222
222
|
height?: 'full' | 'auto' | string | number;
|
|
223
223
|
}
|
|
224
224
|
|
|
225
|
+
/**
|
|
226
|
+
* Read the pane modes a set of Shell children asks for.
|
|
227
|
+
*
|
|
228
|
+
* Runs once at mount, for the reducer's initial state, and again whenever a
|
|
229
|
+
* pane family enters after mount. Pure - it only reads the children's props.
|
|
230
|
+
*/
|
|
231
|
+
function readPaneStateFromChildren(initialChildren: React.ReactNode): PaneState & { hasPanelDefaultOpen: boolean } {
|
|
232
|
+
const childArray = React.Children.toArray(initialChildren) as React.ReactElement[];
|
|
233
|
+
|
|
234
|
+
// Compute initial defaults from immediate children (one-time, uncontrolled defaults)
|
|
235
|
+
const hasPanelDefaultOpen = childArray.some(
|
|
236
|
+
(el) => React.isValidElement(el) && (el as any).type?.displayName === 'Shell.Panel' && Boolean((el as any).props?.defaultOpen),
|
|
237
|
+
);
|
|
238
|
+
|
|
239
|
+
// Rail defaults to open (true) unless explicitly set to false
|
|
240
|
+
// Supports responsive objects: { initial: false, md: true }
|
|
241
|
+
const railEl = childArray.find((el) => React.isValidElement(el) && (el as any).type?.displayName === 'Shell.Rail');
|
|
242
|
+
const railDefaultOpen = railEl ? (railEl as any).props?.defaultOpen : undefined;
|
|
243
|
+
const hasRailDefaultOpen = (() => {
|
|
244
|
+
if (!railEl) return false;
|
|
245
|
+
if (railDefaultOpen === undefined) return true; // Default to open
|
|
246
|
+
if (typeof railDefaultOpen === 'boolean') return railDefaultOpen;
|
|
247
|
+
// Responsive object - use 'initial' value, or first defined value, or default true
|
|
248
|
+
if (typeof railDefaultOpen === 'object') {
|
|
249
|
+
return railDefaultOpen.initial ?? Object.values(railDefaultOpen)[0] ?? true;
|
|
250
|
+
}
|
|
251
|
+
return true;
|
|
252
|
+
})();
|
|
253
|
+
|
|
254
|
+
const hasInspectorDefaultOpen = childArray.some(
|
|
255
|
+
(el) => React.isValidElement(el) && (el as any).type?.displayName === 'Shell.Inspector' && Boolean((el as any).props?.defaultOpen),
|
|
256
|
+
);
|
|
257
|
+
const hasInspectorOpenControlled = childArray.some(
|
|
258
|
+
(el) =>
|
|
259
|
+
React.isValidElement(el) &&
|
|
260
|
+
(el as any).type?.displayName === 'Shell.Inspector' &&
|
|
261
|
+
typeof (el as any).props?.open !== 'undefined' &&
|
|
262
|
+
Boolean((el as any).props?.open),
|
|
263
|
+
);
|
|
264
|
+
|
|
265
|
+
// Detect Panel controlled open state for initial reducer state
|
|
266
|
+
const hasPanelOpenControlled = childArray.some((el) => {
|
|
267
|
+
if (!React.isValidElement(el) || (el as any).type?.displayName !== 'Shell.Panel') return false;
|
|
268
|
+
const openProp = (el as any).props?.open;
|
|
269
|
+
if (typeof openProp === 'undefined') return false;
|
|
270
|
+
if (typeof openProp === 'boolean') return openProp;
|
|
271
|
+
// Responsive object - check 'initial' or first truthy value
|
|
272
|
+
if (typeof openProp === 'object' && openProp !== null) {
|
|
273
|
+
return openProp.initial ?? Object.values(openProp)[0] ?? false;
|
|
274
|
+
}
|
|
275
|
+
return false;
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
// Detect Sidebar initial state from props
|
|
279
|
+
const getSidebarInitialState = (): SidebarMode => {
|
|
280
|
+
const sidebarEl = childArray.find((el) => React.isValidElement(el) && (el as any).type?.displayName === 'Shell.Sidebar');
|
|
281
|
+
if (!sidebarEl) return 'expanded';
|
|
282
|
+
const sidebarProps = (sidebarEl as any).props;
|
|
283
|
+
// Check controlled state first
|
|
284
|
+
if (typeof sidebarProps?.state !== 'undefined') {
|
|
285
|
+
if (typeof sidebarProps.state === 'string') return sidebarProps.state as SidebarMode;
|
|
286
|
+
// Responsive object - use 'initial' breakpoint or first defined value
|
|
287
|
+
if (typeof sidebarProps.state === 'object') {
|
|
288
|
+
return (sidebarProps.state.initial ?? Object.values(sidebarProps.state)[0] ?? 'expanded') as SidebarMode;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
// Check defaultState
|
|
292
|
+
if (typeof sidebarProps?.defaultState !== 'undefined') {
|
|
293
|
+
if (typeof sidebarProps.defaultState === 'string') return sidebarProps.defaultState as SidebarMode;
|
|
294
|
+
if (typeof sidebarProps.defaultState === 'object') {
|
|
295
|
+
return (sidebarProps.defaultState.initial ?? Object.values(sidebarProps.defaultState)[0] ?? 'expanded') as SidebarMode;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
return 'expanded';
|
|
299
|
+
};
|
|
300
|
+
|
|
301
|
+
// Detect Bottom initial state from props
|
|
302
|
+
const getBottomInitialState = (): PaneMode => {
|
|
303
|
+
const bottomEl = childArray.find((el) => React.isValidElement(el) && (el as any).type?.displayName === 'Shell.Bottom');
|
|
304
|
+
if (!bottomEl) return 'collapsed';
|
|
305
|
+
const bottomProps = (bottomEl as any).props;
|
|
306
|
+
// Check controlled open first
|
|
307
|
+
if (typeof bottomProps?.open !== 'undefined') {
|
|
308
|
+
if (typeof bottomProps.open === 'boolean') return bottomProps.open ? 'expanded' : 'collapsed';
|
|
309
|
+
// Responsive object - use 'initial' breakpoint or first defined value
|
|
310
|
+
if (typeof bottomProps.open === 'object') {
|
|
311
|
+
const val = bottomProps.open.initial ?? Object.values(bottomProps.open)[0];
|
|
312
|
+
return val ? 'expanded' : 'collapsed';
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
// Check defaultOpen
|
|
316
|
+
if (typeof bottomProps?.defaultOpen !== 'undefined') {
|
|
317
|
+
return bottomProps.defaultOpen ? 'expanded' : 'collapsed';
|
|
318
|
+
}
|
|
319
|
+
return 'collapsed';
|
|
320
|
+
};
|
|
321
|
+
|
|
322
|
+
return {
|
|
323
|
+
hasPanelDefaultOpen,
|
|
324
|
+
leftMode: hasPanelDefaultOpen || hasPanelOpenControlled || hasRailDefaultOpen ? 'expanded' : ('collapsed' as PaneMode),
|
|
325
|
+
panelMode: hasPanelDefaultOpen || hasPanelOpenControlled ? 'expanded' : ('collapsed' as PaneMode),
|
|
326
|
+
sidebarMode: getSidebarInitialState(),
|
|
327
|
+
inspectorMode: hasInspectorDefaultOpen || hasInspectorOpenControlled ? 'expanded' : ('collapsed' as PaneMode),
|
|
328
|
+
bottomMode: getBottomInitialState(),
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
|
|
225
332
|
const Root = React.forwardRef<HTMLDivElement, ShellRootProps>(({ className, children, height = 'full', ...props }, ref) => {
|
|
226
333
|
const { breakpoint: currentBreakpoint, ready: currentBreakpointReady } = useBreakpoint();
|
|
227
334
|
|
|
@@ -231,105 +338,10 @@ const Root = React.forwardRef<HTMLDivElement, ShellRootProps>(({ className, chil
|
|
|
231
338
|
// Pane state management via reducer with lazy initialization
|
|
232
339
|
// This computation only runs once on mount, not on every render
|
|
233
340
|
const [paneState, dispatchPane] = React.useReducer(paneReducer, children, (initialChildren) => {
|
|
234
|
-
const
|
|
235
|
-
|
|
236
|
-
// Compute initial defaults from immediate children (one-time, uncontrolled defaults)
|
|
237
|
-
const hasPanelDefaultOpen = childArray.some(
|
|
238
|
-
(el) => React.isValidElement(el) && (el as any).type?.displayName === 'Shell.Panel' && Boolean((el as any).props?.defaultOpen),
|
|
239
|
-
);
|
|
341
|
+
const { hasPanelDefaultOpen, ...modes } = readPaneStateFromChildren(initialChildren);
|
|
240
342
|
// Store for use in passthrough props
|
|
241
343
|
hasPanelDefaultOpenRef.current = hasPanelDefaultOpen;
|
|
242
|
-
|
|
243
|
-
// Rail defaults to open (true) unless explicitly set to false
|
|
244
|
-
// Supports responsive objects: { initial: false, md: true }
|
|
245
|
-
const railEl = childArray.find((el) => React.isValidElement(el) && (el as any).type?.displayName === 'Shell.Rail');
|
|
246
|
-
const railDefaultOpen = railEl ? (railEl as any).props?.defaultOpen : undefined;
|
|
247
|
-
const hasRailDefaultOpen = (() => {
|
|
248
|
-
if (!railEl) return false;
|
|
249
|
-
if (railDefaultOpen === undefined) return true; // Default to open
|
|
250
|
-
if (typeof railDefaultOpen === 'boolean') return railDefaultOpen;
|
|
251
|
-
// Responsive object - use 'initial' value, or first defined value, or default true
|
|
252
|
-
if (typeof railDefaultOpen === 'object') {
|
|
253
|
-
return railDefaultOpen.initial ?? Object.values(railDefaultOpen)[0] ?? true;
|
|
254
|
-
}
|
|
255
|
-
return true;
|
|
256
|
-
})();
|
|
257
|
-
|
|
258
|
-
const hasInspectorDefaultOpen = childArray.some(
|
|
259
|
-
(el) => React.isValidElement(el) && (el as any).type?.displayName === 'Shell.Inspector' && Boolean((el as any).props?.defaultOpen),
|
|
260
|
-
);
|
|
261
|
-
const hasInspectorOpenControlled = childArray.some(
|
|
262
|
-
(el) =>
|
|
263
|
-
React.isValidElement(el) &&
|
|
264
|
-
(el as any).type?.displayName === 'Shell.Inspector' &&
|
|
265
|
-
typeof (el as any).props?.open !== 'undefined' &&
|
|
266
|
-
Boolean((el as any).props?.open),
|
|
267
|
-
);
|
|
268
|
-
|
|
269
|
-
// Detect Panel controlled open state for initial reducer state
|
|
270
|
-
const hasPanelOpenControlled = childArray.some((el) => {
|
|
271
|
-
if (!React.isValidElement(el) || (el as any).type?.displayName !== 'Shell.Panel') return false;
|
|
272
|
-
const openProp = (el as any).props?.open;
|
|
273
|
-
if (typeof openProp === 'undefined') return false;
|
|
274
|
-
if (typeof openProp === 'boolean') return openProp;
|
|
275
|
-
// Responsive object - check 'initial' or first truthy value
|
|
276
|
-
if (typeof openProp === 'object' && openProp !== null) {
|
|
277
|
-
return openProp.initial ?? Object.values(openProp)[0] ?? false;
|
|
278
|
-
}
|
|
279
|
-
return false;
|
|
280
|
-
});
|
|
281
|
-
|
|
282
|
-
// Detect Sidebar initial state from props
|
|
283
|
-
const getSidebarInitialState = (): SidebarMode => {
|
|
284
|
-
const sidebarEl = childArray.find((el) => React.isValidElement(el) && (el as any).type?.displayName === 'Shell.Sidebar');
|
|
285
|
-
if (!sidebarEl) return 'expanded';
|
|
286
|
-
const sidebarProps = (sidebarEl as any).props;
|
|
287
|
-
// Check controlled state first
|
|
288
|
-
if (typeof sidebarProps?.state !== 'undefined') {
|
|
289
|
-
if (typeof sidebarProps.state === 'string') return sidebarProps.state as SidebarMode;
|
|
290
|
-
// Responsive object - use 'initial' breakpoint or first defined value
|
|
291
|
-
if (typeof sidebarProps.state === 'object') {
|
|
292
|
-
return (sidebarProps.state.initial ?? Object.values(sidebarProps.state)[0] ?? 'expanded') as SidebarMode;
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
// Check defaultState
|
|
296
|
-
if (typeof sidebarProps?.defaultState !== 'undefined') {
|
|
297
|
-
if (typeof sidebarProps.defaultState === 'string') return sidebarProps.defaultState as SidebarMode;
|
|
298
|
-
if (typeof sidebarProps.defaultState === 'object') {
|
|
299
|
-
return (sidebarProps.defaultState.initial ?? Object.values(sidebarProps.defaultState)[0] ?? 'expanded') as SidebarMode;
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
return 'expanded';
|
|
303
|
-
};
|
|
304
|
-
|
|
305
|
-
// Detect Bottom initial state from props
|
|
306
|
-
const getBottomInitialState = (): PaneMode => {
|
|
307
|
-
const bottomEl = childArray.find((el) => React.isValidElement(el) && (el as any).type?.displayName === 'Shell.Bottom');
|
|
308
|
-
if (!bottomEl) return 'collapsed';
|
|
309
|
-
const bottomProps = (bottomEl as any).props;
|
|
310
|
-
// Check controlled open first
|
|
311
|
-
if (typeof bottomProps?.open !== 'undefined') {
|
|
312
|
-
if (typeof bottomProps.open === 'boolean') return bottomProps.open ? 'expanded' : 'collapsed';
|
|
313
|
-
// Responsive object - use 'initial' breakpoint or first defined value
|
|
314
|
-
if (typeof bottomProps.open === 'object') {
|
|
315
|
-
const val = bottomProps.open.initial ?? Object.values(bottomProps.open)[0];
|
|
316
|
-
return val ? 'expanded' : 'collapsed';
|
|
317
|
-
}
|
|
318
|
-
}
|
|
319
|
-
// Check defaultOpen
|
|
320
|
-
if (typeof bottomProps?.defaultOpen !== 'undefined') {
|
|
321
|
-
return bottomProps.defaultOpen ? 'expanded' : 'collapsed';
|
|
322
|
-
}
|
|
323
|
-
return 'collapsed';
|
|
324
|
-
};
|
|
325
|
-
|
|
326
|
-
return {
|
|
327
|
-
leftMode: hasPanelDefaultOpen || hasPanelOpenControlled || hasRailDefaultOpen ? 'expanded' : ('collapsed' as PaneMode),
|
|
328
|
-
panelMode: hasPanelDefaultOpen || hasPanelOpenControlled ? 'expanded' : ('collapsed' as PaneMode),
|
|
329
|
-
sidebarMode: getSidebarInitialState(),
|
|
330
|
-
inspectorMode: hasInspectorDefaultOpen || hasInspectorOpenControlled ? 'expanded' : ('collapsed' as PaneMode),
|
|
331
|
-
bottomMode: getBottomInitialState(),
|
|
332
|
-
};
|
|
344
|
+
return modes;
|
|
333
345
|
});
|
|
334
346
|
const setLeftMode = React.useCallback((mode: PaneMode) => dispatchPane({ type: 'SET_LEFT_MODE', mode }), []);
|
|
335
347
|
const setPanelMode = React.useCallback((mode: PaneMode) => dispatchPane({ type: 'SET_PANEL_MODE', mode }), []);
|
|
@@ -381,6 +393,41 @@ const Root = React.forwardRef<HTMLDivElement, ShellRootProps>(({ className, chil
|
|
|
381
393
|
return childArray.some((el) => isShellComponentType(el, Sidebar));
|
|
382
394
|
}, [children]);
|
|
383
395
|
|
|
396
|
+
// Re-read the defaults when a pane family enters after mount.
|
|
397
|
+
//
|
|
398
|
+
// The initial modes are computed once, from the children present at mount.
|
|
399
|
+
// That is right until an app swaps families at runtime - a Sidebar giving
|
|
400
|
+
// way to Rail+Panel on one route, and back on the next. The entering pane
|
|
401
|
+
// then renders against the mode left behind by the family that just went:
|
|
402
|
+
// a Rail mounting 'collapsed', at width 0, with its content faded out, put
|
|
403
|
+
// right one layout effect later. The reader sees a flicker in place where
|
|
404
|
+
// the pane should have been there already.
|
|
405
|
+
//
|
|
406
|
+
// Adjusting state during render keeps this out of the paint: React re-runs
|
|
407
|
+
// Root before the children render, so the entering pane never sees the
|
|
408
|
+
// mode of the family it replaced. The ref guard is what makes the update
|
|
409
|
+
// terminate - it fires only on the edge, never on a steady render.
|
|
410
|
+
const enteredFamilyRef = React.useRef({ left: hasLeftChildren, sidebar: hasSidebarChildren });
|
|
411
|
+
if (enteredFamilyRef.current.left !== hasLeftChildren || enteredFamilyRef.current.sidebar !== hasSidebarChildren) {
|
|
412
|
+
const leftEntered = hasLeftChildren && !enteredFamilyRef.current.left;
|
|
413
|
+
const sidebarEntered = hasSidebarChildren && !enteredFamilyRef.current.sidebar;
|
|
414
|
+
enteredFamilyRef.current = { left: hasLeftChildren, sidebar: hasSidebarChildren };
|
|
415
|
+
|
|
416
|
+
if (leftEntered || sidebarEntered) {
|
|
417
|
+
const { hasPanelDefaultOpen, ...modes } = readPaneStateFromChildren(children);
|
|
418
|
+
if (leftEntered) {
|
|
419
|
+
hasPanelDefaultOpenRef.current = hasPanelDefaultOpen;
|
|
420
|
+
// Only when the mode actually differs, so a family that enters
|
|
421
|
+
// already in the right mode dispatches nothing.
|
|
422
|
+
if (modes.leftMode !== paneState.leftMode) dispatchPane({ type: 'SET_LEFT_MODE', mode: modes.leftMode });
|
|
423
|
+
if (modes.panelMode !== paneState.panelMode) dispatchPane({ type: 'SET_PANEL_MODE', mode: modes.panelMode });
|
|
424
|
+
}
|
|
425
|
+
if (sidebarEntered && modes.sidebarMode !== paneState.sidebarMode) {
|
|
426
|
+
dispatchPane({ type: 'SET_SIDEBAR_MODE', mode: modes.sidebarMode });
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
|
|
384
431
|
// Keep a ref to sidebar mode so togglePane doesn't depend on it,
|
|
385
432
|
// preventing ActionsContext churn on every sidebar mode change
|
|
386
433
|
const sidebarModeRef = React.useRef(paneState.sidebarMode);
|