From 702a30b699aa03c54b09361840209319974c2d91 Mon Sep 17 00:00:00 2001 From: Till-JS <101404291+Till-JS@users.noreply.github.com> Date: Fri, 13 Feb 2026 23:29:45 +0100 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20feat(todo):=20add=20settings=20stor?= =?UTF-8?q?e=20and=20improve=20layout?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../web/src/lib/components/FilterStrip.svelte | 421 ++++++++++++++++++ .../web/src/lib/stores/settings.svelte.ts | 28 ++ .../apps/web/src/routes/(app)/+layout.svelte | 198 +++++--- 3 files changed, 595 insertions(+), 52 deletions(-) create mode 100644 apps/todo/apps/web/src/lib/components/FilterStrip.svelte diff --git a/apps/todo/apps/web/src/lib/components/FilterStrip.svelte b/apps/todo/apps/web/src/lib/components/FilterStrip.svelte new file mode 100644 index 000000000..0fb53a17c --- /dev/null +++ b/apps/todo/apps/web/src/lib/components/FilterStrip.svelte @@ -0,0 +1,421 @@ + + + + +
+
+ + + + + + + + {#each priorities as priority (priority.value)} + + {/each} + + + {#each sortOptions as option (option.id)} + + {/each} + + + + + +
e.stopPropagation()}> + + + {#if showFilterDropdown} +
e.stopPropagation()}> +
+
Projekt
+ +
+
+ {/if} +
+
+
+ + diff --git a/apps/todo/apps/web/src/lib/stores/settings.svelte.ts b/apps/todo/apps/web/src/lib/stores/settings.svelte.ts index 46d79c496..a0551029f 100644 --- a/apps/todo/apps/web/src/lib/stores/settings.svelte.ts +++ b/apps/todo/apps/web/src/lib/stores/settings.svelte.ts @@ -42,6 +42,10 @@ export interface TodoAppSettings extends Record { // Immersive Mode immersiveModeEnabled: boolean; + + // Navigation UI + pillNavCollapsed: boolean; + filterStripCollapsed: boolean; } const DEFAULT_SETTINGS: TodoAppSettings = { @@ -76,6 +80,10 @@ const DEFAULT_SETTINGS: TodoAppSettings = { // Immersive Mode immersiveModeEnabled: false, + + // Navigation UI + pillNavCollapsed: true, // PillNav hidden by default, shown via FAB + filterStripCollapsed: false, // FilterStrip shown by default when PillNav is visible }; // Create base store using factory @@ -155,4 +163,24 @@ export const todoSettings = { get immersiveModeEnabled() { return baseStore.settings.immersiveModeEnabled; }, + get pillNavCollapsed() { + return baseStore.settings.pillNavCollapsed; + }, + get filterStripCollapsed() { + return baseStore.settings.filterStripCollapsed; + }, + + // Toggle methods + togglePillNav() { + baseStore.update({ pillNavCollapsed: !baseStore.settings.pillNavCollapsed }); + }, + toggleFilterStrip() { + baseStore.update({ filterStripCollapsed: !baseStore.settings.filterStripCollapsed }); + }, + showPillNav() { + baseStore.update({ pillNavCollapsed: false }); + }, + hidePillNav() { + baseStore.update({ pillNavCollapsed: true }); + }, }; diff --git a/apps/todo/apps/web/src/routes/(app)/+layout.svelte b/apps/todo/apps/web/src/routes/(app)/+layout.svelte index 1acb64552..73596bada 100644 --- a/apps/todo/apps/web/src/routes/(app)/+layout.svelte +++ b/apps/todo/apps/web/src/routes/(app)/+layout.svelte @@ -22,8 +22,7 @@ import { labelsStore } from '$lib/stores/labels.svelte'; import { tasksStore } from '$lib/stores/tasks.svelte'; import { theme } from '$lib/stores/theme'; - import { isToolbarCollapsed as toolbarCollapsedStore } from '$lib/stores/navigation'; - import TodoToolbar from '$lib/components/TodoToolbar.svelte'; + import FilterStrip from '$lib/components/FilterStrip.svelte'; import { THEME_DEFINITIONS, DEFAULT_THEME_VARIANTS, @@ -102,7 +101,11 @@ }); } - let isToolbarCollapsed = $state(true); + // PillNav collapsed state (controlled by FAB) + let isPillNavCollapsed = $state(true); + + // FilterStrip visibility (toggle via Filter button in PillNav) + let isFilterStripVisible = $derived(!todoSettings.filterStripCollapsed); // Use theme store's isDark directly let isDark = $derived(theme.isDark); @@ -151,14 +154,27 @@ // User email for user dropdown let userEmail = $derived(authStore.user?.email || 'Menü'); + // Toggle FilterStrip visibility + function handleFilterToggle() { + todoSettings.toggleFilterStrip(); + } + // Base navigation items for Todo - const baseNavItems: PillNavItem[] = [ + // Note: Filter uses onClick to toggle FilterStrip visibility instead of navigating + let baseNavItems = $derived([ { href: '/', label: 'Aufgaben', icon: 'list' }, { href: '/kanban', label: 'Kanban', icon: 'columns' }, + { + href: '/', + label: 'Filter', + icon: 'filter', + onClick: handleFilterToggle, + active: isFilterStripVisible, + }, { href: '/tags', label: 'Tags', icon: 'tag' }, { href: '/settings', label: 'Einstellungen', icon: 'settings' }, { href: '/feedback', label: 'Feedback', icon: 'chat' }, - ]; + ]); // Navigation items filtered by visibility settings (with fallback for guest mode) const navItems = $derived( @@ -166,7 +182,7 @@ ); // Navigation shortcuts (Ctrl+1-6) - use base items for consistent shortcuts - const navRoutes = baseNavItems.map((item) => item.href); + let navRoutes = $derived(baseNavItems.map((item) => item.href)); function handleKeydown(event: KeyboardEvent) { const target = event.target as HTMLElement; @@ -199,11 +215,11 @@ } } - function handleToolbarCollapsedChange(collapsed: boolean) { - isToolbarCollapsed = collapsed; - toolbarCollapsedStore?.set(collapsed); + // Toggle PillNav visibility (called by FAB) + function handlePillNavToggle() { + isPillNavCollapsed = !isPillNavCollapsed; try { - localStorage?.setItem('todo-toolbar-collapsed', String(collapsed)); + localStorage?.setItem('todo-pillnav-collapsed', String(isPillNavCollapsed)); } catch { // localStorage not available or quota exceeded } @@ -248,12 +264,11 @@ goto(userSettings.startPage, { replaceState: true }); } - // Initialize toolbar collapsed state from localStorage + // Initialize PillNav collapsed state from localStorage try { - const savedToolbarCollapsed = localStorage?.getItem('todo-toolbar-collapsed'); - if (savedToolbarCollapsed === 'false') { - isToolbarCollapsed = false; - toolbarCollapsedStore?.set(false); + const savedPillNavCollapsed = localStorage?.getItem('todo-pillnav-collapsed'); + if (savedPillNavCollapsed === 'false') { + isPillNavCollapsed = false; } } catch { // localStorage not available @@ -292,36 +307,44 @@
{#if !todoSettings.immersiveModeEnabled} - + + {#if !isPillNavCollapsed} + + + + {#if isFilterStripVisible} + + {/if} + {/if} - - + + {/if} @@ -430,4 +475,53 @@ padding-bottom: calc(150px + env(safe-area-inset-bottom)); } } + + /* FAB to toggle PillNav */ + .pillnav-fab { + position: fixed; + bottom: calc(16px + env(safe-area-inset-bottom, 0px)); + right: 1rem; + width: 54px; + height: 54px; + border-radius: 50%; + background: rgba(255, 255, 255, 0.9); + backdrop-filter: blur(12px); + -webkit-backdrop-filter: blur(12px); + border: 1px solid rgba(0, 0, 0, 0.1); + box-shadow: + 0 4px 12px rgba(0, 0, 0, 0.15), + 0 2px 4px rgba(0, 0, 0, 0.1); + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + z-index: 50; + transition: all 0.2s ease; + } + + :global(.dark) .pillnav-fab { + background: rgba(30, 30, 30, 0.9); + border-color: rgba(255, 255, 255, 0.15); + } + + .pillnav-fab:hover { + transform: scale(1.05); + box-shadow: + 0 6px 16px rgba(0, 0, 0, 0.2), + 0 3px 6px rgba(0, 0, 0, 0.15); + } + + .pillnav-fab:active { + transform: scale(0.95); + } + + .fab-icon { + width: 24px; + height: 24px; + color: #374151; + } + + :global(.dark) .fab-icon { + color: #f3f4f6; + }