-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathAuthShell.tsx
More file actions
106 lines (96 loc) · 3.52 KB
/
AuthShell.tsx
File metadata and controls
106 lines (96 loc) · 3.52 KB
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
// src/components/layout/AuthShell.tsx
"use client"
import clsx from "clsx"
import Image from "next/image"
import { usePathname } from "next/navigation"
import { type ReactNode, useCallback, useEffect, useRef, useState } from "react"
import { Sidebar } from "@/components/layout/Sidebar"
import { WhatsNew } from "@/components/layout/WhatsNew"
import { BackToTop } from "@/components/ui/BackToTop"
import { HamburgerIcon } from "@/components/ui/Icons"
import { useLocalStorage } from "@/hooks/useLocalStorage"
export function AuthShell({ children }: { children: ReactNode }) {
const [collapsed, setCollapsed] = useLocalStorage("sidebar-collapsed", false)
const [mounted, setMounted] = useState(false)
const [isMobile, setIsMobile] = useState(false)
const isMobileRef = useRef(false)
useEffect(() => {
const mq = window.matchMedia("(max-width: 767px)")
const mobile = mq.matches
setIsMobile(mobile)
isMobileRef.current = mobile
if (mobile) setCollapsed(true)
const handler = (e: MediaQueryListEvent) => {
setIsMobile(e.matches)
isMobileRef.current = e.matches
if (e.matches) setCollapsed(true)
}
mq.addEventListener("change", handler)
setMounted(true)
return () => mq.removeEventListener("change", handler)
}, [setCollapsed])
const toggle = useCallback(() => {
setCollapsed((prev) => !prev)
}, [setCollapsed])
const mainRef = useRef<HTMLElement>(null)
const pathname = usePathname()
// Scroll main content to top on route change (main is the scroll container, not window)
// biome-ignore lint/correctness/useExhaustiveDependencies: pathname is the trigger — we intentionally re-run on route change
useEffect(() => {
mainRef.current?.scrollTo(0, 0)
}, [pathname])
const effectiveCollapsed = !mounted || collapsed
const showHamburger = isMobile || effectiveCollapsed
return (
<div className="flex h-screen overflow-hidden">
{isMobile && (
<div
className={clsx(
"fixed inset-0 z-20 bg-black/50 transition-opacity duration-300 ease-in-out",
effectiveCollapsed ? "opacity-0 pointer-events-none" : "opacity-100"
)}
onClick={toggle}
aria-hidden="true"
/>
)}
<Sidebar collapsed={effectiveCollapsed} onToggle={toggle} isMobile={isMobile} />
<main
ref={mainRef}
className="flex-1 min-w-0 overflow-y-auto p-4 md:p-6 relative themed-scrollbar"
>
{showHamburger && (
<button
type="button"
onClick={toggle}
className="fixed top-4 left-4 z-40 p-2 bg-base text-secondary hover:text-primary transition-colors duration-150 cursor-pointer nm-raised-sm rounded-nm-sm"
aria-label="Open sidebar"
>
<HamburgerIcon width="20" height="20" />
</button>
)}
<div
className={clsx(
"transition-[padding-left] duration-300 ease-in-out min-h-full",
isMobile && "pt-12",
!isMobile && effectiveCollapsed && "pl-12"
)}
>
{children}
<footer className="flex justify-center pt-16 pb-8">
<Image
src="/img/trackerTracker_logo_nm.svg"
alt="Tracker Tracker"
width={240}
height={73}
className="select-none pointer-events-none"
draggable={false}
priority
/>
</footer>
</div>
<BackToTop scrollRef={mainRef} />
</main>
<WhatsNew />
</div>
)
}