feat(ocr): add OcrProgressBar component with page-based ARIA semantics
Progress bar shows brand-mint fill on brand-sand background with smooth transition. Displays page counter with tabular-nums and skipped-pages warning in amber when applicable. Only renders when totalPages > 0. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
39
frontend/src/lib/components/OcrProgressBar.svelte
Normal file
39
frontend/src/lib/components/OcrProgressBar.svelte
Normal file
@@ -0,0 +1,39 @@
|
||||
<script lang="ts">
|
||||
let {
|
||||
currentPage,
|
||||
totalPages,
|
||||
skippedPages = 0
|
||||
}: {
|
||||
currentPage: number;
|
||||
totalPages: number;
|
||||
skippedPages?: number;
|
||||
} = $props();
|
||||
|
||||
let percentage = $derived((currentPage / totalPages) * 100);
|
||||
</script>
|
||||
|
||||
{#if totalPages > 0}
|
||||
<div class="flex flex-col items-center">
|
||||
<div
|
||||
class="bg-brand-sand mx-auto mt-4 h-2 w-full max-w-xs rounded-full"
|
||||
role="progressbar"
|
||||
aria-valuenow={currentPage}
|
||||
aria-valuemax={totalPages}
|
||||
aria-label="OCR progress"
|
||||
>
|
||||
<div
|
||||
class="h-full rounded-full bg-brand-mint transition-all duration-500"
|
||||
data-testid="progress-fill"
|
||||
style="width: {percentage}%"
|
||||
></div>
|
||||
</div>
|
||||
<span class="mt-1 text-xs text-gray-400 tabular-nums">
|
||||
{currentPage} / {totalPages}
|
||||
</span>
|
||||
{#if skippedPages > 0}
|
||||
<span class="mt-1 text-xs text-amber-600" data-testid="skipped-warning">
|
||||
{skippedPages} Seiten übersprungen
|
||||
</span>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
43
frontend/src/lib/components/OcrProgressBar.svelte.spec.ts
Normal file
43
frontend/src/lib/components/OcrProgressBar.svelte.spec.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
import { describe, it, expect, afterEach } from 'vitest';
|
||||
import { cleanup, render } from 'vitest-browser-svelte';
|
||||
import { page } from 'vitest/browser';
|
||||
import OcrProgressBar from './OcrProgressBar.svelte';
|
||||
|
||||
afterEach(cleanup);
|
||||
|
||||
describe('OcrProgressBar', () => {
|
||||
it('renders progress bar with correct ARIA attributes', async () => {
|
||||
render(OcrProgressBar, { currentPage: 2, totalPages: 5 });
|
||||
const bar = page.getByRole('progressbar');
|
||||
await expect.element(bar).toHaveAttribute('aria-valuenow', '2');
|
||||
await expect.element(bar).toHaveAttribute('aria-valuemax', '5');
|
||||
});
|
||||
|
||||
it('hides progress bar when totalPages is zero', async () => {
|
||||
render(OcrProgressBar, { currentPage: 0, totalPages: 0 });
|
||||
await expect.element(page.getByRole('progressbar')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('fills to 100 percent when current equals total', async () => {
|
||||
render(OcrProgressBar, { currentPage: 5, totalPages: 5 });
|
||||
const fill = page.getByTestId('progress-fill');
|
||||
await expect.element(fill).toBeInTheDocument();
|
||||
const el = fill.element() as HTMLElement;
|
||||
expect(el.style.width).toBe('100%');
|
||||
});
|
||||
|
||||
it('shows page counter text', async () => {
|
||||
render(OcrProgressBar, { currentPage: 3, totalPages: 7 });
|
||||
await expect.element(page.getByText('3 / 7')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('shows skipped pages warning when skippedPages > 0', async () => {
|
||||
render(OcrProgressBar, { currentPage: 5, totalPages: 5, skippedPages: 2 });
|
||||
await expect.element(page.getByTestId('skipped-warning')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('does not show warning when skippedPages is 0', async () => {
|
||||
render(OcrProgressBar, { currentPage: 3, totalPages: 5, skippedPages: 0 });
|
||||
await expect.element(page.getByTestId('skipped-warning')).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user