98 lines
2.5 KiB
Svelte
98 lines
2.5 KiB
Svelte
<script lang="ts">
|
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '$lib/components/ui/card';
|
|
import { Button } from '$lib/components/ui/button';
|
|
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: any[];
|
|
emptyMessage: string;
|
|
emptyDescription?: string;
|
|
emptyActionLabel?: string;
|
|
emptyActionHref?: string;
|
|
fallbackColor?: string | null;
|
|
fallbackTheme?: string | null;
|
|
headerAction?: Snippet;
|
|
searchBar?: Snippet;
|
|
children: Snippet<[any]>;
|
|
} = $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>
|