Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 | 59x 59x 59x 59x 59x 59x 59x 15x 59x 5x 5x 59x 59x | import { useState, useRef, useCallback, type ReactNode } from "react";
import { Menu, X, Bell } from "lucide-react";
import { Link } from "@tanstack/react-router";
import { Sidebar } from "./sidebar";
import { BottomNav } from "./bottom-nav";
import { LogoIcon } from "../ui/logo-icon";
import { ReleaseBadge } from "../ui/release-badge";
import { FeedbackButton } from "../feedback/FeedbackButton";
import { ErrorBoundary } from "../ui/error-boundary";
import { useUnreadCount } from "../../hooks/queries/useNotificationQueries";
import { useScrollDirection } from "../../hooks/useScrollDirection";
import { useIsMobile } from "../../hooks/useIsMobile";
type DashboardLayoutProps = {
children: ReactNode;
};
export function DashboardLayout({ children }: DashboardLayoutProps) {
const [sidebarOpen, setSidebarOpen] = useState(false);
const menuButtonRef = useRef<HTMLButtonElement>(null);
const mainRef = useRef<HTMLElement>(null);
const { data: unreadCount = 0 } = useUnreadCount();
const scrollDirection = useScrollDirection(mainRef);
const isMobile = useIsMobile();
const openSidebar = useCallback(() => {
setSidebarOpen(true);
}, []);
const closeSidebar = useCallback(() => {
setSidebarOpen(false);
menuButtonRef.current?.focus();
}, []);
// Hide header/bottom-nav on scroll down, show on scroll up
const barsVisible = scrollDirection !== "down";
return (
<div className="flex h-screen bg-background-base">
{/* Skip to main content link */}
<a
href="#main-content"
className="sr-only focus:not-sr-only focus:absolute focus:z-50 focus:p-4 focus:bg-background-elevated focus:text-foreground-default focus:rounded-md focus:shadow-lg focus:top-4 focus:left-4"
>
Skip to main content
</a>
{/* Mobile sidebar overlay — only used on tablet now; mobile uses BottomNav */}
{sidebarOpen && (
<button
type="button"
aria-label="Close sidebar overlay"
className="fixed inset-0 z-40 bg-black/50 lg:hidden cursor-default"
onClick={closeSidebar}
/>
)}
{/* Sidebar */}
<Sidebar isOpen={sidebarOpen} onClose={closeSidebar} />
{/* Main content */}
<div
className="flex flex-1 flex-col overflow-hidden"
aria-hidden={sidebarOpen || undefined}
>
{/* Mobile header — hidden on sm+, scroll-to-hide on mobile */}
<header
className={`flex h-14 items-center gap-4 border-b border-border-default bg-background-elevated px-4 sm:hidden transition-transform duration-200 ${
barsVisible ? "translate-y-0" : "-translate-y-full"
}`}
>
{sidebarOpen ? (
<button
type="button"
onClick={closeSidebar}
aria-label="Close sidebar"
className="p-2 -ml-2 text-foreground-muted hover:text-foreground-default hover:bg-background-muted rounded-md"
>
<X className="h-6 w-6" />
</button>
) : (
<button
ref={menuButtonRef}
type="button"
onClick={openSidebar}
aria-label="Open sidebar"
aria-expanded={sidebarOpen}
className="p-2 -ml-2 text-foreground-muted hover:text-foreground-default hover:bg-background-muted rounded-md"
>
<Menu className="h-6 w-6" />
</button>
)}
<div className="flex items-center gap-1.5">
<LogoIcon size={32} className="text-foreground-default" />
<span className="font-semibold text-foreground-default">
Interioring
</span>
<ReleaseBadge />
</div>
</header>
{/* Tablet header — visible on sm-lg range only, keeps hamburger */}
<header className="hidden sm:flex lg:hidden h-14 items-center gap-4 border-b border-border-default bg-background-elevated px-6">
<button
ref={menuButtonRef}
type="button"
onClick={openSidebar}
aria-label="Open sidebar"
aria-expanded={sidebarOpen}
className="p-2 -ml-2 text-foreground-muted hover:text-foreground-default hover:bg-background-muted rounded-md"
>
<Menu className="h-6 w-6" />
</button>
<div className="flex items-center gap-1.5">
<LogoIcon size={32} className="text-foreground-default" />
<span className="font-semibold text-foreground-default">
Interioring
</span>
<ReleaseBadge />
</div>
<div className="flex-1" />
<Link to="/notifications" className="relative p-2 text-foreground-muted hover:text-foreground-default">
<Bell className="h-5 w-5" />
{unreadCount > 0 && (
<span className="absolute -top-0.5 -right-0.5 bg-error text-white text-[9px] font-bold px-1 py-0.5 rounded-full min-w-[16px] text-center leading-tight">
{unreadCount > 99 ? "99+" : unreadCount}
</span>
)}
</Link>
</header>
{/* Page content — add bottom padding on mobile for BottomNav */}
<main id="main-content" ref={mainRef} className="flex-1 overflow-auto">
<div className="p-4 sm:p-6 lg:p-8 pb-20 sm:pb-6 lg:pb-8">
<ErrorBoundary>
{children}
</ErrorBoundary>
</div>
</main>
{/* Feedback button — desktop only */}
<FeedbackButton />
</div>
{/* Bottom navigation — mobile only */}
{isMobile && <BottomNav visible={barsVisible} />}
</div>
);
}
|