Files
frontend-svelte/src/features/DisplayFont/ui/FontSampler/FontSampler.svelte
T

173 lines
5.8 KiB
Svelte
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!--
Component: FontSampler
Displays a sample text with a given font in a contenteditable element.
Visual design matches FontCard: sharp corners, red hover accent, header stats.
-->
<script lang="ts">
import {
FontApplicator,
type FontLoadStatus,
type UnifiedFont,
} from '$entities/Font';
import { typographySettingsStore } from '$features/AdjustTypography/model';
import {
Badge,
ContentEditable,
Divider,
Footnote,
Stat,
} from '$shared/ui';
import { fly } from 'svelte/transition';
interface Props {
/**
* Font info
*/
font: UnifiedFont;
/**
* Current font-load status, supplied by the composing widget so this
* component (and FontApplicator) stay decoupled from the lifecycle store.
* `undefined` means not tracked yet (treated as not-yet-revealed).
*/
status: FontLoadStatus | undefined;
/**
* Sample text
*/
text: string;
/**
* Position index
* @default 0
*/
index?: number;
}
let { font, status, text = $bindable(), index = 0 }: Props = $props();
// Extract provider badge with fallback
const providerBadge = $derived(
font.providerBadge
?? (font.provider === 'google' ? 'Google Fonts' : 'Fontshare'),
);
const stats = $derived([
{ label: 'SZ', value: `${typographySettingsStore.renderedSize}PX` },
{ label: 'WGT', value: `${typographySettingsStore.weight}` },
{ label: 'LH', value: typographySettingsStore.height?.toFixed(2) },
{ label: 'LTR', value: `${typographySettingsStore.spacing}` },
]);
</script>
<div
in:fly={{ y: 20, duration: 400, delay: index * 50 }}
class="
group relative
w-full h-full
surface-card
hover:border-brand dark:hover:border-brand
hover:shadow-stamp-card
transition-all duration-normal
overflow-hidden
flex flex-col
min-h-60
rounded-none
"
style:font-weight={typographySettingsStore.weight}
>
<!-- ── Header bar ─────────────────────────────────────────────────── -->
<div
class="
flex items-center justify-between
px-4 sm:px-5 md:px-6 py-3 sm:py-4
border-b border-subtle
bg-paper dark:bg-dark-card
"
>
<!-- Left: index · name · type badge · provider badge -->
<div class="flex items-center gap-2 sm:gap-4 min-w-0 shrink-0">
<span class="font-mono text-2xs tracking-widest text-neutral-400 uppercase leading-none shrink-0">
{String(index + 1).padStart(2, '0')}
</span>
<Divider orientation="vertical" class="h-3 shrink-0" />
<span
class="font-primary font-bold text-sm text-swiss-black dark:text-neutral-200 leading-none tracking-tight uppercase truncate"
>
{font.name}
</span>
{#if font?.category}
<Badge size="xs" variant="default" nowrap>
{font?.category}
</Badge>
{/if}
<!-- Provider badge -->
{#if providerBadge}
<Badge size="xs" variant="default" nowrap data-provider={font.provider}>
{providerBadge}
</Badge>
{/if}
</div>
<!-- Right: stats, hidden on mobile, fade in on group hover -->
<div
class="
flex-1 min-w-0
hidden md:block @container
opacity-50 group-hover:opacity-100
transition-opacity duration-200 ml-4
"
>
<!-- Switches: narrow → 2×2, wide enough → 1 row -->
<div
class="
max-w-64 ml-auto
grid grid-cols-2 gap-x-3 gap-y-2
@[160px]:grid-cols-4 @[160px]:gap-y-0
items-center
"
>
{#each stats as stat}
<Stat label={stat.label} value={stat.value} />
{/each}
</div>
</div>
</div>
<!-- ── Main content area ──────────────────────────────────────────── -->
<div class="flex-1 p-4 sm:p-5 md:p-8 flex items-center overflow-hidden bg-paper dark:bg-dark-card relative z-10">
<FontApplicator {font} {status}>
<ContentEditable
bind:text
fontSize={typographySettingsStore.renderedSize}
lineHeight={typographySettingsStore.height}
letterSpacing={typographySettingsStore.spacing}
/>
</FontApplicator>
</div>
<!-- ── Mobile stats footer (md:hidden — header stats take over above) -->
<div class="md:hidden px-4 sm:px-5 py-1.5 sm:py-2 border-t border-subtle flex gap-2 sm:gap-4 bg-paper dark:bg-dark-card mt-auto">
{#each stats as stat, i}
<Footnote class="text-5xs sm:text-4xs tracking-wider {i === 0 ? 'ml-auto' : ''}">
{stat.label}:{stat.value}
</Footnote>
{#if i < stats.length - 1}
<div class="w-px h-2 sm:h-2.5 self-center bg-black/10 dark:bg-white/10 hidden sm:block"></div>
{/if}
{/each}
</div>
<!-- ── Red hover line ─────────────────────────────────────────────── -->
<div
class="
absolute bottom-0 left-0 right-0
w-full h-0.5 bg-brand
scale-x-0 group-hover:scale-x-100
transition-transform cubic-bezier(0.25, 0.1, 0.25, 1) origin-left duration-400
z-10
"
>
</div>
</div>