Skip to content

Commit bee3057

Browse files
committed
Update header
- Refactor - Change mobile design - Add notes page to navigation
1 parent d222b10 commit bee3057

10 files changed

Lines changed: 371 additions & 269 deletions

File tree

src/layout/body/header.astro

Lines changed: 171 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -1,105 +1,143 @@
11
---
2-
import LogoIcon from "@layout/body/logo-icon.astro";
3-
import ModeIcon from "@layout/body/mode-icon.astro";
2+
import "@styles/header.scss";
43
54
import {
5+
headerId,
66
logoTitleId,
7+
menuButtonId,
8+
menuTemplateId,
9+
menuTitleId,
710
modeButtonId,
8-
modeTitleId
11+
modeMoonId,
12+
modeSunId,
13+
modeTemplateId,
14+
modeTitleId,
15+
navId,
16+
navListId
917
} from "@layout/body/header.ids.ts";
1018
---
1119

12-
<header>
13-
<a class="logo no-decor" href="/" aria-labelledby={logoTitleId}>
14-
<LogoIcon>
15-
<title id={logoTitleId} slot="title">mce.codes logo</title>
16-
</LogoIcon>
20+
<header id={headerId} class="header no-decor">
21+
<a class="logo" href="/" aria-labelledby={logoTitleId}>
22+
<svg
23+
class="logo-svg"
24+
width="124"
25+
height="104"
26+
viewBox="0 0 124 104"
27+
fill="none"
28+
>
29+
<title id={logoTitleId}>mce.codes logo</title>
30+
<path
31+
d="M121.5 104V4M25.232 94.156l91.924-91.924M59.164 92c-.5 2.5 0 4 3.5 3.874 2.5-.374 2.439-.799 2-3.874"
32+
></path>
33+
<path d="M6.768 2.232l91.924 91.924M2.5 104V4"></path>
34+
</svg>
1735
</a>
18-
<a class="about" href="/">About</a>
19-
<a class="projects" href="/projects">Projects</a>
20-
<button
21-
id={modeButtonId}
22-
class="mode no-button"
23-
aria-labelledby={modeTitleId}
24-
>
25-
<ModeIcon>
26-
<title id={modeTitleId} slot="title"></title>
27-
</ModeIcon>
28-
</button>
36+
<nav id={navId}>
37+
<template id={menuTemplateId}>
38+
<button
39+
id={menuButtonId}
40+
class="menu no-button"
41+
aria-labelledby={menuTitleId}
42+
>
43+
<svg
44+
class="menu-svg"
45+
width="39"
46+
height="34"
47+
viewBox="0 0 39 34"
48+
fill="none"
49+
>
50+
<title id={menuTitleId}>Menu</title>
51+
<path
52+
d="M25.864 6.737H2m35 12.632H2M37 32H2m35-20.526l-4.773-4.737L37 2"
53+
></path>
54+
</svg>
55+
</button>
56+
</template>
57+
<ul id={navListId} class="nav-list">
58+
<li class="nav-item"><a href="/">About</a></li>
59+
<li class="nav-item"><a href="/projects">Projects</a></li>
60+
<li class="nav-item"><a href="/notes">Notes</a></li>
61+
</ul>
62+
</nav>
63+
<template id={modeTemplateId}>
64+
<button
65+
id={modeButtonId}
66+
class="mode no-button"
67+
aria-labelledby={modeTitleId}
68+
>
69+
<svg
70+
class="mode-svg"
71+
width="56"
72+
height="56"
73+
viewBox="0 0 56 56"
74+
fill="none"
75+
>
76+
<title id={modeTitleId}></title>
77+
<g id={modeMoonId} class="mode-moon">
78+
<path
79+
d="M3 28c0 13.8 11.2 25 25 25 13.64-.012 25-12 25-24-4 2-16 6-24-2S25 7 27 3C15 3 3 14.36 3 28z"
80+
></path>
81+
</g>
82+
<g id={modeSunId} class="mode-sun">
83+
<path
84+
d="M28 40.037A12.04 12.04 0 0 0 40.037 28 12.04 12.04 0 0 0 28 15.963 12.04 12.04 0 0 0 15.963 28 12.04 12.04 0 0 0 28 40.037z"
85+
></path>
86+
<path
87+
d="M8.556 47.444l1.852-1.852m35.185 0l1.852 1.852m-1.852-37.037l1.852-1.852m-37.037 1.852L8.556 8.556M6.704 28H3m50 0h-3.704M28 49.296V53m0-50v3.704"
88+
></path>
89+
</g>
90+
</svg>
91+
</button>
92+
</template>
2993
</header>
3094

