trackers: colourful tracker icons on /trackers and home tiles, 3/4-column desktop grid with lift hover
All checks were successful
Deploy production / deploy (push) Successful in 1m9s
All checks were successful
Deploy production / deploy (push) Successful in 1m9s
This commit is contained in:
@@ -1,59 +1,81 @@
|
||||
<script lang="ts">
|
||||
// Card grid shared by the home page, /projects and /trackers.
|
||||
// `variant="tracker"` uses the colourful tracker icons (same source as the header's
|
||||
// Trackers dropdown: #lib/tracker-icons) and, with `wide`, a 3/4-column desktop grid.
|
||||
import { resolve } from '$app/paths';
|
||||
import ArrowRight from 'lucide-svelte/icons/arrow-right';
|
||||
import ExternalLink from 'lucide-svelte/icons/external-link';
|
||||
import * as Card from '#lib/components/ui/card/index.js';
|
||||
import ProjectIcon from '#lib/components/ProjectIcon.svelte';
|
||||
import TrackerIcon from '#lib/components/TrackerIcon.svelte';
|
||||
|
||||
type Item = { name: string; subtitle: string; href: string; external: boolean };
|
||||
let {
|
||||
items,
|
||||
subtitleClass = 'mt-1 block text-sm text-muted-foreground'
|
||||
}: { items: readonly Item[]; subtitleClass?: string } = $props();
|
||||
subtitleClass = 'mt-1 block text-sm text-muted-foreground',
|
||||
variant = 'project',
|
||||
wide = false
|
||||
}: {
|
||||
items: readonly Item[];
|
||||
subtitleClass?: string;
|
||||
variant?: 'project' | 'tracker';
|
||||
wide?: boolean;
|
||||
} = $props();
|
||||
|
||||
const tracker = $derived(variant === 'tracker');
|
||||
</script>
|
||||
|
||||
<ul class="grid gap-3 sm:grid-cols-2">
|
||||
{#snippet body(project: Item)}
|
||||
<span class="flex min-w-0 items-start gap-3">
|
||||
{#if tracker}
|
||||
<TrackerIcon name={project.name} class="catalog-icon mt-0.5 size-10" />
|
||||
{:else}
|
||||
<span
|
||||
class="mt-0.5 flex size-10 shrink-0 items-center justify-center rounded-md border border-border bg-[var(--site-surface)] text-[var(--site-muted)]"
|
||||
>
|
||||
<ProjectIcon name={project.name} class="size-5" />
|
||||
</span>
|
||||
{/if}
|
||||
<span class="min-w-0"
|
||||
><span class="block font-medium">{project.name}</span><span class={subtitleClass}
|
||||
>{project.subtitle}</span
|
||||
></span
|
||||
>
|
||||
</span>
|
||||
{#if project.external}
|
||||
<ExternalLink
|
||||
size={18}
|
||||
aria-hidden="true"
|
||||
class="catalog-arrow shrink-0 text-[var(--site-muted)]"
|
||||
/>
|
||||
{:else}
|
||||
<ArrowRight
|
||||
size={18}
|
||||
aria-hidden="true"
|
||||
class="catalog-arrow shrink-0 text-[var(--site-muted)]"
|
||||
/>
|
||||
{/if}
|
||||
{/snippet}
|
||||
|
||||
<ul class="grid gap-3 sm:grid-cols-2" class:wide>
|
||||
{#each items as project (project.name)}
|
||||
<li>
|
||||
<Card.Root class="h-full py-0 transition-colors hover:bg-muted/50"
|
||||
><Card.Content class="p-0">
|
||||
<Card.Root class="catalog-card h-full py-0 transition-colors hover:bg-muted/50"
|
||||
><Card.Content class="h-full p-0">
|
||||
{#if project.external}
|
||||
<a
|
||||
href={project.href}
|
||||
rel="external"
|
||||
class="flex min-h-28 items-start justify-between gap-4 p-5"
|
||||
class="flex h-full min-h-28 items-start justify-between gap-4 p-5"
|
||||
>
|
||||
<span class="flex min-w-0 items-start gap-3">
|
||||
<span
|
||||
class="mt-0.5 flex size-10 shrink-0 items-center justify-center rounded-md border border-border bg-[var(--site-surface)] text-[var(--site-muted)]"
|
||||
>
|
||||
<ProjectIcon name={project.name} class="size-5" />
|
||||
</span>
|
||||
<span class="min-w-0"
|
||||
><span class="block font-medium">{project.name}</span><span class={subtitleClass}
|
||||
>{project.subtitle}</span
|
||||
></span
|
||||
>
|
||||
</span><ExternalLink size={18} aria-hidden="true" class="text-[var(--site-muted)]" />
|
||||
{@render body(project)}
|
||||
</a>
|
||||
{:else}
|
||||
<a
|
||||
href={resolve(project.href as '/')}
|
||||
class="flex min-h-28 items-start justify-between gap-4 p-5"
|
||||
class="flex h-full min-h-28 items-start justify-between gap-4 p-5"
|
||||
>
|
||||
<span class="flex min-w-0 items-start gap-3">
|
||||
<span
|
||||
class="mt-0.5 flex size-10 shrink-0 items-center justify-center rounded-md border border-border bg-[var(--site-surface)] text-[var(--site-muted)]"
|
||||
>
|
||||
<ProjectIcon name={project.name} class="size-5" />
|
||||
</span>
|
||||
<span class="min-w-0"
|
||||
><span class="block font-medium">{project.name}</span><span class={subtitleClass}
|
||||
>{project.subtitle}</span
|
||||
></span
|
||||
>
|
||||
</span><ArrowRight size={18} aria-hidden="true" class="text-[var(--site-muted)]" />
|
||||
{@render body(project)}
|
||||
</a>
|
||||
{/if}
|
||||
</Card.Content></Card.Root
|
||||
@@ -61,3 +83,56 @@
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
|
||||
<style>
|
||||
@media (min-width: 768px) {
|
||||
.wide {
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
}
|
||||
}
|
||||
@media (min-width: 1024px) {
|
||||
.wide {
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||
}
|
||||
}
|
||||
ul :global(.catalog-card) {
|
||||
transition:
|
||||
transform 160ms cubic-bezier(0.2, 0.8, 0.2, 1),
|
||||
background-color 160ms ease,
|
||||
border-color 160ms ease,
|
||||
box-shadow 160ms ease;
|
||||
}
|
||||
ul :global(.catalog-card:hover) {
|
||||
border-color: color-mix(in oklab, var(--foreground) 22%, transparent);
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 10px 24px -14px rgb(0 0 0 / 0.5);
|
||||
}
|
||||
ul :global(.catalog-card:active) {
|
||||
transform: translateY(0);
|
||||
box-shadow: none;
|
||||
}
|
||||
ul :global(.catalog-icon),
|
||||
ul :global(.catalog-arrow) {
|
||||
transition: transform 160ms cubic-bezier(0.2, 0.8, 0.2, 1);
|
||||
}
|
||||
ul :global(.catalog-card:hover .catalog-icon) {
|
||||
transform: scale(1.06);
|
||||
}
|
||||
ul :global(.catalog-card:hover .catalog-arrow) {
|
||||
transform: translateX(2px);
|
||||
}
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
ul :global(.catalog-card),
|
||||
ul :global(.catalog-icon),
|
||||
ul :global(.catalog-arrow) {
|
||||
transition:
|
||||
background-color 160ms ease,
|
||||
border-color 160ms ease;
|
||||
}
|
||||
ul :global(.catalog-card:hover),
|
||||
ul :global(.catalog-card:hover .catalog-icon),
|
||||
ul :global(.catalog-card:hover .catalog-arrow) {
|
||||
transform: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -139,6 +139,7 @@
|
||||
</h2>
|
||||
<CatalogGrid
|
||||
items={visibleTrackers(data.isOwner)}
|
||||
variant="tracker"
|
||||
subtitleClass="mt-1 block text-base text-[var(--site-muted)]"
|
||||
/>
|
||||
</section>
|
||||
|
||||
@@ -10,5 +10,5 @@
|
||||
<p class="mb-10 text-[var(--site-muted)]">
|
||||
Personal data I keep track of: games, money, houses, books, prices, animals and lists.
|
||||
</p>
|
||||
<CatalogGrid items={visibleTrackers(data.isOwner)} />
|
||||
<CatalogGrid items={visibleTrackers(data.isOwner)} variant="tracker" wide />
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user