- Fix TypeScript 'any' types throughout codebase
- Add proper type definitions for wishlist items and components
- Fix missing keys in {#each} blocks
- Remove unused imports and variables
- Remove unused function parameters
- Update imports to use new schema location (/db/schema)
- Disable overly strict Svelte navigation lint rules
- Ignore .svelte.ts files from ESLint (handled by Svelte compiler)
103 lines
2.8 KiB
Svelte
103 lines
2.8 KiB
Svelte
<script lang="ts">
|
|
import {
|
|
Card,
|
|
CardContent,
|
|
CardDescription,
|
|
CardHeader,
|
|
CardTitle
|
|
} from '$lib/components/ui/card';
|
|
import EmptyState from '$lib/components/layout/EmptyState.svelte';
|
|
import type { Snippet } from 'svelte';
|
|
import { flip } from 'svelte/animate';
|
|
import { getCardStyle } from '$lib/utils/colors';
|
|
import ThemeCard from '$lib/components/themes/ThemeCard.svelte';
|
|
|
|
let {
|
|
title,
|
|
description,
|
|
items,
|
|
emptyMessage,
|
|
emptyDescription,
|
|
emptyActionLabel,
|
|
emptyActionHref,
|
|
fallbackColor = null,
|
|
fallbackTheme = null,
|
|
headerAction,
|
|
searchBar,
|
|
children
|
|
}: {
|
|
title: string;
|
|
description: string;
|
|
items: Array<Record<string, unknown>>;
|
|
emptyMessage: string;
|
|
emptyDescription?: string;
|
|
emptyActionLabel?: string;
|
|
emptyActionHref?: string;
|
|
fallbackColor?: string | null;
|
|
fallbackTheme?: string | null;
|
|
headerAction?: Snippet;
|
|
searchBar?: Snippet;
|
|
children: Snippet<[Record<string, unknown>]>;
|
|
} = $props();
|
|
|
|
const cardStyle = $derived(getCardStyle(fallbackColor, null));
|
|
|
|
let scrollContainer: HTMLElement | null = null;
|
|
|
|
function handleWheel(event: WheelEvent) {
|
|
if (!scrollContainer) return;
|
|
|
|
// Check if we have horizontal overflow
|
|
const hasHorizontalScroll = scrollContainer.scrollWidth > scrollContainer.clientWidth;
|
|
|
|
if (hasHorizontalScroll && event.deltaY !== 0) {
|
|
event.preventDefault();
|
|
scrollContainer.scrollLeft += event.deltaY;
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<Card style={cardStyle} class="relative overflow-hidden">
|
|
<ThemeCard themeName={fallbackTheme} color={fallbackColor} showPattern={false} />
|
|
<CardHeader class="relative z-10">
|
|
<div class="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
|
|
<div class="flex-1 min-w-0">
|
|
<CardTitle>{title}</CardTitle>
|
|
<CardDescription>{description}</CardDescription>
|
|
</div>
|
|
{#if headerAction}
|
|
<div class="flex-shrink-0">
|
|
{@render headerAction()}
|
|
</div>
|
|
{/if}
|
|
</div>
|
|
{#if searchBar}
|
|
<div class="mt-4">
|
|
{@render searchBar()}
|
|
</div>
|
|
{/if}
|
|
</CardHeader>
|
|
<CardContent class="relative z-10">
|
|
{#if items && items.length > 0}
|
|
<div
|
|
bind:this={scrollContainer}
|
|
onwheel={handleWheel}
|
|
class="flex overflow-x-auto gap-4 pb-4 -mx-6 px-6"
|
|
>
|
|
{#each items as item (item.id)}
|
|
<div class="flex-shrink-0 w-80" animate:flip={{ duration: 300 }}>
|
|
{@render children(item)}
|
|
</div>
|
|
{/each}
|
|
</div>
|
|
{:else}
|
|
<EmptyState
|
|
message={emptyMessage}
|
|
description={emptyDescription}
|
|
actionLabel={emptyActionLabel}
|
|
actionHref={emptyActionHref}
|
|
/>
|
|
{/if}
|
|
</CardContent>
|
|
</Card>
|