@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,32 +3,12 @@ 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
8
|
import { BottomHandle, PaneHandle } from './shell-handles.js';
|
|
9
9
|
import { _BREAKPOINTS } from '../shell.types.js';
|
|
10
|
-
import type { Breakpoint, PaneMode, PaneSizePersistence, ResponsivePresentation } from '../shell.types.js';
|
|
11
|
-
|
|
12
|
-
interface PaneProps extends React.ComponentPropsWithoutRef<'div'> {
|
|
13
|
-
presentation?: ResponsivePresentation;
|
|
14
|
-
// legacy mode removed
|
|
15
|
-
expandedSize?: number;
|
|
16
|
-
minSize?: number;
|
|
17
|
-
maxSize?: number;
|
|
18
|
-
resizable?: boolean;
|
|
19
|
-
collapsible?: boolean;
|
|
20
|
-
onExpand?: () => void;
|
|
21
|
-
onCollapse?: () => void;
|
|
22
|
-
onResize?: (size: number) => void;
|
|
23
|
-
resizer?: React.ReactNode;
|
|
24
|
-
onResizeStart?: (size: number) => void;
|
|
25
|
-
onResizeEnd?: (size: number) => void;
|
|
26
|
-
snapPoints?: number[];
|
|
27
|
-
snapTolerance?: number;
|
|
28
|
-
collapseThreshold?: number;
|
|
29
|
-
paneId?: string;
|
|
30
|
-
persistence?: PaneSizePersistence;
|
|
31
|
-
}
|
|
10
|
+
import type { Breakpoint, PaneMode, PaneSizePersistence, ResponsivePresentation, PaneBaseProps } from '../shell.types.js';
|
|
11
|
+
import { extractPaneDomProps, mapResponsiveBooleanToPaneMode } from './shell-prop-helpers.js';
|
|
32
12
|
|
|
33
13
|
type BottomOpenChangeMeta = { reason: 'init' | 'toggle' | 'responsive' };
|
|
34
14
|
type BottomControlledProps = { open: boolean | Partial<Record<Breakpoint, boolean>>; onOpenChange?: (open: boolean, meta: BottomOpenChangeMeta) => void; defaultOpen?: never };
|
|
@@ -36,7 +16,7 @@ type BottomUncontrolledProps = { defaultOpen?: boolean; onOpenChange?: (open: bo
|
|
|
36
16
|
type BottomSizeControlledProps = { size: number | string; defaultSize?: never };
|
|
37
17
|
type BottomSizeUncontrolledProps = { defaultSize?: number | string; size?: never };
|
|
38
18
|
type BottomSizeChangeMeta = { reason: 'init' | 'resize' | 'controlled' };
|
|
39
|
-
type BottomPublicProps =
|
|
19
|
+
type BottomPublicProps = PaneBaseProps &
|
|
40
20
|
(BottomControlledProps | BottomUncontrolledProps) &
|
|
41
21
|
(BottomSizeControlledProps | BottomSizeUncontrolledProps) & {
|
|
42
22
|
onSizeChange?: (size: number, meta: BottomSizeChangeMeta) => void;
|
|
@@ -46,334 +26,338 @@ type BottomPublicProps = PaneProps &
|
|
|
46
26
|
|
|
47
27
|
type BottomComponent = React.ForwardRefExoticComponent<BottomPublicProps & React.RefAttributes<HTMLDivElement>> & { Handle: typeof BottomHandle };
|
|
48
28
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
resizable = false,
|
|
63
|
-
collapsible = true,
|
|
64
|
-
onExpand,
|
|
65
|
-
onCollapse,
|
|
66
|
-
onResize,
|
|
67
|
-
onResizeStart,
|
|
68
|
-
onResizeEnd,
|
|
69
|
-
snapPoints,
|
|
70
|
-
snapTolerance,
|
|
71
|
-
collapseThreshold,
|
|
72
|
-
paneId,
|
|
73
|
-
persistence,
|
|
74
|
-
children,
|
|
75
|
-
style,
|
|
76
|
-
...props
|
|
77
|
-
},
|
|
78
|
-
ref,
|
|
79
|
-
) => {
|
|
80
|
-
const shell = useShell();
|
|
81
|
-
const resolvedPresentation = useResponsivePresentation(presentation);
|
|
82
|
-
const isOverlay = resolvedPresentation === 'overlay';
|
|
83
|
-
const isStacked = resolvedPresentation === 'stacked';
|
|
84
|
-
const localRef = React.useRef<HTMLDivElement | null>(null);
|
|
85
|
-
const setRef = React.useCallback(
|
|
86
|
-
(node: HTMLDivElement | null) => {
|
|
87
|
-
localRef.current = node;
|
|
88
|
-
if (typeof ref === 'function') ref(node);
|
|
89
|
-
else if (ref) (ref as React.MutableRefObject<HTMLDivElement | null>).current = node;
|
|
90
|
-
},
|
|
91
|
-
[ref],
|
|
92
|
-
);
|
|
93
|
-
const childArray = React.Children.toArray(children) as React.ReactElement[];
|
|
94
|
-
const handleChildren = childArray.filter((el: React.ReactElement) => React.isValidElement(el) && el.type === BottomHandle);
|
|
95
|
-
const contentChildren = childArray.filter((el: React.ReactElement) => !(React.isValidElement(el) && el.type === BottomHandle));
|
|
29
|
+
const BOTTOM_DOM_PROP_KEYS = [
|
|
30
|
+
'className',
|
|
31
|
+
'children',
|
|
32
|
+
'defaultOpen',
|
|
33
|
+
'open',
|
|
34
|
+
'onOpenChange',
|
|
35
|
+
'size',
|
|
36
|
+
'defaultSize',
|
|
37
|
+
'onSizeChange',
|
|
38
|
+
'sizeUpdate',
|
|
39
|
+
'sizeUpdateMs',
|
|
40
|
+
'style',
|
|
41
|
+
] as const satisfies readonly (keyof BottomPublicProps)[];
|
|
96
42
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
43
|
+
export const Bottom = React.forwardRef<HTMLDivElement, BottomPublicProps>((initialProps, ref) => {
|
|
44
|
+
const {
|
|
45
|
+
className,
|
|
46
|
+
presentation = 'fixed',
|
|
47
|
+
defaultOpen,
|
|
48
|
+
open,
|
|
49
|
+
onOpenChange,
|
|
50
|
+
expandedSize = 200,
|
|
51
|
+
minSize = 100,
|
|
52
|
+
maxSize = 400,
|
|
53
|
+
resizable = false,
|
|
54
|
+
collapsible = true,
|
|
55
|
+
onExpand,
|
|
56
|
+
onCollapse,
|
|
57
|
+
onResize,
|
|
58
|
+
onResizeStart,
|
|
59
|
+
onResizeEnd,
|
|
60
|
+
snapPoints,
|
|
61
|
+
snapTolerance,
|
|
62
|
+
collapseThreshold,
|
|
63
|
+
paneId,
|
|
64
|
+
persistence,
|
|
65
|
+
children,
|
|
66
|
+
style,
|
|
67
|
+
size,
|
|
68
|
+
defaultSize,
|
|
69
|
+
onSizeChange,
|
|
70
|
+
sizeUpdate,
|
|
71
|
+
sizeUpdateMs = 50,
|
|
72
|
+
} = initialProps;
|
|
73
|
+
const bottomDomProps = extractPaneDomProps(initialProps, BOTTOM_DOM_PROP_KEYS);
|
|
74
|
+
const shell = useShell();
|
|
75
|
+
const resolvedPresentation = useResponsivePresentation(presentation);
|
|
76
|
+
const isOverlay = resolvedPresentation === 'overlay';
|
|
77
|
+
const isStacked = resolvedPresentation === 'stacked';
|
|
78
|
+
const localRef = React.useRef<HTMLDivElement | null>(null);
|
|
79
|
+
const setRef = React.useCallback(
|
|
80
|
+
(node: HTMLDivElement | null) => {
|
|
81
|
+
localRef.current = node;
|
|
82
|
+
if (typeof ref === 'function') ref(node);
|
|
83
|
+
else if (ref) (ref as React.MutableRefObject<HTMLDivElement | null>).current = node;
|
|
84
|
+
},
|
|
85
|
+
[ref],
|
|
86
|
+
);
|
|
87
|
+
const childArray = React.Children.toArray(children) as React.ReactElement[];
|
|
88
|
+
const handleChildren = childArray.filter((el: React.ReactElement) => React.isValidElement(el) && el.type === BottomHandle);
|
|
89
|
+
const contentChildren = childArray.filter((el: React.ReactElement) => !(React.isValidElement(el) && el.type === BottomHandle));
|
|
127
90
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
91
|
+
// Throttled/debounced emitter for onSizeChange
|
|
92
|
+
const normalizedControlledOpen = React.useMemo(() => mapResponsiveBooleanToPaneMode(open), [open]);
|
|
93
|
+
const normalizedDefaultOpen = React.useMemo(() => mapResponsiveBooleanToPaneMode(defaultOpen), [defaultOpen]);
|
|
94
|
+
const openIsResponsive = typeof open === 'object' && open !== null;
|
|
95
|
+
useResponsiveInitialState<PaneMode>({
|
|
96
|
+
controlledValue: normalizedControlledOpen,
|
|
97
|
+
defaultValue: normalizedDefaultOpen,
|
|
98
|
+
currentValue: shell.bottomMode,
|
|
99
|
+
setValue: shell.setBottomMode,
|
|
100
|
+
breakpointReady: shell.currentBreakpointReady,
|
|
101
|
+
controlledIsResponsive: openIsResponsive,
|
|
102
|
+
onResponsiveChange: (next) => onOpenChange?.(next === 'expanded', { reason: 'responsive' }),
|
|
103
|
+
onInit: (initial) => {
|
|
104
|
+
if (typeof open === 'undefined') {
|
|
105
|
+
onOpenChange?.(initial === 'expanded', { reason: 'init' });
|
|
139
106
|
}
|
|
140
|
-
},
|
|
107
|
+
},
|
|
108
|
+
});
|
|
141
109
|
|
|
142
|
-
|
|
143
|
-
const
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
110
|
+
const emitSizeChange = React.useMemo(() => {
|
|
111
|
+
const cb = onSizeChange as undefined | ((s: number, meta: BottomSizeChangeMeta) => void);
|
|
112
|
+
const strategy = sizeUpdate as undefined | 'throttle' | 'debounce';
|
|
113
|
+
const ms = sizeUpdateMs ?? 50;
|
|
114
|
+
if (!cb) return () => {};
|
|
115
|
+
if (strategy === 'debounce') {
|
|
116
|
+
let t: any = null;
|
|
117
|
+
return (s: number, meta: BottomSizeChangeMeta) => {
|
|
118
|
+
if (t) clearTimeout(t);
|
|
119
|
+
t = setTimeout(() => {
|
|
120
|
+
cb(s, meta);
|
|
121
|
+
}, ms);
|
|
122
|
+
};
|
|
151
123
|
}
|
|
124
|
+
if (strategy === 'throttle') {
|
|
125
|
+
let last = 0;
|
|
126
|
+
return (s: number, meta: BottomSizeChangeMeta) => {
|
|
127
|
+
const now = Date.now();
|
|
128
|
+
if (now - last >= ms) {
|
|
129
|
+
last = now;
|
|
130
|
+
cb(s, meta);
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
return (s: number, meta: BottomSizeChangeMeta) => cb(s, meta);
|
|
135
|
+
}, [onSizeChange, sizeUpdate, sizeUpdateMs]);
|
|
152
136
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
}, [open]);
|
|
164
|
-
|
|
165
|
-
// Controlled sync (responsive handled below)
|
|
166
|
-
React.useEffect(() => {
|
|
167
|
-
if (typeof open === 'undefined') return;
|
|
168
|
-
shell.setBottomMode(open ? 'expanded' : 'collapsed');
|
|
169
|
-
}, [shell, open]);
|
|
170
|
-
|
|
171
|
-
const responsiveNotifiedRef = React.useRef(false);
|
|
137
|
+
// Dev guards
|
|
138
|
+
const wasControlledRef = React.useRef<boolean | null>(null);
|
|
139
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
140
|
+
if (typeof open !== 'undefined' && typeof defaultOpen !== 'undefined') {
|
|
141
|
+
console.error('Shell.Bottom: Do not pass both `open` and `defaultOpen`. Choose one.');
|
|
142
|
+
}
|
|
143
|
+
if (typeof size !== 'undefined' && typeof defaultSize !== 'undefined') {
|
|
144
|
+
console.error('Shell.Bottom: Do not pass both `size` and `defaultSize`. Choose one.');
|
|
145
|
+
}
|
|
146
|
+
}
|
|
172
147
|
|
|
173
|
-
|
|
174
|
-
const
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
148
|
+
React.useEffect(() => {
|
|
149
|
+
const isControlled = typeof open !== 'undefined';
|
|
150
|
+
if (wasControlledRef.current === null) {
|
|
151
|
+
wasControlledRef.current = isControlled;
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
if (wasControlledRef.current !== isControlled) {
|
|
155
|
+
console.warn('Shell.Bottom: Switching between controlled and uncontrolled `open` is not supported.');
|
|
156
|
+
wasControlledRef.current = isControlled;
|
|
157
|
+
}
|
|
158
|
+
}, [open]);
|
|
180
159
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
onOpenChange?.(shell.bottomMode === 'expanded', { reason: 'toggle' });
|
|
192
|
-
}
|
|
193
|
-
responsiveNotifiedRef.current = false;
|
|
194
|
-
}
|
|
195
|
-
lastBottomModeRef.current = shell.bottomMode;
|
|
160
|
+
const initNotifiedRef = React.useRef(false);
|
|
161
|
+
const lastBottomModeRef = React.useRef<PaneMode | null>(null);
|
|
162
|
+
React.useEffect(() => {
|
|
163
|
+
if (!initNotifiedRef.current && typeof open === 'undefined' && defaultOpen && shell.bottomMode === 'expanded') {
|
|
164
|
+
onOpenChange?.(true, { reason: 'init' });
|
|
165
|
+
initNotifiedRef.current = true;
|
|
166
|
+
}
|
|
167
|
+
if (typeof open === 'undefined') {
|
|
168
|
+
if (lastBottomModeRef.current !== null && lastBottomModeRef.current !== shell.bottomMode) {
|
|
169
|
+
onOpenChange?.(shell.bottomMode === 'expanded', { reason: 'toggle' });
|
|
196
170
|
}
|
|
197
|
-
|
|
171
|
+
lastBottomModeRef.current = shell.bottomMode;
|
|
172
|
+
}
|
|
173
|
+
}, [shell.bottomMode, open, defaultOpen, onOpenChange]);
|
|
198
174
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
175
|
+
React.useEffect(() => {
|
|
176
|
+
if (shell.bottomMode === 'expanded') {
|
|
177
|
+
onExpand?.();
|
|
178
|
+
} else {
|
|
179
|
+
onCollapse?.();
|
|
180
|
+
}
|
|
181
|
+
}, [shell.bottomMode, onExpand, onCollapse]);
|
|
206
182
|
|
|
207
|
-
|
|
183
|
+
const isExpanded = shell.bottomMode === 'expanded';
|
|
208
184
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
185
|
+
const persistenceAdapter = React.useMemo(() => {
|
|
186
|
+
if (!paneId || persistence) return persistence;
|
|
187
|
+
const key = `kookie-ui:shell:bottom:${paneId}`;
|
|
188
|
+
const adapter: PaneSizePersistence = {
|
|
189
|
+
load: () => {
|
|
190
|
+
if (typeof window === 'undefined') return undefined;
|
|
191
|
+
try {
|
|
215
192
|
const v = window.localStorage.getItem(key);
|
|
216
193
|
return v ? Number(v) : undefined;
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
|
|
194
|
+
} catch (err) {
|
|
195
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
196
|
+
console.warn('Shell.Bottom: failed to load persisted size', err);
|
|
197
|
+
}
|
|
198
|
+
return undefined;
|
|
199
|
+
}
|
|
200
|
+
},
|
|
201
|
+
save: (size: number) => {
|
|
202
|
+
if (typeof window === 'undefined') return;
|
|
203
|
+
try {
|
|
220
204
|
window.localStorage.setItem(key, String(size));
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
React.useEffect(() => {
|
|
227
|
-
let mounted = true;
|
|
228
|
-
(async () => {
|
|
229
|
-
if (!resizable || !persistenceAdapter?.load || isOverlay) return;
|
|
230
|
-
const loaded = await persistenceAdapter.load();
|
|
231
|
-
if (mounted && typeof loaded === 'number' && localRef.current) {
|
|
232
|
-
localRef.current.style.setProperty('--bottom-size', `${loaded}px`);
|
|
233
|
-
onResize?.(loaded);
|
|
205
|
+
} catch (err) {
|
|
206
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
207
|
+
console.warn('Shell.Bottom: failed to save persisted size', err);
|
|
208
|
+
}
|
|
234
209
|
}
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
}, [resizable, persistenceAdapter, onResize, isOverlay]);
|
|
240
|
-
|
|
241
|
-
const handleEl =
|
|
242
|
-
resizable && !isOverlay && isExpanded ? (
|
|
243
|
-
<PaneResizeContext.Provider
|
|
244
|
-
value={{
|
|
245
|
-
containerRef: localRef,
|
|
246
|
-
cssVarName: '--bottom-size',
|
|
247
|
-
minSize,
|
|
248
|
-
maxSize,
|
|
249
|
-
defaultSize: expandedSize,
|
|
250
|
-
orientation: 'horizontal',
|
|
251
|
-
edge: 'start',
|
|
252
|
-
computeNext: (client, startClient, startSize) => {
|
|
253
|
-
const delta = client - startClient;
|
|
254
|
-
return startSize - delta;
|
|
255
|
-
},
|
|
256
|
-
onResize,
|
|
257
|
-
onResizeStart,
|
|
258
|
-
onResizeEnd: (size) => {
|
|
259
|
-
onResizeEnd?.(size);
|
|
260
|
-
emitSizeChange(size, { reason: 'resize' });
|
|
261
|
-
persistenceAdapter?.save?.(size);
|
|
262
|
-
},
|
|
263
|
-
target: 'bottom',
|
|
264
|
-
collapsible,
|
|
265
|
-
snapPoints,
|
|
266
|
-
snapTolerance: snapTolerance ?? 8,
|
|
267
|
-
collapseThreshold,
|
|
268
|
-
requestCollapse: () => shell.setBottomMode('collapsed'),
|
|
269
|
-
requestToggle: () => shell.togglePane('bottom'),
|
|
270
|
-
}}
|
|
271
|
-
>
|
|
272
|
-
{handleChildren.length > 0 ? handleChildren.map((el, i) => React.cloneElement(el, { key: el.key ?? i })) : <PaneHandle />}
|
|
273
|
-
</PaneResizeContext.Provider>
|
|
274
|
-
) : null;
|
|
210
|
+
},
|
|
211
|
+
};
|
|
212
|
+
return adapter;
|
|
213
|
+
}, [paneId, persistence]);
|
|
275
214
|
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
215
|
+
React.useEffect(() => {
|
|
216
|
+
let mounted = true;
|
|
217
|
+
if (!resizable || !persistenceAdapter?.load || isOverlay) return undefined;
|
|
218
|
+
const loaded = persistenceAdapter.load();
|
|
219
|
+
const applyLoaded = (value?: number) => {
|
|
220
|
+
if (!mounted || typeof value !== 'number' || !localRef.current) return;
|
|
221
|
+
localRef.current.style.setProperty('--bottom-size', `${value}px`);
|
|
222
|
+
onResize?.(value);
|
|
223
|
+
};
|
|
224
|
+
if (loaded instanceof Promise) {
|
|
225
|
+
loaded.then(applyLoaded).catch((err) => {
|
|
226
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
227
|
+
console.warn('Shell.Bottom: failed to load persisted size', err);
|
|
228
|
+
}
|
|
229
|
+
});
|
|
230
|
+
} else {
|
|
231
|
+
applyLoaded(loaded);
|
|
232
|
+
}
|
|
233
|
+
return () => {
|
|
234
|
+
mounted = false;
|
|
235
|
+
};
|
|
236
|
+
}, [resizable, persistenceAdapter, onResize, isOverlay]);
|
|
288
237
|
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
238
|
+
const handleEl =
|
|
239
|
+
resizable && !isOverlay && isExpanded ? (
|
|
240
|
+
<PaneResizeContext.Provider
|
|
241
|
+
value={{
|
|
242
|
+
containerRef: localRef,
|
|
243
|
+
cssVarName: '--bottom-size',
|
|
244
|
+
minSize,
|
|
245
|
+
maxSize,
|
|
246
|
+
defaultSize: expandedSize,
|
|
247
|
+
orientation: 'horizontal',
|
|
248
|
+
edge: 'start',
|
|
249
|
+
computeNext: (client, startClient, startSize) => {
|
|
250
|
+
const delta = client - startClient;
|
|
251
|
+
return startSize - delta;
|
|
252
|
+
},
|
|
253
|
+
onResize,
|
|
254
|
+
onResizeStart,
|
|
255
|
+
onResizeEnd: (size) => {
|
|
256
|
+
onResizeEnd?.(size);
|
|
257
|
+
emitSizeChange(size, { reason: 'resize' });
|
|
258
|
+
persistenceAdapter?.save?.(size);
|
|
259
|
+
},
|
|
260
|
+
target: 'bottom',
|
|
261
|
+
collapsible,
|
|
262
|
+
snapPoints,
|
|
263
|
+
snapTolerance: snapTolerance ?? 8,
|
|
264
|
+
collapseThreshold,
|
|
265
|
+
requestCollapse: () => shell.setBottomMode('collapsed'),
|
|
266
|
+
requestToggle: () => shell.togglePane('bottom'),
|
|
267
|
+
}}
|
|
268
|
+
>
|
|
269
|
+
{handleChildren.length > 0 ? handleChildren.map((el, i) => React.cloneElement(el, { key: el.key ?? i })) : <PaneHandle />}
|
|
270
|
+
</PaneResizeContext.Provider>
|
|
271
|
+
) : null;
|
|
308
272
|
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
273
|
+
// Strip control/size props from DOM spread (moved above overlay return to keep hook order consistent)
|
|
274
|
+
// Normalize CSS lengths to px (moved above overlay return to keep hook order consistent)
|
|
275
|
+
const normalizeToPx = React.useCallback((value: number | string | undefined): number | undefined => {
|
|
276
|
+
if (value == null) return undefined;
|
|
277
|
+
if (typeof value === 'number' && Number.isFinite(value)) return value;
|
|
278
|
+
const str = String(value).trim();
|
|
279
|
+
if (!str) return undefined;
|
|
280
|
+
if (str.endsWith('px')) return Number.parseFloat(str);
|
|
281
|
+
if (str.endsWith('rem')) {
|
|
282
|
+
const rem = Number.parseFloat(getComputedStyle(document.documentElement).fontSize || '16') || 16;
|
|
283
|
+
return Number.parseFloat(str) * rem;
|
|
284
|
+
}
|
|
285
|
+
if (str.endsWith('%')) {
|
|
286
|
+
const pct = Number.parseFloat(str);
|
|
287
|
+
const base = document.documentElement.clientHeight || window.innerHeight || 0;
|
|
288
|
+
return (pct / 100) * base;
|
|
289
|
+
}
|
|
290
|
+
const n = Number.parseFloat(str);
|
|
291
|
+
return Number.isFinite(n) ? n : undefined;
|
|
292
|
+
}, []);
|
|
324
293
|
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
const px = normalizeToPx(controlledSize);
|
|
294
|
+
// Apply defaultSize on mount when uncontrolled (moved above overlay return)
|
|
295
|
+
React.useEffect(() => {
|
|
296
|
+
if (!localRef.current) return;
|
|
297
|
+
if (typeof size === 'undefined' && typeof defaultSize !== 'undefined') {
|
|
298
|
+
const px = normalizeToPx(defaultSize);
|
|
331
299
|
if (typeof px === 'number' && Number.isFinite(px)) {
|
|
332
300
|
const minPx = typeof minSize === 'number' ? minSize : undefined;
|
|
333
301
|
const maxPx = typeof maxSize === 'number' ? maxSize : undefined;
|
|
334
302
|
const clamped = Math.min(maxPx ?? px, Math.max(minPx ?? px, px));
|
|
335
303
|
localRef.current.style.setProperty('--bottom-size', `${clamped}px`);
|
|
336
|
-
emitSizeChange(clamped, { reason: '
|
|
304
|
+
emitSizeChange(clamped, { reason: 'init' });
|
|
337
305
|
}
|
|
338
|
-
}
|
|
306
|
+
}
|
|
307
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
308
|
+
}, []);
|
|
339
309
|
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
);
|
|
310
|
+
// Controlled size sync (moved above overlay return)
|
|
311
|
+
const controlledSize = size;
|
|
312
|
+
React.useEffect(() => {
|
|
313
|
+
if (!localRef.current) return;
|
|
314
|
+
if (typeof controlledSize === 'undefined') return;
|
|
315
|
+
const px = normalizeToPx(controlledSize);
|
|
316
|
+
if (typeof px === 'number' && Number.isFinite(px)) {
|
|
317
|
+
const minPx = typeof minSize === 'number' ? minSize : undefined;
|
|
318
|
+
const maxPx = typeof maxSize === 'number' ? maxSize : undefined;
|
|
319
|
+
const clamped = Math.min(maxPx ?? px, Math.max(minPx ?? px, px));
|
|
320
|
+
localRef.current.style.setProperty('--bottom-size', `${clamped}px`);
|
|
321
|
+
emitSizeChange(clamped, { reason: 'controlled' });
|
|
352
322
|
}
|
|
323
|
+
}, [controlledSize, minSize, maxSize, normalizeToPx, emitSizeChange]);
|
|
353
324
|
|
|
325
|
+
if (isOverlay) {
|
|
326
|
+
const open = shell.bottomMode === 'expanded';
|
|
354
327
|
return (
|
|
355
|
-
<
|
|
356
|
-
{
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
data-peek={shell.peekTarget === 'bottom' || undefined}
|
|
361
|
-
data-presentation={shell.currentBreakpointReady ? resolvedPresentation : undefined}
|
|
362
|
-
data-open={(shell.currentBreakpointReady && isStacked && isExpanded) || undefined}
|
|
363
|
-
style={{
|
|
364
|
-
...style,
|
|
365
|
-
['--bottom-size' as any]: `${expandedSize}px`,
|
|
366
|
-
['--bottom-min-size' as any]: `${minSize}px`,
|
|
367
|
-
['--bottom-max-size' as any]: `${maxSize}px`,
|
|
368
|
-
}}
|
|
369
|
-
>
|
|
370
|
-
<div className="rt-ShellBottomContent" data-visible={isExpanded || undefined}>
|
|
328
|
+
<Sheet.Root open={open} onOpenChange={(o) => shell.setBottomMode(o ? 'expanded' : 'collapsed')}>
|
|
329
|
+
<Sheet.Content side="bottom" style={{ padding: 0 }} height={{ initial: `${expandedSize}px` }}>
|
|
330
|
+
<VisuallyHidden>
|
|
331
|
+
<Sheet.Title>Bottom panel</Sheet.Title>
|
|
332
|
+
</VisuallyHidden>
|
|
371
333
|
{contentChildren}
|
|
372
|
-
</
|
|
373
|
-
|
|
374
|
-
</div>
|
|
334
|
+
</Sheet.Content>
|
|
335
|
+
</Sheet.Root>
|
|
375
336
|
);
|
|
376
|
-
}
|
|
377
|
-
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
return (
|
|
340
|
+
<div
|
|
341
|
+
{...bottomDomProps}
|
|
342
|
+
ref={setRef}
|
|
343
|
+
className={classNames('rt-ShellBottom', className)}
|
|
344
|
+
data-mode={shell.bottomMode}
|
|
345
|
+
data-peek={shell.peekTarget === 'bottom' || undefined}
|
|
346
|
+
data-presentation={shell.currentBreakpointReady ? resolvedPresentation : undefined}
|
|
347
|
+
data-open={(shell.currentBreakpointReady && isStacked && isExpanded) || undefined}
|
|
348
|
+
style={{
|
|
349
|
+
...style,
|
|
350
|
+
['--bottom-size' as any]: `${expandedSize}px`,
|
|
351
|
+
['--bottom-min-size' as any]: `${minSize}px`,
|
|
352
|
+
['--bottom-max-size' as any]: `${maxSize}px`,
|
|
353
|
+
}}
|
|
354
|
+
>
|
|
355
|
+
<div className="rt-ShellBottomContent" data-visible={isExpanded || undefined}>
|
|
356
|
+
{contentChildren}
|
|
357
|
+
</div>
|
|
358
|
+
{handleEl}
|
|
359
|
+
</div>
|
|
360
|
+
);
|
|
361
|
+
}) as BottomComponent;
|
|
378
362
|
Bottom.displayName = 'Shell.Bottom';
|
|
379
363
|
Bottom.Handle = BottomHandle;
|