@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.
- 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/chatbar.d.ts +12 -3
- 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/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/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/chatbar.d.ts +12 -3
- 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/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/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/_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/chatbar.tsx +22 -6
- package/src/components/shell.context.tsx +1 -0
- package/src/components/shell.hooks.ts +67 -2
- package/src/components/shell.tsx +186 -200
- package/src/components/shell.types.ts +23 -0
|
@@ -3,39 +3,23 @@ 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,
|
|
6
|
+
import { useResponsivePresentation, useResponsiveInitialState } from '../shell.hooks.js';
|
|
7
7
|
import { PaneResizeContext } from './shell-resize.js';
|
|
8
|
+
import { extractPaneDomProps } from './shell-prop-helpers.js';
|
|
8
9
|
import { SidebarHandle, PaneHandle } from './shell-handles.js';
|
|
9
|
-
import type { Breakpoint, PaneMode, PaneSizePersistence, ResponsivePresentation, SidebarMode, Responsive } from '../shell.types.js';
|
|
10
|
+
import type { Breakpoint, PaneMode, PaneSizePersistence, ResponsivePresentation, SidebarMode, Responsive, PaneBaseProps } from '../shell.types.js';
|
|
10
11
|
import { _BREAKPOINTS } from '../shell.types.js';
|
|
11
12
|
|
|
12
|
-
|
|
13
|
-
presentation?: ResponsivePresentation;
|
|
13
|
+
type SidebarPaneProps = PaneBaseProps & {
|
|
14
14
|
mode?: PaneMode;
|
|
15
15
|
defaultMode?: any;
|
|
16
16
|
onModeChange?: (mode: PaneMode | SidebarMode) => void;
|
|
17
|
-
|
|
18
|
-
minSize?: number;
|
|
19
|
-
maxSize?: number;
|
|
20
|
-
resizable?: boolean;
|
|
21
|
-
collapsible?: boolean;
|
|
22
|
-
onExpand?: () => void;
|
|
23
|
-
onCollapse?: () => void;
|
|
24
|
-
onResize?: (size: number) => void;
|
|
25
|
-
resizer?: React.ReactNode;
|
|
26
|
-
onResizeStart?: (size: number) => void;
|
|
27
|
-
onResizeEnd?: (size: number) => void;
|
|
28
|
-
snapPoints?: number[];
|
|
29
|
-
snapTolerance?: number;
|
|
30
|
-
collapseThreshold?: number;
|
|
31
|
-
paneId?: string;
|
|
32
|
-
persistence?: PaneSizePersistence;
|
|
33
|
-
}
|
|
17
|
+
};
|
|
34
18
|
|
|
35
19
|
type SidebarStateChangeMeta = { reason: 'init' | 'toggle' | 'responsive' };
|
|
36
20
|
type SidebarControlledProps = { state: Responsive<SidebarMode>; onStateChange?: (state: SidebarMode, meta: SidebarStateChangeMeta) => void; defaultState?: never };
|
|
37
21
|
type SidebarUncontrolledProps = { defaultState?: SidebarMode | Partial<Record<Breakpoint, SidebarMode>>; onStateChange?: (state: SidebarMode, meta: SidebarStateChangeMeta) => void; state?: never };
|
|
38
|
-
type SidebarPublicProps = Omit<
|
|
22
|
+
type SidebarPublicProps = Omit<SidebarPaneProps, 'mode' | 'defaultMode' | 'onModeChange'> & {
|
|
39
23
|
// removed legacy mode props
|
|
40
24
|
thinSize?: number;
|
|
41
25
|
toggleModes?: 'both' | 'single';
|
|
@@ -49,406 +33,408 @@ type SidebarPublicProps = Omit<PaneProps, 'mode' | 'defaultMode' | 'onModeChange
|
|
|
49
33
|
|
|
50
34
|
type SidebarComponent = React.ForwardRefExoticComponent<SidebarPublicProps & React.RefAttributes<HTMLDivElement>> & { Handle: typeof SidebarHandle };
|
|
51
35
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
onResizeEnd,
|
|
68
|
-
snapPoints,
|
|
69
|
-
snapTolerance,
|
|
70
|
-
collapseThreshold,
|
|
71
|
-
paneId,
|
|
72
|
-
persistence,
|
|
73
|
-
children,
|
|
74
|
-
style,
|
|
75
|
-
thinSize = 64,
|
|
76
|
-
toggleModes,
|
|
77
|
-
// new state props (XOR)
|
|
78
|
-
state,
|
|
79
|
-
defaultState,
|
|
80
|
-
onStateChange,
|
|
81
|
-
...props
|
|
82
|
-
},
|
|
83
|
-
ref,
|
|
84
|
-
) => {
|
|
85
|
-
const shell = useShell();
|
|
86
|
-
const resolvedPresentation = useResponsivePresentation(presentation);
|
|
87
|
-
const isOverlay = resolvedPresentation === 'overlay';
|
|
88
|
-
const isStacked = resolvedPresentation === 'stacked';
|
|
89
|
-
// Phase sequencing is now CSS-driven; no JS-managed phase
|
|
90
|
-
const localRef = React.useRef<HTMLDivElement | null>(null);
|
|
91
|
-
const setRef = React.useCallback(
|
|
92
|
-
(node: HTMLDivElement | null) => {
|
|
93
|
-
localRef.current = node;
|
|
94
|
-
if (typeof ref === 'function') ref(node);
|
|
95
|
-
else if (ref) (ref as React.MutableRefObject<HTMLDivElement | null>).current = node;
|
|
96
|
-
},
|
|
97
|
-
[ref],
|
|
98
|
-
);
|
|
99
|
-
const childArray = React.Children.toArray(children) as React.ReactElement[];
|
|
100
|
-
const handleChildren = childArray.filter((el: React.ReactElement) => React.isValidElement(el) && el.type === SidebarHandle);
|
|
101
|
-
const contentChildren = childArray.filter((el: React.ReactElement) => !(React.isValidElement(el) && el.type === SidebarHandle));
|
|
36
|
+
const SIDEBAR_DOM_PROP_KEYS = [
|
|
37
|
+
'className',
|
|
38
|
+
'children',
|
|
39
|
+
'state',
|
|
40
|
+
'defaultState',
|
|
41
|
+
'onStateChange',
|
|
42
|
+
'thinSize',
|
|
43
|
+
'toggleModes',
|
|
44
|
+
'size',
|
|
45
|
+
'defaultSize',
|
|
46
|
+
'onSizeChange',
|
|
47
|
+
'sizeUpdate',
|
|
48
|
+
'sizeUpdateMs',
|
|
49
|
+
'style',
|
|
50
|
+
] as const satisfies readonly (keyof SidebarPublicProps)[];
|
|
102
51
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
52
|
+
export const Sidebar = React.forwardRef<HTMLDivElement, SidebarPublicProps>((initialProps, ref) => {
|
|
53
|
+
const {
|
|
54
|
+
className,
|
|
55
|
+
presentation = { initial: 'overlay', md: 'fixed' },
|
|
56
|
+
expandedSize = 288,
|
|
57
|
+
minSize = 200,
|
|
58
|
+
maxSize = 400,
|
|
59
|
+
resizable = false,
|
|
60
|
+
collapsible = true,
|
|
61
|
+
onExpand,
|
|
62
|
+
onCollapse,
|
|
63
|
+
onResize,
|
|
64
|
+
onResizeStart,
|
|
65
|
+
onResizeEnd,
|
|
66
|
+
snapPoints,
|
|
67
|
+
snapTolerance,
|
|
68
|
+
collapseThreshold,
|
|
69
|
+
paneId,
|
|
70
|
+
persistence,
|
|
71
|
+
children,
|
|
72
|
+
style,
|
|
73
|
+
thinSize = 64,
|
|
74
|
+
toggleModes,
|
|
75
|
+
state,
|
|
76
|
+
defaultState,
|
|
77
|
+
onStateChange,
|
|
78
|
+
size,
|
|
79
|
+
defaultSize,
|
|
80
|
+
onSizeChange,
|
|
81
|
+
sizeUpdate,
|
|
82
|
+
sizeUpdateMs = 50,
|
|
83
|
+
} = initialProps;
|
|
84
|
+
const sidebarDomProps = extractPaneDomProps(initialProps, SIDEBAR_DOM_PROP_KEYS);
|
|
85
|
+
const shell = useShell();
|
|
86
|
+
const resolvedPresentation = useResponsivePresentation(presentation);
|
|
87
|
+
const isOverlay = resolvedPresentation === 'overlay';
|
|
88
|
+
const isStacked = resolvedPresentation === 'stacked';
|
|
89
|
+
// Phase sequencing is now CSS-driven; no JS-managed phase
|
|
90
|
+
const localRef = React.useRef<HTMLDivElement | null>(null);
|
|
91
|
+
const setRef = React.useCallback(
|
|
92
|
+
(node: HTMLDivElement | null) => {
|
|
93
|
+
localRef.current = node;
|
|
94
|
+
if (typeof ref === 'function') ref(node);
|
|
95
|
+
else if (ref) (ref as React.MutableRefObject<HTMLDivElement | null>).current = node;
|
|
96
|
+
},
|
|
97
|
+
[ref],
|
|
98
|
+
);
|
|
99
|
+
const childArray = React.Children.toArray(children) as React.ReactElement[];
|
|
100
|
+
const handleChildren = childArray.filter((el: React.ReactElement) => React.isValidElement(el) && el.type === SidebarHandle);
|
|
101
|
+
const contentChildren = childArray.filter((el: React.ReactElement) => !(React.isValidElement(el) && el.type === SidebarHandle));
|
|
133
102
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
103
|
+
// Throttled/debounced emitter for onSizeChange
|
|
104
|
+
const emitSizeChange = React.useMemo(() => {
|
|
105
|
+
const cb = onSizeChange as undefined | ((s: number, meta: { reason: 'init' | 'resize' | 'controlled' }) => void);
|
|
106
|
+
const strategy = sizeUpdate as undefined | 'throttle' | 'debounce';
|
|
107
|
+
const ms = 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);
|
|
140
116
|
};
|
|
141
|
-
}, [shell, sidebarId]);
|
|
142
|
-
|
|
143
|
-
// Dev guards
|
|
144
|
-
const wasControlledRef = React.useRef<boolean | null>(null);
|
|
145
|
-
if (process.env.NODE_ENV !== 'production') {
|
|
146
|
-
if (typeof state !== 'undefined' && typeof defaultState !== 'undefined') {
|
|
147
|
-
console.error('Shell.Sidebar: Do not pass both `state` and `defaultState`. Choose one.');
|
|
148
|
-
}
|
|
149
|
-
if (typeof (props as any).size !== 'undefined' && typeof (props as any).defaultSize !== 'undefined') {
|
|
150
|
-
console.error('Shell.Sidebar: Do not pass both `size` and `defaultSize`. Choose one.');
|
|
151
|
-
}
|
|
152
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
|
+
}, [onSizeChange, sizeUpdate, sizeUpdateMs]);
|
|
153
130
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
console.warn('Shell.Sidebar: Switching between controlled and uncontrolled `state` is not supported.');
|
|
163
|
-
wasControlledRef.current = isControlled;
|
|
164
|
-
}
|
|
165
|
-
}, [state]);
|
|
131
|
+
// Register with shell
|
|
132
|
+
const sidebarId = React.useId();
|
|
133
|
+
React.useEffect(() => {
|
|
134
|
+
shell.setHasSidebar(true);
|
|
135
|
+
return () => {
|
|
136
|
+
shell.setHasSidebar(false);
|
|
137
|
+
};
|
|
138
|
+
}, [shell, sidebarId]);
|
|
166
139
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
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
|
+
console.error('Shell.Sidebar: Do not pass both `state` and `defaultState`. Choose one.');
|
|
145
|
+
}
|
|
146
|
+
if (typeof size !== 'undefined' && typeof defaultSize !== 'undefined') {
|
|
147
|
+
console.error('Shell.Sidebar: Do not pass both `size` and `defaultSize`. Choose one.');
|
|
148
|
+
}
|
|
149
|
+
}
|
|
170
150
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
const initialState = (resolvedDefaultState ?? defaultState) as SidebarMode;
|
|
184
|
-
if (shell.sidebarMode !== initialState) {
|
|
185
|
-
shell.setSidebarMode(initialState);
|
|
186
|
-
}
|
|
187
|
-
onStateChange?.(initialState, { reason: 'init' });
|
|
188
|
-
return;
|
|
189
|
-
}
|
|
190
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
191
|
-
}, [shell.currentBreakpointReady, resolvedDefaultState, resolvedState, state, defaultState]);
|
|
151
|
+
// Warn on mode switch between controlled/uncontrolled
|
|
152
|
+
React.useEffect(() => {
|
|
153
|
+
const isControlled = typeof state !== 'undefined';
|
|
154
|
+
if (wasControlledRef.current === null) {
|
|
155
|
+
wasControlledRef.current = isControlled;
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
if (wasControlledRef.current !== isControlled) {
|
|
159
|
+
console.warn('Shell.Sidebar: Switching between controlled and uncontrolled `state` is not supported.');
|
|
160
|
+
wasControlledRef.current = isControlled;
|
|
161
|
+
}
|
|
162
|
+
}, [state]);
|
|
192
163
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
164
|
+
// Resolve responsive controlled state at top level
|
|
165
|
+
const stateIsResponsive = typeof state === 'object' && state !== null;
|
|
166
|
+
const { resolvedDefault: resolvedSidebarDefault } = useResponsiveInitialState<SidebarMode>({
|
|
167
|
+
controlledValue: state,
|
|
168
|
+
defaultValue: defaultState,
|
|
169
|
+
currentValue: shell.sidebarMode as SidebarMode,
|
|
170
|
+
setValue: shell.setSidebarMode,
|
|
171
|
+
breakpointReady: shell.currentBreakpointReady,
|
|
172
|
+
controlledIsResponsive: stateIsResponsive,
|
|
173
|
+
onResponsiveChange: (next) => onStateChange?.(next, { reason: 'responsive' }),
|
|
174
|
+
onInit: (initial) => onStateChange?.(initial, { reason: 'init' }),
|
|
175
|
+
});
|
|
198
176
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
}
|
|
177
|
+
// Emit mode changes
|
|
178
|
+
const lastNotifyModeRef = React.useRef<SidebarMode | null>(null);
|
|
179
|
+
React.useEffect(() => {
|
|
180
|
+
// notify new API when uncontrolled; skip first run to avoid masking init
|
|
181
|
+
if (typeof state === 'undefined') {
|
|
182
|
+
if (lastNotifyModeRef.current === null) {
|
|
183
|
+
lastNotifyModeRef.current = shell.sidebarMode as SidebarMode;
|
|
184
|
+
} else if (lastNotifyModeRef.current !== shell.sidebarMode) {
|
|
185
|
+
lastNotifyModeRef.current = shell.sidebarMode as SidebarMode;
|
|
186
|
+
onStateChange?.(shell.sidebarMode as SidebarMode, { reason: 'toggle' });
|
|
210
187
|
}
|
|
211
|
-
}
|
|
188
|
+
}
|
|
189
|
+
}, [shell.sidebarMode, state, onStateChange]);
|
|
212
190
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
191
|
+
// Emit expand/collapse events
|
|
192
|
+
React.useEffect(() => {
|
|
193
|
+
if (shell.sidebarMode === 'expanded') {
|
|
194
|
+
onExpand?.();
|
|
195
|
+
} else {
|
|
196
|
+
onCollapse?.();
|
|
197
|
+
}
|
|
198
|
+
}, [shell.sidebarMode, onExpand, onCollapse]);
|
|
221
199
|
|
|
222
|
-
|
|
223
|
-
|
|
200
|
+
// Option A: thin is width-only; content remains visible whenever not collapsed
|
|
201
|
+
const isContentVisible = shell.sidebarMode !== 'collapsed';
|
|
224
202
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
203
|
+
// Default persistence if paneId provided and none supplied (fixed only)
|
|
204
|
+
const persistenceAdapter = React.useMemo(() => {
|
|
205
|
+
if (!paneId || persistence) return persistence;
|
|
206
|
+
const key = `kookie-ui:shell:sidebar:${paneId}`;
|
|
207
|
+
const adapter: PaneSizePersistence = {
|
|
208
|
+
load: () => {
|
|
209
|
+
if (typeof window === 'undefined') return undefined;
|
|
210
|
+
try {
|
|
232
211
|
const v = window.localStorage.getItem(key);
|
|
233
212
|
return v ? Number(v) : undefined;
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
|
|
213
|
+
} catch (err) {
|
|
214
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
215
|
+
console.warn('Shell.Sidebar: failed to load persisted size', err);
|
|
216
|
+
}
|
|
217
|
+
return undefined;
|
|
218
|
+
}
|
|
219
|
+
},
|
|
220
|
+
save: (size: number) => {
|
|
221
|
+
if (typeof window === 'undefined') return;
|
|
222
|
+
try {
|
|
237
223
|
window.localStorage.setItem(key, String(size));
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
React.useEffect(() => {
|
|
244
|
-
let mounted = true;
|
|
245
|
-
(async () => {
|
|
246
|
-
if (!resizable || !persistenceAdapter?.load || isOverlay) return;
|
|
247
|
-
const loaded = await persistenceAdapter.load();
|
|
248
|
-
if (mounted && typeof loaded === 'number' && localRef.current) {
|
|
249
|
-
localRef.current.style.setProperty('--sidebar-size', `${loaded}px`);
|
|
250
|
-
onResize?.(loaded);
|
|
224
|
+
} catch (err) {
|
|
225
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
226
|
+
console.warn('Shell.Sidebar: failed to save persisted size', err);
|
|
227
|
+
}
|
|
251
228
|
}
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
}, [resizable, persistenceAdapter, onResize, isOverlay]);
|
|
229
|
+
},
|
|
230
|
+
};
|
|
231
|
+
return adapter;
|
|
232
|
+
}, [paneId, persistence]);
|
|
257
233
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
234
|
+
React.useEffect(() => {
|
|
235
|
+
let mounted = true;
|
|
236
|
+
(async () => {
|
|
237
|
+
if (!resizable || !persistenceAdapter?.load || isOverlay) return;
|
|
238
|
+
const loaded = await persistenceAdapter.load();
|
|
239
|
+
if (mounted && typeof loaded === 'number' && localRef.current) {
|
|
240
|
+
localRef.current.style.setProperty('--sidebar-size', `${loaded}px`);
|
|
241
|
+
onResize?.(loaded);
|
|
242
|
+
}
|
|
243
|
+
})();
|
|
244
|
+
return () => {
|
|
245
|
+
mounted = false;
|
|
246
|
+
};
|
|
247
|
+
}, [resizable, persistenceAdapter, onResize, isOverlay]);
|
|
264
248
|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
if (current === 'thin') return 'expanded';
|
|
272
|
-
return 'collapsed';
|
|
273
|
-
}
|
|
274
|
-
const target = resolveDefaultSidebarMode();
|
|
275
|
-
if (current === 'collapsed') return target;
|
|
276
|
-
if (current === target) return 'collapsed';
|
|
277
|
-
return target;
|
|
278
|
-
};
|
|
279
|
-
shellForToggle.setSidebarToggleComputer(compute);
|
|
280
|
-
return () => {
|
|
281
|
-
shellForToggle.setSidebarToggleComputer?.((cur) => (cur === 'collapsed' ? 'thin' : cur === 'thin' ? 'expanded' : 'collapsed'));
|
|
282
|
-
};
|
|
283
|
-
}, [shellForToggle, toggleModes, resolveDefaultSidebarMode]);
|
|
249
|
+
// Register custom toggle behavior based on toggleModes (both|single)
|
|
250
|
+
const shellForToggle = useShell();
|
|
251
|
+
const resolveDefaultSidebarMode = React.useCallback((): SidebarMode => {
|
|
252
|
+
const resolved = resolvedSidebarDefault ?? (typeof defaultState === 'string' ? defaultState : undefined) ?? 'expanded';
|
|
253
|
+
return resolved === 'thin' || resolved === 'expanded' ? resolved : 'expanded';
|
|
254
|
+
}, [resolvedSidebarDefault, defaultState]);
|
|
284
255
|
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
256
|
+
React.useEffect(() => {
|
|
257
|
+
if (!shellForToggle.setSidebarToggleComputer) return;
|
|
258
|
+
const strategy: 'both' | 'single' = toggleModes ?? 'both';
|
|
259
|
+
const compute = (current: SidebarMode): SidebarMode => {
|
|
260
|
+
if (strategy === 'both') {
|
|
261
|
+
if (current === 'collapsed') return 'thin';
|
|
262
|
+
if (current === 'thin') return 'expanded';
|
|
263
|
+
return 'collapsed';
|
|
291
264
|
}
|
|
292
|
-
|
|
265
|
+
const target = resolveDefaultSidebarMode();
|
|
266
|
+
if (current === 'collapsed') return target;
|
|
267
|
+
if (current === target) return 'collapsed';
|
|
268
|
+
return target;
|
|
269
|
+
};
|
|
270
|
+
shellForToggle.setSidebarToggleComputer(compute);
|
|
271
|
+
return () => {
|
|
272
|
+
shellForToggle.setSidebarToggleComputer?.((cur) => (cur === 'collapsed' ? 'thin' : cur === 'thin' ? 'expanded' : 'collapsed'));
|
|
273
|
+
};
|
|
274
|
+
}, [shellForToggle, toggleModes, resolveDefaultSidebarMode]);
|
|
293
275
|
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
minSize,
|
|
303
|
-
maxSize,
|
|
304
|
-
defaultSize: expandedSize,
|
|
305
|
-
orientation: 'vertical',
|
|
306
|
-
edge: 'end',
|
|
307
|
-
computeNext: (client, startClient, startSize) => {
|
|
308
|
-
const isRtl = getComputedStyle(localRef.current!).direction === 'rtl';
|
|
309
|
-
const delta = client - startClient;
|
|
310
|
-
return startSize + (isRtl ? -delta : delta);
|
|
311
|
-
},
|
|
312
|
-
onResize,
|
|
313
|
-
onResizeStart,
|
|
314
|
-
onResizeEnd: (size) => {
|
|
315
|
-
onResizeEnd?.(size);
|
|
316
|
-
emitSizeChange(size, { reason: 'resize' });
|
|
317
|
-
persistenceAdapter?.save?.(size);
|
|
318
|
-
},
|
|
319
|
-
target: 'sidebar',
|
|
320
|
-
collapsible,
|
|
321
|
-
snapPoints,
|
|
322
|
-
snapTolerance: snapTolerance ?? 8,
|
|
323
|
-
collapseThreshold,
|
|
324
|
-
requestCollapse: () => shell.setSidebarMode('collapsed'),
|
|
325
|
-
requestToggle: () => shell.togglePane('sidebar'),
|
|
326
|
-
}}
|
|
327
|
-
>
|
|
328
|
-
{handleChildren.length > 0 ? handleChildren.map((el, i) => React.cloneElement(el, { key: el.key ?? i })) : <PaneHandle />}
|
|
329
|
-
</PaneResizeContext.Provider>
|
|
330
|
-
) : null;
|
|
276
|
+
const lastOverlayWidthRef = React.useRef<number>(expandedSize);
|
|
277
|
+
const lastOverlayModeRef = React.useRef<SidebarMode>('expanded');
|
|
278
|
+
React.useEffect(() => {
|
|
279
|
+
if (shell.sidebarMode !== 'collapsed') {
|
|
280
|
+
lastOverlayModeRef.current = shell.sidebarMode as SidebarMode;
|
|
281
|
+
lastOverlayWidthRef.current = shell.sidebarMode === 'thin' ? thinSize : expandedSize;
|
|
282
|
+
}
|
|
283
|
+
}, [shell.sidebarMode, thinSize, expandedSize]);
|
|
331
284
|
|
|
332
|
-
|
|
333
|
-
const { state: _s, defaultState: _ds, onStateChange: _osc, size: _sz, defaultSize: _dsz, onSizeChange: _onsc, sizeUpdate: _szu, sizeUpdateMs: _szums, ...domProps } = props as any;
|
|
285
|
+
// Remove responsive default mode behavior entirely
|
|
334
286
|
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
287
|
+
const handleEl =
|
|
288
|
+
resizable && !isOverlay && shell.sidebarMode === 'expanded' ? (
|
|
289
|
+
<PaneResizeContext.Provider
|
|
290
|
+
value={{
|
|
291
|
+
containerRef: localRef,
|
|
292
|
+
cssVarName: '--sidebar-size',
|
|
293
|
+
minSize,
|
|
294
|
+
maxSize,
|
|
295
|
+
defaultSize: expandedSize,
|
|
296
|
+
orientation: 'vertical',
|
|
297
|
+
edge: 'end',
|
|
298
|
+
computeNext: (client, startClient, startSize) => {
|
|
299
|
+
const isRtl = getComputedStyle(localRef.current!).direction === 'rtl';
|
|
300
|
+
const delta = client - startClient;
|
|
301
|
+
return startSize + (isRtl ? -delta : delta);
|
|
302
|
+
},
|
|
303
|
+
onResize,
|
|
304
|
+
onResizeStart,
|
|
305
|
+
onResizeEnd: (size) => {
|
|
306
|
+
onResizeEnd?.(size);
|
|
307
|
+
emitSizeChange(size, { reason: 'resize' });
|
|
308
|
+
persistenceAdapter?.save?.(size);
|
|
309
|
+
},
|
|
310
|
+
target: 'sidebar',
|
|
311
|
+
collapsible,
|
|
312
|
+
snapPoints,
|
|
313
|
+
snapTolerance: snapTolerance ?? 8,
|
|
314
|
+
collapseThreshold,
|
|
315
|
+
requestCollapse: () => shell.setSidebarMode('collapsed'),
|
|
316
|
+
requestToggle: () => shell.togglePane('sidebar'),
|
|
317
|
+
}}
|
|
318
|
+
>
|
|
319
|
+
{handleChildren.length > 0 ? handleChildren.map((el, i) => React.cloneElement(el, { key: el.key ?? i })) : <PaneHandle />}
|
|
320
|
+
</PaneResizeContext.Provider>
|
|
321
|
+
) : null;
|
|
354
322
|
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
}
|
|
323
|
+
// Normalize CSS lengths to px
|
|
324
|
+
const normalizeToPx = React.useCallback((value: number | string | undefined): number | undefined => {
|
|
325
|
+
if (value == null) return undefined;
|
|
326
|
+
if (typeof value === 'number' && Number.isFinite(value)) return value;
|
|
327
|
+
const str = String(value).trim();
|
|
328
|
+
if (!str) return undefined;
|
|
329
|
+
if (str.endsWith('px')) return Number.parseFloat(str);
|
|
330
|
+
if (str.endsWith('rem')) {
|
|
331
|
+
const rem = Number.parseFloat(getComputedStyle(document.documentElement).fontSize || '16') || 16;
|
|
332
|
+
return Number.parseFloat(str) * rem;
|
|
333
|
+
}
|
|
334
|
+
if (str.endsWith('%')) {
|
|
335
|
+
const pct = Number.parseFloat(str);
|
|
336
|
+
const base = document.documentElement.clientWidth || window.innerWidth || 0;
|
|
337
|
+
return (pct / 100) * base;
|
|
338
|
+
}
|
|
339
|
+
const n = Number.parseFloat(str);
|
|
340
|
+
return Number.isFinite(n) ? n : undefined;
|
|
341
|
+
}, []);
|
|
371
342
|
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
const px = normalizeToPx(controlledSize);
|
|
343
|
+
// Apply defaultSize on mount when uncontrolled
|
|
344
|
+
React.useEffect(() => {
|
|
345
|
+
if (!localRef.current) return;
|
|
346
|
+
if (typeof size === 'undefined' && typeof defaultSize !== 'undefined') {
|
|
347
|
+
const px = normalizeToPx(defaultSize);
|
|
378
348
|
if (typeof px === 'number' && Number.isFinite(px)) {
|
|
379
349
|
const minPx = typeof minSize === 'number' ? minSize : undefined;
|
|
380
350
|
const maxPx = typeof maxSize === 'number' ? maxSize : undefined;
|
|
381
351
|
const clamped = Math.min(maxPx ?? px, Math.max(minPx ?? px, px));
|
|
382
352
|
localRef.current.style.setProperty('--sidebar-size', `${clamped}px`);
|
|
383
|
-
emitSizeChange(clamped, { reason: '
|
|
353
|
+
emitSizeChange(clamped, { reason: 'init' });
|
|
384
354
|
}
|
|
385
|
-
}
|
|
355
|
+
}
|
|
356
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
357
|
+
}, []);
|
|
386
358
|
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
<Sheet.Title>Navigation</Sheet.Title>
|
|
400
|
-
</VisuallyHidden>
|
|
401
|
-
{contentChildren}
|
|
402
|
-
</Sheet.Content>
|
|
403
|
-
</Sheet.Root>
|
|
404
|
-
);
|
|
359
|
+
// Controlled size sync
|
|
360
|
+
const controlledSize = size;
|
|
361
|
+
React.useEffect(() => {
|
|
362
|
+
if (!localRef.current) return;
|
|
363
|
+
if (typeof controlledSize === 'undefined') return;
|
|
364
|
+
const px = normalizeToPx(controlledSize);
|
|
365
|
+
if (typeof px === 'number' && Number.isFinite(px)) {
|
|
366
|
+
const minPx = typeof minSize === 'number' ? minSize : undefined;
|
|
367
|
+
const maxPx = typeof maxSize === 'number' ? maxSize : undefined;
|
|
368
|
+
const clamped = Math.min(maxPx ?? px, Math.max(minPx ?? px, px));
|
|
369
|
+
localRef.current.style.setProperty('--sidebar-size', `${clamped}px`);
|
|
370
|
+
emitSizeChange(clamped, { reason: 'controlled' });
|
|
405
371
|
}
|
|
372
|
+
}, [controlledSize, minSize, maxSize, normalizeToPx, emitSizeChange]);
|
|
373
|
+
|
|
374
|
+
if (isOverlay) {
|
|
375
|
+
const open = shell.sidebarMode !== 'collapsed';
|
|
406
376
|
return (
|
|
407
|
-
<
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
377
|
+
<Sheet.Root open={open} onOpenChange={(o) => shell.setSidebarMode(o ? 'expanded' : 'collapsed')}>
|
|
378
|
+
<Sheet.Content
|
|
379
|
+
side="start"
|
|
380
|
+
style={{ padding: 0 }}
|
|
381
|
+
width={{
|
|
382
|
+
initial: `${open ? (shell.sidebarMode === 'thin' ? thinSize : expandedSize) : lastOverlayWidthRef.current}px`,
|
|
383
|
+
}}
|
|
384
|
+
>
|
|
385
|
+
<VisuallyHidden>
|
|
386
|
+
<Sheet.Title>Navigation</Sheet.Title>
|
|
387
|
+
</VisuallyHidden>
|
|
388
|
+
{contentChildren}
|
|
389
|
+
</Sheet.Content>
|
|
390
|
+
</Sheet.Root>
|
|
391
|
+
);
|
|
392
|
+
}
|
|
393
|
+
return (
|
|
394
|
+
<div
|
|
395
|
+
{...sidebarDomProps}
|
|
396
|
+
ref={setRef}
|
|
397
|
+
className={classNames('rt-ShellSidebar', className)}
|
|
398
|
+
data-mode={shell.sidebarMode}
|
|
399
|
+
data-peek={shell.peekTarget === 'sidebar' || undefined}
|
|
400
|
+
data-presentation={shell.currentBreakpointReady ? resolvedPresentation : undefined}
|
|
401
|
+
data-open={(shell.currentBreakpointReady && isStacked && isContentVisible) || undefined}
|
|
402
|
+
style={{
|
|
403
|
+
...style,
|
|
404
|
+
['--sidebar-size' as any]: `${expandedSize}px`,
|
|
405
|
+
['--sidebar-thin-size' as any]: `${thinSize}px`,
|
|
406
|
+
['--sidebar-min-size' as any]: `${minSize}px`,
|
|
407
|
+
['--sidebar-max-size' as any]: `${maxSize}px`,
|
|
408
|
+
...(shell.peekTarget === 'sidebar' && shell.sidebarMode === 'collapsed' && !isOverlay
|
|
409
|
+
? (() => {
|
|
410
|
+
const strategy: 'both' | 'single' = toggleModes ?? 'both';
|
|
411
|
+
const current = shell.sidebarMode as SidebarMode;
|
|
412
|
+
let next: SidebarMode;
|
|
413
|
+
if (strategy === 'both') {
|
|
414
|
+
next = current === 'collapsed' ? 'thin' : current === 'thin' ? 'expanded' : 'collapsed';
|
|
415
|
+
} else {
|
|
416
|
+
const target = resolveDefaultSidebarMode();
|
|
417
|
+
next = current === 'collapsed' ? target : 'collapsed';
|
|
418
|
+
}
|
|
419
|
+
if (next === 'thin') {
|
|
437
420
|
return {
|
|
438
|
-
['--peek-sidebar-width' as any]:
|
|
421
|
+
['--peek-sidebar-width' as any]: `${thinSize}px`,
|
|
439
422
|
} as React.CSSProperties;
|
|
440
|
-
}
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
{
|
|
446
|
-
|
|
447
|
-
|
|
423
|
+
}
|
|
424
|
+
return {
|
|
425
|
+
['--peek-sidebar-width' as any]: `var(--sidebar-size, ${expandedSize}px)`,
|
|
426
|
+
} as React.CSSProperties;
|
|
427
|
+
})()
|
|
428
|
+
: {}),
|
|
429
|
+
}}
|
|
430
|
+
>
|
|
431
|
+
<div className="rt-ShellSidebarContent" data-visible={isContentVisible || undefined}>
|
|
432
|
+
{contentChildren}
|
|
448
433
|
</div>
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
)
|
|
434
|
+
{handleEl}
|
|
435
|
+
</div>
|
|
436
|
+
);
|
|
437
|
+
}) as SidebarComponent;
|
|
452
438
|
|
|
453
439
|
Sidebar.displayName = 'Shell.Sidebar';
|
|
454
440
|
Sidebar.Handle = SidebarHandle;
|