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">
|
<script lang="ts">
|
||||||
// Card grid shared by the home page, /projects and /trackers.
|
// 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 { resolve } from '$app/paths';
|
||||||
import ArrowRight from 'lucide-svelte/icons/arrow-right';
|
import ArrowRight from 'lucide-svelte/icons/arrow-right';
|
||||||
import ExternalLink from 'lucide-svelte/icons/external-link';
|
import ExternalLink from 'lucide-svelte/icons/external-link';
|
||||||
import * as Card from '#lib/components/ui/card/index.js';
|
import * as Card from '#lib/components/ui/card/index.js';
|
||||||
import ProjectIcon from '#lib/components/ProjectIcon.svelte';
|
import ProjectIcon from '#lib/components/ProjectIcon.svelte';
|
||||||
|
import TrackerIcon from '#lib/components/TrackerIcon.svelte';
|
||||||
|
|
||||||
type Item = { name: string; subtitle: string; href: string; external: boolean };
|
type Item = { name: string; subtitle: string; href: string; external: boolean };
|
||||||
let {
|
let {
|
||||||
items,
|
items,
|
||||||
subtitleClass = 'mt-1 block text-sm text-muted-foreground'
|
subtitleClass = 'mt-1 block text-sm text-muted-foreground',
|
||||||
}: { items: readonly Item[]; subtitleClass?: string } = $props();
|
variant = 'project',
|
||||||
|
wide = false
|
||||||
|
}: {
|
||||||
|
items: readonly Item[];
|
||||||
|
subtitleClass?: string;
|
||||||
|
variant?: 'project' | 'tracker';
|
||||||
|
wide?: boolean;
|
||||||
|
} = $props();
|
||||||
|
|
||||||
|
const tracker = $derived(variant === 'tracker');
|
||||||
</script>
|
</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)}
|
{#each items as project (project.name)}
|
||||||
<li>
|
<li>
|
||||||
<Card.Root class="h-full py-0 transition-colors hover:bg-muted/50"
|
<Card.Root class="catalog-card h-full py-0 transition-colors hover:bg-muted/50"
|
||||||
><Card.Content class="p-0">
|
><Card.Content class="h-full p-0">
|
||||||
{#if project.external}
|
{#if project.external}
|
||||||
<a
|
<a
|
||||||
href={project.href}
|
href={project.href}
|
||||||
rel="external"
|
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">
|
{@render body(project)}
|
||||||
<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)]" />
|
|
||||||
</a>
|
</a>
|
||||||
{:else}
|
{:else}
|
||||||
<a
|
<a
|
||||||
href={resolve(project.href as '/')}
|
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">
|
{@render body(project)}
|
||||||
<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)]" />
|
|
||||||
</a>
|
</a>
|
||||||
{/if}
|
{/if}
|
||||||
</Card.Content></Card.Root
|
</Card.Content></Card.Root
|
||||||
@@ -61,3 +83,56 @@
|
|||||||
</li>
|
</li>
|
||||||
{/each}
|
{/each}
|
||||||
</ul>
|
</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>
|
</h2>
|
||||||
<CatalogGrid
|
<CatalogGrid
|
||||||
items={visibleTrackers(data.isOwner)}
|
items={visibleTrackers(data.isOwner)}
|
||||||
|
variant="tracker"
|
||||||
subtitleClass="mt-1 block text-base text-[var(--site-muted)]"
|
subtitleClass="mt-1 block text-base text-[var(--site-muted)]"
|
||||||
/>
|
/>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@@ -10,5 +10,5 @@
|
|||||||
<p class="mb-10 text-[var(--site-muted)]">
|
<p class="mb-10 text-[var(--site-muted)]">
|
||||||
Personal data I keep track of: games, money, houses, books, prices, animals and lists.
|
Personal data I keep track of: games, money, houses, books, prices, animals and lists.
|
||||||
</p>
|
</p>
|
||||||
<CatalogGrid items={visibleTrackers(data.isOwner)} />
|
<CatalogGrid items={visibleTrackers(data.isOwner)} variant="tracker" wide />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user