feat(FontApplicator): add skeleton snippet prop to replace blur loading state

This commit is contained in:
Ilia Mashkov
2026-04-20 22:20:44 +03:00
parent 092b58e651
commit ecdb1e016d
@@ -1,14 +1,11 @@
<!-- <!--
Component: FontApplicator Component: FontApplicator
Loads fonts from fontshare with link tag Applies a font to its children once the font file is loaded.
- Loads font only if it's not already applied Shows the skeleton snippet while loading; falls back to system font if no skeleton is provided.
- Reacts to font load status to show/hide content
- Adds smooth transition when font appears
--> -->
<script lang="ts"> <script lang="ts">
import clsx from 'clsx'; import clsx from 'clsx';
import type { Snippet } from 'svelte'; import type { Snippet } from 'svelte';
import { prefersReducedMotion } from 'svelte/motion';
import { import {
DEFAULT_FONT_WEIGHT, DEFAULT_FONT_WEIGHT,
type UnifiedFont, type UnifiedFont,
@@ -33,6 +30,11 @@ interface Props {
* Content snippet * Content snippet
*/ */
children?: Snippet; children?: Snippet;
/**
* Shown while the font file is loading.
* When omitted, children render in system font until ready.
*/
skeleton?: Snippet;
} }
let { let {
@@ -40,6 +42,7 @@ let {
weight = DEFAULT_FONT_WEIGHT, weight = DEFAULT_FONT_WEIGHT,
className, className,
children, children,
skeleton,
}: Props = $props(); }: Props = $props();
const status = $derived( const status = $derived(
@@ -50,30 +53,16 @@ const status = $derived(
), ),
); );
// The "Show" condition: Font is loaded OR it errored out OR it's a noTouch preview (like in search)
const shouldReveal = $derived(status === 'loaded' || status === 'error'); const shouldReveal = $derived(status === 'loaded' || status === 'error');
const transitionClasses = $derived(
prefersReducedMotion.current
? 'transition-none' // Disable CSS transitions if motion is reduced
: 'transition-all duration-300 ease-[cubic-bezier(0.22,1,0.36,1)]',
);
</script> </script>
<div {#if !shouldReveal && skeleton}
style:font-family={shouldReveal {@render skeleton()}
? `'${font.name}'` {:else}
: 'system-ui, -apple-system, sans-serif'} <div
class={clsx( style:font-family={shouldReveal ? `'${font.name}'` : 'system-ui, -apple-system, sans-serif'}
transitionClasses, class={clsx(className)}
// If reduced motion is on, we skip the transform/blur entirely >
!shouldReveal {@render children?.()}
&& !prefersReducedMotion.current </div>
&& 'opacity-50 scale-[0.95] blur-sm', {/if}
!shouldReveal && prefersReducedMotion.current && 'opacity-0', // Still hide until font is ready, but no movement
shouldReveal && 'opacity-100 scale-100 blur-0',
className,
)}
>
{@render children?.()}
</div>