Skip to content

Commit 4aae641

Browse files
committed
feat: Popover mobile menu
Use new native html popover api to handle mobile menu. This is a bit more user friendly than a giant menu taking up space on each page. Also add a light and dark mode toggle to allow users to override system default.
1 parent 5fb8f6e commit 4aae641

File tree

7 files changed

+189
-111
lines changed

7 files changed

+189
-111
lines changed

astro.config.mjs

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,16 @@ export default defineConfig({
2828
mdx(),
2929
icon({
3030
include: {
31-
// Include only three `mdi` icons in the bundle
32-
mdi: ["twitter", "github", "link-variant", "download"],
33-
"material-symbols": ["download", "copyright-outline", "rss-feed"],
31+
mdi: ["link-variant", "download"],
32+
"material-symbols": [
33+
"menu",
34+
"close",
35+
"dark-mode",
36+
"light-mode",
37+
"copyright-outline",
38+
"download",
39+
"rss-feed",
40+
],
3441
"fa6-brands": ["soundcloud", "github", "bluesky"],
3542
"fa6-solid": ["square-rss"],
3643
},

biome.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@
99
"recommended": true
1010
}
1111
},
12+
"vcs": {
13+
"enabled": true,
14+
"clientKind": "git"
15+
},
1216
"files": {
1317
"ignore": [".astro/**/*", "dist/**/*"]
1418
},

src/components/Header.astro

Lines changed: 0 additions & 42 deletions
This file was deleted.

src/components/header/Header.astro

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
---
2+
import HeaderLink from "./HeaderLink.astro";
3+
import { Icon } from "astro-icon/components";
4+
---
5+
6+
<header class={`
7+
bg-stone-50
8+
dark:bg-stone-900
9+
text-stone-900
10+
dark:text-stone-50
11+
w-full
12+
font-shortstack
13+
flex flex-row
14+
justify-center
15+
px-8 py-4 lg:py-8`}>
16+
<div class={`
17+
w-full
18+
max-w-6xl
19+
flex sm:flex-row lg:flex-row md:flex-col
20+
sm:align-middle
21+
justify-between items-center
22+
`}>
23+
<a href="/" class="no-underline focus:underline md:mb-4 sm:mb-0 lg:mb-0">
24+
<h2 class="center text-xl">
25+
Snorre.io
26+
</h2>
27+
</a>
28+
29+
<!-- Desktop navigation -->
30+
<nav class="hidden md:flex flex-row flex-wrap justify-center items-center gap-x-6 md:gap-x-8 gap-y-4 font-shortstack text-xl">
31+
<HeaderLink href="/blog">Blog</HeaderLink>
32+
<HeaderLink href="/projects">Projects</HeaderLink>
33+
<HeaderLink href="/talks">Talks</HeaderLink>
34+
<HeaderLink href="/beers">Beers</HeaderLink>
35+
<HeaderLink href="/music">Music</HeaderLink>
36+
<HeaderLink href="/about">About</HeaderLink>
37+
<HeaderLink href="/privacy">Privacy</HeaderLink>
38+
<div class="flex flex-row flex-nowrap gap-8">
39+
<button
40+
id="theme-toggle"
41+
class="flex items-center mr-4"
42+
aria-label="Toggle theme">
43+
<Icon class="hidden dark:block w-[1em]" name="material-symbols:light-mode" />
44+
<Icon class="block dark:hidden w-[1em]" name="material-symbols:dark-mode" />
45+
</button>
46+
<HeaderLink href="https://github.com/snorremd" title="GitHub" target="_blank"><Icon class="-mx-2 w-[1em]" name="fa6-brands:github" /></HeaderLink>
47+
<HeaderLink href="https://bsky.app/profile/snorre.io" title="Bluesky" target="_blank"><Icon class="-mx-2 w-[1em]" name="fa6-brands:bluesky" /></HeaderLink>
48+
<HeaderLink href="/rss.xml" title="RSS Feed"><Icon class="-mx-2 w-[1em]" name="fa6-solid:square-rss" /></HeaderLink>
49+
</div>
50+
</nav>
51+
52+
53+
54+
<!-- Mobile navigation -->
55+
<div class="md:hidden relative w-fit ml-auto">
56+
<button
57+
id="mobile-nav-toggle"
58+
popovertarget="mobile-nav"
59+
class="p-2 hover:bg-stone-200 dark:hover:bg-stone-800 rounded-drawn-sm transition-colors"
60+
aria-label="Menu">
61+
<Icon name="material-symbols:menu" class="w-6 h-6" />
62+
</button>
63+
64+
<nav popover id="mobile-nav"
65+
class={`
66+
fixed
67+
top-0
68+
!left-auto
69+
right-4
70+
mt-12
71+
popover-open:flex
72+
hidden
73+
flex-col
74+
rounded-drawn
75+
p-8
76+
bg-stone-200
77+
dark:bg-stone-800
78+
text-stone-900
79+
dark:text-stone-50
80+
border
81+
border-stone-300
82+
dark:border-stone-700
83+
shadow-[0_4px_12px_rgba(0,0,0,0.1)]
84+
dark:shadow-[0_4px_12px_rgba(0,0,0,0.5)]
85+
gap-y-4
86+
font-shortstack
87+
text-xl
88+
min-w-[200px]
89+
max-h-[calc(100vh-5rem)]
90+
overflow-y-auto
91+
scrollbar-thin
92+
scrollbar-track-stone-300
93+
dark:scrollbar-track-stone-700
94+
scrollbar-thumb-stone-400
95+
dark:scrollbar-thumb-stone-600
96+
`}>
97+
<div class="flex justify-between items-center border-b border-stone-300 dark:border-stone-700 pb-4 mb-2">
98+
<h3 class="text-xl font-shortstack">Menu</h3>
99+
<button
100+
popovertarget="mobile-nav"
101+
popovertargetaction="hide"
102+
class="p-2 hover:bg-stone-300 dark:hover:bg-stone-700 rounded-drawn-sm transition-colors"
103+
aria-label="Close menu">
104+
<Icon name="material-symbols:close" class="w-6 h-6" />
105+
</button>
106+
</div>
107+
<HeaderLink href="/blog">Blog</HeaderLink>
108+
<HeaderLink href="/projects">Projects</HeaderLink>
109+
<HeaderLink href="/talks">Talks</HeaderLink>
110+
<HeaderLink href="/beers">Beers</HeaderLink>
111+
<HeaderLink href="/music">Music</HeaderLink>
112+
<HeaderLink href="/about">About</HeaderLink>
113+
<HeaderLink href="/privacy">Privacy</HeaderLink>
114+
115+
<div class="flex flex-row gap-4 pt-4 mt-2 border-t border-stone-300 dark:border-stone-700">
116+
<button
117+
id="theme-toggle-mobile"
118+
class="flex items-center"
119+
aria-label="Toggle theme">
120+
<Icon class="hidden dark:block w-[1em]" name="material-symbols:light-mode" />
121+
<Icon class="block dark:hidden w-[1em]" name="material-symbols:dark-mode" />
122+
</button>
123+
<HeaderLink href="https://github.com/snorremd" title="GitHub" target="_blank"><Icon class="w-[1em]" name="fa6-brands:github" /></HeaderLink>
124+
<HeaderLink href="https://bsky.app/profile/snorre.io" title="Bluesky" target="_blank"><Icon class="w-[1em]" name="fa6-brands:bluesky" /></HeaderLink>
125+
<HeaderLink href="/rss.xml" title="RSS Feed"><Icon class="w-[1em]" name="fa6-solid:square-rss" /></HeaderLink>
126+
</div>
127+
</nav>
128+
</div>
129+
</div>
130+
</header>
131+
132+
<script>
133+
function setupThemeToggle(buttonId: string) {
134+
const button = document.getElementById(buttonId);
135+
if (!button) return;
136+
137+
button.addEventListener('click', () => {
138+
document.documentElement.classList.toggle('dark');
139+
const isDark = document.documentElement.classList.contains('dark');
140+
localStorage.setItem('theme', isDark ? 'dark' : 'light');
141+
});
142+
}
143+
144+
// Setup both desktop and mobile theme toggles
145+
setupThemeToggle('theme-toggle');
146+
setupThemeToggle('theme-toggle-mobile');
147+
</script>

src/components/HeaderLink.astro renamed to src/components/header/HeaderLink.astro

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ const isActive =
1414
className,
1515
{ active: isActive },
1616
isActive ? 'underline' : 'no-underline',
17-
'focus:outline-none',
17+
'focus:outline-hidden',
1818
'focus:underline'
1919
]} {...props}>
2020
<slot />

