-
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathShowcaseLayout.tsx
More file actions
131 lines (125 loc) · 4.2 KB
/
Copy pathShowcaseLayout.tsx
File metadata and controls
131 lines (125 loc) · 4.2 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
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
import { useEffect, useLayoutEffect, useState } from "react";
import { useLocation } from "react-router-dom";
import { SidebarProvider, SidebarTrigger, useSidebar } from "@/components/ui/sidebar";
import { AppSidebar } from "@/components/AppSidebar";
import { ContactCreator } from "@/components/ContactCreator";
import { Footer } from "@/components/Footer";
import { ThemeToggle } from "@/components/ThemeToggle";
import { Logo } from "@/components/atoms";
import { Outlet } from "react-router-dom";
import { useTheme } from "@/hooks/use-theme";
/**
* Auto-closes the mobile sidebar drawer whenever the route changes,
* so users aren't left with an open overlay after navigating.
*/
function CloseMobileSidebarOnNav() {
const { setOpenMobile, isMobile } = useSidebar();
const location = useLocation();
useEffect(() => {
if (isMobile) setOpenMobile(false);
}, [location.pathname, isMobile, setOpenMobile]);
return null;
}
function ScrollToTop() {
const location = useLocation();
useLayoutEffect(() => {
if (typeof window === "undefined") return;
if (location.hash) {
const el = document.querySelector(location.hash);
if (el) {
el.scrollIntoView({ behavior: "instant" as ScrollBehavior });
return;
}
}
const scroller = document.getElementById("main-scroll");
if (scroller) {
scroller.scrollTo({ top: 0, behavior: "instant" as ScrollBehavior });
} else {
window.scrollTo(0, 0);
}
}, [location.pathname, location.search, location.hash, location.key]);
return null;
}
function ThemeSync() {
const { theme } = useTheme();
useEffect(() => {
const link = document.querySelector<HTMLLinkElement>('link[rel="icon"]');
if (link) {
link.href = theme === "dark" ? "/favicon-dark.png" : "/favicon-light-warm.png";
}
const meta = document.querySelector<HTMLMetaElement>('meta[name="theme-color"]');
if (meta) {
const raw = getComputedStyle(document.documentElement)
.getPropertyValue("--background")
.trim();
if (raw) meta.content = `hsl(${raw})`;
}
}, [theme]);
return null;
}
function TopbarWordmark() {
const [hidden, setHidden] = useState(false);
useEffect(() => {
const sentinel = document.getElementById("hero-sentinel");
if (!sentinel) {
setHidden(false);
return;
}
const observer = new IntersectionObserver(
([entry]) => setHidden(entry.isIntersecting),
{ rootMargin: "-56px 0px 0px 0px" },
);
observer.observe(sentinel);
return () => observer.disconnect();
}, []);
return (
<div className="flex min-w-0 items-center gap-2">
<h1
className={`truncate font-mono text-lg font-semibold tracking-tight lowercase transition-opacity duration-200 ${
hidden ? "opacity-0 pointer-events-none" : "opacity-100"
}`}
aria-hidden={hidden}
>
democrito
</h1>
<span
className={`font-mono text-2xs text-muted-foreground transition-opacity duration-200 ${
hidden ? "opacity-0 pointer-events-none" : "opacity-100"
}`}
>
v3
</span>
</div>
);
}
export function ShowcaseLayout() {
return (
<SidebarProvider>
<CloseMobileSidebarOnNav />
<ThemeSync />
<ScrollToTop />
<div className="flex min-h-screen w-full">
<AppSidebar />
<div className="flex min-w-0 flex-1 flex-col">
<header className="sticky top-0 z-sticky flex h-header items-center justify-between gap-2 border-b border-border bg-surface px-4">
<div className="flex min-w-0 items-center gap-2 sm:gap-3">
<SidebarTrigger className="lg:hidden" />
<Logo size={24} />
<TopbarWordmark />
</div>
<ThemeToggle />
</header>
<main id="main-scroll" className="flex-1 overflow-y-auto px-4 py-6 sm:px-6 sm:py-8 lg:px-10">
<div className="mx-auto w-full max-w-5xl">
<Outlet />
</div>
<div className="mx-auto w-full max-w-5xl mt-16 space-y-4 border-t border-border pt-8 pb-10">
<ContactCreator />
<Footer />
</div>
</main>
</div>
</div>
</SidebarProvider>
);
}