31-
<style lang="scss">
32-
header {
33-
display: grid;
34-
grid-template-areas: "mode mode mode" "about logo projects";
35-
grid-template-columns: repeat(3, 1fr);
36-
row-gap: v-size(2);
37-
margin-bottom: v-size(10);
38-
}
39-
a {
40-
place-self: center;
41-
}
42-
.logo {
43-
grid-area: logo;
44-
}
45-
.about {
46-
grid-area: about;
47-
}
48-
.projects {
49-
grid-area: projects;
50-
}
51-
.mode {
52-
grid-area: mode;
53-
width: fit-content;
54-
}
95+
<script>
96+
import {
97+
headerId,
98+
menuButtonId,
99+
menuTemplateId,
100+
modeButtonId,
101+
modeMoonId,
102+
modeSunId,
103+
modeTemplateId,
104+
modeTitleId,
105+
navId,
106+
navListId
107+
} from "@layout/body/header.ids.ts";
108+
import { colors } from "@styles/data/colors.json";
55109

56-
@include no-js {
57-
.mode {
58-
display: none;
59-
}
60-
}
110+
const header = document.getElementById(headerId) as HTMLElement;
61111

62-
@include m-small {
63-
header {
64-
grid-template-areas: "logo about projects mode";
65-
grid-template-columns: repeat(4, auto);
66-
column-gap: v-size(5);
67-
}
68-
a,
69-
button {
70-
align-self: center;
71-
}
72-
.logo,
73-
.projects {
74-
justify-self: start;
75-
}
76-
.about,
77-
.mode {
78-
justify-self: end;
79-
}
112+
header.append(
113+
(document.getElementById(modeTemplateId) as HTMLTemplateElement).content
114+
);
115+
(document.getElementById(navId) as HTMLElement).prepend(
116+
(document.getElementById(menuTemplateId) as HTMLTemplateElement).content
117+
);
80118

81-
@include no-js {
82-
header {
83-
grid-template-areas: "logo about projects";
84-
grid-template-columns: 1fr auto auto;
85-
}
86-
.projects {
87-
justify-self: end;
88-
}
89-
}
90-
}
91-
</style>
119+
const navList = document.getElementById(navListId) as HTMLUListElement;
120+
const menuButton = document.getElementById(menuButtonId) as HTMLButtonElement;
121+
const modeButton = document.getElementById(modeButtonId) as HTMLButtonElement;
92122

93-
<script>
94-
import { modeButtonId, modeTitleId } from "@layout/body/header.ids.ts";
95-
import { modeChange } from "@lib/events.ts";
96-
import { colors } from "@styles/data/colors.json";
123+
menuButton.addEventListener("click", () => {
124+
document.body.classList.toggle("open-nav");
125+
header.classList.toggle("open-nav");
126+
navList.classList.toggle("open-nav");
127+
modeButton.classList.toggle("open-nav");
128+
});
129+
130+
type Mode = "light" | "dark";
97131

98-
const rootStyle = document.documentElement.style;
99-
const modeButton = document.getElementById(modeButtonId) as HTMLButtonElement;
100-
const modeTitle = document.getElementById(modeTitleId) as HTMLTitleElement;
101132
const darkModeMedia = matchMedia("(prefers-color-scheme: dark)");
102-
const storedMode = localStorage.getItem("mode");
133+
const rootStyle = document.documentElement.style;
134+
const modeTitle = document.getElementById(modeTitleId) as HTMLTitleElement & {
135+
textContent: "Light mode" | "Dark mode" | null;
136+
};
137+
const modeMoon = document.getElementById(
138+
modeMoonId
139+
) as unknown as SVGGElement;
140+
const modeSun = document.getElementById(modeSunId) as unknown as SVGGElement;
103141

