fix(ui): make app shell server compatible
Some checks failed
CI — Common Platform / Build, Test & Typecheck (push) Failing after 0s
CI — Common Platform / Publish @bytelyst/* to Gitea npm registry (push) Has been skipped
CI — Common Platform / Check design token drift (push) Failing after 0s

This commit is contained in:
Saravana Achu Mac 2026-05-06 18:09:54 -07:00
parent efb0162be0
commit 3398574155

View File

@ -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>