fix: dropdown theme colors
This commit is contained in:
@@ -80,8 +80,8 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="flex items-center gap-1 sm:gap-2 flex-shrink-0">
|
<div class="flex items-center gap-1 sm:gap-2 flex-shrink-0">
|
||||||
<ColorPicker bind:color={localColor} onchange={handleColorChange} size="sm" />
|
<ColorPicker bind:color={localColor} onchange={handleColorChange} size="sm" />
|
||||||
<ThemePicker value={dashboardTheme} onValueChange={handleThemeChange} />
|
<ThemePicker value={dashboardTheme} onValueChange={handleThemeChange} color={localColor} />
|
||||||
<LanguageToggle />
|
<LanguageToggle color={localColor} />
|
||||||
<ThemeToggle />
|
<ThemeToggle />
|
||||||
{#if isAuthenticated}
|
{#if isAuthenticated}
|
||||||
<Button variant="outline" onclick={() => signOut({ callbackUrl: '/' })}>{t.auth.signOut}</Button>
|
<Button variant="outline" onclick={() => signOut({ callbackUrl: '/' })}>{t.auth.signOut}</Button>
|
||||||
|
|||||||
@@ -7,10 +7,12 @@
|
|||||||
|
|
||||||
let {
|
let {
|
||||||
isAuthenticated = false,
|
isAuthenticated = false,
|
||||||
showDashboardLink = false
|
showDashboardLink = false,
|
||||||
|
color = null
|
||||||
}: {
|
}: {
|
||||||
isAuthenticated?: boolean;
|
isAuthenticated?: boolean;
|
||||||
showDashboardLink?: boolean;
|
showDashboardLink?: boolean;
|
||||||
|
color?: string | null;
|
||||||
} = $props();
|
} = $props();
|
||||||
|
|
||||||
const t = $derived(languageStore.t);
|
const t = $derived(languageStore.t);
|
||||||
@@ -28,7 +30,7 @@
|
|||||||
</Button>
|
</Button>
|
||||||
{/if}
|
{/if}
|
||||||
<div class="ml-auto flex items-center gap-1 sm:gap-2">
|
<div class="ml-auto flex items-center gap-1 sm:gap-2">
|
||||||
<LanguageToggle />
|
<LanguageToggle {color} />
|
||||||
<ThemeToggle />
|
<ThemeToggle />
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
|
|||||||
119
src/lib/components/ui/Dropdown.svelte
Normal file
119
src/lib/components/ui/Dropdown.svelte
Normal 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>
|
||||||
@@ -1,58 +1,32 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { languageStore } from '$lib/stores/language.svelte';
|
import { languageStore } from '$lib/stores/language.svelte';
|
||||||
import { languages } from '$lib/i18n/translations';
|
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';
|
import { Languages } from 'lucide-svelte';
|
||||||
|
|
||||||
let showMenu = $state(false);
|
let { color }: { color?: string | null } = $props();
|
||||||
|
|
||||||
function toggleMenu() {
|
const languageItems = $derived(
|
||||||
showMenu = !showMenu;
|
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>
|
</script>
|
||||||
|
|
||||||
<div class="relative language-toggle-menu">
|
<Dropdown
|
||||||
<Button variant="outline" size="icon" onclick={toggleMenu} aria-label="Toggle language">
|
items={languageItems}
|
||||||
|
selectedValue={languageStore.current}
|
||||||
|
onSelect={setLanguage}
|
||||||
|
{color}
|
||||||
|
showCheckmark={false}
|
||||||
|
ariaLabel="Toggle language"
|
||||||
|
>
|
||||||
|
{#snippet icon()}
|
||||||
<Languages class="h-[1.2rem] w-[1.2rem]" />
|
<Languages class="h-[1.2rem] w-[1.2rem]" />
|
||||||
</Button>
|
{/snippet}
|
||||||
|
</Dropdown>
|
||||||
{#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>
|
|
||||||
|
|||||||
@@ -1,68 +1,35 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Button } from '$lib/components/ui/button';
|
import Dropdown from '$lib/components/ui/Dropdown.svelte';
|
||||||
import { Palette } from 'lucide-svelte';
|
import { Palette } from 'lucide-svelte';
|
||||||
import { AVAILABLE_THEMES } from '$lib/utils/themes';
|
import { AVAILABLE_THEMES } from '$lib/utils/themes';
|
||||||
|
|
||||||
let {
|
let {
|
||||||
value = 'none',
|
value = 'none',
|
||||||
onValueChange
|
onValueChange,
|
||||||
|
color
|
||||||
}: {
|
}: {
|
||||||
value?: string;
|
value?: string;
|
||||||
onValueChange: (theme: string) => void;
|
onValueChange: (theme: string) => void;
|
||||||
|
color?: string | null;
|
||||||
} = $props();
|
} = $props();
|
||||||
|
|
||||||
let showMenu = $state(false);
|
const themeItems = $derived(
|
||||||
|
Object.entries(AVAILABLE_THEMES).map(([key, theme]) => ({
|
||||||
function toggleMenu() {
|
value: key,
|
||||||
showMenu = !showMenu;
|
label: theme.name
|
||||||
}
|
}))
|
||||||
|
);
|
||||||
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);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="relative theme-picker-menu">
|
<Dropdown
|
||||||
<Button variant="outline" size="icon" onclick={toggleMenu} aria-label="Select theme pattern">
|
items={themeItems}
|
||||||
|
selectedValue={value}
|
||||||
|
onSelect={onValueChange}
|
||||||
|
{color}
|
||||||
|
showCheckmark={true}
|
||||||
|
ariaLabel="Select theme pattern"
|
||||||
|
>
|
||||||
|
{#snippet icon()}
|
||||||
<Palette class="h-[1.2rem] w-[1.2rem]" />
|
<Palette class="h-[1.2rem] w-[1.2rem]" />
|
||||||
</Button>
|
{/snippet}
|
||||||
|
</Dropdown>
|
||||||
{#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>
|
|
||||||
|
|||||||
@@ -128,6 +128,7 @@
|
|||||||
// Force reactivity by updating the wishlist object
|
// Force reactivity by updating the wishlist object
|
||||||
wishlist.theme = theme;
|
wishlist.theme = theme;
|
||||||
}}
|
}}
|
||||||
|
color={wishlistColor}
|
||||||
/>
|
/>
|
||||||
<ColorPicker
|
<ColorPicker
|
||||||
bind:color={wishlistColor}
|
bind:color={wishlistColor}
|
||||||
|
|||||||
@@ -38,6 +38,7 @@
|
|||||||
<Navigation
|
<Navigation
|
||||||
isAuthenticated={data.isAuthenticated}
|
isAuthenticated={data.isAuthenticated}
|
||||||
showDashboardLink={true}
|
showDashboardLink={true}
|
||||||
|
color={data.wishlist.color}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Card style={headerCardStyle}>
|
<Card style={headerCardStyle}>
|
||||||
|
|||||||
@@ -123,6 +123,7 @@
|
|||||||
<Navigation
|
<Navigation
|
||||||
isAuthenticated={data.isAuthenticated}
|
isAuthenticated={data.isAuthenticated}
|
||||||
showDashboardLink={true}
|
showDashboardLink={true}
|
||||||
|
color={currentColor}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<WishlistHeader
|
<WishlistHeader
|
||||||
|
|||||||
Reference in New Issue
Block a user