fix(ui): harden app shell responsive behavior
This commit is contained in:
parent
5e7b349a7c
commit
50617c1813
@ -1,3 +1,5 @@
|
||||
'use client';
|
||||
|
||||
import * as React from 'react';
|
||||
import { clsx } from 'clsx';
|
||||
import { Menu, X } from 'lucide-react';
|
||||
@ -6,6 +8,42 @@ type ShellStyle = React.CSSProperties & {
|
||||
'--bl-app-sidebar-width'?: string;
|
||||
};
|
||||
|
||||
type ShellMediaQueryList = {
|
||||
matches: boolean;
|
||||
addEventListener?: (type: 'change', listener: () => void) => void;
|
||||
removeEventListener?: (type: 'change', listener: () => void) => void;
|
||||
addListener?: (listener: () => void) => void;
|
||||
removeListener?: (listener: () => void) => void;
|
||||
};
|
||||
|
||||
function useDesktopShell() {
|
||||
const [isDesktop, setIsDesktop] = React.useState(false);
|
||||
|
||||
React.useEffect(() => {
|
||||
const browser = globalThis as typeof globalThis & {
|
||||
matchMedia?: (query: string) => ShellMediaQueryList;
|
||||
};
|
||||
const media = browser.matchMedia?.('(min-width: 1024px)');
|
||||
|
||||
if (!media) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const sync = () => setIsDesktop(media.matches);
|
||||
|
||||
sync();
|
||||
if (media.addEventListener && media.removeEventListener) {
|
||||
media.addEventListener('change', sync);
|
||||
return () => media.removeEventListener?.('change', sync);
|
||||
}
|
||||
|
||||
media.addListener?.(sync);
|
||||
return () => media.removeListener?.(sync);
|
||||
}, []);
|
||||
|
||||
return isDesktop;
|
||||
}
|
||||
|
||||
export interface AppShellProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
sidebarWidth?: number;
|
||||
}
|
||||
@ -78,6 +116,12 @@ export function AppShellMobileToggle({
|
||||
children,
|
||||
...props
|
||||
}: AppShellMobileToggleProps) {
|
||||
const isDesktop = useDesktopShell();
|
||||
|
||||
if (isDesktop) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
@ -103,14 +147,18 @@ export interface AppShellOverlayProps extends React.HTMLAttributes<HTMLDivElemen
|
||||
}
|
||||
|
||||
export function AppShellOverlay({ open, className, ...props }: AppShellOverlayProps) {
|
||||
const isDesktop = useDesktopShell();
|
||||
|
||||
if (!open || isDesktop) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
aria-hidden="true"
|
||||
data-open={open ? 'true' : 'false'}
|
||||
className={clsx(
|
||||
'fixed inset-0 z-[39] bg-[var(--bl-overlay-scrim,rgba(0,0,0,0.5))]',
|
||||
open ? 'block' : 'hidden',
|
||||
'lg:hidden',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
@ -133,6 +181,9 @@ export function AppShellSidebar({
|
||||
children,
|
||||
...props
|
||||
}: AppShellSidebarProps) {
|
||||
const isDesktop = useDesktopShell();
|
||||
const isVisible = open || isDesktop;
|
||||
|
||||
return (
|
||||
<aside
|
||||
aria-label={label}
|
||||
@ -141,10 +192,15 @@ export function AppShellSidebar({
|
||||
'fixed left-0 top-0 z-40 flex h-screen flex-col overflow-y-auto',
|
||||
'border-r border-[var(--bl-border)] bg-[var(--bl-surface-sidebar,var(--bl-bg-elevated))]',
|
||||
'transition-transform duration-200 ease-out lg:translate-x-0',
|
||||
open ? 'translate-x-0' : '-translate-x-full',
|
||||
className
|
||||
)}
|
||||
style={{ width, minWidth: width, ...style }}
|
||||
style={{
|
||||
width,
|
||||
minWidth: width,
|
||||
transform: isVisible ? 'translateX(0)' : 'translateX(-100%)',
|
||||
transition: 'transform 200ms ease-out',
|
||||
...style,
|
||||
}}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
@ -160,18 +216,23 @@ export function AppShellMain({
|
||||
id = 'main-content',
|
||||
labelledBy,
|
||||
className,
|
||||
style,
|
||||
children,
|
||||
...props
|
||||
}: AppShellMainProps) {
|
||||
const isDesktop = useDesktopShell();
|
||||
|
||||
return (
|
||||
<main
|
||||
id={id}
|
||||
tabIndex={-1}
|
||||
aria-labelledby={labelledBy}
|
||||
className={clsx(
|
||||
'min-h-screen min-w-0 p-5 pt-16 lg:pl-[var(--bl-app-sidebar-width)] lg:pt-8',
|
||||
className
|
||||
)}
|
||||
className={clsx('min-h-screen min-w-0', className)}
|
||||
style={{
|
||||
padding: isDesktop ? '2rem' : '4rem 1.25rem 1.25rem',
|
||||
paddingLeft: isDesktop ? 'calc(var(--bl-app-sidebar-width, 280px) + 2rem)' : '1.25rem',
|
||||
...style,
|
||||
}}
|
||||
{...props}
|
||||
>
|
||||
<div className="grid gap-6">{children}</div>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user