diff --git a/src/entities/Font/ui/FontVirtualList/FontVirtualList.svelte b/src/entities/Font/ui/FontVirtualList/FontVirtualList.svelte
index ec899d9..984b89a 100644
--- a/src/entities/Font/ui/FontVirtualList/FontVirtualList.svelte
+++ b/src/entities/Font/ui/FontVirtualList/FontVirtualList.svelte
@@ -4,8 +4,12 @@
- Handles font registration with the manager
-->
-
- {#snippet children(scope)}
- {@render children(scope)}
- {/snippet}
-
+{#key isLoading}
+
+ {#if isLoading}
+
+ {#each Array(5) as _, i}
+
+ {/each}
+
+ {:else}
+
+ {#snippet children(scope)}
+ {@render children(scope)}
+ {/snippet}
+
+ {/if}
+
+{/key}
diff --git a/src/features/DisplayFont/ui/FontSampler/FontSampler.svelte b/src/features/DisplayFont/ui/FontSampler/FontSampler.svelte
index 453c23f..9d74d11 100644
--- a/src/features/DisplayFont/ui/FontSampler/FontSampler.svelte
+++ b/src/features/DisplayFont/ui/FontSampler/FontSampler.svelte
@@ -11,9 +11,9 @@ import {
import { controlManager } from '$features/SetupFont';
import {
ContentEditable,
- IconButton,
+ // IconButton,
} from '$shared/ui';
-import XIcon from '@lucide/svelte/icons/x';
+// import XIcon from '@lucide/svelte/icons/x';
interface Props {
/**
@@ -75,14 +75,16 @@ function removeSample() {
-
- {#snippet icon({ className })}
-
- {/snippet}
-
+
diff --git a/src/routes/Page.svelte b/src/routes/Page.svelte
index 6563b4f..87dde72 100644
--- a/src/routes/Page.svelte
+++ b/src/routes/Page.svelte
@@ -10,8 +10,8 @@ import ComparisonSlider from '$widgets/ComparisonSlider/ui/ComparisonSlider/Comp
import { FontSearch } from '$widgets/FontSearch';
import { SampleList } from '$widgets/SampleList';
import CodeIcon from '@lucide/svelte/icons/code';
+import EyeIcon from '@lucide/svelte/icons/eye';
import LineSquiggleIcon from '@lucide/svelte/icons/line-squiggle';
-import ScanEyeIcon from '@lucide/svelte/icons/scan-eye';
import ScanSearchIcon from '@lucide/svelte/icons/search';
import type { Snippet } from 'svelte';
@@ -59,7 +59,7 @@ function handleTitleStatusChanged(index: number, isPast: boolean, title?: Snippe
{#snippet icon({ className })}
-
+
{/snippet}
{#snippet title({ className })}
diff --git a/src/shared/shadcn/ui/spinner/index.ts b/src/shared/shadcn/ui/spinner/index.ts
new file mode 100644
index 0000000..2e459c6
--- /dev/null
+++ b/src/shared/shadcn/ui/spinner/index.ts
@@ -0,0 +1 @@
+export { default as Spinner } from './spinner.svelte';
diff --git a/src/shared/shadcn/ui/spinner/spinner.svelte b/src/shared/shadcn/ui/spinner/spinner.svelte
new file mode 100644
index 0000000..e897fae
--- /dev/null
+++ b/src/shared/shadcn/ui/spinner/spinner.svelte
@@ -0,0 +1,14 @@
+
+
+
diff --git a/src/shared/ui/Loader/Loader.stories.svelte b/src/shared/ui/Loader/Loader.stories.svelte
new file mode 100644
index 0000000..37739e3
--- /dev/null
+++ b/src/shared/ui/Loader/Loader.stories.svelte
@@ -0,0 +1,33 @@
+
+
+
+
+
diff --git a/src/shared/ui/Loader/Loader.svelte b/src/shared/ui/Loader/Loader.svelte
new file mode 100644
index 0000000..2789f74
--- /dev/null
+++ b/src/shared/ui/Loader/Loader.svelte
@@ -0,0 +1,77 @@
+
+
+
+
+
+
+
+
+
+
+
+
+ {message}
+
+
diff --git a/src/shared/ui/Skeleton/Skeleton.stories.svelte b/src/shared/ui/Skeleton/Skeleton.stories.svelte
new file mode 100644
index 0000000..426efb8
--- /dev/null
+++ b/src/shared/ui/Skeleton/Skeleton.stories.svelte
@@ -0,0 +1,41 @@
+
+
+
+
+
diff --git a/src/shared/ui/Skeleton/Skeleton.svelte b/src/shared/ui/Skeleton/Skeleton.svelte
new file mode 100644
index 0000000..0e28399
--- /dev/null
+++ b/src/shared/ui/Skeleton/Skeleton.svelte
@@ -0,0 +1,27 @@
+
+
+
+
+
diff --git a/src/shared/ui/VirtualList/VirtualList.svelte b/src/shared/ui/VirtualList/VirtualList.svelte
index d4e2f05..b0de643 100644
--- a/src/shared/ui/VirtualList/VirtualList.svelte
+++ b/src/shared/ui/VirtualList/VirtualList.svelte
@@ -101,13 +101,25 @@ interface Props {
* @template T - The type of items in the list
*/
children: Snippet<
- [{ item: T; index: number; isFullyVisible: boolean; isPartiallyVisible: boolean; proximity: number }]
+ [
+ {
+ item: T;
+ index: number;
+ isFullyVisible: boolean;
+ isPartiallyVisible: boolean;
+ proximity: number;
+ },
+ ]
>;
/**
* Whether to use the window as the scroll container.
* @default false
*/
useWindowScroll?: boolean;
+ /**
+ * Flag to show loading state
+ */
+ isLoading?: boolean;
}
let {
@@ -120,6 +132,7 @@ let {
onNearBottom,
children,
useWindowScroll = false,
+ isLoading = false,
}: Props = $props();
// Reference to the ScrollArea viewport element for attaching the virtualizer
diff --git a/src/shared/ui/index.ts b/src/shared/ui/index.ts
index a8429cb..c5161a2 100644
--- a/src/shared/ui/index.ts
+++ b/src/shared/ui/index.ts
@@ -1,27 +1,12 @@
-/**
- * Shared UI components exports
- *
- * Exports all shared UI components and their types
- */
-
-import CheckboxFilter from './CheckboxFilter/CheckboxFilter.svelte';
-import ComboControl from './ComboControl/ComboControl.svelte';
-import ComboControlV2 from './ComboControlV2/ComboControlV2.svelte';
-import ContentEditable from './ContentEditable/ContentEditable.svelte';
-import ExpandableWrapper from './ExpandableWrapper/ExpandableWrapper.svelte';
-import IconButton from './IconButton/IconButton.svelte';
-import SearchBar from './SearchBar/SearchBar.svelte';
-import Section from './Section/Section.svelte';
-import VirtualList from './VirtualList/VirtualList.svelte';
-
-export {
- CheckboxFilter,
- ComboControl,
- ComboControlV2,
- ContentEditable,
- ExpandableWrapper,
- IconButton,
- SearchBar,
- Section,
- VirtualList,
-};
+export { default as CheckboxFilter } from './CheckboxFilter/CheckboxFilter.svelte';
+export { default as ComboControl } from './ComboControl/ComboControl.svelte';
+// ComboControlV2 might vary, assuming pattern holds or I'll fix later if build fails
+export { default as ComboControlV2 } from './ComboControlV2/ComboControlV2.svelte';
+export { default as ContentEditable } from './ContentEditable/ContentEditable.svelte';
+export { default as ExpandableWrapper } from './ExpandableWrapper/ExpandableWrapper.svelte';
+export { default as IconButton } from './IconButton/IconButton.svelte';
+export { default as Loader } from './Loader/Loader.svelte';
+export { default as SearchBar } from './SearchBar/SearchBar.svelte';
+export { default as Section } from './Section/Section.svelte';
+export { default as Skeleton } from './Skeleton/Skeleton.svelte';
+export { default as VirtualList } from './VirtualList/VirtualList.svelte';
diff --git a/src/widgets/ComparisonSlider/model/stores/comparisonStore.svelte.ts b/src/widgets/ComparisonSlider/model/stores/comparisonStore.svelte.ts
index 503900f..f6ae670 100644
--- a/src/widgets/ComparisonSlider/model/stores/comparisonStore.svelte.ts
+++ b/src/widgets/ComparisonSlider/model/stores/comparisonStore.svelte.ts
@@ -136,6 +136,10 @@ class ComparisonStore {
return !!this.#fontA && !!this.#fontB;
}
+ get isLoading() {
+ return this.#isRestoring;
+ }
+
/**
* Public initializer (optional, as constructor starts it)
* Kept for compatibility if manual re-init is needed
diff --git a/src/widgets/ComparisonSlider/ui/ComparisonSlider/ComparisonSlider.svelte b/src/widgets/ComparisonSlider/ui/ComparisonSlider/ComparisonSlider.svelte
index 2120233..bcd3eef 100644
--- a/src/widgets/ComparisonSlider/ui/ComparisonSlider/ComparisonSlider.svelte
+++ b/src/widgets/ComparisonSlider/ui/ComparisonSlider/ComparisonSlider.svelte
@@ -15,8 +15,10 @@ import {
createTypographyControl,
} from '$shared/lib';
import type { LineData } from '$shared/lib';
+import { Loader } from '$shared/ui';
import { comparisonStore } from '$widgets/ComparisonSlider/model';
import { Spring } from 'svelte/motion';
+import { fade } from 'svelte/transition';
import CharacterSlot from './components/CharacterSlot.svelte';
import ControlsWrapper from './components/ControlsWrapper.svelte';
import Labels from './components/Labels.svelte';
@@ -26,6 +28,8 @@ import SliderLine from './components/SliderLine.svelte';
const fontA = $derived(comparisonStore.fontA);
const fontB = $derived(comparisonStore.fontB);
+const isLoading = $derived(comparisonStore.isLoading || !comparisonStore.isReady);
+
let container: HTMLElement | undefined = $state();
let controlsWrapperElement = $state(null);
let measureCanvas: HTMLCanvasElement | undefined = $state();
@@ -164,31 +168,33 @@ $effect(() => {
{/snippet}
-{#if fontA && fontB}
-
-
+
+
-
-
-
+
+
+
+ {#if isLoading}
+
+ {:else}
{#each charComparison.lines as line, lineIndex}
{
-
+ {/if}
+
+ {#if fontA && fontB && !isLoading}
{
{sizeControl}
{heightControl}
/>
-
-{/if}
+ {/if}
+
diff --git a/src/widgets/ComparisonSlider/ui/ComparisonSlider/components/ControlsWrapper.svelte b/src/widgets/ComparisonSlider/ui/ComparisonSlider/components/ControlsWrapper.svelte
index c23106b..7dd0fe7 100644
--- a/src/widgets/ComparisonSlider/ui/ComparisonSlider/components/ControlsWrapper.svelte
+++ b/src/widgets/ComparisonSlider/ui/ComparisonSlider/components/ControlsWrapper.svelte
@@ -12,6 +12,7 @@ import { ComboControlV2 } from '$shared/ui';
import { ExpandableWrapper } from '$shared/ui';
import AArrowUP from '@lucide/svelte/icons/a-arrow-up';
import { Spring } from 'svelte/motion';
+import { fade } from 'svelte/transition';
interface Props {
/**
@@ -121,6 +122,8 @@ $effect(() => {
translateX({xSpring.current}px)
rotateZ({rotateSpring.current}deg)
"
+ in:fade={{ duration: 300, delay: 300 }}
+ out:fade={{ duration: 300, delay: 300 }}
>
{
/**
@@ -60,6 +61,8 @@ function selectFontB(font: UnifiedFont) {
e.stopPropagation())}
+ in:fade={{ duration: 300, delay: 300 }}
+ out:fade={{ duration: 300, delay: 300 }}
>
-
+
diff --git a/src/widgets/SampleList/ui/SampleList/SampleList.svelte b/src/widgets/SampleList/ui/SampleList/SampleList.svelte
index 112f3fb..9ffad72 100644
--- a/src/widgets/SampleList/ui/SampleList/SampleList.svelte
+++ b/src/widgets/SampleList/ui/SampleList/SampleList.svelte
@@ -50,11 +50,9 @@ const displayRange = $derived.by(() => {
const loadedCount = Math.min(offset + limit, total);
return `Showing ${loadedCount} of ${total} fonts`;
});
-
-{#if unifiedFontStore.isFetching || unifiedFontStore.isLoading}
- (Loading...)
-{/if}
+const isLoading = $derived(unifiedFontStore.isFetching || unifiedFontStore.isLoading);
+
{
itemHeight={280}
useWindowScroll={true}
weight={controlManager.weight}
+ {isLoading}
>
- {#snippet children({ item: font, isFullyVisible, isPartiallyVisible, proximity, index })}
+ {#snippet children({
+ item: font,
+ isFullyVisible,
+ isPartiallyVisible,
+ proximity,
+ index,
+})}