feat(recipes): add FilterChipRow with effort filter chips
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
20
frontend/src/lib/recipes/FilterChipRow.svelte
Normal file
20
frontend/src/lib/recipes/FilterChipRow.svelte
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
let { activeFilter, onFilter }: { activeFilter: string; onFilter: (filter: string) => void } = $props();
|
||||||
|
|
||||||
|
const chips = ['Alle', 'Leicht', 'Mittel', 'Schwer'];
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="flex gap-[8px] overflow-x-auto px-[16px] py-[12px] scrollbar-none">
|
||||||
|
{#each chips as label (label)}
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
aria-pressed={activeFilter === label}
|
||||||
|
onclick={() => onFilter(label)}
|
||||||
|
class="font-sans text-[11px] font-medium px-[14px] py-[5px] rounded-[12px] border cursor-pointer focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[var(--green-light)] {activeFilter === label
|
||||||
|
? 'bg-[var(--green-tint)] text-[var(--green-dark)] border-[var(--green-light)]'
|
||||||
|
: 'bg-[var(--color-surface)] text-[var(--color-text-muted)] border-[var(--color-border)]'}"
|
||||||
|
>
|
||||||
|
{label}
|
||||||
|
</button>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
51
frontend/src/lib/recipes/FilterChipRow.test.ts
Normal file
51
frontend/src/lib/recipes/FilterChipRow.test.ts
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
import { describe, it, expect, vi } from 'vitest';
|
||||||
|
import { render, screen } from '@testing-library/svelte';
|
||||||
|
import { userEvent } from '@testing-library/user-event';
|
||||||
|
import FilterChipRow from './FilterChipRow.svelte';
|
||||||
|
|
||||||
|
describe('FilterChipRow', () => {
|
||||||
|
it('renders all effort filter chips', () => {
|
||||||
|
render(FilterChipRow, { props: { activeFilter: 'Alle', onFilter: vi.fn() } });
|
||||||
|
expect(screen.getByRole('button', { name: 'Alle' })).toBeInTheDocument();
|
||||||
|
expect(screen.getByRole('button', { name: 'Leicht' })).toBeInTheDocument();
|
||||||
|
expect(screen.getByRole('button', { name: 'Mittel' })).toBeInTheDocument();
|
||||||
|
expect(screen.getByRole('button', { name: 'Schwer' })).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('marks active chip with aria-pressed=true', () => {
|
||||||
|
render(FilterChipRow, { props: { activeFilter: 'Leicht', onFilter: vi.fn() } });
|
||||||
|
expect(screen.getByRole('button', { name: 'Leicht' })).toHaveAttribute('aria-pressed', 'true');
|
||||||
|
expect(screen.getByRole('button', { name: 'Alle' })).toHaveAttribute('aria-pressed', 'false');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('marks inactive chips with aria-pressed=false', () => {
|
||||||
|
render(FilterChipRow, { props: { activeFilter: 'Alle', onFilter: vi.fn() } });
|
||||||
|
expect(screen.getByRole('button', { name: 'Leicht' })).toHaveAttribute('aria-pressed', 'false');
|
||||||
|
expect(screen.getByRole('button', { name: 'Mittel' })).toHaveAttribute('aria-pressed', 'false');
|
||||||
|
expect(screen.getByRole('button', { name: 'Schwer' })).toHaveAttribute('aria-pressed', 'false');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('calls onFilter with the chip label when clicked', async () => {
|
||||||
|
const user = userEvent.setup();
|
||||||
|
const onFilter = vi.fn();
|
||||||
|
render(FilterChipRow, { props: { activeFilter: 'Alle', onFilter } });
|
||||||
|
|
||||||
|
await user.click(screen.getByRole('button', { name: 'Leicht' }));
|
||||||
|
expect(onFilter).toHaveBeenCalledWith('Leicht');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('calls onFilter with Alle when reset chip clicked', async () => {
|
||||||
|
const user = userEvent.setup();
|
||||||
|
const onFilter = vi.fn();
|
||||||
|
render(FilterChipRow, { props: { activeFilter: 'Leicht', onFilter } });
|
||||||
|
|
||||||
|
await user.click(screen.getByRole('button', { name: 'Alle' }));
|
||||||
|
expect(onFilter).toHaveBeenCalledWith('Alle');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('active chip has green-tint styling', () => {
|
||||||
|
render(FilterChipRow, { props: { activeFilter: 'Mittel', onFilter: vi.fn() } });
|
||||||
|
const btn = screen.getByRole('button', { name: 'Mittel' });
|
||||||
|
expect(btn.className).toContain('bg-[var(--green-tint)]');
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user