mirror of
https://github.com/Dictionarry-Hub/profilarr.git
synced 2026-01-22 10:51:02 +01:00
feat(accent): implement accent color store and picker for app theming
This commit is contained in:
39
src/lib/client/stores/accent.ts
Normal file
39
src/lib/client/stores/accent.ts
Normal 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();
|
||||
55
src/lib/client/ui/navigation/navbar/accentPicker.svelte
Normal file
55
src/lib/client/ui/navigation/navbar/accentPicker.svelte
Normal 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>
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user