feat: results skeleton on the search page
The search loader now awaits only the chip cloud (its auth gate) and streams the view-specific results, so switching tag / view / sort / text shows a SearchResultsSkeleton in place of the frozen previous list until the query resolves. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
48
frontend/src/lib/components/SearchResultsSkeleton.svelte
Normal file
48
frontend/src/lib/components/SearchResultsSkeleton.svelte
Normal file
@@ -0,0 +1,48 @@
|
||||
<script lang="ts">
|
||||
import Skeleton from './Skeleton.svelte';
|
||||
|
||||
/**
|
||||
* Placeholder for a tagged-page / chapter / manga results list while a
|
||||
* search re-queries. Mirrors the TaggedPageRow shape (56px 2/3 cover +
|
||||
* two text lines) so the list keeps its height as results swap in.
|
||||
*/
|
||||
let { count = 6, testid = 'search-results-skeleton' }: { count?: number; testid?: string } =
|
||||
$props();
|
||||
</script>
|
||||
|
||||
<ul class="list" data-testid={testid} aria-hidden="true">
|
||||
{#each Array(count) as _}
|
||||
<li class="row">
|
||||
<Skeleton width="56px" aspectRatio="2 / 3" radius="sm" />
|
||||
<div class="meta">
|
||||
<Skeleton variant="text" width="60%" radius="sm" />
|
||||
<Skeleton variant="text" width="40%" radius="sm" />
|
||||
</div>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
|
||||
<style>
|
||||
.list {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-3);
|
||||
}
|
||||
|
||||
.row {
|
||||
display: grid;
|
||||
grid-template-columns: 56px 1fr;
|
||||
gap: var(--space-3);
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.meta {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-2);
|
||||
min-width: 0;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,22 @@
|
||||
import { describe, it, expect, afterEach } from 'vitest';
|
||||
import { render, screen, cleanup } from '@testing-library/svelte';
|
||||
import SearchResultsSkeleton from './SearchResultsSkeleton.svelte';
|
||||
|
||||
afterEach(() => cleanup());
|
||||
|
||||
describe('SearchResultsSkeleton', () => {
|
||||
it('renders the requested number of row placeholders', () => {
|
||||
render(SearchResultsSkeleton, { props: { count: 3, testid: 'sk' } });
|
||||
expect(screen.getByTestId('sk').querySelectorAll('.row').length).toBe(3);
|
||||
});
|
||||
|
||||
it('defaults to 6 rows', () => {
|
||||
render(SearchResultsSkeleton, { props: { testid: 'sk' } });
|
||||
expect(screen.getByTestId('sk').querySelectorAll('.row').length).toBe(6);
|
||||
});
|
||||
|
||||
it('is decorative (aria-hidden)', () => {
|
||||
render(SearchResultsSkeleton, { props: { testid: 'sk' } });
|
||||
expect(screen.getByTestId('sk').getAttribute('aria-hidden')).toBe('true');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user