@hyphen/hyphen-components 7.2.0 → 7.3.1
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/components/Sidebar/Sidebar.d.ts +20 -13
- package/dist/components/Sidebar/Sidebar.stories.d.ts +2 -0
- package/dist/hyphen-components.cjs.development.js +280 -161
- package/dist/hyphen-components.cjs.development.js.map +1 -1
- package/dist/hyphen-components.cjs.production.min.js +1 -1
- package/dist/hyphen-components.cjs.production.min.js.map +1 -1
- package/dist/hyphen-components.esm.js +280 -161
- package/dist/hyphen-components.esm.js.map +1 -1
- package/package.json +2 -2
- package/src/components/Sidebar/Sidebar.stories.tsx +104 -1
- package/src/components/Sidebar/Sidebar.test.tsx +178 -3
- package/src/components/Sidebar/Sidebar.tsx +424 -194
|
@@ -23,35 +23,225 @@ import {
|
|
|
23
23
|
|
|
24
24
|
const SIDEBAR_WIDTH = '16rem';
|
|
25
25
|
const SIDEBAR_WIDTH_ICON = '44px';
|
|
26
|
-
const
|
|
26
|
+
const SIDEBAR_KEYBOARD_SHORTCUT_LEFT = '[';
|
|
27
|
+
const SIDEBAR_KEYBOARD_SHORTCUT_RIGHT = ']';
|
|
27
28
|
|
|
28
|
-
|
|
29
|
+
type SidebarSide = 'left' | 'right';
|
|
30
|
+
|
|
31
|
+
type SidebarOpenState = Record<SidebarSide, boolean>;
|
|
32
|
+
|
|
33
|
+
type SidebarOpenValue = boolean | Partial<SidebarOpenState>;
|
|
34
|
+
|
|
35
|
+
type SidebarStorageKey = string | Partial<Record<SidebarSide, string>>;
|
|
36
|
+
|
|
37
|
+
interface SidebarContextSideState {
|
|
29
38
|
state: 'expanded' | 'collapsed';
|
|
30
39
|
open: boolean;
|
|
31
|
-
setOpen: (open: boolean) => void;
|
|
40
|
+
setOpen: (open: boolean | ((open: boolean) => boolean)) => void;
|
|
32
41
|
openMobile: boolean;
|
|
33
|
-
setOpenMobile: (open: boolean) => void;
|
|
34
|
-
isMobile: boolean;
|
|
42
|
+
setOpenMobile: (open: boolean | ((open: boolean) => boolean)) => void;
|
|
35
43
|
toggleSidebar: () => void;
|
|
36
44
|
}
|
|
37
45
|
|
|
38
|
-
const
|
|
46
|
+
const SidebarIsMobileContext = React.createContext<boolean | null>(null);
|
|
47
|
+
const SidebarLeftContext = React.createContext<SidebarContextSideState | null>(
|
|
48
|
+
null
|
|
49
|
+
);
|
|
50
|
+
const SidebarRightContext = React.createContext<SidebarContextSideState | null>(
|
|
51
|
+
null
|
|
52
|
+
);
|
|
53
|
+
const SidebarSideContext = React.createContext<SidebarSide>('left');
|
|
54
|
+
|
|
55
|
+
const resolveSideValue = (
|
|
56
|
+
value: SidebarOpenValue | undefined,
|
|
57
|
+
side: SidebarSide,
|
|
58
|
+
fallback: boolean
|
|
59
|
+
) => {
|
|
60
|
+
if (typeof value === 'boolean') {
|
|
61
|
+
return value;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (value && typeof value === 'object' && typeof value[side] === 'boolean') {
|
|
65
|
+
return value[side] as boolean;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return fallback;
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const resolveControlledOpen = (
|
|
72
|
+
value: SidebarOpenValue | undefined,
|
|
73
|
+
side: SidebarSide
|
|
74
|
+
) => {
|
|
75
|
+
if (typeof value === 'boolean') {
|
|
76
|
+
return value;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (value && typeof value === 'object' && typeof value[side] === 'boolean') {
|
|
80
|
+
return value[side] as boolean;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return undefined;
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
const resolveStorageKey = (
|
|
87
|
+
storageKey: SidebarStorageKey,
|
|
88
|
+
side: SidebarSide
|
|
89
|
+
) => {
|
|
90
|
+
if (typeof storageKey === 'string') {
|
|
91
|
+
return side === 'left' ? storageKey : `${storageKey}_right`;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (storageKey && typeof storageKey === 'object') {
|
|
95
|
+
return (
|
|
96
|
+
storageKey[side] ??
|
|
97
|
+
(side === 'left' ? 'sidebar_expanded' : 'sidebar_expanded_right')
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return side === 'left' ? 'sidebar_expanded' : 'sidebar_expanded_right';
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
const getSidebarWidth = (
|
|
105
|
+
state: SidebarContextSideState['state'],
|
|
106
|
+
collapsible: 'offcanvas' | 'icon' | 'none'
|
|
107
|
+
) =>
|
|
108
|
+
state === 'collapsed' && collapsible === 'icon'
|
|
109
|
+
? 'var(--sidebar-width-icon)'
|
|
110
|
+
: state === 'collapsed'
|
|
111
|
+
? '0'
|
|
112
|
+
: 'var(--sidebar-width)';
|
|
113
|
+
|
|
114
|
+
const getSidebarOffsetStyles = (
|
|
115
|
+
side: SidebarSide,
|
|
116
|
+
state: SidebarContextSideState['state'],
|
|
117
|
+
collapsible: 'offcanvas' | 'icon' | 'none'
|
|
118
|
+
) => {
|
|
119
|
+
const isVisible = state === 'expanded' || collapsible === 'icon';
|
|
120
|
+
return {
|
|
121
|
+
left:
|
|
122
|
+
side === 'left'
|
|
123
|
+
? isVisible
|
|
124
|
+
? '0'
|
|
125
|
+
: 'calc(var(--sidebar-width)*-1)'
|
|
126
|
+
: undefined,
|
|
127
|
+
right:
|
|
128
|
+
side === 'right'
|
|
129
|
+
? isVisible
|
|
130
|
+
? '0'
|
|
131
|
+
: 'calc(var(--sidebar-width)*-1)'
|
|
132
|
+
: undefined,
|
|
133
|
+
};
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
const useSidebarSideState = ({
|
|
137
|
+
side,
|
|
138
|
+
isMobile,
|
|
139
|
+
defaultOpen,
|
|
140
|
+
openProp,
|
|
141
|
+
onOpenChange,
|
|
142
|
+
storageKey,
|
|
143
|
+
lastToggledSideRef,
|
|
144
|
+
}: {
|
|
145
|
+
side: SidebarSide;
|
|
146
|
+
isMobile: boolean;
|
|
147
|
+
defaultOpen: SidebarOpenValue | undefined;
|
|
148
|
+
openProp: SidebarOpenValue | undefined;
|
|
149
|
+
onOpenChange?: (open: boolean, side?: SidebarSide) => void;
|
|
150
|
+
storageKey: SidebarStorageKey;
|
|
151
|
+
lastToggledSideRef: React.MutableRefObject<SidebarSide>;
|
|
152
|
+
}): SidebarContextSideState => {
|
|
153
|
+
const defaultFallback = typeof defaultOpen === 'boolean' ? defaultOpen : true;
|
|
154
|
+
const initialDefaultOpen = resolveSideValue(
|
|
155
|
+
defaultOpen,
|
|
156
|
+
side,
|
|
157
|
+
defaultFallback
|
|
158
|
+
);
|
|
159
|
+
const controlledOpen = resolveControlledOpen(openProp, side);
|
|
160
|
+
const isControlled = typeof controlledOpen === 'boolean';
|
|
161
|
+
|
|
162
|
+
const [uncontrolledOpen, setUncontrolledOpen] = useState(
|
|
163
|
+
controlledOpen ?? initialDefaultOpen
|
|
164
|
+
);
|
|
165
|
+
const [openMobile, setOpenMobile] = useState(() =>
|
|
166
|
+
isMobile ? false : controlledOpen ?? initialDefaultOpen
|
|
167
|
+
);
|
|
168
|
+
|
|
169
|
+
const open = controlledOpen ?? uncontrolledOpen;
|
|
170
|
+
|
|
171
|
+
useEffect(() => {
|
|
172
|
+
if (isMobile) {
|
|
173
|
+
setOpenMobile(false);
|
|
174
|
+
} else {
|
|
175
|
+
setUncontrolledOpen(controlledOpen ?? initialDefaultOpen);
|
|
176
|
+
}
|
|
177
|
+
}, [isMobile, controlledOpen, initialDefaultOpen]);
|
|
178
|
+
|
|
179
|
+
const setOpen = useCallback(
|
|
180
|
+
(value: boolean | ((value: boolean) => boolean)) => {
|
|
181
|
+
const newOpenState = typeof value === 'function' ? value(open) : value;
|
|
182
|
+
if (newOpenState === open) {
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (!isControlled) {
|
|
187
|
+
setUncontrolledOpen(newOpenState);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
onOpenChange?.(newOpenState, side);
|
|
191
|
+
|
|
192
|
+
const key = resolveStorageKey(storageKey, side);
|
|
193
|
+
localStorage.setItem(key, `${newOpenState}`);
|
|
194
|
+
},
|
|
195
|
+
[open, isControlled, onOpenChange, side, storageKey]
|
|
196
|
+
);
|
|
197
|
+
|
|
198
|
+
const toggleSidebar = useCallback(() => {
|
|
199
|
+
lastToggledSideRef.current = side;
|
|
200
|
+
isMobile ? setOpenMobile((value) => !value) : setOpen((value) => !value);
|
|
201
|
+
}, [isMobile, setOpen, side, lastToggledSideRef]);
|
|
202
|
+
|
|
203
|
+
const state = open ? 'expanded' : 'collapsed';
|
|
204
|
+
|
|
205
|
+
return useMemo(
|
|
206
|
+
() => ({
|
|
207
|
+
state,
|
|
208
|
+
open,
|
|
209
|
+
setOpen,
|
|
210
|
+
openMobile,
|
|
211
|
+
setOpenMobile,
|
|
212
|
+
toggleSidebar,
|
|
213
|
+
}),
|
|
214
|
+
[state, open, setOpen, openMobile, setOpenMobile, toggleSidebar]
|
|
215
|
+
);
|
|
216
|
+
};
|
|
39
217
|
|
|
40
|
-
function useSidebar() {
|
|
41
|
-
const
|
|
42
|
-
if (
|
|
218
|
+
function useSidebar(sideOverride?: SidebarSide) {
|
|
219
|
+
const isMobile = React.useContext(SidebarIsMobileContext);
|
|
220
|
+
if (typeof isMobile !== 'boolean') {
|
|
43
221
|
throw new Error('useSidebar must be used within a SidebarProvider.');
|
|
44
222
|
}
|
|
45
|
-
|
|
223
|
+
const contextSide = React.useContext(SidebarSideContext);
|
|
224
|
+
const side = sideOverride ?? contextSide;
|
|
225
|
+
const sideContext = React.useContext(
|
|
226
|
+
side === 'left' ? SidebarLeftContext : SidebarRightContext
|
|
227
|
+
);
|
|
228
|
+
if (!sideContext) {
|
|
229
|
+
throw new Error('useSidebar must be used within a SidebarProvider.');
|
|
230
|
+
}
|
|
231
|
+
return {
|
|
232
|
+
...sideContext,
|
|
233
|
+
isMobile,
|
|
234
|
+
side,
|
|
235
|
+
};
|
|
46
236
|
}
|
|
47
237
|
|
|
48
238
|
const SidebarProvider = forwardRef<
|
|
49
239
|
HTMLDivElement,
|
|
50
240
|
React.ComponentProps<'div'> & {
|
|
51
|
-
defaultOpen?:
|
|
52
|
-
open?:
|
|
53
|
-
storageKey?:
|
|
54
|
-
onOpenChange?: (open: boolean) => void;
|
|
241
|
+
defaultOpen?: SidebarOpenValue;
|
|
242
|
+
open?: SidebarOpenValue;
|
|
243
|
+
storageKey?: SidebarStorageKey;
|
|
244
|
+
onOpenChange?: (open: boolean, side?: SidebarSide) => void;
|
|
55
245
|
}
|
|
56
246
|
>(
|
|
57
247
|
(
|
|
@@ -68,97 +258,103 @@ const SidebarProvider = forwardRef<
|
|
|
68
258
|
ref
|
|
69
259
|
) => {
|
|
70
260
|
const isMobile = useIsMobile();
|
|
71
|
-
const
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
261
|
+
const lastToggledSideRef = React.useRef<SidebarSide>('left');
|
|
262
|
+
const leftToggleRef = React.useRef<
|
|
263
|
+
SidebarContextSideState['toggleSidebar'] | null
|
|
264
|
+
>(null);
|
|
265
|
+
const rightToggleRef = React.useRef<
|
|
266
|
+
SidebarContextSideState['toggleSidebar'] | null
|
|
267
|
+
>(null);
|
|
268
|
+
const leftState = useSidebarSideState({
|
|
269
|
+
side: 'left',
|
|
270
|
+
isMobile,
|
|
271
|
+
defaultOpen,
|
|
272
|
+
openProp,
|
|
273
|
+
onOpenChange: setOpenProp,
|
|
274
|
+
storageKey,
|
|
275
|
+
lastToggledSideRef,
|
|
276
|
+
});
|
|
277
|
+
const rightState = useSidebarSideState({
|
|
278
|
+
side: 'right',
|
|
279
|
+
isMobile,
|
|
280
|
+
defaultOpen,
|
|
281
|
+
openProp,
|
|
282
|
+
onOpenChange: setOpenProp,
|
|
283
|
+
storageKey,
|
|
284
|
+
lastToggledSideRef,
|
|
285
|
+
});
|
|
78
286
|
|
|
79
|
-
// Update open state when openProp or isMobile changes
|
|
80
287
|
useEffect(() => {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
} else {
|
|
84
|
-
_setOpen(openProp ?? defaultOpen); // Use desktop state
|
|
85
|
-
}
|
|
86
|
-
}, [isMobile, openProp, defaultOpen]);
|
|
87
|
-
|
|
88
|
-
const setOpen = useCallback(
|
|
89
|
-
(value: boolean | ((value: boolean) => boolean)) => {
|
|
90
|
-
const newOpenState = typeof value === 'function' ? value(open) : value;
|
|
91
|
-
|
|
92
|
-
if (newOpenState !== open) {
|
|
93
|
-
if (setOpenProp) {
|
|
94
|
-
setOpenProp(newOpenState);
|
|
95
|
-
} else {
|
|
96
|
-
_setOpen(newOpenState);
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
localStorage.setItem(storageKey, `${newOpenState}`);
|
|
100
|
-
}
|
|
101
|
-
},
|
|
102
|
-
[setOpenProp, open, storageKey]
|
|
103
|
-
);
|
|
288
|
+
leftToggleRef.current = leftState.toggleSidebar;
|
|
289
|
+
}, [leftState.toggleSidebar]);
|
|
104
290
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
}, [isMobile, setOpen, setOpenMobile]);
|
|
291
|
+
useEffect(() => {
|
|
292
|
+
rightToggleRef.current = rightState.toggleSidebar;
|
|
293
|
+
}, [rightState.toggleSidebar]);
|
|
109
294
|
|
|
110
|
-
// Keydown event handler for toggling sidebar
|
|
111
295
|
useEffect(() => {
|
|
112
296
|
const handleKeyDown = (event: KeyboardEvent) => {
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
297
|
+
const target = event.target as HTMLElement | null;
|
|
298
|
+
if (
|
|
299
|
+
target &&
|
|
300
|
+
(target.tagName === 'INPUT' ||
|
|
301
|
+
target.tagName === 'TEXTAREA' ||
|
|
302
|
+
target.tagName === 'SELECT' ||
|
|
303
|
+
target.isContentEditable)
|
|
304
|
+
) {
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
const shortcutSide =
|
|
308
|
+
event.key === SIDEBAR_KEYBOARD_SHORTCUT_LEFT
|
|
309
|
+
? 'left'
|
|
310
|
+
: event.key === SIDEBAR_KEYBOARD_SHORTCUT_RIGHT
|
|
311
|
+
? 'right'
|
|
312
|
+
: null;
|
|
313
|
+
|
|
314
|
+
if (!shortcutSide) {
|
|
315
|
+
return;
|
|
116
316
|
}
|
|
317
|
+
|
|
318
|
+
event.preventDefault();
|
|
319
|
+
|
|
320
|
+
const toggleSidebar =
|
|
321
|
+
shortcutSide === 'left'
|
|
322
|
+
? leftToggleRef.current
|
|
323
|
+
: rightToggleRef.current;
|
|
324
|
+
toggleSidebar?.();
|
|
117
325
|
};
|
|
118
326
|
|
|
119
327
|
window.addEventListener('keydown', handleKeyDown);
|
|
120
328
|
return () => window.removeEventListener('keydown', handleKeyDown);
|
|
121
|
-
}, [
|
|
122
|
-
|
|
123
|
-
// Assign state for data attributes
|
|
124
|
-
const state = open ? 'expanded' : 'collapsed';
|
|
125
|
-
|
|
126
|
-
const contextValue = useMemo<SidebarContextProps>(
|
|
127
|
-
() => ({
|
|
128
|
-
state,
|
|
129
|
-
open,
|
|
130
|
-
setOpen,
|
|
131
|
-
isMobile,
|
|
132
|
-
openMobile,
|
|
133
|
-
setOpenMobile,
|
|
134
|
-
toggleSidebar,
|
|
135
|
-
}),
|
|
136
|
-
[state, open, setOpen, isMobile, openMobile, setOpenMobile, toggleSidebar]
|
|
137
|
-
);
|
|
329
|
+
}, []);
|
|
138
330
|
|
|
139
331
|
return (
|
|
140
|
-
<
|
|
141
|
-
<
|
|
142
|
-
<
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
332
|
+
<SidebarIsMobileContext.Provider value={isMobile}>
|
|
333
|
+
<SidebarLeftContext.Provider value={leftState}>
|
|
334
|
+
<SidebarRightContext.Provider value={rightState}>
|
|
335
|
+
<TooltipProvider delayDuration={0}>
|
|
336
|
+
<div
|
|
337
|
+
style={
|
|
338
|
+
{
|
|
339
|
+
'--sidebar-width': SIDEBAR_WIDTH,
|
|
340
|
+
'--sidebar-width-icon': SIDEBAR_WIDTH_ICON,
|
|
341
|
+
minBlockSize: '100svh',
|
|
342
|
+
...style,
|
|
343
|
+
} as React.CSSProperties
|
|
344
|
+
}
|
|
345
|
+
className={classNames(
|
|
346
|
+
'display-flex w-100 background-color-secondary',
|
|
347
|
+
className
|
|
348
|
+
)}
|
|
349
|
+
ref={ref}
|
|
350
|
+
{...props}
|
|
351
|
+
>
|
|
352
|
+
{children}
|
|
353
|
+
</div>
|
|
354
|
+
</TooltipProvider>
|
|
355
|
+
</SidebarRightContext.Provider>
|
|
356
|
+
</SidebarLeftContext.Provider>
|
|
357
|
+
</SidebarIsMobileContext.Provider>
|
|
162
358
|
);
|
|
163
359
|
}
|
|
164
360
|
);
|
|
@@ -167,7 +363,7 @@ SidebarProvider.displayName = 'SidebarProvider';
|
|
|
167
363
|
const Sidebar = React.forwardRef<
|
|
168
364
|
HTMLDivElement,
|
|
169
365
|
React.ComponentProps<'div'> & {
|
|
170
|
-
side?: 'left'
|
|
366
|
+
side?: 'left' | 'right';
|
|
171
367
|
collapsible?: 'offcanvas' | 'icon' | 'none';
|
|
172
368
|
}
|
|
173
369
|
>(
|
|
@@ -175,106 +371,116 @@ const Sidebar = React.forwardRef<
|
|
|
175
371
|
{ side = 'left', collapsible = 'offcanvas', className, children, ...props },
|
|
176
372
|
ref
|
|
177
373
|
) => {
|
|
178
|
-
const { isMobile, state, openMobile, setOpenMobile } = useSidebar();
|
|
374
|
+
const { isMobile, state, openMobile, setOpenMobile } = useSidebar(side);
|
|
179
375
|
|
|
180
376
|
if (isMobile) {
|
|
181
377
|
return (
|
|
182
|
-
<
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
378
|
+
<SidebarSideContext.Provider value={side}>
|
|
379
|
+
<Drawer
|
|
380
|
+
isOpen={openMobile}
|
|
381
|
+
onDismiss={() => setOpenMobile(false)}
|
|
382
|
+
placement={side}
|
|
383
|
+
>
|
|
384
|
+
<Box data-sidebar="sidebar" data-mobile="true" height="100">
|
|
385
|
+
{children}
|
|
386
|
+
</Box>
|
|
387
|
+
</Drawer>
|
|
388
|
+
</SidebarSideContext.Provider>
|
|
191
389
|
);
|
|
192
390
|
}
|
|
193
391
|
|
|
194
392
|
if (collapsible === 'none') {
|
|
195
393
|
return (
|
|
196
|
-
<
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
394
|
+
<SidebarSideContext.Provider value={side}>
|
|
395
|
+
<div
|
|
396
|
+
className={classNames(
|
|
397
|
+
'group display-flex h-100 font-size-xs flex-direction-column background-color-secondary font-color-base',
|
|
398
|
+
className
|
|
399
|
+
)}
|
|
400
|
+
style={{
|
|
401
|
+
width: 'var(--sidebar-width)',
|
|
402
|
+
}}
|
|
403
|
+
ref={ref}
|
|
404
|
+
{...props}
|
|
405
|
+
>
|
|
406
|
+
{children}
|
|
407
|
+
</div>
|
|
408
|
+
</SidebarSideContext.Provider>
|
|
209
409
|
);
|
|
210
410
|
}
|
|
211
411
|
|
|
212
412
|
return (
|
|
213
|
-
<
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
transitionDuration: 'var(--sidebar-transition-duration, 200ms)',
|
|
231
|
-
animationDuration: 'var(--sidebar-transition-duration, 200ms)',
|
|
232
|
-
transitionProperty: 'width',
|
|
233
|
-
width:
|
|
234
|
-
state === 'collapsed' && collapsible === 'icon'
|
|
235
|
-
? 'var(--sidebar-width-icon)'
|
|
236
|
-
: state === 'collapsed'
|
|
237
|
-
? '0'
|
|
238
|
-
: 'var(--sidebar-width)',
|
|
239
|
-
height: '100svh',
|
|
240
|
-
}}
|
|
241
|
-
className={classNames('position-relative', className)}
|
|
242
|
-
/>
|
|
243
|
-
<div
|
|
244
|
-
className={classNames(
|
|
245
|
-
'position-absolute display-none display-flex-desktop ',
|
|
246
|
-
className
|
|
247
|
-
)}
|
|
248
|
-
style={{
|
|
249
|
-
left:
|
|
250
|
-
state === 'expanded' || collapsible === 'icon'
|
|
251
|
-
? '0'
|
|
252
|
-
: 'calc(var(--sidebar-width)*-1)',
|
|
253
|
-
top: '0',
|
|
254
|
-
bottom: '0',
|
|
255
|
-
zIndex: 'var(--size-z-index-drawer)',
|
|
256
|
-
animationTimingFunction: 'var(--sidebar-transition-timing, linear)',
|
|
257
|
-
transitionTimingFunction:
|
|
258
|
-
'var(--sidebar-transition-timing, linear)',
|
|
259
|
-
transitionDuration: 'var(--sidebar-transition-duration, 200ms)',
|
|
260
|
-
animationDuration: 'var(--sidebar-transition-duration, 200ms)',
|
|
261
|
-
transitionProperty: 'left, right, width',
|
|
262
|
-
width:
|
|
263
|
-
state === 'collapsed' && collapsible === 'icon'
|
|
264
|
-
? 'var(--sidebar-width-icon)'
|
|
265
|
-
: 'var(--sidebar-width)',
|
|
266
|
-
height: '100svh',
|
|
267
|
-
}}
|
|
268
|
-
{...props}
|
|
413
|
+
<SidebarSideContext.Provider value={side}>
|
|
414
|
+
<Box
|
|
415
|
+
ref={ref}
|
|
416
|
+
background="primary"
|
|
417
|
+
display={{ base: 'none', desktop: 'block' }}
|
|
418
|
+
color="base"
|
|
419
|
+
fontSize="sm"
|
|
420
|
+
position="relative"
|
|
421
|
+
style={
|
|
422
|
+
side === 'right' && collapsible === 'offcanvas'
|
|
423
|
+
? { overflowX: 'hidden' }
|
|
424
|
+
: undefined
|
|
425
|
+
}
|
|
426
|
+
data-state={state}
|
|
427
|
+
data-collapsible={collapsible}
|
|
428
|
+
data-side={side}
|
|
429
|
+
className="group"
|
|
269
430
|
>
|
|
270
431
|
<div
|
|
271
|
-
|
|
272
|
-
|
|
432
|
+
style={{
|
|
433
|
+
animationTimingFunction:
|
|
434
|
+
'var(--sidebar-transition-timing, linear)',
|
|
435
|
+
transitionTimingFunction:
|
|
436
|
+
'var(--sidebar-transition-timing, linear)',
|
|
437
|
+
transitionDuration: 'var(--sidebar-transition-duration, 200ms)',
|
|
438
|
+
animationDuration: 'var(--sidebar-transition-duration, 200ms)',
|
|
439
|
+
transitionProperty: 'width',
|
|
440
|
+
width: getSidebarWidth(state, collapsible),
|
|
441
|
+
height: '100svh',
|
|
442
|
+
}}
|
|
443
|
+
className={classNames('position-relative', className)}
|
|
444
|
+
/>
|
|
445
|
+
<div
|
|
446
|
+
className={classNames(
|
|
447
|
+
'position-absolute display-none display-flex-desktop ',
|
|
448
|
+
className
|
|
449
|
+
)}
|
|
450
|
+
style={{
|
|
451
|
+
...getSidebarOffsetStyles(side, state, collapsible),
|
|
452
|
+
top: '0',
|
|
453
|
+
bottom: '0',
|
|
454
|
+
zIndex: 'var(--size-z-index-drawer)',
|
|
455
|
+
animationTimingFunction:
|
|
456
|
+
'var(--sidebar-transition-timing, linear)',
|
|
457
|
+
transitionTimingFunction:
|
|
458
|
+
'var(--sidebar-transition-timing, linear)',
|
|
459
|
+
transitionDuration: 'var(--sidebar-transition-duration, 200ms)',
|
|
460
|
+
animationDuration: 'var(--sidebar-transition-duration, 200ms)',
|
|
461
|
+
transitionProperty: 'left, right, width',
|
|
462
|
+
width:
|
|
463
|
+
state === 'collapsed' && collapsible === 'icon'
|
|
464
|
+
? 'var(--sidebar-width-icon)'
|
|
465
|
+
: 'var(--sidebar-width)',
|
|
466
|
+
height: '100svh',
|
|
467
|
+
}}
|
|
468
|
+
{...props}
|
|
273
469
|
>
|
|
274
|
-
|
|
470
|
+
<div
|
|
471
|
+
data-sidebar="sidebar"
|
|
472
|
+
className={classNames(
|
|
473
|
+
'display-flex h-100 w-100 flex-direction-column background-color-secondary font-color-base',
|
|
474
|
+
{
|
|
475
|
+
'p-right-lg-desktop': side === 'right',
|
|
476
|
+
}
|
|
477
|
+
)}
|
|
478
|
+
>
|
|
479
|
+
{children}
|
|
480
|
+
</div>
|
|
275
481
|
</div>
|
|
276
|
-
</
|
|
277
|
-
</
|
|
482
|
+
</Box>
|
|
483
|
+
</SidebarSideContext.Provider>
|
|
278
484
|
);
|
|
279
485
|
}
|
|
280
486
|
);
|
|
@@ -282,9 +488,12 @@ Sidebar.displayName = 'Sidebar';
|
|
|
282
488
|
|
|
283
489
|
const SidebarTrigger = React.forwardRef<
|
|
284
490
|
React.ElementRef<typeof Button>,
|
|
285
|
-
React.ComponentProps<typeof Button>
|
|
286
|
-
|
|
287
|
-
|
|
491
|
+
React.ComponentProps<typeof Button> & {
|
|
492
|
+
side?: SidebarSide;
|
|
493
|
+
iconName?: IconName;
|
|
494
|
+
}
|
|
495
|
+
>(({ className, onClick, side, iconName = 'dock-left', ...props }, ref) => {
|
|
496
|
+
const { toggleSidebar, side: contextSide } = useSidebar(side);
|
|
288
497
|
|
|
289
498
|
return (
|
|
290
499
|
<Button
|
|
@@ -292,13 +501,19 @@ const SidebarTrigger = React.forwardRef<
|
|
|
292
501
|
data-sidebar="trigger"
|
|
293
502
|
variant="tertiary"
|
|
294
503
|
size="sm"
|
|
295
|
-
iconPrefix=
|
|
296
|
-
className={classNames(
|
|
504
|
+
iconPrefix={iconName}
|
|
505
|
+
className={classNames(
|
|
506
|
+
{
|
|
507
|
+
'm-left-sm m-left-0-tablet': contextSide === 'left',
|
|
508
|
+
'm-right-sm m-right-0-tablet': contextSide === 'right',
|
|
509
|
+
},
|
|
510
|
+
className
|
|
511
|
+
)}
|
|
297
512
|
onClick={(event) => {
|
|
298
513
|
onClick?.(event);
|
|
299
514
|
toggleSidebar();
|
|
300
515
|
}}
|
|
301
|
-
aria-label=
|
|
516
|
+
aria-label={`Toggle ${contextSide} sidebar`}
|
|
302
517
|
{...props}
|
|
303
518
|
/>
|
|
304
519
|
);
|
|
@@ -429,7 +644,7 @@ const SidebarMenuButton = React.forwardRef<
|
|
|
429
644
|
ref
|
|
430
645
|
) => {
|
|
431
646
|
const Comp = asChild ? Slot : 'button';
|
|
432
|
-
const { isMobile, state } = useSidebar();
|
|
647
|
+
const { isMobile, state, side } = useSidebar();
|
|
433
648
|
|
|
434
649
|
const button = (
|
|
435
650
|
<Comp
|
|
@@ -464,7 +679,7 @@ const SidebarMenuButton = React.forwardRef<
|
|
|
464
679
|
<Tooltip>
|
|
465
680
|
<TooltipTrigger asChild>{button}</TooltipTrigger>
|
|
466
681
|
<TooltipContent
|
|
467
|
-
side=
|
|
682
|
+
side={side === 'right' ? 'left' : 'right'}
|
|
468
683
|
align="center"
|
|
469
684
|
hidden={state !== 'collapsed' || isMobile}
|
|
470
685
|
{...tooltip}
|
|
@@ -594,9 +809,19 @@ const SidebarRail = React.forwardRef<
|
|
|
594
809
|
HTMLButtonElement,
|
|
595
810
|
React.ComponentProps<'button'>
|
|
596
811
|
>(({ className, ...props }, ref) => {
|
|
597
|
-
const { open, toggleSidebar } = useSidebar();
|
|
598
|
-
|
|
599
|
-
|
|
812
|
+
const { open, toggleSidebar, side } = useSidebar();
|
|
813
|
+
const shortcutLabel =
|
|
814
|
+
side === 'left'
|
|
815
|
+
? SIDEBAR_KEYBOARD_SHORTCUT_LEFT
|
|
816
|
+
: SIDEBAR_KEYBOARD_SHORTCUT_RIGHT;
|
|
817
|
+
|
|
818
|
+
const caretIcon = open
|
|
819
|
+
? side === 'right'
|
|
820
|
+
? 'caret-sm-right'
|
|
821
|
+
: 'caret-sm-left'
|
|
822
|
+
: side === 'right'
|
|
823
|
+
? 'caret-sm-left'
|
|
824
|
+
: 'caret-sm-right';
|
|
600
825
|
|
|
601
826
|
return (
|
|
602
827
|
<button
|
|
@@ -605,20 +830,23 @@ const SidebarRail = React.forwardRef<
|
|
|
605
830
|
aria-label="Toggle Sidebar"
|
|
606
831
|
tabIndex={-1}
|
|
607
832
|
onClick={toggleSidebar}
|
|
608
|
-
title=
|
|
833
|
+
title={`Toggle Sidebar ${shortcutLabel}`}
|
|
609
834
|
className={classNames(
|
|
610
835
|
styles.rail,
|
|
611
836
|
'hover-show-child background-color-transparent display-flex p-top-5xl p-left-xl p-right-0 justify-content-center position-absolute',
|
|
612
837
|
{
|
|
613
|
-
'cursor-w-resize':
|
|
614
|
-
|
|
838
|
+
'cursor-w-resize':
|
|
839
|
+
(open && side === 'left') || (!open && side === 'right'),
|
|
840
|
+
'cursor-e-resize':
|
|
841
|
+
(!open && side === 'left') || (open && side === 'right'),
|
|
615
842
|
},
|
|
616
843
|
className
|
|
617
844
|
)}
|
|
618
845
|
style={{
|
|
619
846
|
top: '20px',
|
|
620
847
|
bottom: '20px',
|
|
621
|
-
right: '-14px',
|
|
848
|
+
right: side === 'left' ? '-14px' : undefined,
|
|
849
|
+
left: side === 'right' ? '-14px' : undefined,
|
|
622
850
|
width: '10px',
|
|
623
851
|
}}
|
|
624
852
|
{...props}
|
|
@@ -638,8 +866,10 @@ const SidebarRail = React.forwardRef<
|
|
|
638
866
|
className={classNames(
|
|
639
867
|
'hover-child',
|
|
640
868
|
{
|
|
641
|
-
'cursor-w-resize':
|
|
642
|
-
|
|
869
|
+
'cursor-w-resize':
|
|
870
|
+
(open && side === 'left') || (!open && side === 'right'),
|
|
871
|
+
'cursor-e-resize':
|
|
872
|
+
(!open && side === 'left') || (open && side === 'right'),
|
|
643
873
|
},
|
|
644
874
|
className
|
|
645
875
|
)}
|