summaryrefslogblamecommitdiffstatshomepage
path: root/frontend/src/lib/components/Card.svelte
blob: 8cfe34dcbc51c052d09f701a7bd2dfaecb00b5c9 (plain) (tree)
1
2
3
4
5
6
7
8
9
10
11
12
13




                                                          







                                         




                                     

                                   
                                 







                                                      

                         
                       

                            



              
                                                                                                                                                                                     

                                              
                 
 
                             










                                                                              

                                                                 
                                                                                                                    
















                                                                                                               
                                                                                                                 
                                                      
                                  





                                                          

























                                                   
<script lang="ts">
	import type { ImageFragment } from '$gql/graphql';
	import { src } from '$lib/Utils';
	import Star from '$lib/icons/Star.svelte';
	import type { Snippet } from 'svelte';

	interface CardDetails {
		title: string;
		favourite?: boolean;
		subtitle?: string | null;
		cover?: ImageFragment;
	}

	interface Props {
		href: string;
		details: CardDetails;
		compact?: boolean;
		coverOnly?: boolean;
		overlay?: Snippet;
		children?: Snippet;
		footer?: Snippet;
		onclick?: (event: MouseEvent) => void;
	}

	let {
		href,
		details,
		compact = false,
		coverOnly = false,
		overlay,
		children,
		footer,
		onclick
	}: Props = $props();
</script>

<a
	{href}
	class="grid-card-v sm:grid-card-h relative grid overflow-hidden rounded-sm bg-slate-900 shadow-md shadow-slate-950/30 focus-visible:outline-4 focus-visible:outline-blue-600"
	class:compact
	class:grid-card-cover-only={coverOnly}
	{onclick}
>
	{@render overlay?.()}
	{#if details.cover}
		<img
			class="h-full w-full object-cover object-[center_top]"
			width={details.cover.width}
			height={details.cover.height}
			src={src(details.cover)}
			alt=""
			title={details.title}
		/>
	{/if}
	{#if !coverOnly}
		<article class="p flex h-full flex-col p-2 pb-1">
			<header class="mb-2">
				<h2 class="self-center text-sm font-medium [grid-area:title]" title={details.title}>
					{details.title}
				</h2>
				{#if details.subtitle}
					<h3
						class="ellipsis-nowrap text-xs opacity-60 [grid-area:subtitle]"
						title={details.subtitle}
					>
						{details.subtitle}
					</h3>
				{/if}
				{#if details.favourite}
					<div class="flex items-center text-lg [grid-area:fav]">
						<Star favourite />
					</div>
				{/if}
			</header>

			<section class="max-h-full grow overflow-auto border-y border-slate-800/80 pt-2 text-xs">
				{@render children?.()}
			</section>

			{#if footer}
				<div class="mt-1 text-xs">
					{@render footer()}
				</div>
			{/if}
		</article>
	{/if}
</a>

<style>
	a.compact {
		grid-template-columns: 175px 1fr;
		grid-template-rows: 250px;
	}

	img {
		border-start-start-radius: inherit;
		border-end-start-radius: inherit;
	}

	article > header {
		display: grid;

		grid-template-columns: 1fr auto;
		grid-template-rows: auto;

		grid-template-areas:
			'title fav'
			'subtitle fav';
	}
</style>