feat(sidebar): implement collapsible sidebar functionality with localStorage support

This commit is contained in:
Sam Chau
2025-12-30 05:07:01 +10:30
parent 4aa914664e
commit 3b14b300d5
4 changed files with 63 additions and 5 deletions

View File

@@ -0,0 +1,36 @@
import { writable } from 'svelte/store';
import { browser } from '$app/environment';
const STORAGE_KEY = 'sidebar-collapsed';
function createSidebarStore() {
// Get initial value from localStorage
const initial = browser ? localStorage.getItem(STORAGE_KEY) === 'true' : false;
const { subscribe, set, update } = writable(initial);
return {
subscribe,
toggle: () =>
update((collapsed) => {
const newValue = !collapsed;
if (browser) {
localStorage.setItem(STORAGE_KEY, String(newValue));
}
return newValue;
}),
collapse: () => {
set(true);
if (browser) {
localStorage.setItem(STORAGE_KEY, 'true');
}
},
expand: () => {
set(false);
if (browser) {
localStorage.setItem(STORAGE_KEY, 'false');
}
}
};
}
export const sidebarCollapsed = createSidebarStore();

View File

@@ -2,10 +2,13 @@
import AccentPicker from './accentPicker.svelte';
import ThemeToggle from './themeToggle.svelte';
import logo from '$assets/logo-firefox-circular-arrow.png';
export let collapsed: boolean = false;
</script>
<nav
class="fixed top-0 left-0 z-50 w-72 border-b border-r border-neutral-200 bg-neutral-50 dark:border-neutral-800 dark:bg-neutral-900"
class="fixed top-0 left-0 z-50 w-72 border-b border-r border-neutral-200 bg-neutral-50 transition-transform duration-200 dark:border-neutral-800 dark:bg-neutral-900"
class:-translate-x-[calc(100%-24px)]={collapsed}
>
<div class="flex items-center justify-between px-4 py-4">
<!-- Left: Brand name with logo -->

View File

@@ -2,10 +2,13 @@
import Group from './group.svelte';
import GroupItem from './groupItem.svelte';
import Version from './version.svelte';
export let collapsed: boolean = false;
</script>
<nav
class="fixed top-16 left-0 flex h-[calc(100vh-4rem)] w-72 flex-col border-r border-neutral-200 bg-neutral-50 dark:border-neutral-800 dark:bg-neutral-900"
class="fixed top-16 left-0 flex h-[calc(100vh-4rem)] w-72 flex-col border-r border-neutral-200 bg-neutral-50 transition-transform duration-200 dark:border-neutral-800 dark:bg-neutral-900"
class:-translate-x-[calc(100%-24px)]={collapsed}
>
<div class="flex-1 overflow-y-auto p-4">
<Group label="🏠 Home" href="/" hasItems={true}>

View File

@@ -4,6 +4,7 @@
import Navbar from '$ui/navigation/navbar/navbar.svelte';
import PageNav from '$ui/navigation/pageNav/pageNav.svelte';
import AlertContainer from '$alerts/AlertContainer.svelte';
import { sidebarCollapsed } from '$lib/client/stores/sidebar';
</script>
<svelte:head>
@@ -11,10 +12,25 @@
<title>Profilarr</title>
</svelte:head>
<Navbar />
<PageNav />
<Navbar collapsed={$sidebarCollapsed} />
<PageNav collapsed={$sidebarCollapsed} />
<AlertContainer />
<main class="pl-72">
<!-- Sidebar collapse toggle button -->
<button
type="button"
on:click={() => sidebarCollapsed.toggle()}
class="fixed top-16 z-50 flex h-6 w-6 -translate-x-1/2 -translate-y-1/3 items-center justify-center rounded-md border border-neutral-300 bg-white shadow-sm transition-all hover:bg-neutral-50 dark:border-neutral-700 dark:bg-neutral-800 dark:hover:bg-neutral-700"
style="left: {$sidebarCollapsed ? '24px' : '288px'}"
aria-label={$sidebarCollapsed ? 'Expand sidebar' : 'Collapse sidebar'}
>
<div class="flex flex-col gap-[3px]">
<div class="h-[2px] w-3 rounded-full bg-neutral-400 dark:bg-neutral-500"></div>
<div class="h-[2px] w-3 rounded-full bg-neutral-400 dark:bg-neutral-500"></div>
<div class="h-[2px] w-3 rounded-full bg-neutral-400 dark:bg-neutral-500"></div>
</div>
</button>
<main class="transition-all duration-200 {$sidebarCollapsed ? 'pl-[24px]' : 'pl-72'}">
<slot />
</main>