fix(navigation): implement persistent shell with client-side highlighting for rock-solid mobile menu
All checks were successful
Deploy Theme / deploy (push) Successful in 14s
All checks were successful
Deploy Theme / deploy (push) Successful in 14s
This commit is contained in:
35
default.hbs
35
default.hbs
@@ -3,6 +3,7 @@
|
|||||||
x-data="{
|
x-data="{
|
||||||
theme: localStorage.getItem('theme') || (window.matchMedia('(prefers-color-scheme: light)').matches ? 'light' : 'dark'),
|
theme: localStorage.getItem('theme') || (window.matchMedia('(prefers-color-scheme: light)').matches ? 'light' : 'dark'),
|
||||||
mobileMenuOpen: false,
|
mobileMenuOpen: false,
|
||||||
|
currentPath: window.location.pathname,
|
||||||
init() {
|
init() {
|
||||||
$watch('theme', val => {
|
$watch('theme', val => {
|
||||||
localStorage.setItem('theme', val);
|
localStorage.setItem('theme', val);
|
||||||
@@ -10,10 +11,15 @@
|
|||||||
});
|
});
|
||||||
document.documentElement.setAttribute('data-theme', this.theme);
|
document.documentElement.setAttribute('data-theme', this.theme);
|
||||||
document.documentElement.classList.remove('hidden');
|
document.documentElement.classList.remove('hidden');
|
||||||
|
|
||||||
|
// Update currentPath on navigation to keep menu highlights in sync
|
||||||
|
document.addEventListener('htmx:afterSettle', () => {
|
||||||
|
this.currentPath = window.location.pathname;
|
||||||
|
this.mobileMenuOpen = false;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
:data-theme="theme"
|
:data-theme="theme"
|
||||||
@htmx:before-request.window="mobileMenuOpen = false"
|
|
||||||
class="hidden">
|
class="hidden">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
@@ -30,26 +36,25 @@
|
|||||||
{{ghost_head}}
|
{{ghost_head}}
|
||||||
</head>
|
</head>
|
||||||
<body
|
<body
|
||||||
class="{{body_class}} font-sans antialiased"
|
class="{{body_class}} font-sans antialiased min-h-screen flex flex-col"
|
||||||
hx-boost="true"
|
hx-boost="true"
|
||||||
hx-target="#viewport"
|
hx-target="#main-content"
|
||||||
hx-select="#viewport"
|
hx-select="#main-content"
|
||||||
|
hx-swap="innerHTML show:top"
|
||||||
>
|
>
|
||||||
|
|
||||||
{{!--
|
{{!--
|
||||||
This container is the target for HTMX swaps.
|
PERSISTENT SHELL:
|
||||||
Moving everything inside ensures navigation highlights and states update on every click.
|
These elements are OUTSIDE #main-content, so they never swap.
|
||||||
|
This keeps Alpine state (mobileMenuOpen) perfectly stable.
|
||||||
--}}
|
--}}
|
||||||
<div id="viewport" class="min-h-screen flex flex-col">
|
{{> mobile-menu}}
|
||||||
{{> mobile-menu}}
|
{{> header}}
|
||||||
{{> header}}
|
|
||||||
|
|
||||||
<main class="flex-grow">
|
<main id="main-content" class="flex-grow">
|
||||||
{{{body}}}
|
{{{body}}}
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
{{> footer}}
|
{{> footer}}
|
||||||
</div>
|
|
||||||
|
|
||||||
{{ghost_foot}}
|
{{ghost_foot}}
|
||||||
<script src="{{asset "js/ghost-config.js"}}"></script>
|
<script src="{{asset "js/ghost-config.js"}}"></script>
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
<ul class="nav">
|
<ul class="nav">
|
||||||
{{#foreach navigation}}
|
{{#foreach navigation}}
|
||||||
<li class="{{link_class for=url class=(concat "nav-" slug)}}">
|
{{!--
|
||||||
|
We use Alpine to dynamically apply the nav-current class based on currentPath.
|
||||||
|
This works even when the navigation itself is not swapped by HTMX.
|
||||||
|
--}}
|
||||||
|
<li class="nav-{{slug}}" :class="currentPath === '{{url}}' ? 'nav-current' : ''">
|
||||||
<a href="{{url absolute="true"}}" class="nav-link">
|
<a href="{{url absolute="true"}}" class="nav-link">
|
||||||
{{label}}
|
{{label}}
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
Reference in New Issue
Block a user