summaryrefslogtreecommitdiffstats
path: root/src/ui/components/SiteHeader.tsx
diff options
context:
space:
mode:
authorVolpeon <git@volpeon.ink>2026-07-22 19:40:59 +0200
committerVolpeon <git@volpeon.ink>2026-07-22 19:40:59 +0200
commitc1e28224aa2e1450031669d9a7e359b63e04687d (patch)
tree68f3d8905c763a91f68001d868197effa92adafa /src/ui/components/SiteHeader.tsx
downloadcorvos-website-c1e28224aa2e1450031669d9a7e359b63e04687d.tar.gz
corvos-website-c1e28224aa2e1450031669d9a7e359b63e04687d.tar.bz2
corvos-website-c1e28224aa2e1450031669d9a7e359b63e04687d.zip
Init
Diffstat (limited to 'src/ui/components/SiteHeader.tsx')
-rw-r--r--src/ui/components/SiteHeader.tsx121
1 files changed, 121 insertions, 0 deletions
diff --git a/src/ui/components/SiteHeader.tsx b/src/ui/components/SiteHeader.tsx
new file mode 100644
index 0000000..17831a1
--- /dev/null
+++ b/src/ui/components/SiteHeader.tsx
@@ -0,0 +1,121 @@
1import type { HTMLAttributes } from "corvos/plugins/jsx.ts";
2
3import { Icon } from "$/ui/objects/Icon.tsx";
4
5import { nav } from "../../data/nav.ts";
6import { ActionButton } from "../objects/ActionButton.tsx";
7import { Navbar } from "../objects/Navbar.tsx";
8import { NavbarItem } from "../objects/NavbarItem.tsx";
9import { Logo } from "./Logo.tsx";
10
11export type Props = HTMLAttributes<"header"> & {
12 "url": string;
13 "mode"?: "compact" | "none";
14 "at-end"?: boolean;
15 "hidden"?: boolean;
16 "sm"?: JSX.Element;
17 "end"?: JSX.Element;
18 "children"?: JSX.Element;
19};
20
21export const SiteHeader = ({
22 children,
23 sm,
24 url,
25 mode,
26 "at-end": atEnd,
27 hidden,
28 end,
29 class: cls,
30 ...props
31}: Props) => {
32 const navItems = nav.map((item) => ({
33 ...item,
34 selected: new RegExp(`^${item.url.replace("/#", "/")}`).test(url),
35 }));
36
37 const parent = navItems
38 .filter((item) => item.url !== url)
39 .reduce(
40 (parent: (typeof navItems)[number] | undefined, item) =>
41 item.selected && item.url.length > (parent?.url.length ?? 0) ? item : parent,
42 undefined,
43 );
44 const selected = navItems.reduce(
45 (sel, item) => (item.selected && item.url.length > sel.url.length ? item : sel),
46 {
47 icon: "",
48 label: "Back",
49 selected: true,
50 url: "/",
51 },
52 );
53
54 const variant = atEnd ? "static-white" : undefined;
55
56 return (
57 <header
58 class:list={[
59 "c-site-header",
60 "l-media",
61 { "c-site-header--end": atEnd, "c-site-header--hidden": hidden },
62 cls,
63 ]}
64 {...props}
65 >
66 <Logo class="c-site-header__logo l-media__block" variant={variant} />
67
68 {mode !== "none" && (
69 <>
70 <Navbar aria-label="Mainmenu" class="c-site-header__nav l-media__block" variant={variant}>
71 {mode === "compact" ? (
72 <NavbarItem
73 href={selected.url}
74 pre={<Icon icon="arrow-left" variant="bold" />}
75 selected
76 >
77 {selected.label}
78 </NavbarItem>
79 ) : (
80 navItems
81 .filter((item) => item.url !== "/" && !item.hidden)
82 .map((item) => (
83 <NavbarItem as="a" href={item.url} selected={item.selected}>
84 {item.label}
85 </NavbarItem>
86 ))
87 )}
88 </Navbar>
89
90 {parent && (
91 <ActionButton
92 align-block
93 class="c-site-header__back l-media__block"
94 href={parent.url}
95 icon={<Icon icon="arrow-left" variant="bold" />}
96 level="quiet"
97 pill
98 />
99 )}
100 </>
101 )}
102
103 {children}
104
105 {sm && <div class="c-site-header__slot c-site-header__slot--sm">{sm}</div>}
106
107 {end && (
108 <>
109 <div class="c-site-header__spacer" />
110 <div class="c-site-header__slot c-site-header__slot--end">{end}</div>
111 </>
112 )}
113
114 <Logo
115 class="c-site-header__logo c-site-header__logo--sm l-media__block"
116 size="sm"
117 variant={variant}
118 />
119 </header>
120 );
121};