Skip to content

Commit 313aeb5

Browse files
committed
Refactor navigation to client/server folder pattern, move CSS style classes to Tailwind CSS classes on elements and a CSS modules file
1 parent b707a45 commit 313aeb5

13 files changed

Lines changed: 347 additions & 346 deletions

File tree

‎src/components/Header/index.astro‎

Lines changed: 2 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
---
22
import Brand from '@components/Brand/index.astro'
3-
import Menu from '@components/Navigation/Menu.astro'
4-
import NavToggle from '@components/Navigation/NavToggle.astro'
3+
import Navigation from '@components/Navigation/index.astro'
54
import ThemeButton from '@components/ThemePicker/ThemeButton.astro'
65
76
export interface Props {
@@ -61,19 +60,7 @@ const { path } = Astro.props
6160
<Brand />
6261
</span>
6362
{/* Focus trap container for mobile navigation - invisible wrapper for accessibility */}
64-
<div id="mobile-nav-focus-container" class="contents">
65-
{/* Main Navigation Menu */}
66-
<span id="header__main-nav" class="lg:z-(--z-nav) lg:flex-1 lg:mx-8">
67-
<Menu path={path} />
68-
</span>
69-
{/* Mobile navigation toggle button to show menu on full-page splash screen */}
70-
<span
71-
id="header__nav-icon"
72-
class="block fixed right-4 sm:right-6 top-2 z-(--z-mobile-close-btn) lg:hidden"
73-
>
74-
<NavToggle />
75-
</span>
76-
</div>
63+
<Navigation path={path} />
7764
{/* Theme picker toggle button to show modal */}
7865
<span
7966
id="header__theme-icon"

‎src/components/Navigation/Menu.astro‎

Lines changed: 14 additions & 241 deletions
Original file line numberDiff line numberDiff line change
@@ -1,265 +1,38 @@
11
---
22
import { navigationItems } from '@components/Navigation/server'
3+
import menuStyles from './menu.module.css'
34
export interface Props {
45
path: string
56
}
67
78
const { path } = Astro.props
89
910
const activeMenuItem = (url: string) => {
10-
return path === url ? 'nav-item-active' : ''
11+
return path === url ? ['nav-item-active', menuStyles['navItemActive']] : []
1112
}
1213
---
1314

14-
<style>
15-
/* No-scroll class used by toggleMenu() method in Navigation script */
16-
:global(.no-scroll) {
17-
overflow: hidden;
18-
}
19-
20-
/* Mobile menu - hidden by default */
21-
.main-nav-menu {
22-
display: none;
23-
}
24-
25-
/* Mobile menu items - initially hidden with transform */
26-
.main-nav-item {
27-
opacity: 0;
28-
transform: translateY(-1rem);
29-
transition: opacity 0.4s ease, transform 0.4s ease;
30-
}
31-
32-
/* Desktop menu - visible */
33-
@media (width >= 1024px) {
34-
.main-nav-menu {
35-
display: flex;
36-
}
37-
38-
.main-nav-item {
39-
opacity: 1;
40-
transform: translateY(0);
41-
}
42-
}
43-
44-
/* Staggered animation delays for mobile menu items - start after splash completes */
45-
@media (width <= 1023px) {
46-
:global(#header.aria-expanded-true) .main-nav-menu.menu-visible .main-nav-item:nth-child(1) {
47-
transition-delay: 0.1s !important;
48-
}
49-
50-
:global(#header.aria-expanded-true) .main-nav-menu.menu-visible .main-nav-item:nth-child(2) {
51-
transition-delay: 0.2s !important;
52-
}
53-
54-
:global(#header.aria-expanded-true) .main-nav-menu.menu-visible .main-nav-item:nth-child(3) {
55-
transition-delay: 0.3s !important;
56-
}
57-
58-
:global(#header.aria-expanded-true) .main-nav-menu.menu-visible .main-nav-item:nth-child(4) {
59-
transition-delay: 0.4s !important;
60-
}
61-
62-
:global(#header.aria-expanded-true) .main-nav-menu.menu-visible .main-nav-item:nth-child(5) {
63-
transition-delay: 0.5s !important;
64-
}
65-
66-
:global(#header.aria-expanded-true) .main-nav-menu.menu-visible .main-nav-item:nth-child(6) {
67-
transition-delay: 0.6s !important;
68-
}
69-
70-
/* Mobile menu items - remove desktop underlines */
71-
:global(#header.aria-expanded-true) .main-nav-item a {
72-
text-decoration: none !important;
73-
}
74-
75-
/* Mobile menu item - remove focus outline */
76-
:global(#header.aria-expanded-true) .main-nav-item a:focus {
77-
outline: none !important;
78-
}
79-
80-
/* Mobile menu item hover/focus/active underline effects */
81-
:global(#header.aria-expanded-true) .main-nav-item a:hover,
82-
:global(#header.aria-expanded-true) .main-nav-item a:focus,
83-
:global(#header.aria-expanded-true) .main-nav-item a:active {
84-
outline: none !important;
85-
text-decoration: underline !important;
86-
text-decoration-color: var(--color-bg) !important;
87-
text-decoration-style: dotted !important;
88-
text-decoration-thickness: 2px !important;
89-
text-underline-offset: 8px !important;
90-
}
91-
}
92-
93-
/* Mobile splash background animation */
94-
95-
/*
96-
Use a fixed, centered pseudo-element that scales from the
97-
toggle button origin to cover the entire viewport. Keep the
98-
splash slightly translucent and ensure it sits *under* the
99-
menu items so links can appear above it.
100-
*/
101-
:global(#mobile-splash) {
102-
/* anchor point for the splash (small element near toggle) */
103-
height: 1px;
104-
pointer-events: none;
105-
position: fixed;
106-
107-
/* keep near the top-right where the hamburger lives but respect safe-area */
108-
right: env(safe-area-inset-right, 20px);
109-
top: env(safe-area-inset-top, 20px);
110-
width: 1px;
111-
z-index: var(--z-mobile-splash);
112-
}
113-
114-
:global(#mobile-splash::after) {
115-
/* keep the splash slightly translucent so page chrome is visible under it */
116-
background-color: var(--color-bg-inverse);
117-
border-radius: 50%;
118-
content: '';
119-
height: 300vmax;
120-
opacity: 1;
121-
pointer-events: none;
122-
position: fixed;
123-
124-
/* Position the circle center at the hamburger button location */
125-
right: calc(env(safe-area-inset-right, 20px) + 20px); /* Adjust to button center */
126-
top: calc(env(safe-area-inset-top, 20px) + 20px); /* Adjust to button center */
127-
128-
/* Center the circle on this point */
129-
transform: translate(50%, -50%) scale(0);
130-
transform-origin: center;
131-
transition: transform 0.55s cubic-bezier(0.755, 0.05, 0.855, 0.06);
132-
133-
/* make it big enough to guarantee full coverage on all viewports */
134-
width: 300vmax;
135-
will-change: transform, opacity;
136-
137-
/* place the splash above everything except nav menu items and close button */
138-
z-index: var(--z-mobile-splash);
139-
}
140-
141-
/* Mobile menu open state splash - scale up to cover viewport */
142-
/* stylelint-disable-next-line selector-max-specificity */
143-
:global(#header.aria-expanded-true #mobile-splash::after) {
144-
transform: translate(50%, -50%) scale(1);
145-
}
146-
147-
/* Keep splash backdrop non-interactive */
148-
149-
/* Mobile menu open state display */
150-
:global(#header.aria-expanded-true) .main-nav {
151-
/* ensure menu items sit above the splash (120 > 110) */
152-
z-index: var(--z-mobile-menu);
153-
}
154-
155-
:global(#header.aria-expanded-true) .main-nav-menu {
156-
align-items: center;
157-
158-
/* When open, make the menu full-screen and center items. We keep this
159-
in CSS because it's only applied when the header has the expanded
160-
class and relies on fixed positioning and z-index coordination.
161-
*/
162-
display: flex;
163-
flex-direction: column;
164-
height: 100vh;
165-
justify-content: center;
166-
left: 0;
167-
168-
/* Delay menu appearance until splash covers viewport */
169-
opacity: 0;
170-
position: fixed;
171-
top: 0;
172-
transition: opacity 0.3s ease 0.5s;
173-
width: 100vw;
174-
175-
/* ensure menu items sit above the splash (120 > 110) */
176-
z-index: var(--z-mobile-menu);
177-
}
178-
179-
:global(#header.aria-expanded-true) .main-nav-menu.menu-visible {
180-
opacity: 1;
181-
}
182-
183-
:global(#header.aria-expanded-true) .main-nav-menu.menu-visible .main-nav-item {
184-
opacity: 1;
185-
transform: translateY(0);
186-
187-
/* ensure menu items sit above the splash (120 > 110) */
188-
z-index: var(--z-mobile-menu);
189-
}
190-
191-
:global(#header.aria-expanded-true) .main-nav-item a {
192-
color: var(--color-bg) !important;
193-
opacity: 1;
194-
195-
/* ensure menu items sit above the splash (120 > 110) */
196-
z-index: var(--z-mobile-menu-item) !important;
197-
}
198-
199-
/* Desktop navigation item underline effects */
200-
@media (width >= 1024px) {
201-
.main-nav-item a {
202-
color: var(--color-primary-offset);
203-
text-decoration: underline;
204-
text-decoration-color: var(--color-primary-offset);
205-
text-decoration-style: dotted;
206-
text-decoration-thickness: 2px;
207-
text-underline-offset: 8px;
208-
transition: color 0.2s, text-decoration-color 0.2s;
209-
}
210-
211-
.nav-item-active a {
212-
color: var(--color-primary);
213-
text-decoration: none;
214-
}
215-
216-
.main-nav-item a:hover,
217-
.main-nav-item a:focus {
218-
color: var(--color-primary);
219-
text-decoration-color: transparent;
220-
}
221-
222-
.main-nav-item::after {
223-
background-color: var(--color-primary);
224-
bottom: -0.25rem;
225-
content: '';
226-
display: block;
227-
height: 0.25rem;
228-
left: 1.25rem;
229-
position: absolute;
230-
right: 1.25rem;
231-
transform: scaleX(0);
232-
transform-origin: 0 50%;
233-
transition: transform 0.3s cubic-bezier(0.86, 0, 0.07, 1) 0.1s;
234-
}
235-
236-
.main-nav-item:hover::after {
237-
transform: scaleX(1);
238-
}
239-
240-
.nav-item-active::after {
241-
background-color: var(--color-primary);
242-
transform: none;
243-
}
244-
245-
.nav-item-active:hover::after {
246-
background-color: var(--color-primary);
247-
}
248-
}
249-
</style>
250-
25115
<nav id="main-nav" class="main-nav" role="navigation" aria-label="Main">
25216
<ul
253-
class="main-nav-menu flex flex-row justify-center relative lg:items-center lg:flex-row"
17+
class:list={[
18+
'main-nav-menu',
19+
menuStyles['mainNavMenu'],
20+
'w-full font-heading text-[0.85rem] uppercase tracking-[0.22em] text-primary',
21+
'lg:relative lg:text-[0.8rem] lg:tracking-[0.12em]'
22+
]}
25423
tabindex="-1"
25524
aria-label="main navigation"
25625
>
25726
{
25827
navigationItems.map(navItem => (
259-
<li class:list={['main-nav-item lg:relative', activeMenuItem(navItem.url)]}>
28+
<li class:list={['main-nav-item', menuStyles['mainNavItem'], 'lg:relative', ...activeMenuItem(navItem.url)]}>
26029
<a
26130
href={navItem.url}
262-
class="block text-primary text-xl lg:text-lg font-bold tracking-[5px] lg:tracking-normal p-4 lg:px-5 lg:py-3 text-center uppercase lg:lowercase no-underline lg:hover:text-bg-offset lg:hover:bg-inherit lg:focus:text-bg-offset lg:focus:bg-inherit focus:outline-0 lg:flex lg:items-center lg:relative lg:whitespace-nowrap lg:z-10"
31+
class:list={[
32+
'block text-primary text-xl font-bold tracking-[5px] uppercase no-underline text-center p-4 focus:outline-0',
33+
'lg:text-lg lg:lowercase lg:tracking-normal lg:px-5 lg:py-3 lg:flex lg:items-center lg:relative lg:whitespace-nowrap lg:z-10',
34+
menuStyles['mainNavLink']
35+
]}
26336
>
26437
{navItem.title}
26538
</a>

0 commit comments

Comments
 (0)