From 9ed8e261d4961ef7a827236d1ce04644eefd3aee Mon Sep 17 00:00:00 2001 From: Bryan Van Deusen Date: Thu, 23 Apr 2026 18:41:32 -0400 Subject: [PATCH] feat(web): add LibrarySkeleton with list/grid/album variants Shimmer placeholders that mirror the layout of each library page so the real content slides in without shifting. --- web/src/lib/components/LibrarySkeleton.svelte | 48 +++++++++++++++++++ .../lib/components/LibrarySkeleton.test.ts | 23 +++++++++ 2 files changed, 71 insertions(+) create mode 100644 web/src/lib/components/LibrarySkeleton.svelte create mode 100644 web/src/lib/components/LibrarySkeleton.test.ts diff --git a/web/src/lib/components/LibrarySkeleton.svelte b/web/src/lib/components/LibrarySkeleton.svelte new file mode 100644 index 00000000..deef89f9 --- /dev/null +++ b/web/src/lib/components/LibrarySkeleton.svelte @@ -0,0 +1,48 @@ + + +{#if variant === 'list'} +
+ {#each Array.from({ length: count }) as _, i (i)} +
+ + + +
+ {/each} +
+{:else if variant === 'grid'} +
+ {#each Array.from({ length: count }) as _, i (i)} +
+
+
+
+
+ {/each} +
+{:else} +
+
+
+
+
+
+
+
+
+
+ {#each Array.from({ length: 10 }) as _, i (i)} +
+ {/each} +
+
+{/if} diff --git a/web/src/lib/components/LibrarySkeleton.test.ts b/web/src/lib/components/LibrarySkeleton.test.ts new file mode 100644 index 00000000..78784f39 --- /dev/null +++ b/web/src/lib/components/LibrarySkeleton.test.ts @@ -0,0 +1,23 @@ +import { describe, expect, test } from 'vitest'; +import { render, screen } from '@testing-library/svelte'; +import LibrarySkeleton from './LibrarySkeleton.svelte'; + +describe('LibrarySkeleton', () => { + test('variant="list" renders count placeholder rows', () => { + render(LibrarySkeleton, { props: { variant: 'list', count: 5 } }); + const container = screen.getByTestId('skeleton-list'); + expect(container.querySelectorAll('[data-skeleton-row]').length).toBe(5); + }); + + test('variant="grid" renders count placeholder cards', () => { + render(LibrarySkeleton, { props: { variant: 'grid', count: 6 } }); + const container = screen.getByTestId('skeleton-grid'); + expect(container.querySelectorAll('[data-skeleton-card]').length).toBe(6); + }); + + test('variant="album" renders a hero placeholder with cover block + bars', () => { + render(LibrarySkeleton, { props: { variant: 'album' } }); + expect(screen.getByTestId('skeleton-album')).toBeInTheDocument(); + expect(screen.getByTestId('skeleton-album-cover')).toBeInTheDocument(); + }); +});