src/layouts/BaseLayout.astro

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import { ViewTransitions } from "astro:transitions";
33
44
import BaseHead from "../components/BaseHead.astro";
5-
import Header from "../components/Header.astro";
5+
import Header from "../components/header/Header.astro";
66
import Footer from "../components/Footer.astro";
77
88
const { title, description } = Astro.props;
@@ -19,6 +19,24 @@ const { title, description } = Astro.props;
1919
type="font/woff2"
2020
crossorigin
2121
/>
22+
<script is:inline>
23+
// Initialize theme before page renders
24+
const theme = (() => {
25+
if (typeof localStorage !== 'undefined') {
26+
const stored = localStorage.getItem('theme');
27+
if (stored) {
28+
return stored;
29+
}
30+
}
31+
// If no stored preference, use system default
32+
return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
33+
})();
34+
35+
// Only add class if dark
36+
if (theme === 'dark') {
37+
document.documentElement.classList.add('dark');
38+
}
39+
</script>
2240
<slot name="head" />
2341
</head>
2442
<body class={`

tailwind.config.cjs

Lines changed: 8 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
11
/** @type {import('tailwindcss').Config} */
22
module.exports = {
3+
darkMode: "class",
34
content: ["./src/**/*.{astro,html,js,jsx,md,mdx,svelte,ts,tsx,vue}"],
4-
plugins: [require("@kobalte/tailwindcss")],
5+
plugins: [
6+
require("@kobalte/tailwindcss"),
7+
({ addVariant }) => {
8+
addVariant("popover-open", "&:popover-open");
9+
},
10+
],
511
theme: {
612
fontFamily: {
713
shortstack: ["shortstack"],
@@ -15,69 +21,7 @@ module.exports = {
1521
"layered-waves-light": "url('/graphics/layered-waves-light.svg')",
1622
"layered-waves-dark": "url('/graphics/layered-waves-dark.svg')",
1723
},
18-
colors: {
19-
"neon-gray": {
20-
50: "#f7f8f8",
21-
100: "#dee3e2",
22-
200: "#bcc8c7",
23-
300: "#93a5a4",
24-
400: "#6c7f7e",
25-
500: "#506262",
26-
600: "#3b494a",
27-
700: "#2c3435",
28-
800: "#212527",
29-
900: "#181b1b",
30-
},
31-
32-
"neon-turquoise": {
33-
50: "#e7fff9",
34-
100: "#c6fff0",
35-
200: "#92ffe6",
36-
300: "#4dffdf",
37-
400: "#00ffd1",
38-
500: "#00e8bc",
39-
600: "#00be9b",
40-
700: "#009881",
41-
800: "#007867",
42-
900: "#006256",
43-
},
44-
"neon-blue": {
45-
50: "#eefdfd",
46-
100: "#d5f7f8",
47-
200: "#afeef2",
48-
300: "#78e0e8",
49-
400: "#31c6d4",
50-
500: "#1eacbc",
51-
600: "#1c8b9e",
52-
700: "#1d7081",
53-
800: "#205b6a",
54-
900: "#1f4d5a",
55-
},
56-
"neon-red": {
57-
50: "#fff1f1",
58-
100: "#ffdfdf",
59-
200: "#ffc5c5",
60-
300: "#ff9d9d",
61-
400: "#ff6464",
62-
500: "#ff1e1e",
63-
600: "#ed1515",
64-
700: "#c80d0d",
65-
800: "#a50f0f",
66-
900: "#881414",
67-
},
68-
"neon-yellow": {
69-
50: "#fbfee8",
70-
100: "#f6ffc2",
71-
200: "#f2ff88",
72-
300: "#f2ff45",
73-
400: "#f7fd04",
74-
500: "#ede905",
75-
600: "#cdb901",
76-
700: "#a38605",
77-
800: "#87690c",
78-
900: "#725511",
79-
},
80-
},
24+
colors: {},
8125
},
8226
},
8327
};

0 commit comments

Comments
 (0)