feat(staples): add StapleChip component with aria-pressed toggle and focus ring
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
20
frontend/src/lib/onboarding/StapleChip.svelte
Normal file
20
frontend/src/lib/onboarding/StapleChip.svelte
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
let { name, selected, onToggle }: {
|
||||||
|
name: string;
|
||||||
|
selected: boolean;
|
||||||
|
onToggle: (value: boolean) => void;
|
||||||
|
} = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
aria-pressed={selected}
|
||||||
|
onclick={() => onToggle(!selected)}
|
||||||
|
class="inline-flex text-[12px] font-medium px-[12px] py-[6px] rounded-full border cursor-pointer
|
||||||
|
focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[var(--green-light)]
|
||||||
|
{selected
|
||||||
|
? 'bg-[var(--green-tint)] border-[var(--green-light)] text-[var(--green-dark)]'
|
||||||
|
: 'bg-[var(--color-surface)] border-[var(--color-border)] text-[var(--color-text-muted)]'}"
|
||||||
|
>
|
||||||
|
{name}
|
||||||
|
</button>
|
||||||
45
frontend/src/lib/onboarding/StapleChip.test.ts
Normal file
45
frontend/src/lib/onboarding/StapleChip.test.ts
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
import { describe, it, expect, vi } from 'vitest';
|
||||||
|
import { render, screen } from '@testing-library/svelte';
|
||||||
|
import { userEvent } from '@testing-library/user-event';
|
||||||
|
import StapleChip from './StapleChip.svelte';
|
||||||
|
|
||||||
|
describe('StapleChip', () => {
|
||||||
|
it('renders a button with the ingredient name', () => {
|
||||||
|
render(StapleChip, { props: { name: 'Olivenöl', selected: false, onToggle: vi.fn() } });
|
||||||
|
expect(screen.getByRole('button', { name: 'Olivenöl' })).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('is aria-pressed="false" when unselected', () => {
|
||||||
|
render(StapleChip, { props: { name: 'Olivenöl', selected: false, onToggle: vi.fn() } });
|
||||||
|
expect(screen.getByRole('button', { name: 'Olivenöl' })).toHaveAttribute('aria-pressed', 'false');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('is aria-pressed="true" when selected', () => {
|
||||||
|
render(StapleChip, { props: { name: 'Olivenöl', selected: true, onToggle: vi.fn() } });
|
||||||
|
expect(screen.getByRole('button', { name: 'Olivenöl' })).toHaveAttribute('aria-pressed', 'true');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('calls onToggle with true when unselected chip is clicked', async () => {
|
||||||
|
const user = userEvent.setup();
|
||||||
|
const onToggle = vi.fn();
|
||||||
|
render(StapleChip, { props: { name: 'Olivenöl', selected: false, onToggle } });
|
||||||
|
|
||||||
|
await user.click(screen.getByRole('button', { name: 'Olivenöl' }));
|
||||||
|
expect(onToggle).toHaveBeenCalledWith(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('calls onToggle with false when selected chip is clicked', async () => {
|
||||||
|
const user = userEvent.setup();
|
||||||
|
const onToggle = vi.fn();
|
||||||
|
render(StapleChip, { props: { name: 'Olivenöl', selected: true, onToggle } });
|
||||||
|
|
||||||
|
await user.click(screen.getByRole('button', { name: 'Olivenöl' }));
|
||||||
|
expect(onToggle).toHaveBeenCalledWith(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('has a visible focus ring class for keyboard accessibility', () => {
|
||||||
|
render(StapleChip, { props: { name: 'Olivenöl', selected: false, onToggle: vi.fn() } });
|
||||||
|
const btn = screen.getByRole('button', { name: 'Olivenöl' });
|
||||||
|
expect(btn.className).toContain('focus-visible:outline');
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user