feat: implement navigation icon style store and UI settings for emoji/icon toggle

This commit is contained in:
Sam Chau
2026-01-15 00:06:02 +10:30
parent 74b38df686
commit eb22e0385c
6 changed files with 123 additions and 11 deletions

View File

@@ -0,0 +1,43 @@
/**
* Navigation icon style store (emoji vs lucide icons)
*/
import { writable } from 'svelte/store';
import { browser } from '$app/environment';
export type NavIconStyle = 'emoji' | 'lucide';
function createNavIconStore() {
let initialStyle: NavIconStyle = 'lucide';
if (browser) {
const stored = localStorage.getItem('navIconStyle') as NavIconStyle | null;
if (stored && (stored === 'emoji' || stored === 'lucide')) {
initialStyle = stored;
}
}
const { subscribe, set } = writable<NavIconStyle>(initialStyle);
function setStyle(style: NavIconStyle) {
set(style);
if (browser) {
localStorage.setItem('navIconStyle', style);
}
}
function toggle() {
if (browser) {
const current = localStorage.getItem('navIconStyle') as NavIconStyle | null;
const newStyle = current === 'emoji' ? 'lucide' : 'emoji';
setStyle(newStyle);
}
}
return {
subscribe,
setStyle,
toggle
};
}
export const navIconStore = createNavIconStore();

View File

