feat(Board): add CandidateCard mini switcher
This commit is contained in:
@@ -0,0 +1,36 @@
|
|||||||
|
<script module>
|
||||||
|
import { defineMeta } from '@storybook/addon-svelte-csf';
|
||||||
|
import CandidateCard from './CandidateCard.svelte';
|
||||||
|
|
||||||
|
const { Story } = defineMeta({
|
||||||
|
title: 'Widgets/Board/CandidateCard',
|
||||||
|
component: CandidateCard,
|
||||||
|
tags: ['autodocs'],
|
||||||
|
parameters: {
|
||||||
|
docs: {
|
||||||
|
description: {
|
||||||
|
component:
|
||||||
|
'Compact rail switcher for one pairing: the two font names in their own fonts. Click makes the pairing focal (aria-current). Not an evaluation surface — no real-length specimen.',
|
||||||
|
},
|
||||||
|
story: { inline: false },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import type { ComponentProps } from 'svelte';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Story
|
||||||
|
name="Default"
|
||||||
|
args={{
|
||||||
|
pairing: { id: 'demo-1', headerFontId: 'Playfair Display', bodyFontId: 'Source Sans Pro' },
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{#snippet template(args: ComponentProps<typeof CandidateCard>)}
|
||||||
|
<div style="max-width: 220px;">
|
||||||
|
<CandidateCard {...args} />
|
||||||
|
</div>
|
||||||
|
{/snippet}
|
||||||
|
</Story>
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
<!--
|
||||||
|
Component: CandidateCard
|
||||||
|
Compact switcher for one pairing in the rail — NOT an evaluation surface. Shows
|
||||||
|
the two font names rendered in their own fonts at a small decorative size
|
||||||
|
(clamp/cqi is fine here: chrome, not the honest-measure specimen). Click makes
|
||||||
|
the pairing focal. Container-query driven so the same card works anywhere.
|
||||||
|
-->
|
||||||
|
<script lang="ts">
|
||||||
|
import {
|
||||||
|
FontApplicator,
|
||||||
|
type UnifiedFont,
|
||||||
|
getFontLifecycleManager,
|
||||||
|
} from '$entities/Font';
|
||||||
|
import type {
|
||||||
|
Pairing,
|
||||||
|
Role,
|
||||||
|
} from '$entities/Pairing';
|
||||||
|
import { getBoard } from '$features/CompareBoard';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
/**
|
||||||
|
* The pairing this card switches to.
|
||||||
|
*/
|
||||||
|
pairing: Pairing;
|
||||||
|
}
|
||||||
|
|
||||||
|
let { pairing }: Props = $props();
|
||||||
|
|
||||||
|
const board = getBoard();
|
||||||
|
const lifecycle = getFontLifecycleManager();
|
||||||
|
|
||||||
|
const isFocal = $derived(board.focalId === pairing.id);
|
||||||
|
const fonts = $derived(board.resolvePairingFonts(pairing));
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="
|
||||||
|
@container flex w-full flex-col gap-1 rounded-lg border p-3 text-left transition-colors
|
||||||
|
aria-current:border-indigo-500 aria-current:bg-indigo-50
|
||||||
|
border-slate-200 hover:border-slate-300
|
||||||
|
"
|
||||||
|
aria-current={isFocal ? 'true' : undefined}
|
||||||
|
onclick={() => board.setFocal(pairing.id)}
|
||||||
|
>
|
||||||
|
{@render name('header', fonts.header?.name ?? pairing.headerFontId, fonts.header)}
|
||||||
|
{@render name('body', fonts.body?.name ?? pairing.bodyFontId, fonts.body)}
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{#snippet name(role: Role, label: string, font: UnifiedFont | undefined)}
|
||||||
|
{@const size = role === 'header' ? 'clamp(0.9rem, 5cqi, 1.25rem)' : 'clamp(0.75rem, 4cqi, 1rem)'}
|
||||||
|
{#if font}
|
||||||
|
<FontApplicator
|
||||||
|
{font}
|
||||||
|
status={lifecycle.getFontStatus(font.id, board.typo[role].weight, font.features?.isVariable)}
|
||||||
|
>
|
||||||
|
<span class="block truncate" style:font-size={size}>{label}</span>
|
||||||
|
</FontApplicator>
|
||||||
|
{:else}
|
||||||
|
<span class="block truncate text-slate-500" style:font-size={size}>{label}</span>
|
||||||
|
{/if}
|
||||||
|
{/snippet}
|
||||||
Reference in New Issue
Block a user