feat(FontList): unified skeleton — rows stay skeletal until font file loaded
This commit is contained in:
@@ -9,6 +9,7 @@ import {
|
||||
FontVirtualList,
|
||||
type UnifiedFont,
|
||||
VIRTUAL_INDEX_NOT_LOADED,
|
||||
appliedFontsManager,
|
||||
fontStore,
|
||||
} from '$entities/Font';
|
||||
import { getSkeletonWidth } from '$shared/lib/utils';
|
||||
@@ -18,6 +19,7 @@ import {
|
||||
Skeleton,
|
||||
} from '$shared/ui';
|
||||
import DotIcon from '@lucide/svelte/icons/dot';
|
||||
import { fade } from 'svelte/transition';
|
||||
import {
|
||||
createDotCrossfade,
|
||||
getDotTransitionParams,
|
||||
@@ -71,6 +73,21 @@ function handleSelect(font: UnifiedFont) {
|
||||
comparisonStore.fontB = font;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true once the font file is loaded (or errored) and safe to render.
|
||||
* Called inside the template — Svelte 5 tracks the $state reads inside
|
||||
* appliedFontsManager.getFontStatus(), so each row re-renders reactively
|
||||
* when its file arrives.
|
||||
*/
|
||||
function isFontReady(font: UnifiedFont): boolean {
|
||||
const status = appliedFontsManager.getFontStatus(
|
||||
font.id,
|
||||
DEFAULT_FONT_WEIGHT,
|
||||
font.features?.isVariable,
|
||||
);
|
||||
return status === 'loaded' || status === 'error';
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="flex-1 min-h-0 h-full">
|
||||
@@ -83,39 +100,52 @@ function handleSelect(font: UnifiedFont) {
|
||||
<FontVirtualList
|
||||
data-font-list
|
||||
weight={DEFAULT_FONT_WEIGHT}
|
||||
itemHeight={45}
|
||||
itemHeight={44}
|
||||
class="bg-transparent min-h-0 h-full scroll-stable py-2 pl-6 pr-4"
|
||||
>
|
||||
{#snippet skeleton()}
|
||||
{#each { length: 12 } as _, i (i)}
|
||||
<div class="w-full px-4 py-3 flex items-center justify-between border border-transparent bg-white/50 dark:bg-[#1e1e1e]/50">
|
||||
<div class="py-2.5 md:py-3 px-7">
|
||||
{#each { length: 50 } as _, index (index)}
|
||||
<div class="w-full px-3 py-3 flex items-center justify-between">
|
||||
<div class="flex-1 flex items-center gap-3">
|
||||
<Skeleton
|
||||
class="h-4 bg-neutral-200/70 dark:bg-neutral-800/70"
|
||||
style="width: {getSkeletonWidth(i)}"
|
||||
class="h-4 w-32 bg-neutral-200/70 dark:bg-neutral-800/70"
|
||||
style="width: {getSkeletonWidth(index)}"
|
||||
/>
|
||||
</div>
|
||||
<Skeleton class="w-1.5 h-1.5 rounded-full bg-neutral-200/70 dark:bg-neutral-800/70" />
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{/snippet}
|
||||
|
||||
{#snippet children({ item: font, index })}
|
||||
<div class="relative h-[44px] w-full">
|
||||
{#if !isFontReady(font)}
|
||||
<div
|
||||
class="absolute inset-0 px-3 md:px-4 flex items-center justify-between border border-transparent"
|
||||
transition:fade={{ duration: 300 }}
|
||||
>
|
||||
<Skeleton
|
||||
class="h-4 bg-neutral-200/70 dark:bg-neutral-800/70"
|
||||
style="width: {getSkeletonWidth(index)}"
|
||||
/>
|
||||
<Skeleton class="w-1.5 h-1.5 rounded-full bg-neutral-200/70 dark:bg-neutral-800/70" />
|
||||
</div>
|
||||
{:else}
|
||||
{@const isSelectedA = font.id === comparisonStore.fontA?.id}
|
||||
{@const isSelectedB = font.id === comparisonStore.fontB?.id}
|
||||
{@const active = (side === 'A' && isSelectedA) || (side === 'B' && isSelectedB)}
|
||||
|
||||
<div transition:fade={{ duration: 300 }} class="h-full">
|
||||
<Button
|
||||
variant="tertiary"
|
||||
{active}
|
||||
onclick={() => handleSelect(font)}
|
||||
class="w-full px-3 md:px-4 py-2.5 md:py-3 flex !justify-between text-left text-sm"
|
||||
class="w-full h-full px-3 md:px-4 py-2.5 md:py-3 flex !justify-between text-left text-sm"
|
||||
iconPosition="right"
|
||||
>
|
||||
<FontApplicator {font}>
|
||||
{#snippet skeleton()}
|
||||
<Skeleton class="h-4 w-32 bg-neutral-200/70 dark:bg-neutral-800/70" />
|
||||
{/snippet}
|
||||
{font.name}
|
||||
</FontApplicator>
|
||||
|
||||
@@ -154,6 +184,9 @@ function handleSelect(font: UnifiedFont) {
|
||||
{/if}
|
||||
{/snippet}
|
||||
</Button>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/snippet}
|
||||
</FontVirtualList>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user