@@ -1,17 +1,19 @@
<script lang="ts">
import GroupHeader from './groupHeader.svelte';
import type { Snippet } from 'svelte';
import type { Snippet, Component } from 'svelte';
import type { IconProps } from 'lucide-svelte';
import { slide } from 'svelte/transition';
interface Props {
label: string;
href: string;
icon?: Component<IconProps>;
initialOpen?: boolean;
hasItems?: boolean;
children?: Snippet;
}
let { label, href, initialOpen = true, hasItems = false, children }: Props = $props();
let { label, href, icon, initialOpen = true, hasItems = false, children }: Props = $props();
let isOpen = $state(initialOpen);
function toggleOpen() {
@@ -20,7 +22,7 @@
</script>
<div class="mb-4">
<GroupHeader {label} {href} {isOpen} {hasItems} onToggle={toggleOpen} />
<GroupHeader {label} {href} {icon} {isOpen} {hasItems} onToggle={toggleOpen} />
{#if isOpen && hasItems && children}
<div class="mt-2 grid grid-cols-[auto_1fr]" transition:slide={{ duration: 200 }}>

View File

@@ -1,15 +1,18 @@
<script lang="ts">
import { page } from '$app/stores';
import type { Component } from 'svelte';
import type { IconProps } from 'lucide-svelte';
interface Props {
label: string;
href: string;
icon?: Component<IconProps>;
isOpen: boolean;
hasItems: boolean;
onToggle: () => void;
}
let { label, href, isOpen, hasItems, onToggle }: Props = $props();
let { label, href, icon, isOpen, hasItems, onToggle }: Props = $props();
const isActive = $derived($page.url.pathname === href || $page.url.pathname.startsWith(href + '/'));
</script>
@@ -24,6 +27,9 @@
? 'bg-neutral-200 hover:bg-neutral-300 dark:bg-neutral-800 dark:hover:bg-neutral-700'
: ''}"
>
{#if icon}
<svelte:component this={icon} class="h-4 w-4" />
{/if}
{label}
</a>

View File

@@ -2,8 +2,12 @@
import Group from './group.svelte';
import GroupItem from './groupItem.svelte';
import Version from './version.svelte';
import { Home, Sliders, Palette, Microscope, Tag, Clock, Settings } from 'lucide-svelte';
import { navIconStore } from '$stores/navIcons';
export let collapsed: boolean = false;
$: useEmoji = $navIconStore === 'emoji';
</script>
<nav
@@ -11,24 +15,24 @@
class:-translate-x-[calc(100%-24px)]={collapsed}
>
<div class="flex-1 overflow-y-auto p-4">
<Group label="🏠 Home" href="/" hasItems={true}>
<Group label={useEmoji ? '🏠 Home' : 'Home'} href="/" icon={useEmoji ? undefined : Home} hasItems={true}>
<GroupItem label="Databases" href="/databases" />
<GroupItem label="Arrs" href="/arr" />
</Group>
<Group label="⚡ Quality Profiles" href="/quality-profiles" initialOpen={true} hasItems={true}>
<Group label={useEmoji ? '⚡ Quality Profiles' : 'Quality Profiles'} href="/quality-profiles" icon={useEmoji ? undefined : Sliders} initialOpen={true} hasItems={true}>
<GroupItem label="Entity Testing" href="/quality-profiles/entity-testing" />
</Group>
<Group label="🎨 Custom Formats" href="/custom-formats" initialOpen={false} />
<Group label={useEmoji ? '🎨 Custom Formats' : 'Custom Formats'} href="/custom-formats" icon={useEmoji ? undefined : Palette} initialOpen={false} />
<Group label="🔬 Regular Expressions" href="/regular-expressions" initialOpen={false} />
<Group label={useEmoji ? '🔬 Regular Expressions' : 'Regular Expressions'} href="/regular-expressions" icon={useEmoji ? undefined : Microscope} initialOpen={false} />
<Group label="🏷️ Media Management" href="/media-management" initialOpen={false} />
<Group label={useEmoji ? '🏷️ Media Management' : 'Media Management'} href="/media-management" icon={useEmoji ? undefined : Tag} initialOpen={false} />
<Group label="⏱️ Delay Profiles" href="/delay-profiles" initialOpen={false} />
<Group label={useEmoji ? '⏱️ Delay Profiles' : 'Delay Profiles'} href="/delay-profiles" icon={useEmoji ? undefined : Clock} initialOpen={false} />
<Group label="⚙️ Settings" href="/settings" initialOpen={true} hasItems={true}>
<Group label={useEmoji ? '⚙️ Settings' : 'Settings'} href="/settings" icon={useEmoji ? undefined : Settings} initialOpen={true} hasItems={true}>
<GroupItem label="General" href="/settings/general" />
<GroupItem label="Jobs" href="/settings/jobs" />
<GroupItem label="Logs" href="/settings/logs" />

View File

@@ -3,6 +3,7 @@
import BackupSettings from './components/BackupSettings.svelte';
import AISettings from './components/AISettings.svelte';
import TMDBSettings from './components/TMDBSettings.svelte';
import UISettings from './components/UISettings.svelte';
import type { PageData } from './$types';
export let data: PageData;
@@ -17,6 +18,9 @@
</div>
<div class="space-y-8">
<!-- UI Preferences -->
<UISettings />
<!-- Backup Configuration -->
<BackupSettings settings={data.backupSettings} />

View File

@@ -0,0 +1,53 @@
<script lang="ts">
import { navIconStore, type NavIconStyle } from '$stores/navIcons';
import { Check } from 'lucide-svelte';
import IconCheckbox from '$ui/form/IconCheckbox.svelte';
let useEmojis = $derived($navIconStore === 'emoji');
function toggle() {
navIconStore.toggle();
}
</script>
<div
class="rounded-lg border border-neutral-200 bg-white dark:border-neutral-800 dark:bg-neutral-900"
>
<!-- Header -->
<div class="border-b border-neutral-200 px-6 py-4 dark:border-neutral-800">
<h2 class="text-xl font-semibold text-neutral-900 dark:text-neutral-50">
Interface
</h2>
<p class="mt-1 text-sm text-neutral-600 dark:text-neutral-400">
Customize the look and feel of the application
</p>
</div>
<!-- Settings -->
<div class="p-6">
<div class="space-y-3">
<h3 class="text-sm font-semibold text-neutral-900 dark:text-neutral-50">Navigation</h3>
<div class="space-y-2">
<div class="flex items-center gap-3">
<IconCheckbox
icon={Check}
checked={useEmojis}
on:click={toggle}
/>
<button
type="button"
class="flex-1 text-left"
on:click={toggle}
>
<span class="text-sm font-medium text-neutral-900 dark:text-neutral-50">
Use Emojis
</span>
<p class="text-xs text-neutral-500 dark:text-neutral-400">
Show emojis instead of icons in the sidebar navigation
</p>
</button>
</div>
</div>
</div>
</div>
</div>