From 893c6ef0fbba534a5684c46f05577e548d5f7d64 Mon Sep 17 00:00:00 2001 From: Till-JS <101404291+Till-JS@users.noreply.github.com> Date: Mon, 15 Dec 2025 03:34:15 +0100 Subject: [PATCH] feat(shared-ui): unify ImmersiveModeToggle across Calendar, Contacts, and Todo apps MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Move ImmersiveModeToggle component from Calendar to shared-ui package - Add immersiveModeEnabled setting to Contacts and Todo settings stores - Update all three app layouts with F-key shortcut and conditional UI rendering - Consistent glass-pill styling on hover for toggle button 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- .../apps/web/src/routes/(app)/+layout.svelte | 14 +- .../web/src/lib/stores/settings.svelte.ts | 20 +++ .../apps/web/src/routes/(app)/+layout.svelte | 148 +++++++++++------- .../web/src/lib/stores/settings.svelte.ts | 20 +++ .../apps/web/src/routes/(app)/+layout.svelte | 145 +++++++++++------ .../components}/ImmersiveModeToggle.svelte | 17 +- packages/shared-ui/src/index.ts | 3 + 7 files changed, 246 insertions(+), 121 deletions(-) rename {apps/calendar/apps/web/src/lib/components/calendar => packages/shared-ui/src/components}/ImmersiveModeToggle.svelte (87%) diff --git a/apps/calendar/apps/web/src/routes/(app)/+layout.svelte b/apps/calendar/apps/web/src/routes/(app)/+layout.svelte index 935a73b98..7d22c42d0 100644 --- a/apps/calendar/apps/web/src/routes/(app)/+layout.svelte +++ b/apps/calendar/apps/web/src/routes/(app)/+layout.svelte @@ -3,7 +3,12 @@ import { page } from '$app/stores'; import { onMount } from 'svelte'; import { locale } from 'svelte-i18n'; - import { PillNavigation, QuickInputBar, InputBarHelpModal } from '@manacore/shared-ui'; + import { + PillNavigation, + QuickInputBar, + InputBarHelpModal, + ImmersiveModeToggle, + } from '@manacore/shared-ui'; import { SplitPaneContainer, setSplitPanelContext, @@ -58,7 +63,6 @@ import TagStrip from '$lib/components/calendar/TagStrip.svelte'; import EventContextMenu from '$lib/components/event/EventContextMenu.svelte'; import ViewModePillContextMenu from '$lib/components/calendar/ViewModePillContextMenu.svelte'; - import ImmersiveModeToggle from '$lib/components/calendar/ImmersiveModeToggle.svelte'; import { eventContextMenuStore } from '$lib/stores/eventContextMenu.svelte'; import type { CalendarViewType } from '@calendar/shared'; @@ -687,7 +691,11 @@ {/if} - + settingsStore.toggleImmersiveMode()} + visible={showCalendarToolbar} + />
- - + {#if !contactsSettings.immersiveModeEnabled} + + + + + contactsFilterStore.setSearchQuery(query)} + placeholder="Neuer Kontakt oder suchen..." + emptyText="Keine Kontakte gefunden" + searchingText="Suche..." + onCreate={handleCreate} + onParseCreate={handleParseCreate} + createText="Erstellen" + appIcon="contacts" + bottomOffset={inputBarBottomOffset} + hasFabRight={showContactsToolbar} + /> + + + {#if showContactsToolbar} + + {/if} + {/if} + + + contactsSettings.toggleImmersiveMode()} /> @@ -346,8 +388,9 @@ class="main-content bg-background" class:sidebar-mode={isSidebarMode && !isCollapsed} class:floating-mode={!isSidebarMode} + class:immersive={contactsSettings.immersiveModeEnabled} > -
+
{@render children()}
@@ -361,27 +404,6 @@ {#if newContactModalStore.isOpen} newContactModalStore.close()} /> {/if} - - - contactsFilterStore.setSearchQuery(query)} - placeholder="Neuer Kontakt oder suchen..." - emptyText="Keine Kontakte gefunden" - searchingText="Suche..." - onCreate={handleCreate} - onParseCreate={handleParseCreate} - createText="Erstellen" - appIcon="contacts" - bottomOffset={inputBarBottomOffset} - hasFabRight={showContactsToolbar} - /> - - - {#if showContactsToolbar} - - {/if} @@ -416,6 +438,18 @@ padding-left: 180px; } + /* Immersive mode - fullscreen, no padding */ + .main-content.immersive { + padding: 0 !important; + height: 100vh; + width: 100vw; + } + + .content-wrapper.immersive { + padding: 0; + height: 100%; + } + .content-wrapper { /* No max-width - let individual views control their own width */ padding: 1rem; 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 d2fe38e1e..da510d5e3 100644 --- a/apps/todo/apps/web/src/lib/stores/settings.svelte.ts +++ b/apps/todo/apps/web/src/lib/stores/settings.svelte.ts @@ -58,6 +58,10 @@ export interface TodoAppSettings { dailyGoal: number | null; /** Show productivity streak */ showStreak: boolean; + + // Immersive Mode + /** Fullscreen mode - hides all UI elements */ + immersiveModeEnabled: boolean; } const DEFAULT_SETTINGS: TodoAppSettings = { @@ -89,6 +93,9 @@ const DEFAULT_SETTINGS: TodoAppSettings = { pomodoroEnabled: false, dailyGoal: null, showStreak: false, + + // Immersive Mode + immersiveModeEnabled: false, }; const STORAGE_KEY = 'todo-settings'; @@ -198,6 +205,19 @@ export const todoSettings = { return settings.showStreak; }, + // Immersive Mode + get immersiveModeEnabled() { + return settings.immersiveModeEnabled; + }, + + /** + * Toggle Immersive Mode (fullscreen, hide all UI) + */ + toggleImmersiveMode() { + settings = { ...settings, immersiveModeEnabled: !settings.immersiveModeEnabled }; + saveSettings(settings); + }, + /** * Initialize settings from localStorage */ diff --git a/apps/todo/apps/web/src/routes/(app)/+layout.svelte b/apps/todo/apps/web/src/routes/(app)/+layout.svelte index b365e8a16..dc9f27600 100644 --- a/apps/todo/apps/web/src/routes/(app)/+layout.svelte +++ b/apps/todo/apps/web/src/routes/(app)/+layout.svelte @@ -3,7 +3,7 @@ import { page } from '$app/stores'; import { onMount } from 'svelte'; import { locale } from 'svelte-i18n'; - import { PillNavigation, QuickInputBar } from '@manacore/shared-ui'; + import { PillNavigation, QuickInputBar, ImmersiveModeToggle } from '@manacore/shared-ui'; import { SplitPaneContainer, setSplitPanelContext, @@ -18,6 +18,7 @@ } from '@manacore/shared-ui'; import { authStore } from '$lib/stores/auth.svelte'; import { userSettings } from '$lib/stores/user-settings.svelte'; + import { todoSettings } from '$lib/stores/settings.svelte'; import { projectsStore } from '$lib/stores/projects.svelte'; import { labelsStore } from '$lib/stores/labels.svelte'; import { tasksStore } from '$lib/stores/tasks.svelte'; @@ -215,6 +216,18 @@ } } } + + // F = Toggle Immersive Mode (no modifier keys) + if ( + (event.key === 'f' || event.key === 'F') && + !event.ctrlKey && + !event.metaKey && + !event.shiftKey && + !event.altKey + ) { + event.preventDefault(); + todoSettings.toggleImmersiveMode(); + } } function handleModeChange(isSidebar: boolean) { @@ -262,6 +275,9 @@ // Initialize split-panel from URL/localStorage splitPanel.initialize(); + // Initialize todo settings + todoSettings.initialize(); + // Load data await Promise.all([ projectsStore.fetchProjects(), @@ -328,66 +344,80 @@
- + {#if !todoSettings.immersiveModeEnabled} + + + + + {/if} + + + todoSettings.toggleImmersiveMode()} />
-
+
{@render children()}
- - -
@@ -414,6 +444,19 @@ padding-left: 180px; } + /* Immersive mode - fullscreen, no padding */ + .main-content.immersive { + padding: 0 !important; + height: 100vh; + width: 100vw; + } + + .content-wrapper.immersive { + padding: 0; + max-width: none; + height: 100%; + } + .content-wrapper { max-width: 900px; margin-left: auto; diff --git a/apps/calendar/apps/web/src/lib/components/calendar/ImmersiveModeToggle.svelte b/packages/shared-ui/src/components/ImmersiveModeToggle.svelte similarity index 87% rename from apps/calendar/apps/web/src/lib/components/calendar/ImmersiveModeToggle.svelte rename to packages/shared-ui/src/components/ImmersiveModeToggle.svelte index 45706f95d..b28864589 100644 --- a/apps/calendar/apps/web/src/lib/components/calendar/ImmersiveModeToggle.svelte +++ b/packages/shared-ui/src/components/ImmersiveModeToggle.svelte @@ -1,26 +1,23 @@ {#if visible}