feat(FontSampler): refactor component to align it with new design
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
<!--
|
<!--
|
||||||
Component: FontSampler
|
Component: FontSampler
|
||||||
Displays a sample text with a given font in a contenteditable element.
|
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">
|
<script lang="ts">
|
||||||
import {
|
import {
|
||||||
@@ -11,78 +12,115 @@ import { controlManager } from '$features/SetupFont';
|
|||||||
import {
|
import {
|
||||||
ContentEditable,
|
ContentEditable,
|
||||||
Footnote,
|
Footnote,
|
||||||
// IconButton,
|
|
||||||
} from '$shared/ui';
|
} from '$shared/ui';
|
||||||
// import XIcon from '@lucide/svelte/icons/x';
|
import { fly } from 'svelte/transition';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
/**
|
/** Font info */
|
||||||
* Font info
|
|
||||||
*/
|
|
||||||
font: UnifiedFont;
|
font: UnifiedFont;
|
||||||
/**
|
/** Editable sample text */
|
||||||
* Text to display
|
|
||||||
*/
|
|
||||||
text: string;
|
text: string;
|
||||||
/**
|
/** Position index — drives the staggered entrance delay */
|
||||||
* Index of the font sampler
|
|
||||||
*/
|
|
||||||
index?: number;
|
index?: number;
|
||||||
/**
|
|
||||||
* Font settings
|
|
||||||
*/
|
|
||||||
fontSize?: number;
|
|
||||||
lineHeight?: number;
|
|
||||||
letterSpacing?: number;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let { font, text = $bindable(), index = 0, ...restProps }: Props = $props();
|
let { font, text = $bindable(), index = 0 }: Props = $props();
|
||||||
|
|
||||||
const fontWeight = $derived(controlManager.weight);
|
const fontWeight = $derived(controlManager.weight);
|
||||||
const fontSize = $derived(controlManager.renderedSize);
|
const fontSize = $derived(controlManager.renderedSize);
|
||||||
const lineHeight = $derived(controlManager.height);
|
const lineHeight = $derived(controlManager.height);
|
||||||
const letterSpacing = $derived(controlManager.spacing);
|
const letterSpacing = $derived(controlManager.spacing);
|
||||||
|
|
||||||
|
// Adjust the property name to match your UnifiedFont type
|
||||||
|
const fontType = $derived((font as any).type ?? (font as any).category ?? '');
|
||||||
|
|
||||||
|
const stats = $derived([
|
||||||
|
{ label: 'SZ', value: `${fontSize}PX` },
|
||||||
|
{ label: 'WGT', value: `${fontWeight}` },
|
||||||
|
{ label: 'LH', value: lineHeight?.toFixed(2) },
|
||||||
|
{ label: 'LTR', value: `${letterSpacing}` },
|
||||||
|
]);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
|
in:fly={{ y: 20, duration: 400, delay: index * 50 }}
|
||||||
class="
|
class="
|
||||||
w-full h-full rounded-xl sm:rounded-2xl
|
group relative
|
||||||
|
bg-paper dark:bg-dark-card
|
||||||
|
border border-black/5 dark:border-white/10
|
||||||
|
hover:border-brand dark:hover:border-brand
|
||||||
|
hover:shadow-[5px_5px_0px_0px_theme('colors.brand')]
|
||||||
|
transition-all duration-200
|
||||||
|
overflow-hidden
|
||||||
flex flex-col
|
flex flex-col
|
||||||
bg-background-80
|
min-h-60
|
||||||
border border-border-muted
|
rounded-none
|
||||||
shadow-[0_1px_3px_rgba(0,0,0,0.04)]
|
|
||||||
relative overflow-hidden
|
|
||||||
"
|
"
|
||||||
style:font-weight={fontWeight}
|
style:font-weight={fontWeight}
|
||||||
>
|
>
|
||||||
<div class="px-4 sm:px-5 md:px-6 py-2.5 sm:py-3 border-b border-border-subtle flex items-center justify-between">
|
<!-- ── Header bar ─────────────────────────────────────────────────── -->
|
||||||
<div class="flex items-center gap-2 sm:gap-2.5">
|
<div
|
||||||
<Footnote>
|
class="
|
||||||
typeface_{String(index).padStart(3, '0')}
|
flex items-center justify-between
|
||||||
</Footnote>
|
px-4 sm:px-5 md:px-6 py-3 sm:py-4
|
||||||
<div class="w-px h-2 sm:h-2.5 bg-border-subtle"></div>
|
border-b border-black/5 dark:border-white/10
|
||||||
<div class="font-bold text-foreground">
|
bg-paper dark:bg-dark-card
|
||||||
{font.name}
|
"
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!--
|
|
||||||
<IconButton
|
|
||||||
onclick={removeSample}
|
|
||||||
class="w-5 h-5 rounded-full hover:bg-transparent flex items-center justify-center transition-colors group translate-x-1/2 cursor-pointer"
|
|
||||||
>
|
>
|
||||||
{#snippet icon({ className })}
|
<!-- Left: index · name · type badge -->
|
||||||
<XIcon class={className} />
|
<div class="flex items-center gap-2 sm:gap-4 min-w-0">
|
||||||
{/snippet}
|
<span class="font-mono text-[0.625rem] tracking-widest text-neutral-400 uppercase leading-none shrink-0">
|
||||||
</IconButton>
|
{String(index + 1).padStart(2, '0')}
|
||||||
-->
|
</span>
|
||||||
|
|
||||||
|
<div class="w-px h-3 bg-black/10 dark:bg-white/10 shrink-0"></div>
|
||||||
|
|
||||||
|
<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 fontType}
|
||||||
|
<span
|
||||||
|
class="
|
||||||
|
hidden sm:inline-block shrink-0
|
||||||
|
px-2 py-0.5
|
||||||
|
border border-black/10 dark:border-white/10 rounded-full
|
||||||
|
text-[0.5rem] font-secondary font-medium uppercase
|
||||||
|
text-neutral-500 tracking-wide leading-none
|
||||||
|
"
|
||||||
|
>
|
||||||
|
{fontType}
|
||||||
|
</span>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="p-4 sm:p-5 md:p-8 relative z-10">
|
<!-- Right: stats, hidden on mobile, fade in on group hover -->
|
||||||
|
<div class="hidden md:flex items-center gap-3 lg:gap-4 opacity-50 group-hover:opacity-100 transition-opacity duration-200 shrink-0 ml-4">
|
||||||
|
{#each stats as stat, i}
|
||||||
|
<div class="flex items-center gap-1">
|
||||||
|
<span class="font-mono text-[0.5rem] tracking-widest text-neutral-400 uppercase">
|
||||||
|
{stat.label}:
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
class="font-mono text-[0.5rem] font-bold tracking-widest text-swiss-black dark:text-neutral-200 uppercase"
|
||||||
|
>
|
||||||
|
{stat.value}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
{#if i < stats.length - 1}
|
||||||
|
<div class="w-px h-2 bg-black/10 dark:bg-white/10"></div>
|
||||||
|
{/if}
|
||||||
|
{/each}
|
||||||
|
</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} weight={fontWeight}>
|
<FontApplicator {font} weight={fontWeight}>
|
||||||
<ContentEditable
|
<ContentEditable
|
||||||
bind:text
|
bind:text
|
||||||
{...restProps}
|
|
||||||
{fontSize}
|
{fontSize}
|
||||||
{lineHeight}
|
{lineHeight}
|
||||||
{letterSpacing}
|
{letterSpacing}
|
||||||
@@ -90,21 +128,26 @@ const letterSpacing = $derived(controlManager.spacing);
|
|||||||
</FontApplicator>
|
</FontApplicator>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="px-4 sm:px-5 md:px-6 py-1.5 sm:py-2 border-t border-border-subtle w-full flex flex-row gap-2 sm:gap-4 bg-background mt-auto">
|
<!-- ── Mobile stats footer (md:hidden — header stats take over above) -->
|
||||||
<Footnote class="text-[7px] sm:text-[8px] tracking-wider ml-auto">
|
<div class="md:hidden px-4 sm:px-5 py-1.5 sm:py-2 border-t border-black/5 dark:border-white/10 flex gap-2 sm:gap-4 bg-paper dark:bg-dark-card mt-auto">
|
||||||
SZ:{fontSize}PX
|
{#each stats as stat, i}
|
||||||
</Footnote>
|
<Footnote class="text-[0.4375rem] sm:text-[0.5rem] tracking-wider {i === 0 ? 'ml-auto' : ''}">
|
||||||
<div class="w-px h-2 sm:h-2.5 self-center bg-border-subtle hidden sm:block"></div>
|
{stat.label}:{stat.value}
|
||||||
<Footnote class="text-[7px] sm:text-[8px] tracking-wider">
|
|
||||||
WGT:{fontWeight}
|
|
||||||
</Footnote>
|
|
||||||
<div class="w-px h-2 sm:h-2.5 self-center bg-border-subtle hidden sm:block"></div>
|
|
||||||
<Footnote class="text-[7px] sm:text-[8px] tracking-wider">
|
|
||||||
LH:{lineHeight?.toFixed(2)}
|
|
||||||
</Footnote>
|
|
||||||
<div class="w-px h-2 sm:h-2.5 self-center bg-border-subtle hidden sm:block"></div>
|
|
||||||
<Footnote class="text-[0.4375rem] sm:text-[0.5rem] tracking-wider">
|
|
||||||
LTR:{letterSpacing}
|
|
||||||
</Footnote>
|
</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
|
||||||
|
w-full h-0.5 bg-brand
|
||||||
|
scale-x-0 group-hover:scale-x-100
|
||||||
|
transition-transform origin-left duration-300
|
||||||
|
"
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user