Files
wishlist/src/lib/components/wishlist/WishlistHeader.svelte
T

212 lines
5.8 KiB
Svelte

<script lang="ts">
import { Card, CardContent } from "$lib/components/ui/card";
import { Input } from "$lib/components/ui/input";
import { Label } from "$lib/components/ui/label";
import { Textarea } from "$lib/components/ui/textarea";
import { Pencil, Check, X } from "lucide-svelte";
import ColorPicker from "$lib/components/ui/ColorPicker.svelte";
import ThemePicker from "$lib/components/ui/theme-picker.svelte";
import type { Wishlist } from "$lib/server/schema";
import { languageStore } from '$lib/stores/language.svelte';
import { getCardStyle } from '$lib/utils/colors';
let {
wishlist,
onTitleUpdate,
onDescriptionUpdate,
onColorUpdate,
onEndDateUpdate,
onThemeUpdate
}: {
wishlist: Wishlist;
onTitleUpdate: (title: string) => Promise<boolean>;
onDescriptionUpdate: (description: string | null) => Promise<boolean>;
onColorUpdate: (color: string | null) => void;
onEndDateUpdate: (endDate: string | null) => void;
onThemeUpdate: (theme: string | null) => void;
} = $props();
const t = $derived(languageStore.t);
let editingTitle = $state(false);
let editingDescription = $state(false);
let wishlistTitle = $state(wishlist.title);
let wishlistDescription = $state(wishlist.description || "");
let wishlistColor = $state<string | null>(wishlist.color);
let wishlistTheme = $state<string>(wishlist.theme || 'none');
let wishlistEndDate = $state<string | null>(
wishlist.endDate
? new Date(wishlist.endDate).toISOString().split("T")[0]
: null,
);
const cardStyle = $derived(getCardStyle(null, wishlistColor));
async function saveTitle() {
if (!wishlistTitle.trim()) {
wishlistTitle = wishlist.title;
editingTitle = false;
return;
}
const success = await onTitleUpdate(wishlistTitle.trim());
if (success) {
editingTitle = false;
} else {
wishlistTitle = wishlist.title;
editingTitle = false;
}
}
async function saveDescription() {
const success = await onDescriptionUpdate(wishlistDescription.trim() || null);
if (success) {
editingDescription = false;
} else {
wishlistDescription = wishlist.description || "";
editingDescription = false;
}
}
function handleEndDateChange(e: Event) {
const input = e.target as HTMLInputElement;
wishlistEndDate = input.value || null;
onEndDateUpdate(wishlistEndDate);
}
function clearEndDate() {
wishlistEndDate = null;
onEndDateUpdate(null);
}
</script>
<!-- Title Header -->
<div class="flex items-center justify-between gap-4 mb-6">
<div class="flex items-center gap-2 flex-1 min-w-0">
{#if editingTitle}
<Input
bind:value={wishlistTitle}
class="text-3xl font-bold h-auto py-0 leading-[2.25rem]"
onkeydown={(e) => {
if (e.key === "Enter") {
saveTitle();
} else if (e.key === "Escape") {
wishlistTitle = wishlist.title;
editingTitle = false;
}
}}
autofocus
/>
{:else}
<h1 class="text-3xl font-bold leading-[2.25rem]">{wishlistTitle}</h1>
{/if}
<button
type="button"
onclick={() => {
if (editingTitle) {
saveTitle();
} else {
editingTitle = true;
}
}}
class="shrink-0 w-8 h-8 flex items-center justify-center rounded-full border border-input hover:bg-accent transition-colors"
aria-label={editingTitle ? "Save title" : "Edit title"}
>
{#if editingTitle}
<Check class="w-4 h-4" />
{:else}
<Pencil class="w-4 h-4" />
{/if}
</button>
</div>
<div class="flex items-center gap-2 shrink-0">
<ThemePicker
value={wishlistTheme}
onValueChange={async (theme) => {
wishlistTheme = theme;
onThemeUpdate(theme);
// Force reactivity by updating the wishlist object
wishlist.theme = theme;
}}
/>
<ColorPicker
bind:color={wishlistColor}
onchange={() => onColorUpdate(wishlistColor)}
size="sm"
/>
</div>
</div>
<!-- Settings Card -->
<Card style={cardStyle}>
<CardContent class="pt-6 space-y-4">
<!-- Description -->
<div class="space-y-2">
<div class="flex items-center justify-between gap-2">
<Label for="wishlist-description">{t.form.descriptionOptional}</Label>
<button
type="button"
onclick={() => {
if (editingDescription) {
saveDescription();
} else {
editingDescription = true;
}
}}
class="flex-shrink-0 w-8 h-8 flex items-center justify-center rounded-full border border-input hover:bg-accent transition-colors"
aria-label={editingDescription ? "Save description" : "Edit description"}
>
{#if editingDescription}
<Check class="w-4 h-4" />
{:else}
<Pencil class="w-4 h-4" />
{/if}
</button>
</div>
{#if editingDescription}
<Textarea
id="wishlist-description"
bind:value={wishlistDescription}
class="w-full"
rows={3}
onkeydown={(e) => {
if (e.key === "Escape") {
wishlistDescription = wishlist.description || "";
editingDescription = false;
}
}}
autofocus
/>
{:else}
<div class="w-full py-2 px-3 rounded-md border border-input bg-transparent text-sm min-h-[80px]">
{wishlistDescription || t.form.noDescription}
</div>
{/if}
</div>
<!-- End Date -->
<div class="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-2 sm:gap-4">
<Label for="wishlist-end-date">{t.form.endDateOptional}</Label>
<div class="flex items-center gap-2">
{#if wishlistEndDate}
<button
type="button"
onclick={clearEndDate}
class="flex-shrink-0 w-8 h-8 flex items-center justify-center rounded-full border border-input hover:bg-accent transition-colors"
aria-label="Clear end date"
>
<X class="w-4 h-4" />
</button>
{/if}
<Input
id="wishlist-end-date"
type="date"
value={wishlistEndDate || ""}
onchange={handleEndDateChange}
class="w-full sm:w-auto"
/>
</div>
</div>
</CardContent>
</Card>