fix: dropdown theme colors

This commit is contained in:
rasmusq
2025-12-19 23:13:03 +01:00
parent b381a6d669
commit 466704a23a
8 changed files with 171 additions and 106 deletions

View File

@@ -80,8 +80,8 @@
</div>
<div class="flex items-center gap-1 sm:gap-2 flex-shrink-0">
<ColorPicker bind:color={localColor} onchange={handleColorChange} size="sm" />
<ThemePicker value={dashboardTheme} onValueChange={handleThemeChange} />
<LanguageToggle />
<ThemePicker value={dashboardTheme} onValueChange={handleThemeChange} color={localColor} />
<LanguageToggle color={localColor} />
<ThemeToggle />
{#if isAuthenticated}
<Button variant="outline" onclick={() => signOut({ callbackUrl: '/' })}>{t.auth.signOut}</Button>

View File

@@ -7,10 +7,12 @@
let {
isAuthenticated = false,
showDashboardLink = false
showDashboardLink = false,
color = null
}: {
isAuthenticated?: boolean;
showDashboardLink?: boolean;
color?: string | null;
} = $props();
const t = $derived(languageStore.t);
@@ -28,7 +30,7 @@
</Button>
{/if}
<div class="ml-auto flex items-center gap-1 sm:gap-2">
<LanguageToggle />
<LanguageToggle {color} />
<ThemeToggle />
</div>
</nav>

View File

@@ -0,0 +1,119 @@
<script lang="ts">
import { Button } from '$lib/components/ui/button';
import { scale } from 'svelte/transition';
import { cubicOut } from 'svelte/easing';
import type { Snippet } from 'svelte';
let {
items,
selectedValue,
onSelect,
color,
showCheckmark = true,
icon,
ariaLabel
}: {
items: Array<{ value: string; label: string }>;
selectedValue: string;
onSelect: (value: string) => void;
color?: string | null;
showCheckmark?: boolean;
icon: Snippet;
ariaLabel: string;
} = $props();
let showMenu = $state(false);
const menuClasses = $derived(
color
? 'absolute left-0 sm:right-0 sm:left-auto mt-2 w-40 rounded-md border shadow-lg z-50 backdrop-blur-md'
: 'absolute left-0 sm:right-0 sm:left-auto mt-2 w-40 rounded-md border shadow-lg z-50 backdrop-blur-md border-slate-200 dark:border-slate-800 bg-white/90 dark:bg-slate-950/90'
);
const menuStyle = $derived(
color
? `border-color: ${color}; background-color: ${color}20; backdrop-filter: blur(12px);`
: ''
);
function getItemStyle(itemValue: string): string {
if (!color) return '';
return selectedValue === itemValue ? `background-color: ${color}20;` : '';
}
function toggleMenu() {
showMenu = !showMenu;
}
function handleSelect(value: string) {
onSelect(value);
showMenu = false;
}
function handleClickOutside(event: MouseEvent) {
const target = event.target as HTMLElement;
if (!target.closest('.dropdown-menu')) {
showMenu = false;
}
}
function handleMouseEnter(e: MouseEvent) {
if (color) {
(e.currentTarget as HTMLElement).style.backgroundColor = `${color}15`;
}
}
function handleMouseLeave(e: MouseEvent, itemValue: string) {
if (color) {
(e.currentTarget as HTMLElement).style.backgroundColor =
selectedValue === itemValue ? `${color}20` : 'transparent';
}
}
$effect(() => {
if (showMenu) {
document.addEventListener('click', handleClickOutside);
return () => document.removeEventListener('click', handleClickOutside);
}
});
</script>
<div class="relative dropdown-menu">
<Button variant="outline" size="icon" onclick={toggleMenu} aria-label={ariaLabel}>
{@render icon()}
</Button>
{#if showMenu}
<div
class={menuClasses}
style={menuStyle}
transition:scale={{ duration: 150, start: 0.95, opacity: 0, easing: cubicOut }}
>
<div class="py-1">
{#each items as item}
<button
type="button"
class="w-full text-left px-4 py-2 text-sm transition-colors"
class:hover:bg-slate-100={!color}
class:dark:hover:bg-slate-900={!color}
class:font-bold={selectedValue === item.value}
class:bg-slate-100={selectedValue === item.value && !color}
class:dark:bg-slate-900={selectedValue === item.value && !color}
class:flex={showCheckmark}
class:items-center={showCheckmark}
class:justify-between={showCheckmark}
style={getItemStyle(item.value)}
onmouseenter={handleMouseEnter}
onmouseleave={(e) => handleMouseLeave(e, item.value)}
onclick={() => handleSelect(item.value)}
>
<span>{item.label}</span>
{#if showCheckmark && selectedValue === item.value}
<span class="ml-2"></span>
{/if}
</button>
{/each}
</div>
</div>
{/if}
</div>

View File

@@ -1,58 +1,32 @@
<script lang="ts">
import { languageStore } from '$lib/stores/language.svelte';
import { languages } from '$lib/i18n/translations';
import { Button } from '$lib/components/ui/button';
import Dropdown from '$lib/components/ui/Dropdown.svelte';
import { Languages } from 'lucide-svelte';
let showMenu = $state(false);
let { color }: { color?: string | null } = $props();
function toggleMenu() {
showMenu = !showMenu;
const languageItems = $derived(
languages.map((lang) => ({
value: lang.code,
label: lang.name
}))
);
function setLanguage(code: string) {
languageStore.setLanguage(code as 'en' | 'da');
}
function setLanguage(code: 'en' | 'da') {
languageStore.setLanguage(code);
showMenu = false;
}
function handleClickOutside(event: MouseEvent) {
const target = event.target as HTMLElement;
if (!target.closest('.language-toggle-menu')) {
showMenu = false;
}
}
$effect(() => {
if (showMenu) {
document.addEventListener('click', handleClickOutside);
return () => document.removeEventListener('click', handleClickOutside);
}
});
</script>
<div class="relative language-toggle-menu">
<Button variant="outline" size="icon" onclick={toggleMenu} aria-label="Toggle language">
<Dropdown
items={languageItems}
selectedValue={languageStore.current}
onSelect={setLanguage}
{color}
showCheckmark={false}
ariaLabel="Toggle language"
>
{#snippet icon()}
<Languages class="h-[1.2rem] w-[1.2rem]" />
</Button>
{#if showMenu}
<div
class="absolute left-0 sm:right-0 sm:left-auto mt-2 w-40 rounded-md border border-slate-200 dark:border-slate-800 bg-white dark:bg-slate-950 shadow-lg z-50"
>
<div class="py-1">
{#each languages as lang}
<button
type="button"
class="w-full text-left px-4 py-2 text-sm hover:bg-slate-100 dark:hover:bg-slate-900 transition-colors"
class:font-bold={languageStore.current === lang.code}
class:bg-slate-100={languageStore.current === lang.code}
class:dark:bg-slate-900={languageStore.current === lang.code}
onclick={() => setLanguage(lang.code)}
>
{lang.name}
</button>
{/each}
</div>
</div>
{/if}
</div>
{/snippet}
</Dropdown>

View File

@@ -1,68 +1,35 @@
<script lang="ts">
import { Button } from '$lib/components/ui/button';
import Dropdown from '$lib/components/ui/Dropdown.svelte';
import { Palette } from 'lucide-svelte';
import { AVAILABLE_THEMES } from '$lib/utils/themes';
let {
value = 'none',
onValueChange
onValueChange,
color
}: {
value?: string;
onValueChange: (theme: string) => void;
color?: string | null;
} = $props();
let showMenu = $state(false);
function toggleMenu() {
showMenu = !showMenu;
}
function handleSelect(themeName: string) {
onValueChange(themeName);
showMenu = false;
}
function handleClickOutside(event: MouseEvent) {
const target = event.target as HTMLElement;
if (!target.closest('.theme-picker-menu')) {
showMenu = false;
}
}
$effect(() => {
if (showMenu) {
document.addEventListener('click', handleClickOutside);
return () => document.removeEventListener('click', handleClickOutside);
}
});
const themeItems = $derived(
Object.entries(AVAILABLE_THEMES).map(([key, theme]) => ({
value: key,
label: theme.name
}))
);
</script>
<div class="relative theme-picker-menu">
<Button variant="outline" size="icon" onclick={toggleMenu} aria-label="Select theme pattern">
<Dropdown
items={themeItems}
selectedValue={value}
onSelect={onValueChange}
{color}
showCheckmark={true}
ariaLabel="Select theme pattern"
>
{#snippet icon()}
<Palette class="h-[1.2rem] w-[1.2rem]" />
</Button>
{#if showMenu}
<div
class="absolute left-0 sm:right-0 sm:left-auto mt-2 w-40 rounded-md border border-slate-200 dark:border-slate-800 bg-white dark:bg-slate-950 shadow-lg z-50"
>
<div class="py-1">
{#each Object.entries(AVAILABLE_THEMES) as [key, theme]}
<button
type="button"
class="w-full text-left px-4 py-2 text-sm hover:bg-slate-100 dark:hover:bg-slate-900 transition-colors flex items-center justify-between"
class:font-bold={value === key}
class:bg-slate-100={value === key}
class:dark:bg-slate-900={value === key}
onclick={() => handleSelect(key)}
>
<span>{theme.name}</span>
{#if value === key}
<span class="ml-2"></span>
{/if}
</button>
{/each}
</div>
</div>
{/if}
</div>
{/snippet}
</Dropdown>

View File

@@ -128,6 +128,7 @@
// Force reactivity by updating the wishlist object
wishlist.theme = theme;
}}
color={wishlistColor}
/>
<ColorPicker
bind:color={wishlistColor}

View File

@@ -38,6 +38,7 @@
<Navigation
isAuthenticated={data.isAuthenticated}
showDashboardLink={true}
color={data.wishlist.color}
/>
<Card style={headerCardStyle}>

View File

@@ -123,6 +123,7 @@
<Navigation
isAuthenticated={data.isAuthenticated}
showDashboardLink={true}
color={currentColor}
/>
<WishlistHeader