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