♻️ refactor(shared-ui): simplify PillNavigation - remove sidebar mode

- Remove isSidebarMode and onModeChange props from PillNavigation
- Remove desktopPosition prop (always bottom now)
- Remove toolbarContent snippet support
- Simplify PillTabGroup (remove sidebar mode)
- Update navigation-simple.ts store factory
- Remove navigation position settings from GlobalSettingsSection
- Update all 12 app layouts to use simplified navigation
- Add missing @sqlite.org/sqlite-wasm dependency to calendar

BREAKING CHANGE: PillNavigation no longer supports sidebar mode.
Navigation is now always horizontal at the bottom of the screen.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Till-JS 2026-02-17 13:27:23 +01:00
parent e5109da732
commit 497b12c561
27 changed files with 77 additions and 1104 deletions

View file

@ -55,6 +55,7 @@
"@manacore/shared-ui": "workspace:*",
"@manacore/shared-utils": "workspace:*",
"@manacore/shared-app-onboarding": "workspace:*",
"@sqlite.org/sqlite-wasm": "^3.49.1-build1",
"@neodrag/svelte": "^2.3.3",
"d3-force": "^3.0.0",
"date-fns": "^4.1.0",

View file

@ -1,6 +1,6 @@
import { createSimpleNavigationStores } from '@manacore/shared-stores';
export const { isSidebarMode, isNavCollapsed, isToolbarCollapsed } = createSimpleNavigationStores({
export const { isNavCollapsed, isToolbarCollapsed } = createSimpleNavigationStores({
withToolbar: true,
toolbarCollapsedDefault: false,
});

View file

@ -520,7 +520,6 @@
homeRoute="/"
onToggleTheme={handleToggleTheme}
{isDark}
desktopPosition="bottom"
showThemeToggle={true}
showThemeVariants={true}
{themeVariantItems}
@ -542,13 +541,7 @@
profileHref="/profile"
allAppsHref="/apps"
onOpenInPanel={handleOpenInPanel}
>
{#snippet toolbarContent()}
{#if showCalendarToolbar}
<CalendarToolbarContent vertical={true} />
{/if}
{/snippet}
</PillNavigation>
/>
<!-- Date strip (only on main calendar page) -->
{#if showCalendarToolbar}

View file

@ -1,3 +1,3 @@
import { createSimpleNavigationStores } from '@manacore/shared-stores';
export const { isSidebarMode, isNavCollapsed } = createSimpleNavigationStores();
export const { isNavCollapsed } = createSimpleNavigationStores();

View file

@ -15,10 +15,7 @@
} from '@manacore/shared-theme';
import type { ThemeVariant } from '@manacore/shared-theme';
import { filterHiddenNavItems } from '@manacore/shared-theme';
import {
isSidebarMode as sidebarModeStore,
isNavCollapsed as collapsedStore,
} from '$lib/stores/navigation';
import { isNavCollapsed as collapsedStore } from '$lib/stores/navigation';
import { PillNavigation } from '@manacore/shared-ui';
import type { PillNavItem, PillDropdownItem } from '@manacore/shared-ui';
import { getPillAppItems } from '@manacore/shared-branding';
@ -32,7 +29,6 @@
let { children, data }: { children: any; data: LayoutData } = $props();
let isChecking = $state(true);
let isSidebarMode = $state(false);
let isCollapsed = $state(false);
// Use theme store's isDark directly
@ -125,14 +121,6 @@
}
}
function handleModeChange(isSidebar: boolean) {
isSidebarMode = isSidebar;
sidebarModeStore.set(isSidebar);
if (typeof localStorage !== 'undefined') {
localStorage.setItem('chat-nav-sidebar', String(isSidebar));
}
}
function handleCollapsedChange(collapsed: boolean) {
isCollapsed = collapsed;
collapsedStore.set(collapsed);
@ -166,13 +154,6 @@
// Initialize theme
theme.initialize();
// Initialize sidebar mode from localStorage
const savedSidebar = localStorage.getItem('chat-nav-sidebar');
if (savedSidebar === 'true') {
isSidebarMode = true;
sidebarModeStore.set(true);
}
// Initialize collapsed state from localStorage
const savedCollapsed = localStorage.getItem('chat-nav-collapsed');
if (savedCollapsed === 'true') {
@ -213,7 +194,7 @@
{:else}
<!-- Navigation Layout -->
<div class="layout-container">
<!-- Floating/Sidebar Pill Navigation -->
<!-- Floating Pill Navigation -->
<PillNavigation
items={navItems}
currentPath={$page.url.pathname}
@ -221,11 +202,8 @@
homeRoute="/chat"
onToggleTheme={handleToggleTheme}
{isDark}
{isSidebarMode}
onModeChange={handleModeChange}
{isCollapsed}
onCollapsedChange={handleCollapsedChange}
desktopPosition={userSettings.nav?.desktopPosition || 'bottom'}
showThemeToggle={true}
showThemeVariants={true}
{themeVariantItems}
@ -247,13 +225,8 @@
allAppsHref="/apps"
/>
<!-- Main Content with dynamic padding based on nav mode -->
<main
class="main-content bg-background"
class:sidebar-mode={isSidebarMode && !isCollapsed}
class:floating-mode={!isSidebarMode && !isCollapsed}
class:chat-page={isChatPage}
>
<!-- Main Content -->
<main class="main-content bg-background" class:chat-page={isChatPage}>
{#if isChatPage}
<!-- Full-width layout for chat pages -->
{@render children()}
@ -275,17 +248,7 @@
.main-content {
flex: 1;
transition: all 300ms ease;
}
/* Floating nav mode - add top padding for fixed nav */
.main-content.floating-mode {
padding-top: 100px;
}
/* Sidebar mode - add left padding for sidebar nav */
.main-content.sidebar-mode {
padding-left: 180px;
padding-bottom: 100px;
}
/* Chat page - no content wrapper, but keep nav padding */

View file

@ -1,5 +1,5 @@
import { createSimpleNavigationStores } from '@manacore/shared-stores';
export const { isSidebarMode, isNavCollapsed } = createSimpleNavigationStores({
export const { isNavCollapsed } = createSimpleNavigationStores({
storageKey: 'clock',
});

View file

@ -24,10 +24,7 @@
} from '@manacore/shared-theme';
import type { ThemeVariant } from '@manacore/shared-theme';
import { filterHiddenNavItems } from '@manacore/shared-theme';
import {
isSidebarMode as sidebarModeStore,
isNavCollapsed as collapsedStore,
} from '$lib/stores/navigation';
import { isNavCollapsed as collapsedStore } from '$lib/stores/navigation';
import { getLanguageDropdownItems, getCurrentLanguageLabel } from '@manacore/shared-i18n';
import { getPillAppItems } from '@manacore/shared-branding';
import { setLocale, supportedLocales } from '$lib/i18n';
@ -114,7 +111,6 @@
}
}
let isSidebarMode = $state(false);
let isCollapsed = $state(false);
// Use theme store's isDark directly
@ -213,14 +209,6 @@
}
}
function handleModeChange(isSidebar: boolean) {
isSidebarMode = isSidebar;
sidebarModeStore.set(isSidebar);
if (typeof localStorage !== 'undefined') {
localStorage.setItem('clock-nav-sidebar', String(isSidebar));
}
}
function handleCollapsedChange(collapsed: boolean) {
isCollapsed = collapsed;
collapsedStore.set(collapsed);
@ -250,13 +238,6 @@
return;
}
// Initialize sidebar mode from localStorage
const savedSidebar = localStorage.getItem('clock-nav-sidebar');
if (savedSidebar === 'true') {
isSidebarMode = true;
sidebarModeStore.set(true);
}
// Initialize collapsed state from localStorage
const savedCollapsed = localStorage.getItem('clock-nav-collapsed');
if (savedCollapsed === 'true') {
@ -291,11 +272,8 @@
currentPath={$page.url.pathname}
appName="Clock"
homeRoute="/"
desktopPosition={userSettings.nav?.desktopPosition || 'bottom'}
onToggleTheme={handleToggleTheme}
{isDark}
{isSidebarMode}
onModeChange={handleModeChange}
{isCollapsed}
onCollapsedChange={handleCollapsedChange}
showThemeToggle={true}
@ -320,11 +298,7 @@
allAppsHref="/apps"
/>
<main
class="main-content bg-background"
class:sidebar-mode={isSidebarMode && !isCollapsed}
class:floating-mode={!isSidebarMode && !isCollapsed}
>
<main class="main-content bg-background">
<div class="content-wrapper">
{@render children()}
</div>
@ -351,17 +325,9 @@
}
.main-content {
transition: all 300ms ease;
position: relative;
z-index: 0;
}
.main-content.floating-mode {
padding-top: 70px;
}
.main-content.sidebar-mode {
padding-left: 180px;
padding-bottom: 100px;
}
.content-wrapper {

View file

@ -1,3 +1,3 @@
import { createSimpleNavigationStores } from '@manacore/shared-stores';
export const { isSidebarMode, isNavCollapsed } = createSimpleNavigationStores();
export const { isNavCollapsed } = createSimpleNavigationStores();

View file

@ -17,10 +17,7 @@
import { theme } from '$lib/stores/theme';
import { authStore } from '$lib/stores/auth.svelte';
import { userSettings } from '$lib/stores/user-settings.svelte';
import {
isSidebarMode as sidebarModeStore,
isNavCollapsed as collapsedStore,
} from '$lib/stores/navigation';
import { isNavCollapsed as collapsedStore } from '$lib/stores/navigation';
import { getPillAppItems } from '@manacore/shared-branding';
import { onboardingStore } from '$lib/stores/onboarding.svelte';
import { OnboardingWizard } from '$lib/components/onboarding';
@ -31,7 +28,6 @@
const appItems = getPillAppItems('manacore');
let loading = $state(true);
let isSidebarMode = $state(false);
let isCollapsed = $state(false);
// Get theme state
@ -121,14 +117,6 @@
}
}
function handleModeChange(isSidebar: boolean) {
isSidebarMode = isSidebar;
sidebarModeStore.set(isSidebar);
if (typeof localStorage !== 'undefined') {
localStorage.setItem('manacore-nav-sidebar', String(isSidebar));
}
}
function handleCollapsedChange(collapsed: boolean) {
isCollapsed = collapsed;
collapsedStore.set(collapsed);
@ -177,13 +165,6 @@
return;
}
// Initialize sidebar mode from localStorage
const savedSidebar = localStorage.getItem('manacore-nav-sidebar');
if (savedSidebar === 'true') {
isSidebarMode = true;
sidebarModeStore.set(true);
}
// Initialize collapsed state from localStorage
const savedCollapsed = localStorage.getItem('manacore-nav-collapsed');
if (savedCollapsed === 'true') {
@ -239,11 +220,8 @@
onLogout={handleSignOut}
onToggleTheme={handleToggleTheme}
{isDark}
{isSidebarMode}
onModeChange={handleModeChange}
{isCollapsed}
onCollapsedChange={handleCollapsedChange}
desktopPosition={userSettings.nav?.desktopPosition || 'bottom'}
showThemeToggle={true}
showThemeVariants={true}
{themeVariantItems}
@ -264,14 +242,8 @@
allAppsHref="/apps"
/>
<!-- Main content with dynamic padding -->
<main
class="transition-all duration-300 {isCollapsed
? ''
: isSidebarMode
? 'pl-[180px]'
: 'pt-20'}"
>
<!-- Main content -->
<main class="pb-24">
<div class="mx-auto max-w-7xl px-4 py-8 sm:px-6 lg:px-8">
{@render children()}
</div>

View file

@ -1,3 +1,3 @@
import { createSimpleNavigationStores } from '@manacore/shared-stores';
export const { isSidebarMode, isNavCollapsed } = createSimpleNavigationStores();
export const { isNavCollapsed } = createSimpleNavigationStores();

View file

@ -6,10 +6,7 @@
import { authStore } from '$lib/stores/auth.svelte';
import { userSettings } from '$lib/stores/user-settings.svelte';
import { theme } from '$lib/stores/theme';
import {
isSidebarMode as sidebarModeStore,
isNavCollapsed as collapsedStore,
} from '$lib/stores/navigation';
import { isNavCollapsed as collapsedStore } from '$lib/stores/navigation';
import { PillNavigation } from '@manacore/shared-ui';
import type { PillNavItem, PillDropdownItem } from '@manacore/shared-ui';
import {
@ -28,7 +25,6 @@
let { children } = $props();
let isSidebarMode = $state(false);
let isCollapsed = $state(false);
// Get theme state
@ -120,14 +116,6 @@
}
}
function handleModeChange(isSidebar: boolean) {
isSidebarMode = isSidebar;
sidebarModeStore.set(isSidebar);
if (typeof localStorage !== 'undefined') {
localStorage.setItem('manadeck-nav-sidebar', String(isSidebar));
}
}
function handleCollapsedChange(collapsed: boolean) {
isCollapsed = collapsed;
collapsedStore.set(collapsed);
@ -166,13 +154,6 @@
goto(userSettings.startPage, { replaceState: true });
}
// Initialize sidebar mode from localStorage
const savedSidebar = localStorage.getItem('manadeck-nav-sidebar');
if (savedSidebar === 'true') {
isSidebarMode = true;
sidebarModeStore.set(true);
}
// Initialize collapsed state from localStorage
const savedCollapsed = localStorage.getItem('manadeck-nav-collapsed');
if (savedCollapsed === 'true') {
@ -204,11 +185,8 @@
onLogout={handleSignOut}
onToggleTheme={handleToggleTheme}
{isDark}
{isSidebarMode}
onModeChange={handleModeChange}
{isCollapsed}
onCollapsedChange={handleCollapsedChange}
desktopPosition={userSettings.nav?.desktopPosition || 'bottom'}
showThemeToggle={true}
showThemeVariants={true}
{themeVariantItems}
@ -229,14 +207,8 @@
allAppsHref="/apps"
/>
<!-- Main content with dynamic padding -->
<main
class="transition-all duration-300 {isCollapsed
? ''
: isSidebarMode
? 'pl-[180px]'
: 'pt-20'}"
>
<!-- Main content -->
<main class="pb-24">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
{@render children()}
</div>

