feat(accent): implement accent color store and picker for app theming

This commit is contained in:
Sam Chau
2025-12-29 00:55:40 +10:30
parent 862dc0c097
commit 0af19ed7ea
4 changed files with 104 additions and 6 deletions

View File

@@ -0,0 +1,39 @@
/**
* Accent color store for app theming
*/
import { writable } from 'svelte/store';
import { browser } from '$app/environment';
export type AccentColor = 'blue' | 'yellow';
export const accentColors: { value: AccentColor; label: string; color: string }[] = [
{ value: 'blue', label: 'Blue', color: '#2563eb' },
{ value: 'yellow', label: 'Yellow', color: '#eab308' }
];
function createAccentStore() {
let initialAccent: AccentColor = 'blue';
if (browser) {
const stored = localStorage.getItem('accent') as AccentColor | null;
if (stored && accentColors.some((c) => c.value === stored)) {
initialAccent = stored;
}
}
const { subscribe, set } = writable<AccentColor>(initialAccent);
function setAccent(accent: AccentColor) {
set(accent);
if (browser) {
localStorage.setItem('accent', accent);
}
}
return {
subscribe,
set: setAccent
};
}
export const accentStore = createAccentStore();

View File

@@ -0,0 +1,55 @@
<script lang="ts">
import { accentStore, accentColors, type AccentColor } from '$stores/accent';
import Dropdown from '$ui/dropdown/Dropdown.svelte';
import { Check } from 'lucide-svelte';
let open = false;
$: currentColor = accentColors.find((c) => c.value === $accentStore) ?? accentColors[0];
function select(accent: AccentColor) {
accentStore.set(accent);
open = false;
}
function handleClickOutside(event: MouseEvent) {
const target = event.target as HTMLElement;
if (!target.closest('.accent-picker')) {
open = false;
}
}
</script>
<svelte:window on:click={handleClickOutside} />
<div class="accent-picker relative">
<button
on:click|stopPropagation={() => (open = !open)}
class="flex h-9 w-9 items-center justify-center rounded-md transition-colors hover:bg-neutral-200 dark:hover:bg-neutral-800"
aria-label="Select accent color"
>
<span
class="h-4 w-4 rounded-full"
style="background-color: {currentColor.color}"
></span>
</button>
{#if open}
<Dropdown position="middle" minWidth="auto">
<div class="flex flex-wrap gap-2 p-2">
{#each accentColors as accent}
<button
on:click|stopPropagation={() => select(accent.value)}
class="relative flex h-6 w-6 items-center justify-center rounded-full transition-transform hover:scale-110"
style="background-color: {accent.color}"
aria-label={accent.label}
>
{#if $accentStore === accent.value}
<Check size={14} class="text-white" />
{/if}
</button>
{/each}
</div>
</Dropdown>
{/if}
</div>

View File

@@ -1,10 +1,11 @@
<script lang="ts">
import AccentPicker from './accentPicker.svelte';
import ThemeToggle from './themeToggle.svelte';
import logo from '$assets/logo-firefox-circular-arrow.png';
</script>
<nav
class="fixed top-0 right-0 left-0 z-50 border-b 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 dark:border-neutral-800 dark:bg-neutral-900"
>
<div class="flex items-center justify-between px-4 py-4">
<!-- Left: Brand name with logo -->
@@ -13,7 +14,10 @@
<div class="text-xl font-bold text-neutral-900 dark:text-neutral-100">profilarr</div>
</div>
<!-- Right: Theme toggle -->
<ThemeToggle />
<!-- Right: Accent picker and Theme toggle -->
<div class="flex items-center gap-1">
<AccentPicker />
<ThemeToggle />
</div>
</div>
</nav>

View File

@@ -1,6 +1,6 @@
<script lang="ts">
import { themeStore } from '$stores/theme.ts';
import { Moon, Sun } from 'lucide-svelte';
import { MoonStar, Sun } from 'lucide-svelte';
$: isDark = $themeStore === 'dark';
</script>
@@ -16,7 +16,7 @@
? 'scale-100 rotate-0 opacity-100'
: 'scale-75 rotate-180 opacity-0'}"
>
<Sun class="h-[18px] w-[18px] text-neutral-700 dark:text-neutral-300" />
<MoonStar class="h-[18px] w-[18px] text-neutral-700 dark:text-neutral-300" />
</div>
<!-- Moon icon (visible in light mode) -->
@@ -25,6 +25,6 @@
? 'scale-75 -rotate-180 opacity-0'
: 'scale-100 rotate-0 opacity-100'}"
>
<Moon class="h-[18px] w-[18px] text-neutral-700 dark:text-neutral-300" />
<Sun class="h-[18px] w-[18px] text-neutral-700 dark:text-neutral-300" />
</div>
</button>