104142
function setModeTitleBasedOnMedia() {
105143
if (darkModeMedia.matches) {
@@ -110,7 +148,7 @@ import {
110148
modeTitle.textContent = "Dark mode";
111149
}
112150

113-
function setMode(mode: string) {
151+
function setMode(mode: Mode, animateModeSvg = true) {
114152
// Once the user has set a mode preference, follow that instead of the
115153
// system default.
116154
darkModeMedia.removeEventListener("change", setModeTitleBasedOnMedia);
@@ -121,8 +159,12 @@ import {
121159
rootStyle.setProperty("--c-background", colors.white);
122160
rootStyle.setProperty("--c-accent", colors.grayDark);
123161

124-
modeTitle.textContent = "Dark mode";
162+
if (animateModeSvg) {
163+
modeMoon.classList.add("rotate-appear");
164+
modeSun.classList.add("rotate-vanish");
165+
}
125166

167+
modeTitle.textContent = "Dark mode";
126168
localStorage.setItem("mode", "light");
127169
break;
128170
}
@@ -131,26 +173,62 @@ import {
131173
rootStyle.setProperty("--c-background", colors.black);
132174
rootStyle.setProperty("--c-accent", colors.grayLight);
133175

134-
modeTitle.textContent = "Light mode";
176+
if (animateModeSvg) {
177+
modeMoon.classList.add("rotate-vanish");
178+
modeSun.classList.add("rotate-appear");
179+
}
135180

181+
modeTitle.textContent = "Light mode";
136182
localStorage.setItem("mode", "dark");
137183
break;
138184
}
139185
}
186+
}
187+
188+
function resetModeSvgState(mode: Mode) {
189+
switch (mode) {
190+
case "light": {
191+
modeMoon.style.opacity = "1";
192+
modeSun.style.opacity = "0";
193+
break;
194+
}
195+
case "dark": {
196+
modeMoon.style.opacity = "0";
197+
modeSun.style.opacity = "1";
198+
break;
199+
}
200+
}
140201

141-
modeChange.dispatch(mode);
202+
modeMoon.classList.remove("rotate-appear");
203+
modeMoon.classList.remove("rotate-vanish");
204+
modeSun.classList.remove("rotate-appear");
205+
modeSun.classList.remove("rotate-vanish");
142206
}
143207

208+
darkModeMedia.addEventListener("change", setModeTitleBasedOnMedia);
144209
modeButton.addEventListener("click", () => {
145210
setMode(modeTitle.textContent === "Light mode" ? "light" : "dark");
146211
});
147-
darkModeMedia.addEventListener("change", setModeTitleBasedOnMedia);
212+
// Mode moon and sun animations end at the same time so put 'animationend'
213+
// handling on only one of them.
214+
modeMoon.addEventListener("animationend", () => {
215+
const storedMode = localStorage.getItem("mode") as Mode | undefined;
216+
217+
if (typeof storedMode !== "undefined") {
218+
resetModeSvgState(storedMode);
219+
}
220+
});
148221

149222
addEventListener("DOMContentLoaded", () => {
150-
if (typeof storedMode === "string") {
151-
setMode(storedMode);
152-
} else {
223+
const storedMode = localStorage.getItem("mode") as Mode | undefined;
224+
225+
if (typeof storedMode === "undefined") {
153226
setModeTitleBasedOnMedia();
227+
return;
154228
}
229+
230+
// Do not animate mode SVG on the first run, it is already in the correct
231+
// state.
232+
setMode(storedMode, false);
155233
});
156234
</script>

src/layout/body/header.ids.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,16 @@
1+
export const headerId = "";
2+
13
export const logoTitleId = "";
4+
5+
export const navId = "";
6+
export const navListId = "";
7+
8+
export const menuTemplateId = "";
9+
export const menuButtonId = "";
10+
export const menuTitleId = "";
11+
12+
export const modeTemplateId = "";
213
export const modeButtonId = "";
314
export const modeTitleId = "";
15+
export const modeMoonId = "";
16+
export const modeSunId = "";

src/layout/body/logo-icon.astro

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

0 commit comments

Comments
 (0)