nandi/gleanpublic Fork 0
83ddff1
Commits
Clone
git clone https://git.rickub.com/nandi/glean.git
git clone ssh://git@rickub.com/nandi/glean.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

feat(web): replace mobile nav row with hamburger menuUnverified

Julien Robert committed 2026-08-09T15:48:41+02:00 Browse files
83ddff1 parent: 60367de
modified web/src/lib/components/Icon.svelte +1 -0
@@ -50,6 +50,7 @@
5050 globe: "M12 2a10 10 0 1 0 0 20 10 10 0 0 0 0-20zM2 12h20M12 2a15 15 0 0 1 0 20M12 2a15 15 0 0 0 0 20",
5151 sparkles:
5252 "M12 3l1.8 4.7L18.5 9.5 13.8 11.3 12 16l-1.8-4.7L5.5 9.5l4.7-1.8zM19 14l.9 2.3 2.3.9-2.3.9L19 21l-.9-2.3-2.3-.9 2.3-.9z",
53+ menu: "M4 7h16M4 12h16M4 17h16",
5354 };
5455 </script>
5556
@@ -50,6 +50,7 @@
50 globe: "M12 2a10 10 0 1 0 0 20 10 10 0 0 0 0-20zM2 12h20M12 2a15 15 0 0 1 0 20M12 2a15 15 0 0 0 0 20",50 globe: "M12 2a10 10 0 1 0 0 20 10 10 0 0 0 0-20zM2 12h20M12 2a15 15 0 0 1 0 20M12 2a15 15 0 0 0 0 20",
51 sparkles:51 sparkles:
52 "M12 3l1.8 4.7L18.5 9.5 13.8 11.3 12 16l-1.8-4.7L5.5 9.5l4.7-1.8zM19 14l.9 2.3 2.3.9-2.3.9L19 21l-.9-2.3-2.3-.9 2.3-.9z",52 "M12 3l1.8 4.7L18.5 9.5 13.8 11.3 12 16l-1.8-4.7L5.5 9.5l4.7-1.8zM19 14l.9 2.3 2.3.9-2.3.9L19 21l-.9-2.3-2.3-.9 2.3-.9z",
53+ menu: "M4 7h16M4 12h16M4 17h16",
53 };54 };
54 </script>55 </script>
55 56
modified web/src/routes/+layout.svelte +51 -22
@@ -1,7 +1,7 @@
11 <script lang="ts">
22 import "../app.css";
33 import { page } from "$app/state";
4- import { goto } from "$app/navigation";
4+ import { goto, afterNavigate } from "$app/navigation";
55 import type { LayoutData } from "./$types";
66 import Logo from "$lib/components/Logo.svelte";
77 import Icon from "$lib/components/Icon.svelte";
@@ -20,6 +20,11 @@
2020 setCsrfToken(data.csrfToken);
2121 });
2222
23+ afterNavigate(() => {
24+ menuOpen = false;
25+ navOpen = false;
26+ });
27+
2328 const chromeless = $derived(
2429 page.url.pathname === "/" ||
2530 page.url.pathname.startsWith("/auth/") ||
@@ -27,6 +32,7 @@
2732 );
2833
2934 let menuOpen = $state(false);
35+ let navOpen = $state(false);
3036 let showShortcuts = $state(false);
3137 let showInstall = $state(false);
3238
@@ -94,6 +100,7 @@
94100 // ignore
95101 }
96102 menuOpen = false;
103+ navOpen = false;
97104 goto("/", { invalidateAll: true });
98105 }
99106
@@ -174,6 +181,49 @@
174181 >
175182 <Icon name="keyboard" class="h-4 w-4" />
176183 </button>
184+
185+ <!-- Mobile hamburger nav -->
186+ <div class="relative md:hidden">
187+ <button
188+ class="btn btn-ghost px-2"
189+ onclick={() => (navOpen = !navOpen)}
190+ title="Menu"
191+ aria-label="Menu"
192+ aria-expanded={navOpen}
193+ >
194+ <Icon name="menu" class="h-4 w-4" />
195+ </button>
196+ {#if navOpen}
197+ <button
198+ class="fixed inset-0 z-40 cursor-default"
199+ onclick={() => (navOpen = false)}
200+ aria-label="Close menu"
201+ tabindex="-1"
202+ ></button>
203+ <nav
204+ class="absolute right-0 top-full z-50 mt-1 w-48 border-2 border-[var(--border)] bg-[var(--bg)] shadow-[4px_4px_0_0_var(--border)]"
205+ >
206+ {#each navItems as item}
207+ <a
208+ href={item.href}
209+ class="flex items-center gap-2 border-b-2 border-[var(--border)] px-4 py-2.5 text-xs font-bold uppercase {isActive(
210+ item.match,
211+ )
212+ ? 'text-[var(--accent)]'
213+ : 'text-[var(--muted)] hover:text-[var(--fg)] hover:bg-[var(--surface)]'}"
214+ onclick={() => (navOpen = false)}
215+ >
216+ <Icon
217+ name={item.icon}
218+ class="h-3.5 w-3.5"
219+ />
220+ {item.label}
221+ </a>
222+ {/each}
223+ </nav>
224+ {/if}
225+ </div>
226+
177227 {#if data.user}
178228 <div class="relative">
179229 <button
@@ -237,27 +287,6 @@
237287 </div>
238288 {/if}
239289 </div>
240-
241- <!-- Mobile nav row -->
242- {#if !chromeless}
243- <nav
244- class="flex items-center gap-1 overflow-x-auto border-t-2 border-[var(--border)] px-2 py-1 md:hidden"
245- >
246- {#each navItems as item}
247- <a
248- href={item.href}
249- class="inline-flex shrink-0 items-center gap-1.5 px-2.5 py-1.5 text-[0.7rem] font-bold uppercase {isActive(
250- item.match,
251- )
252- ? 'bg-[var(--fg)] text-[var(--bg)]'
253- : 'text-[var(--muted)]'}"
254- >
255- <Icon name={item.icon} class="h-3.5 w-3.5" />
256- {item.label}
257- </a>
258- {/each}
259- </nav>
260- {/if}
261290 </header>
262291 {/if}
263292
@@ -1,7 +1,7 @@
1 <script lang="ts">1 <script lang="ts">
2 import "../app.css";2 import "../app.css";
3 import { page } from "$app/state";3 import { page } from "$app/state";
4- import { goto } from "$app/navigation";4+ import { goto, afterNavigate } from "$app/navigation";
5 import type { LayoutData } from "./$types";5 import type { LayoutData } from "./$types";
6 import Logo from "$lib/components/Logo.svelte";6 import Logo from "$lib/components/Logo.svelte";
7 import Icon from "$lib/components/Icon.svelte";7 import Icon from "$lib/components/Icon.svelte";
@@ -20,6 +20,11 @@
20 setCsrfToken(data.csrfToken);20 setCsrfToken(data.csrfToken);
21 });21 });
22 22
23+ afterNavigate(() => {
24+ menuOpen = false;
25+ navOpen = false;
26+ });
27+
23 const chromeless = $derived(28 const chromeless = $derived(
24 page.url.pathname === "/" ||29 page.url.pathname === "/" ||
25 page.url.pathname.startsWith("/auth/") ||30 page.url.pathname.startsWith("/auth/") ||
@@ -27,6 +32,7 @@
27 );32 );
28 33
29 let menuOpen = $state(false);34 let menuOpen = $state(false);
35+ let navOpen = $state(false);
30 let showShortcuts = $state(false);36 let showShortcuts = $state(false);
31 let showInstall = $state(false);37 let showInstall = $state(false);
32 38
@@ -94,6 +100,7 @@
94 // ignore100 // ignore
95 }101 }
96 menuOpen = false;102 menuOpen = false;
103+ navOpen = false;
97 goto("/", { invalidateAll: true });104 goto("/", { invalidateAll: true });
98 }105 }
99 106
@@ -174,6 +181,49 @@
174 >181 >
175 <Icon name="keyboard" class="h-4 w-4" />182 <Icon name="keyboard" class="h-4 w-4" />
176 </button>183 </button>
184+
185+ <!-- Mobile hamburger nav -->
186+ <div class="relative md:hidden">
187+ <button
188+ class="btn btn-ghost px-2"
189+ onclick={() => (navOpen = !navOpen)}
190+ title="Menu"
191+ aria-label="Menu"
192+ aria-expanded={navOpen}
193+ >
194+ <Icon name="menu" class="h-4 w-4" />
195+ </button>
196+ {#if navOpen}
197+ <button
198+ class="fixed inset-0 z-40 cursor-default"
199+ onclick={() => (navOpen = false)}
200+ aria-label="Close menu"
201+ tabindex="-1"
202+ ></button>
203+ <nav
204+ class="absolute right-0 top-full z-50 mt-1 w-48 border-2 border-[var(--border)] bg-[var(--bg)] shadow-[4px_4px_0_0_var(--border)]"
205+ >
206+ {#each navItems as item}
207+ <a
208+ href={item.href}
209+ class="flex items-center gap-2 border-b-2 border-[var(--border)] px-4 py-2.5 text-xs font-bold uppercase {isActive(
210+ item.match,
211+ )
212+ ? 'text-[var(--accent)]'
213+ : 'text-[var(--muted)] hover:text-[var(--fg)] hover:bg-[var(--surface)]'}"
214+ onclick={() => (navOpen = false)}
215+ >
216+ <Icon
217+ name={item.icon}
218+ class="h-3.5 w-3.5"
219+ />
220+ {item.label}
221+ </a>
222+ {/each}
223+ </nav>
224+ {/if}
225+ </div>
226+
177 {#if data.user}227 {#if data.user}
178 <div class="relative">228 <div class="relative">
179 <button229 <button
@@ -237,27 +287,6 @@
237 </div>287 </div>
238 {/if}288 {/if}
239 </div>289 </div>
240-
241- <!-- Mobile nav row -->
242- {#if !chromeless}
243- <nav
244- class="flex items-center gap-1 overflow-x-auto border-t-2 border-[var(--border)] px-2 py-1 md:hidden"
245- >
246- {#each navItems as item}
247- <a
248- href={item.href}
249- class="inline-flex shrink-0 items-center gap-1.5 px-2.5 py-1.5 text-[0.7rem] font-bold uppercase {isActive(
250- item.match,
251- )
252- ? 'bg-[var(--fg)] text-[var(--bg)]'
253- : 'text-[var(--muted)]'}"
254- >
255- <Icon name={item.icon} class="h-3.5 w-3.5" />
256- {item.label}
257- </a>
258- {/each}
259- </nav>
260- {/if}
261 </header>290 </header>
262 {/if}291 {/if}
263 292