fix(ui): make app shell server compatible
This commit is contained in:
parent
efb0162be0
commit
3398574155
@ -1,5 +1,3 @@
|
||||
'use client';
|
||||
|
||||
import * as React from 'react';
|
||||
import { clsx } from 'clsx';
|
||||
import { Menu, X } from 'lucide-react';
|
||||
@ -8,42 +6,6 @@ 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;
|
||||
}
|
||||
@ -63,12 +25,55 @@ export function AppShell({
|
||||
return (
|
||||
<div
|
||||
className={clsx(
|
||||
'min-h-screen bg-[var(--bl-bg-canvas)] text-[var(--bl-text-primary)]',
|
||||
'bl-app-shell min-h-screen bg-[var(--bl-bg-canvas)] text-[var(--bl-text-primary)]',
|
||||
className
|
||||
)}
|
||||
style={shellStyle}
|
||||
{...props}
|
||||
>
|
||||
<style>
|
||||
{`
|
||||
.bl-app-shell-mobile-toggle {
|
||||
display: inline-flex;
|
||||
}
|
||||
|
||||
.bl-app-shell-overlay {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.bl-app-shell-overlay[data-open="true"] {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.bl-app-shell-sidebar {
|
||||
transform: translateX(-100%);
|
||||
transition: transform 200ms ease-out;
|
||||
}
|
||||
|
||||
.bl-app-shell-sidebar[data-open="true"] {
|
||||
transform: translateX(0);
|
||||
}
|
||||
|
||||
.bl-app-shell-main {
|
||||
padding: 4rem 1.25rem 1.25rem;
|
||||
}
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
.bl-app-shell-mobile-toggle,
|
||||
.bl-app-shell-overlay {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.bl-app-shell-sidebar {
|
||||
transform: translateX(0);
|
||||
}
|
||||
|
||||
.bl-app-shell-main {
|
||||
padding: 2rem 2rem 2rem calc(var(--bl-app-sidebar-width, 280px) + 2rem);
|
||||
}
|
||||
}
|
||||
`}
|
||||
</style>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
@ -116,18 +121,12 @@ export function AppShellMobileToggle({
|
||||
children,
|
||||
...props
|
||||
}: AppShellMobileToggleProps) {
|
||||
const isDesktop = useDesktopShell();
|
||||
|
||||
if (isDesktop) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
aria-label={open ? closeLabel : openLabel}
|
||||
className={clsx(
|
||||
'fixed left-3 top-3 z-[38] inline-flex h-10 w-10 items-center justify-center',
|
||||
'bl-app-shell-mobile-toggle fixed left-3 top-3 z-[38] h-10 w-10 items-center justify-center',
|
||||
'rounded-md border border-[var(--bl-border)] bg-[var(--bl-bg-elevated)]',
|
||||
'text-[var(--bl-text-primary)] shadow-sm transition-colors',
|
||||
'hover:bg-[var(--bl-surface-muted)] focus-visible:outline-none',
|
||||
@ -147,18 +146,12 @@ 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))]',
|
||||
'bl-app-shell-overlay fixed inset-0 z-[39] bg-[var(--bl-overlay-scrim,rgba(0,0,0,0.5))]',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
@ -181,15 +174,12 @@ export function AppShellSidebar({
|
||||
children,
|
||||
...props
|
||||
}: AppShellSidebarProps) {
|
||||
const isDesktop = useDesktopShell();
|
||||
const isVisible = open || isDesktop;
|
||||
|
||||
return (
|
||||
<aside
|
||||
aria-label={label}
|
||||
data-open={open ? 'true' : 'false'}
|
||||
className={clsx(
|
||||
'fixed left-0 top-0 z-40 flex h-screen flex-col overflow-y-auto',
|
||||
'bl-app-shell-sidebar 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',
|
||||
className
|
||||
@ -197,8 +187,6 @@ export function AppShellSidebar({
|
||||
style={{
|
||||
width,
|
||||
minWidth: width,
|
||||
transform: isVisible ? 'translateX(0)' : 'translateX(-100%)',
|
||||
transition: 'transform 200ms ease-out',
|
||||
...style,
|
||||
}}
|
||||
{...props}
|
||||
@ -220,19 +208,13 @@ export function AppShellMain({
|
||||
children,
|
||||
...props
|
||||
}: AppShellMainProps) {
|
||||
const isDesktop = useDesktopShell();
|
||||
|
||||
return (
|
||||
<main
|
||||
id={id}
|
||||
tabIndex={-1}
|
||||
aria-labelledby={labelledBy}
|
||||
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,
|
||||
}}
|
||||
className={clsx('bl-app-shell-main min-h-screen min-w-0', className)}
|
||||
style={style}
|
||||
{...props}
|
||||
>
|
||||
<div className="grid gap-6">{children}</div>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user