View file

@ -3,32 +3,9 @@
import { browser } from '$app/environment';
// Check if on mobile/tablet width
function checkSidebarMode(): boolean {
if (!browser) return false;
return window.innerWidth < 1024;
}
// Create reactive stores using Svelte 5 runes
let _isSidebarMode = $state(checkSidebarMode());
let _isNavCollapsed = $state(false);
// Listen for resize events
if (browser) {
window.addEventListener('resize', () => {
_isSidebarMode = checkSidebarMode();
});
}
export const isSidebarMode = {
get value() {
return _isSidebarMode;
},
set(value: boolean) {
_isSidebarMode = value;
},
};
export const isNavCollapsed = {
get value() {
return _isNavCollapsed;

View file

@ -19,10 +19,7 @@
EXTENDED_THEME_VARIANTS,
} from '@manacore/shared-theme';
import type { ThemeVariant } from '@manacore/shared-theme';
import {
isSidebarMode as sidebarModeStore,
isNavCollapsed as collapsedStore,
} from '$lib/stores/navigation.svelte';
import { isNavCollapsed as collapsedStore } from '$lib/stores/navigation.svelte';
import { PillNavigation } from '@manacore/shared-ui';
import type { PillNavItem, PillDropdownItem } from '@manacore/shared-ui';
import { getPillAppItems } from '@manacore/shared-branding';
@ -86,7 +83,6 @@
let initError = $state<string | null>(null);
// Navigation state
let isSidebarMode = $state(false);
let isCollapsed = $state(false);
// Theme state
@ -145,14 +141,6 @@
}
}
function handleModeChange(isSidebar: boolean) {
isSidebarMode = isSidebar;
sidebarModeStore.set(isSidebar);
if (typeof localStorage !== 'undefined') {
localStorage.setItem('matrix-nav-sidebar', String(isSidebar));
}
}
function handleCollapsedChange(collapsed: boolean) {
isCollapsed = collapsed;
collapsedStore.set(collapsed);
@ -176,13 +164,6 @@
}
onMount(async () => {
// Initialize sidebar mode from localStorage
const savedSidebar = localStorage.getItem('matrix-nav-sidebar');
if (savedSidebar === 'true') {
isSidebarMode = true;
sidebarModeStore.set(true);
}
// Initialize collapsed state from localStorage
const savedCollapsed = localStorage.getItem('matrix-nav-collapsed');
if (savedCollapsed === 'true') {
@ -333,11 +314,8 @@
homeRoute="/chat"
onToggleTheme={handleToggleTheme}
{isDark}
{isSidebarMode}
onModeChange={handleModeChange}
{isCollapsed}
onCollapsedChange={handleCollapsedChange}
desktopPosition="bottom"
showThemeToggle={true}
showThemeVariants={true}
{themeVariantItems}
@ -358,11 +336,7 @@
/>
<!-- Main Content -->
<main
class="main-content bg-background"
class:sidebar-mode={isSidebarMode && !isCollapsed}
class:floating-mode={!isSidebarMode && !isCollapsed}
>
<main class="main-content bg-background">
{@render children()}
</main>
</div>
@ -385,11 +359,6 @@
flex: 1;
min-height: 0;
overflow: hidden;
transition: all 300ms ease;
}
/* Sidebar mode - add left padding for sidebar nav */
.main-content.sidebar-mode {
padding-left: 180px;
padding-bottom: 80px;
}
</style>

View file

@ -29,24 +29,16 @@
let { children } = $props();
// PillNav state
let isSidebarMode = $state(false);
let isCollapsed = $state(false);
// Load persisted nav state
$effect(() => {
if (browser) {
const savedSidebarMode = localStorage.getItem('picture-nav-sidebar');
const savedCollapsed = localStorage.getItem('picture-nav-collapsed');
if (savedSidebarMode !== null) isSidebarMode = savedSidebarMode === 'true';
if (savedCollapsed !== null) isCollapsed = savedCollapsed === 'true';
}
});
function handleModeChange(isSidebar: boolean) {
isSidebarMode = isSidebar;
if (browser) localStorage.setItem('picture-nav-sidebar', String(isSidebar));
}
function handleCollapsedChange(collapsed: boolean) {
isCollapsed = collapsed;
if (browser) localStorage.setItem('picture-nav-collapsed', String(collapsed));
@ -263,11 +255,8 @@
onLogout={handleLogout}
onToggleTheme={handleToggleTheme}
isDark={theme.isDark}
{isSidebarMode}
onModeChange={handleModeChange}
{isCollapsed}
onCollapsedChange={handleCollapsedChange}
desktopPosition={userSettings.nav?.desktopPosition || 'bottom'}
showThemeToggle={true}
showThemeVariants={true}
{themeVariantItems}
@ -290,11 +279,7 @@
{/if}
<!-- Main Content Area -->
<main
class="main-content transition-all duration-300"
class:sidebar-mode={isSidebarMode && !isCollapsed && $isUIVisible}
class:floating-mode={!isSidebarMode && !isCollapsed && $isUIVisible}
>
<main class="main-content pb-24">
<div class="min-h-screen">
{@render children?.()}
</div>
@ -306,24 +291,7 @@
{/if}
<style>
/* Floating nav mode - add top padding for fixed nav */
.main-content.floating-mode {
padding-top: 80px;
}
/* Sidebar mode - add left padding for sidebar nav */
.main-content.sidebar-mode {
padding-left: 180px;
}
/* Mobile adjustments */
@media (max-width: 768px) {
.main-content.floating-mode {
padding-top: 70px;
}
.main-content.sidebar-mode {
padding-left: 0;
padding-top: 70px;
}
.main-content {
padding-bottom: 100px;
}
</style>

View file

@ -1,3 +1,3 @@
import { createSimpleNavigationStores } from '@manacore/shared-stores';
export const { isSidebarMode, isNavCollapsed } = createSimpleNavigationStores();
export const { isNavCollapsed } = createSimpleNavigationStores();

View file

@ -9,10 +9,7 @@
import { userSettings } from '$lib/stores/user-settings.svelte';
import { theme } from '$lib/stores/theme';
import { THEME_DEFINITIONS } from '@manacore/shared-theme';
import {
isSidebarMode as sidebarModeStore,
isNavCollapsed as collapsedStore,
} from '$lib/stores/navigation';
import { isNavCollapsed as collapsedStore } from '$lib/stores/navigation';
import { getLanguageDropdownItems, getCurrentLanguageLabel } from '@manacore/shared-i18n';
import { getPillAppItems } from '@manacore/shared-branding';
import { setLocale, supportedLocales } from '$lib/i18n';
@ -22,7 +19,6 @@
let { children } = $props();
let isSidebarMode = $state(false);
let isCollapsed = $state(false);
// Theme variant dropdown items
@ -72,14 +68,6 @@
return hideNavRoutes.some((route) => pathname.startsWith(route));
}
function handleModeChange(isSidebar: boolean) {
isSidebarMode = isSidebar;
sidebarModeStore.set(isSidebar);
if (typeof localStorage !== 'undefined') {
localStorage.setItem('presi-nav-sidebar', String(isSidebar));
}
}
function handleCollapsedChange(collapsed: boolean) {
isCollapsed = collapsed;
collapsedStore.set(collapsed);
@ -132,13 +120,6 @@
goto(userSettings.startPage, { replaceState: true });
}
// Initialize sidebar mode from localStorage
const savedSidebar = localStorage.getItem('presi-nav-sidebar');
if (savedSidebar === 'true') {
isSidebarMode = true;
sidebarModeStore.set(true);
}
// Initialize collapsed state from localStorage
const savedCollapsed = localStorage.getItem('presi-nav-collapsed');
if (savedCollapsed === 'true') {
@ -158,7 +139,7 @@
{:else}
<!-- Navigation Layout -->
<div class="layout-container">
<!-- Floating/Sidebar Pill Navigation -->
<!-- Floating Pill Navigation -->
<PillNavigation
items={navItems}
currentPath={$page.url.pathname}
@ -166,11 +147,8 @@
homeRoute="/"
onToggleTheme={handleToggleTheme}
isDark={theme.isDark}
{isSidebarMode}
onModeChange={handleModeChange}
{isCollapsed}
onCollapsedChange={handleCollapsedChange}
desktopPosition={userSettings.nav.desktopPosition}
showThemeToggle={true}
showThemeVariants={true}
{themeVariantItems}
@ -192,12 +170,8 @@
allAppsHref="/apps"
/>
<!-- Main Content with dynamic padding based on nav mode -->
<main
class="main-content"
class:sidebar-mode={isSidebarMode && !isCollapsed}
class:floating-mode={!isSidebarMode && !isCollapsed}
>
<!-- Main Content -->
<main class="main-content">
<div class="content-wrapper">
{@render children()}
</div>
@ -214,17 +188,7 @@
.main-content {
flex: 1;
transition: all 300ms ease;
}
/* Floating nav mode - add top padding for fixed nav */
.main-content.floating-mode {
padding-top: 100px;
}
/* Sidebar mode - add left padding for sidebar nav */
.main-content.sidebar-mode {
padding-left: 180px;
padding-bottom: 100px;
}
.content-wrapper {

View file

@ -31,7 +31,6 @@
}
// Navigation mode state
let isSidebarMode = $state(false);
let isCollapsed = $state(false);
// Theme state
@ -61,11 +60,6 @@
// Restore nav mode from localStorage
if (browser) {
const savedSidebar = localStorage.getItem('questions-nav-sidebar');
if (savedSidebar === 'true') {
isSidebarMode = true;
}
const savedCollapsed = localStorage.getItem('questions-nav-collapsed');
if (savedCollapsed === 'true') {
isCollapsed = true;
@ -83,13 +77,6 @@
theme.toggle();
}
function handleModeChange(isSidebar: boolean) {
isSidebarMode = isSidebar;
if (typeof localStorage !== 'undefined') {
localStorage.setItem('questions-nav-sidebar', String(isSidebar));
}
}
function handleCollapsedChange(collapsed: boolean) {
isCollapsed = collapsed;
if (typeof localStorage !== 'undefined') {
@ -205,11 +192,8 @@
homeRoute="/"
onToggleTheme={handleToggleTheme}
{isDark}
{isSidebarMode}
onModeChange={handleModeChange}
{isCollapsed}
onCollapsedChange={handleCollapsedChange}
desktopPosition="bottom"
showThemeToggle={true}
showLogout={true}
onLogout={handleSignOut}
@ -232,11 +216,11 @@
onParseCreate={handleParseCreate}
createText="Create"
appIcon="help-circle"
bottomOffset={isMobile ? '70px' : isSidebarMode ? '0px' : '70px'}
bottomOffset={isMobile ? '70px' : '70px'}
/>
<!-- Main Content -->
<main class="main-content bg-background" class:sidebar-mode={isSidebarMode && !isCollapsed}>
<main class="main-content bg-background">
<div class="content-wrapper">
{@render children()}
</div>
@ -257,12 +241,6 @@
flex-direction: column;
min-height: 0;
padding-bottom: calc(80px + env(safe-area-inset-bottom));
transition: all 300ms ease;
}
.main-content.sidebar-mode {
padding-left: 180px;
padding-bottom: 0;
}
.content-wrapper {

View file

@ -1,3 +1,3 @@
import { createSimpleNavigationStores } from '@manacore/shared-stores';
export const { isSidebarMode, isNavCollapsed } = createSimpleNavigationStores();
export const { isNavCollapsed } = createSimpleNavigationStores();

View file

@ -9,10 +9,7 @@
import { authStore } from '$lib/stores/auth.svelte';
import { userSettings } from '$lib/stores/user-settings.svelte';
import { THEME_DEFINITIONS } from '@manacore/shared-theme';
import {
isSidebarMode as sidebarModeStore,
isNavCollapsed as collapsedStore,
} from '$lib/stores/navigation';
import { isNavCollapsed as collapsedStore } from '$lib/stores/navigation';
import { getLanguageDropdownItems, getCurrentLanguageLabel } from '@manacore/shared-i18n';
import { getPillAppItems } from '@manacore/shared-branding';
import { setLocale, supportedLocales } from '$lib/i18n';
@ -25,7 +22,6 @@
let { children } = $props();
let loading = $state(true);
let isSidebarMode = $state(false);
let isCollapsed = $state(false);
// Use theme store's isDark directly
@ -111,14 +107,6 @@
}
}
function handleModeChange(isSidebar: boolean) {
isSidebarMode = isSidebar;
sidebarModeStore.set(isSidebar);
if (typeof localStorage !== 'undefined') {
localStorage.setItem('storage-nav-sidebar', String(isSidebar));
}
}
function handleCollapsedChange(collapsed: boolean) {
isCollapsed = collapsed;
collapsedStore.set(collapsed);
@ -155,13 +143,6 @@
// Load user settings
await userSettings.load();
// Initialize sidebar mode from localStorage
const savedSidebar = localStorage.getItem('storage-nav-sidebar');
if (savedSidebar === 'true') {
isSidebarMode = true;
sidebarModeStore.set(true);
}
// Initialize collapsed state from localStorage
const savedCollapsed = localStorage.getItem('storage-nav-collapsed');
if (savedCollapsed === 'true') {
@ -204,11 +185,8 @@
homeRoute="/files"
onToggleTheme={handleToggleTheme}
{isDark}
{isSidebarMode}
onModeChange={handleModeChange}
{isCollapsed}
onCollapsedChange={handleCollapsedChange}
desktopPosition={userSettings.nav.desktopPosition}
showThemeToggle={true}
showThemeVariants={true}
{themeVariantItems}
@ -231,11 +209,7 @@
allAppsHref="/apps"
/>
<main
class="main-content bg-background"
class:sidebar-mode={isSidebarMode && !isCollapsed}
class:floating-mode={!isSidebarMode && !isCollapsed}
>
<main class="main-content bg-background">
<div class="content-wrapper">
{@render children()}
</div>
@ -252,15 +226,7 @@
.main-content {
flex: 1;
transition: all 300ms ease;
}
.main-content.floating-mode {
padding-top: 100px;
}
.main-content.sidebar-mode {
padding-left: 180px;
padding-bottom: 100px;
}
.content-wrapper {

View file

@ -1,6 +1,6 @@
import { createSimpleNavigationStores } from '@manacore/shared-stores';
export const { isSidebarMode, isNavCollapsed, isToolbarCollapsed } = createSimpleNavigationStores({
export const { isNavCollapsed, isToolbarCollapsed } = createSimpleNavigationStores({
withToolbar: true,
toolbarCollapsedDefault: true,
});

View file

@ -317,7 +317,6 @@
homeRoute="/"
onToggleTheme={handleToggleTheme}
{isDark}
desktopPosition={userSettings.nav?.desktopPosition || 'bottom'}
showThemeToggle={true}
showThemeVariants={true}
{themeVariantItems}

View file

@ -238,7 +238,6 @@
currentPath={$page.url.pathname}
appName="Zitare"
homeRoute="/"
desktopPosition="bottom"
onToggleTheme={handleToggleTheme}
{isDark}
showThemeToggle={true}

View file

@ -6,8 +6,6 @@
import { writable, type Writable } from 'svelte/store';
export interface SimpleNavigationStores {
/** Whether the app is in sidebar mode (desktop) */
isSidebarMode: Writable<boolean>;
/** Whether the nav is collapsed */
isNavCollapsed: Writable<boolean>;
/** Whether the toolbar is collapsed (optional) */
@ -15,7 +13,7 @@ export interface SimpleNavigationStores {
}
export interface SimpleNavigationOptions {
/** App name for localStorage keys (e.g., 'clock' -> 'clock_sidebar_mode') */
/** App name for localStorage keys (e.g., 'clock' -> 'clock_nav_collapsed') */
storageKey?: string;
/** Include isToolbarCollapsed store */
withToolbar?: boolean;
@ -28,17 +26,17 @@ export interface SimpleNavigationOptions {
*
* @example
* // Basic usage (no persistence)
* export const { isSidebarMode, isNavCollapsed } = createSimpleNavigationStores();
* export const { isNavCollapsed } = createSimpleNavigationStores();
*
* @example
* // With persistence
* export const { isSidebarMode, isNavCollapsed } = createSimpleNavigationStores({
* export const { isNavCollapsed } = createSimpleNavigationStores({
* storageKey: 'clock',
* });
*
* @example
* // With toolbar
* export const { isSidebarMode, isNavCollapsed, isToolbarCollapsed } = createSimpleNavigationStores({
* export const { isNavCollapsed, isToolbarCollapsed } = createSimpleNavigationStores({
* withToolbar: true,
* toolbarCollapsedDefault: true,
* });
@ -71,16 +69,11 @@ export function createSimpleNavigationStores(
}
// Create stores (persisted if storageKey provided, otherwise simple)
const isSidebarMode = storageKey
? createPersistedWritable('sidebar_mode', false)
: writable(false);
const isNavCollapsed = storageKey
? createPersistedWritable('nav_collapsed', false)
: writable(false);
const result: SimpleNavigationStores = {
isSidebarMode,
isNavCollapsed,
};

View file

@ -194,10 +194,6 @@
onToggleTheme?: () => void;
/** Whether dark mode is active */
isDark?: boolean;
/** Whether sidebar mode is enabled */
isSidebarMode?: boolean;
/** Called when sidebar mode changes */
onModeChange?: (isSidebar: boolean) => void;
/** Whether navigation is collapsed */
isCollapsed?: boolean;
/** Called when collapsed state changes */
@ -257,12 +253,8 @@
onA11yReduceMotionChange?: (reduce: boolean) => void;
/** Show a11y quick toggles in theme dropdown */
showA11yQuickToggles?: boolean;
/** Desktop navigation position (mobile always at bottom) */
desktopPosition?: 'top' | 'bottom';
/** Called when an app should be opened in a split panel */
onOpenInPanel?: (appId: string, url: string) => void;
/** Toolbar content snippet (shown in sidebar mode) */
toolbarContent?: Snippet;
}
let {
@ -274,8 +266,6 @@
onLogout,
onToggleTheme,
isDark = false,
isSidebarMode: externalSidebarMode,
onModeChange,
isCollapsed: externalCollapsed,
onCollapsedChange,
languageItems = [],
@ -305,9 +295,7 @@
a11yReduceMotion = false,
onA11yReduceMotionChange,
showA11yQuickToggles = false,
desktopPosition = 'top',
onOpenInPanel,
toolbarContent,
}: Props = $props();
// Type guards for elements
@ -338,48 +326,15 @@
}
// Local state for uncontrolled mode
let internalSidebarMode = $state(false);
let internalCollapsed = $state(false);
// Use external or internal state
const isSidebarMode = $derived(
onModeChange !== undefined ? (externalSidebarMode ?? false) : internalSidebarMode
);
const isCollapsed = $derived(
onCollapsedChange !== undefined ? (externalCollapsed ?? false) : internalCollapsed
);
// Mobile detection for dropdown direction
let isMobile = $state(false);
$effect(() => {
if (typeof window !== 'undefined') {
const checkMobile = () => {
isMobile = window.innerWidth <= 768;
};
checkMobile();
window.addEventListener('resize', checkMobile);
return () => window.removeEventListener('resize', checkMobile);
}
});
// Dropdown direction: up when nav is at bottom (mobile or desktop-bottom), down otherwise
const dropdownDirection = $derived<'up' | 'down'>(
// Mobile: always up (nav at bottom) unless in sidebar mode
(isMobile && !isSidebarMode) ||
// Desktop with bottom position: up unless in sidebar mode
(!isMobile && desktopPosition === 'bottom' && !isSidebarMode)
? 'up'
: 'down'
);
function toggleSidebarMode() {
const newValue = !isSidebarMode;
if (onModeChange) {
onModeChange(newValue);
} else {
internalSidebarMode = newValue;
}
}
// Dropdown direction: always up since nav is always at bottom
const dropdownDirection = 'up' as const;
function collapseNav() {
if (onCollapsedChange) {
@ -463,12 +418,7 @@
chevronLeft: 'M15 19l-7-7 7-7',
chevronRight: 'M9 5l7 7-7 7',
menu: 'M4 6h16M4 12h16M4 18h16',
// Layout icons
sidebar: 'M3 3h7v18H3V3zm9 0h9v18h-9V3z', // Sidebar layout icon
layoutBottom: 'M3 3h18v9H3V3zm0 12h18v6H3v-6z', // Bottom bar layout icon
panelRight: 'M9 3h12v18H9V3zM3 3h3v18H3V3z', // Panel right icon
minimize: 'M4 12h16', // Minimize (minus) icon
maximize: 'M4 8h16M4 16h16', // Two lines for expand
fire: 'M17.657 18.657A8 8 0 016.343 7.343S7 9 9 10c0-2 .5-5 2.986-7C14 5 16.09 5.777 17.656 7.343A7.975 7.975 0 0120 13a7.975 7.975 0 01-2.343 5.657z',
grid: 'M4 5a1 1 0 011-1h4a1 1 0 011 1v4a1 1 0 01-1 1H5a1 1 0 01-1-1V5zM14 5a1 1 0 011-1h4a1 1 0 011 1v4a1 1 0 01-1 1h-4a1 1 0 01-1-1V5zM4 15a1 1 0 011-1h4a1 1 0 011 1v4a1 1 0 01-1 1H5a1 1 0 01-1-1v-4zM14 15a1 1 0 011-1h4a1 1 0 011 1v4a1 1 0 01-1 1h-4a1 1 0 01-1-1v-4z',
gridSmall:
@ -493,13 +443,8 @@
</script>
{#if !isCollapsed}
<nav
class="pill-nav"
class:sidebar-mode={isSidebarMode}
class:desktop-bottom={desktopPosition === 'bottom'}
style={primaryColor ? `--pill-primary-color: ${primaryColor}` : ''}
>
<div class="pill-nav-container" class:sidebar-container={isSidebarMode}>
<nav class="pill-nav" style={primaryColor ? `--pill-primary-color: ${primaryColor}` : ''}>
<div class="pill-nav-container">
<!-- Logo pill / App Switcher -->
{#if showAppSwitcher && appItems.length > 0}
<PillDropdown
@ -507,7 +452,7 @@
direction={dropdownDirection}
label={appName}
icon="grid"
iconOnly={!isSidebarMode}
iconOnly={false}
/>
{:else}
<a href={homeRoute} class="pill glass-pill logo-pill">
@ -528,11 +473,10 @@
onChange={element.onChange}
sectionLabel={element.sectionLabel}
onContextMenu={element.onContextMenu}
{isSidebarMode}
{primaryColor}
/>
{:else if isDivider(element)}
<div class="pill-divider" class:sidebar-divider={isSidebarMode}></div>
<div class="pill-divider"></div>
{:else if isTagSelector(element)}
<PillTagSelector
tags={element.tags}
@ -634,11 +578,10 @@
onChange={element.onChange}
sectionLabel={element.sectionLabel}
onContextMenu={element.onContextMenu}
{isSidebarMode}
{primaryColor}
/>
{:else if isDivider(element)}
<div class="pill-divider" class:sidebar-divider={isSidebarMode}></div>
<div class="pill-divider"></div>
{:else if isTagSelector(element)}
<PillTagSelector
tags={element.tags}
@ -916,83 +859,24 @@
</button>
{/if}
<!-- Control Button (right position in horizontal mode, bottom in sidebar mode) -->
{#if !isSidebarMode}
<div class="pill glass-pill segmented-control">
<button onclick={collapseNav} class="segment-btn" title="Navigation minimieren">
<svg class="pill-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d={getIconPath('chevronRight')}
/>
</svg>
</button>
<div class="segment-divider"></div>
<button onclick={toggleSidebarMode} class="segment-btn" title="Zur Sidebar wechseln">
<svg class="pill-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d={getIconPath('sidebar')}
/>
</svg>
</button>
</div>
{/if}
<!-- Control Button (bottom position in sidebar mode) -->
{#if isSidebarMode}
<!-- Toolbar content (if provided) -->
{#if toolbarContent}
<div class="pill-divider sidebar-divider"></div>
<div class="sidebar-toolbar-content">
{@render toolbarContent()}
</div>
{/if}
<div class="sidebar-spacer"></div>
<div class="pill glass-pill segmented-control sidebar-segmented">
<button
onclick={toggleSidebarMode}
class="segment-btn"
title="Zur Bottom-Navigation wechseln"
>
<svg class="pill-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d={getIconPath('layoutBottom')}
/>
</svg>
</button>
<div class="segment-divider"></div>
<button onclick={collapseNav} class="segment-btn" title="Sidebar minimieren">
<svg class="pill-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d={getIconPath('chevronRight')}
/>
</svg>
</button>
</div>
{/if}
<!-- Collapse Button -->
<button onclick={collapseNav} class="pill glass-pill" title="Navigation minimieren">
<svg class="pill-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d={getIconPath('chevronRight')}
/>
</svg>
</button>
</div>
</nav>
{/if}
<!-- FAB for collapsed state -->
{#if isCollapsed}
<button
onclick={expandNav}
class="nav-fab glass-pill"
class:desktop-bottom={desktopPosition === 'bottom'}
title="Expand navigation"
>
<button onclick={expandNav} class="nav-fab glass-pill" title="Expand navigation">
<svg class="pill-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path
stroke-linecap="round"
@ -1007,35 +891,17 @@
<style>
.pill-nav {
position: fixed;
top: 0;
bottom: 0;
left: 0;
right: 0;
z-index: 1000;
padding: 0.75rem 0 1.5rem;
padding: 1rem 0 calc(env(safe-area-inset-bottom, 0px) + 0.75rem);
pointer-events: none;
/* Container query context */
container-type: inline-size;
container-name: pillnav;
}
/* Desktop bottom position */
@media (min-width: 769px) {
.pill-nav.desktop-bottom:not(.sidebar-mode) {
top: auto;
bottom: 0;
padding: 1rem 0 0.75rem;
}
}
/* Mobile: always position at bottom */
@media (max-width: 768px) {
.pill-nav:not(.sidebar-mode) {
top: auto;
bottom: 0;
padding: 1rem 0 calc(env(safe-area-inset-bottom, 0px) + 0.75rem);
}
}
.pill-nav-container {
display: flex;
align-items: center;
@ -1052,15 +918,7 @@
/* Center when container has enough space (> 600px) */
@container pillnav (min-width: 600px) {
.pill-nav-container:not(.sidebar-container) {
margin-left: auto;
margin-right: auto;
}
}
/* Larger screens: always centered */
@container pillnav (min-width: 900px) {
.pill-nav-container:not(.sidebar-container) {
.pill-nav-container {
margin-left: auto;
margin-right: auto;
}
@ -1153,12 +1011,6 @@
background: rgba(255, 255, 255, 0.2);
}
.sidebar-divider {
width: 100%;
height: 1px;
margin: 0.5rem 0;
}
/* Logout pill */
.logout-pill {
color: #dc2626;
@ -1183,431 +1035,22 @@
display: inline;
}
/* Sidebar mode styles */
.pill-nav.sidebar-mode {
top: 0;
left: 0;
bottom: 0;
right: auto;
width: 180px;
padding: 0.75rem 0;
background: transparent;
backdrop-filter: none;
-webkit-backdrop-filter: none;
border: none;
}
:global(.dark) .pill-nav.sidebar-mode {
background: transparent;
border: none;
}
/* Mobile: Sidebar as bottom sheet */
@media (max-width: 768px) {
.pill-nav.sidebar-mode {
top: auto;
left: 0;
right: 0;
bottom: 0;
width: 100%;
max-height: 70vh;
padding: 1.5rem 0 calc(env(safe-area-inset-bottom, 0px) + 0.75rem);
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
border-top: 1px solid rgba(0, 0, 0, 0.1);
border-radius: 1.5rem 1.5rem 0 0;
box-shadow: 0 -4px 20px rgba(0, 0, 0, 0.15);
}
/* Drag handle */
.pill-nav.sidebar-mode::before {
content: '';
position: absolute;
top: 0.625rem;
left: 50%;
transform: translateX(-50%);
width: 2.5rem;
height: 0.25rem;
background: rgba(0, 0, 0, 0.2);
border-radius: 9999px;
}
:global(.dark) .pill-nav.sidebar-mode {
background: rgba(30, 30, 30, 0.95);
border-top: 1px solid rgba(255, 255, 255, 0.1);
}
:global(.dark) .pill-nav.sidebar-mode::before {
background: rgba(255, 255, 255, 0.3);
}
}
.sidebar-container {
flex-direction: column;
align-items: stretch;
gap: 0.5rem;
overflow-y: auto;
overflow-x: hidden;
padding: 1rem 1rem;
height: 100%;
}
/* Toolbar content in sidebar mode */
.sidebar-toolbar-content {
display: flex;
flex-direction: column;
gap: 0.5rem;
width: 100%;
padding: 0.5rem 0;
max-height: 40vh;
overflow-y: auto;
overflow-x: hidden;
scrollbar-width: thin;
scrollbar-color: rgba(0, 0, 0, 0.2) transparent;
}
.sidebar-toolbar-content::-webkit-scrollbar {
width: 4px;
}
.sidebar-toolbar-content::-webkit-scrollbar-track {
background: transparent;
}
.sidebar-toolbar-content::-webkit-scrollbar-thumb {
background: rgba(0, 0, 0, 0.2);
border-radius: 4px;
}
:global(.dark) .sidebar-toolbar-content {
scrollbar-color: rgba(255, 255, 255, 0.2) transparent;
}
:global(.dark) .sidebar-toolbar-content::-webkit-scrollbar-thumb {
background: rgba(255, 255, 255, 0.2);
}
.sidebar-toolbar-content :global(.toolbar-bar) {
flex-direction: column;
background: transparent;
backdrop-filter: none;
border: none;
box-shadow: none;
border-radius: 0;
padding: 0;
gap: 0.5rem;
}
.sidebar-toolbar-content :global(.toolbar-content) {
flex-direction: column;
align-items: stretch;
gap: 0.5rem;
width: 100%;
}
/* All buttons in sidebar toolbar - full width, left aligned */
.sidebar-toolbar-content :global(.pill-toolbar-btn),
.sidebar-toolbar-content :global(.pill-dropdown .trigger-button),
.sidebar-toolbar-content :global(button) {
width: 100%;
justify-content: flex-start;
text-align: left;
background: transparent;
border: 1px solid transparent;
box-shadow: none;
}
.sidebar-toolbar-content :global(.pill-toolbar-btn:hover),
.sidebar-toolbar-content :global(.pill-dropdown .trigger-button:hover),
.sidebar-toolbar-content :global(button:hover) {
background: rgba(0, 0, 0, 0.05);
}
:global(.dark) .sidebar-toolbar-content :global(.pill-toolbar-btn:hover),
:global(.dark) .sidebar-toolbar-content :global(.pill-dropdown .trigger-button:hover),
:global(.dark) .sidebar-toolbar-content :global(button:hover) {
background: rgba(255, 255, 255, 0.1);
}
/* Style for PillViewSwitcher in sidebar - vertical layout */
.sidebar-toolbar-content :global(.pill-view-switcher) {
flex-direction: column;
gap: 0.25rem;
width: 100%;
padding: 0;
background: transparent;
border: none;
box-shadow: none;
}
/* Hide the sliding indicator in vertical mode */
.sidebar-toolbar-content :global(.pill-view-switcher .sliding-indicator) {
display: none;
}
.sidebar-toolbar-content :global(.pill-view-switcher .switcher-btn) {
width: 100%;
justify-content: flex-start;
padding: 0.5rem 0.875rem;
border-radius: 9999px;
background: transparent;
border: 1px solid transparent;
}
.sidebar-toolbar-content :global(.pill-view-switcher .switcher-btn:hover) {
background: rgba(0, 0, 0, 0.05);
}
:global(.dark) .sidebar-toolbar-content :global(.pill-view-switcher .switcher-btn:hover) {
background: rgba(255, 255, 255, 0.1);
}
.sidebar-toolbar-content :global(.pill-view-switcher .switcher-btn.active) {
background: color-mix(in srgb, var(--pill-primary-color, #3b82f6) 15%, transparent 85%);
border-color: color-mix(in srgb, var(--pill-primary-color, #3b82f6) 25%, transparent 75%);
}
:global(.dark) .sidebar-toolbar-content :global(.pill-view-switcher .switcher-btn.active) {
background: color-mix(in srgb, var(--pill-primary-color, #3b82f6) 25%, transparent 75%);
border-color: color-mix(in srgb, var(--pill-primary-color, #3b82f6) 35%, transparent 65%);
}
/* PillTimeRangeSelector in sidebar */
.sidebar-toolbar-content :global(.pill-time-range-selector),
.sidebar-toolbar-content :global(.pill-dropdown) {
width: 100%;
}
/* PillCalendarSelector in sidebar */
.sidebar-toolbar-content :global(.calendar-selector) {
width: 100%;
}
/* Mobile: Sidebar container adjustments */
@media (max-width: 768px) {
.sidebar-container {
padding: 1rem 1.5rem 1rem;
gap: 0.5rem;
height: auto;
max-height: calc(70vh - 2rem);
}
/* Hide spacer on mobile - not needed in bottom sheet */
.sidebar-container .sidebar-spacer {
display: none;
}
}
.sidebar-container .pill {
justify-content: flex-start;
width: 100%;
}
.sidebar-container :global(.pill-dropdown) {
width: 100%;
}
.sidebar-container :global(.pill-dropdown .trigger-button) {
width: 100%;
justify-content: flex-start;
}
.sidebar-container .segmented-control {
width: 100%;
}
.sidebar-container .segmented-control .segment-btn {
flex: 1;
}
/* Transparent pills in sidebar mode (desktop) */
.sidebar-container .glass-pill,
.sidebar-container :global(.pill-dropdown .trigger-button) {
background: transparent;
backdrop-filter: none;
-webkit-backdrop-filter: none;
border: 1px solid transparent;
box-shadow: none;
}
.sidebar-container .glass-pill:hover,
.sidebar-container :global(.pill-dropdown .trigger-button:hover) {
background: rgba(0, 0, 0, 0.05);
border-color: rgba(0, 0, 0, 0.1);
transform: none;
box-shadow: none;
}
:global(.dark) .sidebar-container .glass-pill:hover,
:global(.dark) .sidebar-container :global(.pill-dropdown .trigger-button:hover) {
background: rgba(255, 255, 255, 0.05);
border-color: rgba(255, 255, 255, 0.1);
}
/* Mobile: Visible pills in sidebar/bottom-sheet mode */
@media (max-width: 768px) {
.sidebar-container .glass-pill,
.sidebar-container :global(.pill-dropdown .trigger-button) {
background: rgba(0, 0, 0, 0.05);
border: 1px solid rgba(0, 0, 0, 0.08);
}
.sidebar-container .glass-pill:hover,
.sidebar-container :global(.pill-dropdown .trigger-button:hover) {
background: rgba(0, 0, 0, 0.1);
border-color: rgba(0, 0, 0, 0.15);
}
:global(.dark) .sidebar-container .glass-pill,
:global(.dark) .sidebar-container :global(.pill-dropdown .trigger-button) {
background: rgba(255, 255, 255, 0.08);
border: 1px solid rgba(255, 255, 255, 0.1);
}
:global(.dark) .sidebar-container .glass-pill:hover,
:global(.dark) .sidebar-container :global(.pill-dropdown .trigger-button:hover) {
background: rgba(255, 255, 255, 0.15);
border-color: rgba(255, 255, 255, 0.2);
}
}
/* Keep active state visible */
.sidebar-container .pill.active {
background: color-mix(
in srgb,
var(--pill-primary-color, var(--color-primary-500, #f8d62b)) 20%,
transparent 80%
);
border-color: color-mix(
in srgb,
var(--pill-primary-color, var(--color-primary-500, #f8d62b)) 30%,
transparent 70%
);
}
:global(.dark) .sidebar-container .pill.active {
background: color-mix(
in srgb,
var(--pill-primary-color, var(--color-primary-500, #f8d62b)) 15%,
transparent 85%
);
border-color: color-mix(
in srgb,
var(--pill-primary-color, var(--color-primary-500, #f8d62b)) 25%,
transparent 75%
);
}
/* Logo pill in sidebar - same as other pills (transparent) */
.sidebar-container .logo-pill {
background: transparent;
border-color: transparent;
}
.sidebar-container .logo-pill:hover {
background: rgba(0, 0, 0, 0.05);
border-color: rgba(0, 0, 0, 0.1);
}
:global(.dark) .sidebar-container .logo-pill:hover {
background: rgba(255, 255, 255, 0.05);
border-color: rgba(255, 255, 255, 0.1);
}
/* Spacer to push toggle button to bottom */
.sidebar-spacer {
flex: 1;
min-height: 1rem;
}
/* Note: .toggle-pill class may be applied dynamically */
/* Segmented control */
.segmented-control {
display: flex;
align-items: center;
padding: 0;
gap: 0;
}
.segment-btn {
display: flex;
align-items: center;
justify-content: center;
padding: 0.5rem 0.625rem;
background: transparent;
border: none;
cursor: pointer;
color: inherit;
transition: background 0.2s;
}
.segment-btn:first-child {
border-radius: 9999px 0 0 9999px;
}
.segment-btn:last-child {
border-radius: 0 9999px 9999px 0;
}
.segment-btn:hover {
background: rgba(0, 0, 0, 0.05);
}
:global(.dark) .segment-btn:hover {
background: rgba(255, 255, 255, 0.1);
}
.segment-divider {
width: 1px;
height: 1rem;
background: rgba(0, 0, 0, 0.15);
}
:global(.dark) .segment-divider {
background: rgba(255, 255, 255, 0.2);
}
.sidebar-segmented {
margin: 0;
}
/* FAB for collapsed state - positioned at right */
/* FAB for collapsed state - positioned at bottom right */
.nav-fab {
position: fixed;
top: 0;
bottom: 0;
right: 0;
z-index: 1001;
display: flex;
align-items: center;
justify-content: center;
padding: 0.875rem;
border-radius: 0 0 0 1rem;
padding-bottom: calc(env(safe-area-inset-bottom, 0px) + 0.875rem);
border-radius: 1rem 0 0 0;
cursor: pointer;
border: none;
}
/* Desktop: FAB at bottom when desktop-bottom */
@media (min-width: 769px) {
.nav-fab.desktop-bottom {
top: auto;
bottom: 0;
border-radius: 1rem 0 0 0;
}
}
/* Mobile: FAB always at bottom right */
@media (max-width: 768px) {
.nav-fab {
top: auto;
bottom: 0;
right: 0;
border-radius: 1rem 0 0 0;
padding-bottom: calc(env(safe-area-inset-bottom, 0px) + 0.875rem);
}
}
/* Transitions */
.pill-nav {
transition: all 0.3s ease;

View file

@ -10,23 +10,13 @@
onChange: (id: string) => void;
/** Optional section label */
sectionLabel?: string;
/** Whether in sidebar mode (affects layout) */
isSidebarMode?: boolean;
/** Primary color for active state */
primaryColor?: string;
/** Called on right-click (context menu) - receives click coordinates */
onContextMenu?: (x: number, y: number) => void;
}
let {
options,
value,
onChange,
sectionLabel,
isSidebarMode = false,
primaryColor,
onContextMenu,
}: Props = $props();
let { options, value, onChange, sectionLabel, primaryColor, onContextMenu }: Props = $props();
function handleContextMenu(event: MouseEvent) {
if (onContextMenu) {
@ -66,13 +56,9 @@
</script>
<!-- svelte-ignore a11y_no_static_element_interactions -->
<div class="pill-tab-group" class:sidebar-mode={isSidebarMode} oncontextmenu={handleContextMenu}>
{#if sectionLabel && isSidebarMode}
<p class="section-label">{sectionLabel}</p>
{/if}
<div class="pill-tab-group" oncontextmenu={handleContextMenu}>
<div
class="tab-container glass-pill"
class:sidebar-tabs={isSidebarMode}
style={primaryColor ? `--pill-primary-color: ${primaryColor}` : ''}
>
{#each options as option, index}
@ -116,20 +102,6 @@
gap: 0.5rem;
}
.section-label {
font-size: 0.75rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.05em;
color: #6b7280;
margin: 0;
padding: 0 0.25rem;
}
:global(.dark) .section-label {
color: #9ca3af;
}
.tab-container {
display: flex;
align-items: center;
@ -154,20 +126,6 @@
border: 1px solid rgba(255, 255, 255, 0.15);
}
/* Sidebar mode - transparent */
.sidebar-tabs {
background: transparent;
backdrop-filter: none;
-webkit-backdrop-filter: none;
border: 1px solid rgba(0, 0, 0, 0.1);
box-shadow: none;
}
:global(.dark) .sidebar-tabs {
background: transparent;
border: 1px solid rgba(255, 255, 255, 0.1);
}
.tab-btn {
display: flex;
align-items: center;
@ -253,14 +211,4 @@
font-weight: 500;
white-space: nowrap;
}
/* Sidebar mode adjustments */
.sidebar-mode .tab-container {
width: 100%;
}
.sidebar-mode .tab-btn {
justify-content: flex-start;
padding: 0.5rem 0.625rem;
}
</style>

View file

@ -153,10 +153,6 @@ export interface PillNavigationProps {
onToggleTheme?: () => void;
/** Whether dark mode is active */
isDark?: boolean;
/** Whether sidebar mode is enabled (controlled) */
isSidebarMode?: boolean;
/** Called when sidebar mode changes */
onModeChange?: (isSidebar: boolean) => void;
/** Whether navigation is collapsed (controlled) */
isCollapsed?: boolean;
/** Called when collapsed state changes */

View file

@ -1,10 +1,5 @@
<script lang="ts">
import type {
UserSettingsStore,
NavPosition,
ThemeMode,
WeekStartDay,
} from '@manacore/shared-theme';
import type { UserSettingsStore, ThemeMode, WeekStartDay } from '@manacore/shared-theme';
import { getAvailableRoutes, getDefaultRoute } from '@manacore/shared-theme';
import SettingsSection from './SettingsSection.svelte';
import SettingsCard from './SettingsCard.svelte';
@ -25,8 +20,6 @@
navItems?: NavItem[];
/** Items that should always be visible (e.g., home route) */
alwaysVisibleHrefs?: string[];
/** Whether to show navigation settings */
showNavigation?: boolean;
/** Whether to show nav visibility settings */
showNavVisibility?: boolean;
/** Whether to show theme settings */
@ -48,7 +41,6 @@
appId,
navItems = [],
alwaysVisibleHrefs = [],
showNavigation = true,
showNavVisibility = true,
showTheme = true,
showLanguage = true,
@ -65,20 +57,6 @@
appId ? userSettings.general?.startPages?.[appId] || defaultRoute : '/'
);
// Navigation position handler
async function handleNavPositionChange(position: NavPosition) {
await userSettings.updateGlobal({
nav: { ...userSettings.globalSettings.nav, desktopPosition: position },
});
}
// Sidebar collapsed handler
async function handleSidebarChange(collapsed: boolean) {
await userSettings.updateGlobal({
nav: { ...userSettings.globalSettings.nav, sidebarCollapsed: collapsed },
});
}
// Theme mode handler
async function handleThemeModeChange(mode: ThemeMode) {
await userSettings.updateGlobal({
@ -155,78 +133,9 @@
<p class="text-sm text-[hsl(var(--muted-foreground))] mb-6">{description}</p>
<div class="space-y-6">
{#if showNavigation}
<!-- Navigation Settings -->
<div class="space-y-4">
<h3
class="text-xs font-semibold text-[hsl(var(--muted-foreground))] uppercase tracking-wider"
>
Navigation
</h3>
<div class="flex items-center justify-between py-2">
<div>
<p class="font-medium text-[hsl(var(--foreground))]">Position (Desktop)</p>
<p class="text-sm text-[hsl(var(--muted-foreground))]">
Position der Navigation auf großen Bildschirmen
</p>
</div>
<div class="flex gap-2">
<button
class="px-3 py-1.5 text-sm font-medium rounded-lg transition-colors {userSettings
.globalSettings.nav.desktopPosition === 'top'
? 'bg-[hsl(var(--primary))] text-[hsl(var(--primary-foreground))]'
: 'bg-[hsl(var(--muted))] hover:bg-[hsl(var(--muted))]/80 text-[hsl(var(--foreground))]'}"
onclick={() => handleNavPositionChange('top')}
>
Oben
</button>
<button
class="px-3 py-1.5 text-sm font-medium rounded-lg transition-colors {userSettings
.globalSettings.nav.desktopPosition === 'bottom'
? 'bg-[hsl(var(--primary))] text-[hsl(var(--primary-foreground))]'
: 'bg-[hsl(var(--muted))] hover:bg-[hsl(var(--muted))]/80 text-[hsl(var(--foreground))]'}"
onclick={() => handleNavPositionChange('bottom')}
>
Unten
</button>
</div>
</div>
<div
class="flex items-center justify-between py-2 border-t border-[hsl(var(--border))]"
>
<div>
<p class="font-medium text-[hsl(var(--foreground))]">Sidebar eingeklappt</p>
<p class="text-sm text-[hsl(var(--muted-foreground))]">
Standard-Zustand der Sidebar
</p>
</div>
<button
class="relative inline-flex h-6 w-11 items-center rounded-full transition-colors {userSettings
.globalSettings.nav.sidebarCollapsed
? 'bg-[hsl(var(--primary))]'
: 'bg-gray-200 dark:bg-gray-700'}"
onclick={() =>
handleSidebarChange(!userSettings.globalSettings.nav.sidebarCollapsed)}
aria-label="Toggle sidebar collapsed state"
>
<span
class="inline-block h-4 w-4 transform rounded-full bg-white transition-transform {userSettings
.globalSettings.nav.sidebarCollapsed
? 'translate-x-6'
: 'translate-x-1'}"
></span>
</button>
</div>
</div>
{/if}
{#if showNavVisibility && appId && navItems.length > 0}
<!-- Navigation Visibility Settings -->
<div
class="space-y-4 {showNavigation ? 'pt-4 border-t border-[hsl(var(--border))]' : ''}"
>
<div class="space-y-4">
<NavVisibilitySettings {userSettings} {appId} {navItems} {alwaysVisibleHrefs} />
</div>
{/if}
@ -234,7 +143,7 @@
{#if showTheme}
<!-- Theme Settings -->
<div
class="space-y-4 {showNavigation || (showNavVisibility && appId)
class="space-y-4 {showNavVisibility && appId
? 'pt-4 border-t border-[hsl(var(--border))]'
: ''}"
>
@ -294,7 +203,7 @@
{#if showLanguage}
<!-- Language Settings -->
<div
class="space-y-4 {showTheme || showNavigation || (showNavVisibility && appId)
class="space-y-4 {showTheme || (showNavVisibility && appId)
? 'pt-4 border-t border-[hsl(var(--border))]'
: ''}"
>
@ -331,10 +240,7 @@
{#if showGeneral}
<!-- General Settings -->
<div
class="space-y-4 {showLanguage ||
showTheme ||
showNavigation ||
(showNavVisibility && appId)
class="space-y-4 {showLanguage || showTheme || (showNavVisibility && appId)
? 'pt-4 border-t border-[hsl(var(--border))]'
: ''}"
>