@sybilion/uilib 1.3.5 → 1.3.8
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/esm/components/ui/GridLayout/GridLayout.js +15 -0
- package/dist/esm/components/ui/GridLayout/GridLayout.styl.js +7 -0
- package/dist/esm/components/ui/Page/AppShell/AppShell.styl.js +1 -1
- package/dist/esm/components/ui/Sidebar/Sidebar.js +26 -21
- package/dist/esm/hooks/index.js +1 -0
- package/dist/esm/hooks/panelWidth.js +5 -1
- package/dist/esm/hooks/useIsSidebarSheetLayout.js +22 -0
- package/dist/esm/index.js +3 -0
- package/dist/esm/types/src/components/ui/GridLayout/GridLayout.d.ts +9 -0
- package/dist/esm/types/src/docs/pages/GridLayoutPage.d.ts +1 -0
- package/dist/esm/types/src/hooks/index.d.ts +1 -0
- package/dist/esm/types/src/hooks/panelWidth.d.ts +4 -0
- package/dist/esm/types/src/hooks/useIsSidebarSheetLayout.d.ts +2 -0
- package/dist/esm/types/src/index.d.ts +3 -0
- package/package.json +1 -1
- package/src/components/ui/GridLayout/GridLayout.styl +9 -0
- package/src/components/ui/GridLayout/GridLayout.styl.d.ts +7 -0
- package/src/components/ui/GridLayout/GridLayout.tsx +38 -0
- package/src/components/ui/Page/AppShell/AppShell.styl +4 -2
- package/src/components/ui/Sidebar/Sidebar.tsx +28 -21
- package/src/docs/pages/GridLayoutPage.tsx +64 -0
- package/src/docs/registry.ts +6 -0
- package/src/hooks/index.ts +1 -0
- package/src/hooks/panelWidth.ts +8 -0
- package/src/hooks/useIsSidebarSheetLayout.ts +25 -0
- package/src/index.ts +8 -0
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { jsx } from 'react/jsx-runtime';
|
|
2
|
+
import cn from 'classnames';
|
|
3
|
+
import S from './GridLayout.styl.js';
|
|
4
|
+
|
|
5
|
+
function GridLayout({ children, colWidth = 300, gap = 'var(--p-2)', className, as: asProp, }) {
|
|
6
|
+
const As = asProp ?? 'div';
|
|
7
|
+
return (jsx(As, { className: cn(S.root, className), style: {
|
|
8
|
+
maxWidth: '100%',
|
|
9
|
+
gap: gap,
|
|
10
|
+
display: 'grid',
|
|
11
|
+
gridTemplateColumns: `repeat(auto-fit, minmax(${colWidth}px, 1fr))`,
|
|
12
|
+
}, children: children }));
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export { GridLayout };
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import styleInject from 'style-inject';
|
|
2
|
+
|
|
3
|
+
var css_248z = "@media (max-width:768px){:root{--page-x-padding:var(--p-6);--page-y-padding:var(--p-6)}}.GridLayout_root__VF9sR{display:grid;min-width:0;width:100%}.GridLayout_root__VF9sR>*{min-width:300px}";
|
|
4
|
+
var S = {"root":"GridLayout_root__VF9sR"};
|
|
5
|
+
styleInject(css_248z);
|
|
6
|
+
|
|
7
|
+
export { S as default };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import styleInject from 'style-inject';
|
|
2
2
|
|
|
3
|
-
var css_248z = "@media (max-width:768px){:root{--page-x-padding:var(--p-6);--page-y-padding:var(--p-6)}}.AppShell_root__ONlNK{display:grid;flex:1;grid-template-areas:\"main\";grid-template-columns:1fr;min-height:0;min-height:100%;width:100%}@media (min-width:
|
|
3
|
+
var css_248z = "@media (max-width:768px){:root{--page-x-padding:var(--p-6);--page-y-padding:var(--p-6)}}.AppShell_root__ONlNK{display:grid;flex:1;grid-template-areas:\"main\";grid-template-columns:1fr;min-height:0;min-height:100%;width:100%}@media (min-width:1100px){.AppShell_root__ONlNK{grid-template-areas:\"sidebar main\";grid-template-columns:var(--sidebar-width) 1fr}[data-slot=sidebar-wrapper][data-state=collapsed] .AppShell_root__ONlNK{grid-template-columns:0 1fr}}.AppShell_mainColumn__Emn1p{display:flex;flex:1;flex-direction:column;grid-area:main;min-height:0;min-width:0}[data-slot=sidebar-wrapper][data-state=collapsed] .AppShell_mainColumn__Emn1p{margin-left:var(--p-3)}.AppShell_mainBody__IoVuy{background-color:var(--page-color);border-radius:var(--p-4);display:flex;flex:1;flex-direction:column;min-width:0;padding-bottom:var(--page-y-padding)}";
|
|
4
4
|
var S = {"root":"AppShell_root__ONlNK","mainColumn":"AppShell_mainColumn__Emn1p","mainBody":"AppShell_mainBody__IoVuy"};
|
|
5
5
|
styleInject(css_248z);
|
|
6
6
|
|
|
@@ -8,7 +8,7 @@ import { TooltipProvider, Tooltip, TooltipTrigger, TooltipContent } from '../Too
|
|
|
8
8
|
import { clampSidebarWidthPx, CHAT_WIDTH_STORAGE_KEY, SIDEBAR_WIDTH_DEFAULT_PX, SIDEBAR_WIDTH_MIN_PX, SIDEBAR_WIDTH_STORAGE_KEY_PX, SIDEBAR_WIDTH_STORAGE_KEY_PCT, CHAT_WIDTH_DEFAULT_PX, CHAT_WIDTH_MIN_PX, defaultChatWidthPx, clampChatWidthPx } from '../../../hooks/panelWidth.js';
|
|
9
9
|
import useElemDrag from '../../../hooks/useDragElem.js';
|
|
10
10
|
import useEvent from '../../../hooks/useEvent.js';
|
|
11
|
-
import {
|
|
11
|
+
import { useIsSidebarSheetLayout } from '../../../hooks/useIsSidebarSheetLayout.js';
|
|
12
12
|
import { useShellWidthObserver } from '../../../hooks/useShellWidthObserver.js';
|
|
13
13
|
import { setCookie, getCookie } from '../../../lib/cookie.js';
|
|
14
14
|
import { getCookiePreferences } from '../../../lib/cookie-consent/cookie-consent.js';
|
|
@@ -68,8 +68,8 @@ function SidebarProvider({ defaultOpen = getCookie('isSidebarOpen') ?? true, cla
|
|
|
68
68
|
const sidebarLsKey = sidebarWidthStorageKey ?? SIDEBAR_WIDTH_STORAGE_KEY_PX;
|
|
69
69
|
const allowPersistSidebarWidth = persistSidebarWidthWithoutConsent ||
|
|
70
70
|
Boolean(getCookiePreferences(userId)?.functional);
|
|
71
|
-
const
|
|
72
|
-
const [isOpen, setIsOpen] = useState(
|
|
71
|
+
const isSidebarSheetLayout = useIsSidebarSheetLayout();
|
|
72
|
+
const [isOpen, setIsOpen] = useState(isSidebarSheetLayout ? false : defaultOpen);
|
|
73
73
|
const wrapperRef = useRef(null);
|
|
74
74
|
const shellWidthRef = useRef(0);
|
|
75
75
|
const prevShellWidthRef = useRef(0);
|
|
@@ -194,14 +194,20 @@ function SidebarProvider({ defaultOpen = getCookie('isSidebarOpen') ?? true, cla
|
|
|
194
194
|
allowPersistSidebarWidth,
|
|
195
195
|
]);
|
|
196
196
|
useLayoutEffect(() => {
|
|
197
|
-
if (!shellEl ||
|
|
197
|
+
if (!shellEl || isSidebarSheetLayout)
|
|
198
198
|
return;
|
|
199
199
|
fitPanelWidthsToShell();
|
|
200
|
-
}, [
|
|
200
|
+
}, [
|
|
201
|
+
shellEl,
|
|
202
|
+
chatPanelOpen,
|
|
203
|
+
isSidebarSheetLayout,
|
|
204
|
+
isOpen,
|
|
205
|
+
fitPanelWidthsToShell,
|
|
206
|
+
]);
|
|
201
207
|
useShellWidthObserver(shellEl, handleShellResize);
|
|
202
208
|
const setOpen = useCallback((value, options) => {
|
|
203
209
|
const useViewTransition = options?.viewTransition !== false &&
|
|
204
|
-
!
|
|
210
|
+
!isSidebarSheetLayout &&
|
|
205
211
|
'startViewTransition' in document &&
|
|
206
212
|
document.startViewTransition;
|
|
207
213
|
if (useViewTransition) {
|
|
@@ -215,7 +221,7 @@ function SidebarProvider({ defaultOpen = getCookie('isSidebarOpen') ?? true, cla
|
|
|
215
221
|
if (getCookiePreferences(userId)?.functional) {
|
|
216
222
|
setCookie('isSidebarOpen', value.toString(), 60 * 60 * 24 * 7);
|
|
217
223
|
}
|
|
218
|
-
}, [
|
|
224
|
+
}, [isSidebarSheetLayout, userId]);
|
|
219
225
|
const toggleSidebar = useCallback(() => setOpen(!isOpen), [isOpen, setOpen]);
|
|
220
226
|
useEffect(() => {
|
|
221
227
|
const shell = wrapperRef.current;
|
|
@@ -223,7 +229,7 @@ function SidebarProvider({ defaultOpen = getCookie('isSidebarOpen') ?? true, cla
|
|
|
223
229
|
return;
|
|
224
230
|
if (chatPanelOpen) {
|
|
225
231
|
shell.setAttribute('data-chat-open', '');
|
|
226
|
-
if (
|
|
232
|
+
if (isSidebarSheetLayout) {
|
|
227
233
|
shell.style.setProperty('--chat-panel-width', '100%');
|
|
228
234
|
shell.style.setProperty('--chat-panel-height', '100dvh');
|
|
229
235
|
}
|
|
@@ -237,7 +243,7 @@ function SidebarProvider({ defaultOpen = getCookie('isSidebarOpen') ?? true, cla
|
|
|
237
243
|
shell.style.setProperty('--chat-panel-width', '0px');
|
|
238
244
|
shell.style.removeProperty('--chat-panel-height');
|
|
239
245
|
}
|
|
240
|
-
}, [chatPanelOpen,
|
|
246
|
+
}, [chatPanelOpen, isSidebarSheetLayout, chatWidthPx]);
|
|
241
247
|
useEffect(() => {
|
|
242
248
|
return () => {
|
|
243
249
|
const shell = wrapperRef.current;
|
|
@@ -291,18 +297,17 @@ function SidebarProvider({ defaultOpen = getCookie('isSidebarOpen') ?? true, cla
|
|
|
291
297
|
}, className: cn(SidebarStem.sidebarWrapper, className), ...props, children: [jsx("div", { className: SidebarStem.sidebarMainShell, children: children }), jsx("div", { ref: onChatPanelMount, className: SidebarStem.chatPanelMount, "data-slot": "chat-panel-mount" })] }) }) }));
|
|
292
298
|
}
|
|
293
299
|
function Sidebar({ defaultOpen = true, open: openProp, onOpenChange: setOpenProp, className, side = 'left', variant = 'sidebar', collapsible = 'offcanvas', children, fullHeightResizer = false, ...props }) {
|
|
294
|
-
const
|
|
300
|
+
const isSidebarSheetLayout = useIsSidebarSheetLayout();
|
|
295
301
|
const { isOpen, setOpen } = useSidebar();
|
|
296
|
-
|
|
297
|
-
// load and overwrote the isSidebarOpen cookie (collapsed → opened + cookie "true").
|
|
302
|
+
/** Narrow shell: sidebar is a sheet; collapse when crossing into this mode (preserve cookie semantics elsewhere). */
|
|
298
303
|
useEffect(() => {
|
|
299
|
-
if (
|
|
304
|
+
if (isSidebarSheetLayout)
|
|
300
305
|
setOpen(false);
|
|
301
|
-
}, [
|
|
306
|
+
}, [isSidebarSheetLayout, setOpen]);
|
|
302
307
|
if (collapsible === 'none') {
|
|
303
308
|
return (jsx("div", { "data-slot": "sidebar", className: cn(SidebarStem.sidebarNone, className), ...props, children: children }));
|
|
304
309
|
}
|
|
305
|
-
if (
|
|
310
|
+
if (isSidebarSheetLayout) {
|
|
306
311
|
return (jsx(Sheet, { open: isOpen, onOpenChange: setOpen, ...props, children: jsxs(SheetContent, { "data-sidebar": "sidebar", "data-slot": "sidebar", className: cn(SidebarStem.sheetContentSidebar, className), style: {
|
|
307
312
|
'--gap-top': '40px',
|
|
308
313
|
'--sidebar-width': SIDEBAR_WIDTH_MOBILE,
|
|
@@ -364,7 +369,7 @@ function PanelResizeHandle({ className, edge, isActive, startWidthPx, getShellWi
|
|
|
364
369
|
}
|
|
365
370
|
function SidebarResizeHandle({ className, side = 'left', ...props }) {
|
|
366
371
|
const { isOpen, sidebarWidthPx, setSidebarWidthPx, getShellWidth } = useSidebar();
|
|
367
|
-
const
|
|
372
|
+
const isSidebarSheetLayout = useIsSidebarSheetLayout();
|
|
368
373
|
const edge = side === 'left' ? 'trailing' : 'leading';
|
|
369
374
|
const onDragWidth = useCallback((rawPx) => {
|
|
370
375
|
setSidebarWidthPx(rawPx, { persist: false });
|
|
@@ -372,10 +377,10 @@ function SidebarResizeHandle({ className, side = 'left', ...props }) {
|
|
|
372
377
|
const onDragComplete = useCallback((finalRawPx) => {
|
|
373
378
|
setSidebarWidthPx(finalRawPx, { persist: true });
|
|
374
379
|
}, [setSidebarWidthPx]);
|
|
375
|
-
if (
|
|
380
|
+
if (isSidebarSheetLayout || !isOpen) {
|
|
376
381
|
return null;
|
|
377
382
|
}
|
|
378
|
-
return (jsx(PanelResizeHandle, { edge: edge, isActive: isOpen && !
|
|
383
|
+
return (jsx(PanelResizeHandle, { edge: edge, isActive: isOpen && !isSidebarSheetLayout, startWidthPx: sidebarWidthPx, getShellWidth: getShellWidth, onDragWidth: onDragWidth, onDragComplete: onDragComplete, "data-sidebar": "resize-handle", "data-slot": "sidebar-resize-handle", "data-side": side, className: cn(SidebarStem.sidebarResizeHandle, className), ...props }));
|
|
379
384
|
}
|
|
380
385
|
function SidebarFooter({ className, ...props }) {
|
|
381
386
|
return (jsx("div", { "data-slot": "sidebar-footer", "data-sidebar": "footer", className: cn(SidebarStem.sidebarFooter, className), ...props }));
|
|
@@ -421,9 +426,9 @@ function SidebarMenuSubItem({ className, ...props }) {
|
|
|
421
426
|
function SidebarMenuSubButton({ asChild = false, size = 'md', isActive = false, tooltip, className, onClick, ...props }) {
|
|
422
427
|
const Comp = asChild ? Slot : 'a';
|
|
423
428
|
const { isOpen, setOpen } = useSidebar();
|
|
424
|
-
const
|
|
429
|
+
const isSidebarSheetLayout = useIsSidebarSheetLayout();
|
|
425
430
|
const handleClick = (event) => {
|
|
426
|
-
if (
|
|
431
|
+
if (isSidebarSheetLayout && isOpen)
|
|
427
432
|
setOpen(false);
|
|
428
433
|
onClick?.(event);
|
|
429
434
|
};
|
|
@@ -435,7 +440,7 @@ function SidebarMenuSubButton({ asChild = false, size = 'md', isActive = false,
|
|
|
435
440
|
children: tooltip,
|
|
436
441
|
};
|
|
437
442
|
}
|
|
438
|
-
return (jsxs(Tooltip, { children: [jsx(TooltipTrigger, { asChild: true, children: button }), jsx(TooltipContent, { side: "right", align: "center", hidden: !isOpen ||
|
|
443
|
+
return (jsxs(Tooltip, { children: [jsx(TooltipTrigger, { asChild: true, children: button }), jsx(TooltipContent, { side: "right", align: "center", hidden: !isOpen || isSidebarSheetLayout, ...tooltip })] }));
|
|
439
444
|
}
|
|
440
445
|
|
|
441
446
|
export { PanelResizeHandle, Sidebar, SidebarContent, SidebarFooter, SidebarGroup, SidebarGroupAction, SidebarMenu, SidebarMenuAction, SidebarMenuBadge, SidebarMenuButton, SidebarMenuItem, SidebarMenuSub, SidebarMenuSubButton, SidebarMenuSubItem, SidebarProvider, SidebarRail, SidebarResizeHandle, SidebarSeparator, SidebarTrigger, useSidebar };
|
package/dist/esm/hooks/index.js
CHANGED
|
@@ -8,6 +8,10 @@ const CHAT_WIDTH_DEFAULT_PX = 800;
|
|
|
8
8
|
const SHELL_MAX_FRACTION = 0.4;
|
|
9
9
|
const CHAT_WIDTH_ABS_MAX_PX = 800;
|
|
10
10
|
const CENTRAL_AREA_MIN_PX = 800;
|
|
11
|
+
/** Viewport below this uses sheet sidebar + single-column shell (central + sidebar mins cannot coexist). */
|
|
12
|
+
const SIDEBAR_SHEET_SPLIT_MIN_VIEWPORT_PX = CENTRAL_AREA_MIN_PX + SIDEBAR_WIDTH_MIN_PX;
|
|
13
|
+
/** `max-width` for media queries (`innerWidth < SIDEBAR_SHEET_SPLIT_MIN_VIEWPORT_PX`). */
|
|
14
|
+
const SIDEBAR_SHEET_LAYOUT_MAX_WIDTH_PX = SIDEBAR_SHEET_SPLIT_MIN_VIEWPORT_PX - 1;
|
|
11
15
|
function maxSidebarWidthPx(shellWidth) {
|
|
12
16
|
return Math.floor(shellWidth * SHELL_MAX_FRACTION);
|
|
13
17
|
}
|
|
@@ -43,4 +47,4 @@ function defaultChatWidthPx(shellWidth) {
|
|
|
43
47
|
return clampChatWidthPx(CHAT_WIDTH_DEFAULT_PX, shellWidth, SIDEBAR_WIDTH_DEFAULT_PX);
|
|
44
48
|
}
|
|
45
49
|
|
|
46
|
-
export { CENTRAL_AREA_MIN_PX, CHAT_WIDTH_ABS_MAX_PX, CHAT_WIDTH_DEFAULT_PX, CHAT_WIDTH_MIN_PX, CHAT_WIDTH_STORAGE_KEY, SHELL_MAX_FRACTION, SIDEBAR_WIDTH_DEFAULT_PX, SIDEBAR_WIDTH_MIN_PX, SIDEBAR_WIDTH_STORAGE_KEY_PCT, SIDEBAR_WIDTH_STORAGE_KEY_PX, clampChatWidthPx, clampSidebarWidthPx, defaultChatWidthPx, maxChatWidthPx, maxSidebarWidthPx };
|
|
50
|
+
export { CENTRAL_AREA_MIN_PX, CHAT_WIDTH_ABS_MAX_PX, CHAT_WIDTH_DEFAULT_PX, CHAT_WIDTH_MIN_PX, CHAT_WIDTH_STORAGE_KEY, SHELL_MAX_FRACTION, SIDEBAR_SHEET_LAYOUT_MAX_WIDTH_PX, SIDEBAR_SHEET_SPLIT_MIN_VIEWPORT_PX, SIDEBAR_WIDTH_DEFAULT_PX, SIDEBAR_WIDTH_MIN_PX, SIDEBAR_WIDTH_STORAGE_KEY_PCT, SIDEBAR_WIDTH_STORAGE_KEY_PX, clampChatWidthPx, clampSidebarWidthPx, defaultChatWidthPx, maxChatWidthPx, maxSidebarWidthPx };
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { useState, useEffect } from 'react';
|
|
2
|
+
import { SIDEBAR_SHEET_SPLIT_MIN_VIEWPORT_PX } from './panelWidth.js';
|
|
3
|
+
|
|
4
|
+
/** True when viewport cannot fit mains min + sidebar min — use Sheet sidebar + single-column AppShell. */
|
|
5
|
+
function useIsSidebarSheetLayout() {
|
|
6
|
+
const [narrow, setNarrow] = useState(typeof window !== 'undefined'
|
|
7
|
+
? window.innerWidth < SIDEBAR_SHEET_SPLIT_MIN_VIEWPORT_PX
|
|
8
|
+
: undefined);
|
|
9
|
+
useEffect(() => {
|
|
10
|
+
const maxW = SIDEBAR_SHEET_SPLIT_MIN_VIEWPORT_PX - 1;
|
|
11
|
+
const mql = window.matchMedia(`(max-width: ${maxW}px)`);
|
|
12
|
+
const onChange = () => {
|
|
13
|
+
setNarrow(window.innerWidth < SIDEBAR_SHEET_SPLIT_MIN_VIEWPORT_PX);
|
|
14
|
+
};
|
|
15
|
+
mql.addEventListener('change', onChange);
|
|
16
|
+
setNarrow(window.innerWidth < SIDEBAR_SHEET_SPLIT_MIN_VIEWPORT_PX);
|
|
17
|
+
return () => mql.removeEventListener('change', onChange);
|
|
18
|
+
}, []);
|
|
19
|
+
return !!narrow;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export { useIsSidebarSheetLayout };
|
package/dist/esm/index.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
export { useIsMobile } from './hooks/useIsMobile.js';
|
|
2
|
+
export { useIsSidebarSheetLayout } from './hooks/useIsSidebarSheetLayout.js';
|
|
3
|
+
export { CENTRAL_AREA_MIN_PX, SIDEBAR_SHEET_LAYOUT_MAX_WIDTH_PX, SIDEBAR_SHEET_SPLIT_MIN_VIEWPORT_PX, SIDEBAR_WIDTH_MIN_PX } from './hooks/panelWidth.js';
|
|
2
4
|
export { ThemeProvider, useTheme } from './contexts/theme-context.js';
|
|
3
5
|
export { DEFAULT_THEME_ACTIVE_COLOR } from './docs/lib/theme.js';
|
|
4
6
|
export { SybilionAuthProvider, createSybilionApiFetch, getSybilionApiOriginFromSdk, sybilionApiFetch, useSybilionApiFetch, useSybilionAuth } from './sybilion-auth/SybilionAuthProvider.js';
|
|
@@ -36,6 +38,7 @@ export { DropZone } from './components/ui/DropZone/DropZone.js';
|
|
|
36
38
|
export { FlickeringGrid } from './components/ui/FlickeringGrid/FlickeringGrid.js';
|
|
37
39
|
export { Foldable } from './components/ui/Foldable/Foldable.js';
|
|
38
40
|
export { Gap } from './components/ui/Gap/Gap.js';
|
|
41
|
+
export { GridLayout } from './components/ui/GridLayout/GridLayout.js';
|
|
39
42
|
export { Image } from './components/ui/Image/Image.js';
|
|
40
43
|
export { ImageWithFallback } from './components/ui/ImageWithFallback/ImageWithFallback.js';
|
|
41
44
|
export { Input } from './components/ui/Input/Input.js';
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { ElementType, ReactNode } from 'react';
|
|
2
|
+
export interface GridLayoutProps {
|
|
3
|
+
children: ReactNode;
|
|
4
|
+
colWidth?: number;
|
|
5
|
+
gap?: string;
|
|
6
|
+
className?: string;
|
|
7
|
+
as?: ElementType;
|
|
8
|
+
}
|
|
9
|
+
export declare function GridLayout({ children, colWidth, gap, className, as: asProp, }: GridLayoutProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export default function GridLayoutPage(): import("react/jsx-runtime").JSX.Element;
|
|
@@ -8,6 +8,10 @@ export declare const CHAT_WIDTH_DEFAULT_PX = 800;
|
|
|
8
8
|
export declare const SHELL_MAX_FRACTION = 0.4;
|
|
9
9
|
export declare const CHAT_WIDTH_ABS_MAX_PX = 800;
|
|
10
10
|
export declare const CENTRAL_AREA_MIN_PX = 800;
|
|
11
|
+
/** Viewport below this uses sheet sidebar + single-column shell (central + sidebar mins cannot coexist). */
|
|
12
|
+
export declare const SIDEBAR_SHEET_SPLIT_MIN_VIEWPORT_PX: number;
|
|
13
|
+
/** `max-width` for media queries (`innerWidth < SIDEBAR_SHEET_SPLIT_MIN_VIEWPORT_PX`). */
|
|
14
|
+
export declare const SIDEBAR_SHEET_LAYOUT_MAX_WIDTH_PX: number;
|
|
11
15
|
export type ClampSidebarWidthOpts = {
|
|
12
16
|
chatPanelOpen: boolean;
|
|
13
17
|
chatWidthPx: number;
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
export { useIsMobile } from './hooks/useIsMobile';
|
|
2
|
+
export { useIsSidebarSheetLayout } from './hooks/useIsSidebarSheetLayout';
|
|
3
|
+
export { CENTRAL_AREA_MIN_PX, SIDEBAR_SHEET_LAYOUT_MAX_WIDTH_PX, SIDEBAR_SHEET_SPLIT_MIN_VIEWPORT_PX, SIDEBAR_WIDTH_MIN_PX, } from './hooks/panelWidth';
|
|
2
4
|
export * from './contexts/theme-context';
|
|
3
5
|
export { DEFAULT_THEME_ACTIVE_COLOR } from './docs/lib/theme';
|
|
4
6
|
export * from './sybilion-auth';
|
|
@@ -25,6 +27,7 @@ export * from './components/ui/DropZone/DropZone';
|
|
|
25
27
|
export * from './components/ui/FlickeringGrid';
|
|
26
28
|
export * from './components/ui/Foldable';
|
|
27
29
|
export * from './components/ui/Gap/Gap';
|
|
30
|
+
export * from './components/ui/GridLayout/GridLayout';
|
|
28
31
|
export * from './components/ui/Image';
|
|
29
32
|
export * from './components/ui/ImageWithFallback';
|
|
30
33
|
export * from './components/ui/Input';
|
package/package.json
CHANGED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import cn from 'classnames';
|
|
2
|
+
import type { CSSProperties, ElementType, ReactNode } from 'react';
|
|
3
|
+
|
|
4
|
+
import S from './GridLayout.styl';
|
|
5
|
+
|
|
6
|
+
export interface GridLayoutProps {
|
|
7
|
+
children: ReactNode;
|
|
8
|
+
colWidth?: number;
|
|
9
|
+
gap?: string;
|
|
10
|
+
className?: string;
|
|
11
|
+
as?: ElementType;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function GridLayout({
|
|
15
|
+
children,
|
|
16
|
+
colWidth = 300,
|
|
17
|
+
gap = 'var(--p-2)',
|
|
18
|
+
className,
|
|
19
|
+
as: asProp,
|
|
20
|
+
}: GridLayoutProps) {
|
|
21
|
+
const As = asProp ?? 'div';
|
|
22
|
+
|
|
23
|
+
return (
|
|
24
|
+
<As
|
|
25
|
+
className={cn(S.root, className)}
|
|
26
|
+
style={
|
|
27
|
+
{
|
|
28
|
+
maxWidth: '100%',
|
|
29
|
+
gap: gap,
|
|
30
|
+
display: 'grid',
|
|
31
|
+
gridTemplateColumns: `repeat(auto-fit, minmax(${colWidth}px, 1fr))`,
|
|
32
|
+
} as CSSProperties
|
|
33
|
+
}
|
|
34
|
+
>
|
|
35
|
+
{children}
|
|
36
|
+
</As>
|
|
37
|
+
);
|
|
38
|
+
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
@import 'lib/theme.styl'
|
|
2
2
|
|
|
3
|
+
SIDEBAR_SHEET_SPLIT = 1100px
|
|
4
|
+
|
|
3
5
|
.root
|
|
4
6
|
display grid
|
|
5
7
|
flex 1
|
|
@@ -9,12 +11,12 @@
|
|
|
9
11
|
grid-template-columns 1fr
|
|
10
12
|
grid-template-areas "main"
|
|
11
13
|
|
|
12
|
-
@media (min-width
|
|
14
|
+
@media (min-width SIDEBAR_SHEET_SPLIT)
|
|
13
15
|
grid-template-columns var(--sidebar-width) 1fr
|
|
14
16
|
grid-template-areas "sidebar main"
|
|
15
17
|
|
|
16
18
|
:global([data-slot="sidebar-wrapper"][data-state="collapsed"]) &
|
|
17
|
-
@media (min-width
|
|
19
|
+
@media (min-width SIDEBAR_SHEET_SPLIT)
|
|
18
20
|
grid-template-columns 0 1fr
|
|
19
21
|
|
|
20
22
|
.mainColumn
|
|
@@ -37,7 +37,7 @@ import {
|
|
|
37
37
|
} from '#uilib/hooks/panelWidth';
|
|
38
38
|
import useElemDrag, { Delta } from '#uilib/hooks/useDragElem';
|
|
39
39
|
import useEvent from '#uilib/hooks/useEvent';
|
|
40
|
-
import {
|
|
40
|
+
import { useIsSidebarSheetLayout } from '#uilib/hooks/useIsSidebarSheetLayout';
|
|
41
41
|
import { useShellWidthObserver } from '#uilib/hooks/useShellWidthObserver';
|
|
42
42
|
import { getCookie, setCookie } from '#uilib/lib/cookie';
|
|
43
43
|
import { getCookiePreferences } from '#uilib/lib/cookie-consent/cookie-consent';
|
|
@@ -153,8 +153,10 @@ function SidebarProvider({
|
|
|
153
153
|
persistSidebarWidthWithoutConsent ||
|
|
154
154
|
Boolean(getCookiePreferences(userId)?.functional);
|
|
155
155
|
|
|
156
|
-
const
|
|
157
|
-
const [isOpen, setIsOpen] = useState(
|
|
156
|
+
const isSidebarSheetLayout = useIsSidebarSheetLayout();
|
|
157
|
+
const [isOpen, setIsOpen] = useState(
|
|
158
|
+
isSidebarSheetLayout ? false : defaultOpen,
|
|
159
|
+
);
|
|
158
160
|
const wrapperRef = useRef<HTMLDivElement | null>(null);
|
|
159
161
|
const shellWidthRef = useRef(0);
|
|
160
162
|
const prevShellWidthRef = useRef(0);
|
|
@@ -297,9 +299,15 @@ function SidebarProvider({
|
|
|
297
299
|
]);
|
|
298
300
|
|
|
299
301
|
useLayoutEffect(() => {
|
|
300
|
-
if (!shellEl ||
|
|
302
|
+
if (!shellEl || isSidebarSheetLayout) return;
|
|
301
303
|
fitPanelWidthsToShell();
|
|
302
|
-
}, [
|
|
304
|
+
}, [
|
|
305
|
+
shellEl,
|
|
306
|
+
chatPanelOpen,
|
|
307
|
+
isSidebarSheetLayout,
|
|
308
|
+
isOpen,
|
|
309
|
+
fitPanelWidthsToShell,
|
|
310
|
+
]);
|
|
303
311
|
|
|
304
312
|
useShellWidthObserver(shellEl, handleShellResize);
|
|
305
313
|
|
|
@@ -307,7 +315,7 @@ function SidebarProvider({
|
|
|
307
315
|
(value: boolean, options?: SetSidebarOpenOptions) => {
|
|
308
316
|
const useViewTransition =
|
|
309
317
|
options?.viewTransition !== false &&
|
|
310
|
-
!
|
|
318
|
+
!isSidebarSheetLayout &&
|
|
311
319
|
'startViewTransition' in document &&
|
|
312
320
|
document.startViewTransition;
|
|
313
321
|
|
|
@@ -323,7 +331,7 @@ function SidebarProvider({
|
|
|
323
331
|
setCookie('isSidebarOpen', value.toString(), 60 * 60 * 24 * 7);
|
|
324
332
|
}
|
|
325
333
|
},
|
|
326
|
-
[
|
|
334
|
+
[isSidebarSheetLayout, userId],
|
|
327
335
|
);
|
|
328
336
|
|
|
329
337
|
const toggleSidebar = useCallback(() => setOpen(!isOpen), [isOpen, setOpen]);
|
|
@@ -333,7 +341,7 @@ function SidebarProvider({
|
|
|
333
341
|
if (!shell) return;
|
|
334
342
|
if (chatPanelOpen) {
|
|
335
343
|
shell.setAttribute('data-chat-open', '');
|
|
336
|
-
if (
|
|
344
|
+
if (isSidebarSheetLayout) {
|
|
337
345
|
shell.style.setProperty('--chat-panel-width', '100%');
|
|
338
346
|
shell.style.setProperty('--chat-panel-height', '100dvh');
|
|
339
347
|
} else {
|
|
@@ -345,7 +353,7 @@ function SidebarProvider({
|
|
|
345
353
|
shell.style.setProperty('--chat-panel-width', '0px');
|
|
346
354
|
shell.style.removeProperty('--chat-panel-height');
|
|
347
355
|
}
|
|
348
|
-
}, [chatPanelOpen,
|
|
356
|
+
}, [chatPanelOpen, isSidebarSheetLayout, chatWidthPx]);
|
|
349
357
|
|
|
350
358
|
useEffect(() => {
|
|
351
359
|
return () => {
|
|
@@ -447,14 +455,13 @@ function Sidebar({
|
|
|
447
455
|
fullHeightResizer = false,
|
|
448
456
|
...props
|
|
449
457
|
}: SidebarProps) {
|
|
450
|
-
const
|
|
458
|
+
const isSidebarSheetLayout = useIsSidebarSheetLayout();
|
|
451
459
|
const { isOpen, setOpen } = useSidebar();
|
|
452
460
|
|
|
453
|
-
|
|
454
|
-
// load and overwrote the isSidebarOpen cookie (collapsed → opened + cookie "true").
|
|
461
|
+
/** Narrow shell: sidebar is a sheet; collapse when crossing into this mode (preserve cookie semantics elsewhere). */
|
|
455
462
|
useEffect(() => {
|
|
456
|
-
if (
|
|
457
|
-
}, [
|
|
463
|
+
if (isSidebarSheetLayout) setOpen(false);
|
|
464
|
+
}, [isSidebarSheetLayout, setOpen]);
|
|
458
465
|
|
|
459
466
|
if (collapsible === 'none') {
|
|
460
467
|
return (
|
|
@@ -468,7 +475,7 @@ function Sidebar({
|
|
|
468
475
|
);
|
|
469
476
|
}
|
|
470
477
|
|
|
471
|
-
if (
|
|
478
|
+
if (isSidebarSheetLayout) {
|
|
472
479
|
return (
|
|
473
480
|
<Sheet open={isOpen} onOpenChange={setOpen} {...props}>
|
|
474
481
|
<SheetContent
|
|
@@ -640,7 +647,7 @@ function SidebarResizeHandle({
|
|
|
640
647
|
}: ComponentProps<'div'> & { side?: 'left' | 'right' }) {
|
|
641
648
|
const { isOpen, sidebarWidthPx, setSidebarWidthPx, getShellWidth } =
|
|
642
649
|
useSidebar();
|
|
643
|
-
const
|
|
650
|
+
const isSidebarSheetLayout = useIsSidebarSheetLayout();
|
|
644
651
|
|
|
645
652
|
const edge: 'leading' | 'trailing' = side === 'left' ? 'trailing' : 'leading';
|
|
646
653
|
|
|
@@ -658,14 +665,14 @@ function SidebarResizeHandle({
|
|
|
658
665
|
[setSidebarWidthPx],
|
|
659
666
|
);
|
|
660
667
|
|
|
661
|
-
if (
|
|
668
|
+
if (isSidebarSheetLayout || !isOpen) {
|
|
662
669
|
return null;
|
|
663
670
|
}
|
|
664
671
|
|
|
665
672
|
return (
|
|
666
673
|
<PanelResizeHandle
|
|
667
674
|
edge={edge}
|
|
668
|
-
isActive={isOpen && !
|
|
675
|
+
isActive={isOpen && !isSidebarSheetLayout}
|
|
669
676
|
startWidthPx={sidebarWidthPx}
|
|
670
677
|
getShellWidth={getShellWidth}
|
|
671
678
|
onDragWidth={onDragWidth}
|
|
@@ -877,10 +884,10 @@ function SidebarMenuSubButton({
|
|
|
877
884
|
}) {
|
|
878
885
|
const Comp = asChild ? Slot : 'a';
|
|
879
886
|
const { isOpen, setOpen } = useSidebar();
|
|
880
|
-
const
|
|
887
|
+
const isSidebarSheetLayout = useIsSidebarSheetLayout();
|
|
881
888
|
|
|
882
889
|
const handleClick = (event: React.MouseEvent<HTMLAnchorElement>) => {
|
|
883
|
-
if (
|
|
890
|
+
if (isSidebarSheetLayout && isOpen) setOpen(false);
|
|
884
891
|
onClick?.(event);
|
|
885
892
|
};
|
|
886
893
|
|
|
@@ -910,7 +917,7 @@ function SidebarMenuSubButton({
|
|
|
910
917
|
<TooltipContent
|
|
911
918
|
side="right"
|
|
912
919
|
align="center"
|
|
913
|
-
hidden={!isOpen ||
|
|
920
|
+
hidden={!isOpen || isSidebarSheetLayout}
|
|
914
921
|
{...tooltip}
|
|
915
922
|
/>
|
|
916
923
|
</Tooltip>
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import type { CSSProperties } from 'react';
|
|
2
|
+
|
|
3
|
+
import { GridLayout } from '#uilib/components/ui/GridLayout/GridLayout';
|
|
4
|
+
import { PageContentSection } from '#uilib/components/ui/Page';
|
|
5
|
+
|
|
6
|
+
import { AppPageHeader } from '../components/AppPageHeader/AppPageHeader';
|
|
7
|
+
import { DocsHeaderActions } from '../docsHeaderActions';
|
|
8
|
+
|
|
9
|
+
const tileStyle: CSSProperties = {
|
|
10
|
+
margin: 0,
|
|
11
|
+
padding: 'var(--p-4)',
|
|
12
|
+
borderRadius: 'var(--p-3)',
|
|
13
|
+
border: '1px solid var(--border)',
|
|
14
|
+
backgroundColor: 'var(--muted)',
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
function Tile({ label }: { label: string }) {
|
|
18
|
+
return <p style={tileStyle}>{label}</p>;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export default function GridLayoutPage() {
|
|
22
|
+
return (
|
|
23
|
+
<>
|
|
24
|
+
<AppPageHeader
|
|
25
|
+
breadcrumbs={[{ label: 'GridLayout' }]}
|
|
26
|
+
title="GridLayout"
|
|
27
|
+
subheader="Responsive CSS grid (`repeat(auto-fit, minmax(colWidth, 1fr))`), optional gap, polymorphic root via `as`."
|
|
28
|
+
actions={<DocsHeaderActions />}
|
|
29
|
+
/>
|
|
30
|
+
<PageContentSection
|
|
31
|
+
style={{ display: 'flex', flexDirection: 'column', gap: '2rem' }}
|
|
32
|
+
>
|
|
33
|
+
<section>
|
|
34
|
+
<h3 style={{ marginBottom: '0.75rem' }}>Default (colWidth 300)</h3>
|
|
35
|
+
<p
|
|
36
|
+
style={{
|
|
37
|
+
marginBottom: '1rem',
|
|
38
|
+
color: 'var(--muted-foreground)',
|
|
39
|
+
}}
|
|
40
|
+
>
|
|
41
|
+
Tiles wrap to new rows when the viewport is narrow; each cell keeps
|
|
42
|
+
at least 300px min width before wrapping.
|
|
43
|
+
</p>
|
|
44
|
+
<GridLayout>
|
|
45
|
+
<Tile label="Item A" />
|
|
46
|
+
<Tile label="Item B" />
|
|
47
|
+
<Tile label="Item C" />
|
|
48
|
+
<Tile label="Item D" />
|
|
49
|
+
</GridLayout>
|
|
50
|
+
</section>
|
|
51
|
+
<section>
|
|
52
|
+
<h3 style={{ marginBottom: '0.75rem' }}>
|
|
53
|
+
colWidth 180, gap var(--p-4)
|
|
54
|
+
</h3>
|
|
55
|
+
<GridLayout colWidth={180} gap="var(--p-4)">
|
|
56
|
+
<Tile label="Narrow 1" />
|
|
57
|
+
<Tile label="Narrow 2" />
|
|
58
|
+
<Tile label="Narrow 3" />
|
|
59
|
+
</GridLayout>
|
|
60
|
+
</section>
|
|
61
|
+
</PageContentSection>
|
|
62
|
+
</>
|
|
63
|
+
);
|
|
64
|
+
}
|
package/src/docs/registry.ts
CHANGED
|
@@ -157,6 +157,12 @@ export const DOC_REGISTRY: DocEntry[] = [
|
|
|
157
157
|
section: 'Layout',
|
|
158
158
|
load: () => import('./pages/FoldablePage'),
|
|
159
159
|
},
|
|
160
|
+
{
|
|
161
|
+
slug: 'grid-layout',
|
|
162
|
+
title: 'GridLayout',
|
|
163
|
+
section: 'Layout',
|
|
164
|
+
load: () => import('./pages/GridLayoutPage'),
|
|
165
|
+
},
|
|
160
166
|
{
|
|
161
167
|
slug: 'image',
|
|
162
168
|
title: 'Image',
|
package/src/hooks/index.ts
CHANGED
package/src/hooks/panelWidth.ts
CHANGED
|
@@ -10,6 +10,14 @@ export const SHELL_MAX_FRACTION = 0.4;
|
|
|
10
10
|
export const CHAT_WIDTH_ABS_MAX_PX = 800;
|
|
11
11
|
export const CENTRAL_AREA_MIN_PX = 800;
|
|
12
12
|
|
|
13
|
+
/** Viewport below this uses sheet sidebar + single-column shell (central + sidebar mins cannot coexist). */
|
|
14
|
+
export const SIDEBAR_SHEET_SPLIT_MIN_VIEWPORT_PX =
|
|
15
|
+
CENTRAL_AREA_MIN_PX + SIDEBAR_WIDTH_MIN_PX;
|
|
16
|
+
|
|
17
|
+
/** `max-width` for media queries (`innerWidth < SIDEBAR_SHEET_SPLIT_MIN_VIEWPORT_PX`). */
|
|
18
|
+
export const SIDEBAR_SHEET_LAYOUT_MAX_WIDTH_PX =
|
|
19
|
+
SIDEBAR_SHEET_SPLIT_MIN_VIEWPORT_PX - 1;
|
|
20
|
+
|
|
13
21
|
export type ClampSidebarWidthOpts = {
|
|
14
22
|
chatPanelOpen: boolean;
|
|
15
23
|
chatWidthPx: number;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react';
|
|
2
|
+
|
|
3
|
+
import { SIDEBAR_SHEET_SPLIT_MIN_VIEWPORT_PX } from '#uilib/hooks/panelWidth';
|
|
4
|
+
|
|
5
|
+
/** True when viewport cannot fit mains min + sidebar min — use Sheet sidebar + single-column AppShell. */
|
|
6
|
+
export function useIsSidebarSheetLayout(): boolean {
|
|
7
|
+
const [narrow, setNarrow] = useState<boolean | undefined>(
|
|
8
|
+
typeof window !== 'undefined'
|
|
9
|
+
? window.innerWidth < SIDEBAR_SHEET_SPLIT_MIN_VIEWPORT_PX
|
|
10
|
+
: undefined,
|
|
11
|
+
);
|
|
12
|
+
|
|
13
|
+
useEffect(() => {
|
|
14
|
+
const maxW = SIDEBAR_SHEET_SPLIT_MIN_VIEWPORT_PX - 1;
|
|
15
|
+
const mql = window.matchMedia(`(max-width: ${maxW}px)`);
|
|
16
|
+
const onChange = () => {
|
|
17
|
+
setNarrow(window.innerWidth < SIDEBAR_SHEET_SPLIT_MIN_VIEWPORT_PX);
|
|
18
|
+
};
|
|
19
|
+
mql.addEventListener('change', onChange);
|
|
20
|
+
setNarrow(window.innerWidth < SIDEBAR_SHEET_SPLIT_MIN_VIEWPORT_PX);
|
|
21
|
+
return () => mql.removeEventListener('change', onChange);
|
|
22
|
+
}, []);
|
|
23
|
+
|
|
24
|
+
return !!narrow;
|
|
25
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,4 +1,11 @@
|
|
|
1
1
|
export { useIsMobile } from './hooks/useIsMobile';
|
|
2
|
+
export { useIsSidebarSheetLayout } from './hooks/useIsSidebarSheetLayout';
|
|
3
|
+
export {
|
|
4
|
+
CENTRAL_AREA_MIN_PX,
|
|
5
|
+
SIDEBAR_SHEET_LAYOUT_MAX_WIDTH_PX,
|
|
6
|
+
SIDEBAR_SHEET_SPLIT_MIN_VIEWPORT_PX,
|
|
7
|
+
SIDEBAR_WIDTH_MIN_PX,
|
|
8
|
+
} from './hooks/panelWidth';
|
|
2
9
|
export * from './contexts/theme-context';
|
|
3
10
|
export { DEFAULT_THEME_ACTIVE_COLOR } from './docs/lib/theme';
|
|
4
11
|
export * from './sybilion-auth';
|
|
@@ -25,6 +32,7 @@ export * from './components/ui/DropZone/DropZone';
|
|
|
25
32
|
export * from './components/ui/FlickeringGrid';
|
|
26
33
|
export * from './components/ui/Foldable';
|
|
27
34
|
export * from './components/ui/Gap/Gap';
|
|
35
|
+
export * from './components/ui/GridLayout/GridLayout';
|
|
28
36
|
export * from './components/ui/Image';
|
|
29
37
|
export * from './components/ui/ImageWithFallback';
|
|
30
38
|
export * from './components/ui/Input';
|