From c9e9300216962f46cc5dcfdd92973eb9ff5486c7 Mon Sep 17 00:00:00 2001 From: Marcel Date: Sun, 26 Apr 2026 00:16:26 +0200 Subject: [PATCH] fix(persons): use semantic color tokens in PersonTypeSelector for dark mode Replaces hardcoded brand-navy/brand-sand/white classes with semantic tokens (bg-primary/text-primary-fg, bg-surface/text-ink, border-line, ring-focus-ring) so the segmented control adapts correctly in dark mode. Co-Authored-By: Claude Sonnet 4.6 --- .../lib/components/PersonTypeSelector.svelte | 6 +-- .../PersonTypeSelector.svelte.spec.ts | 37 +++++++++++++++++++ 2 files changed, 40 insertions(+), 3 deletions(-) create mode 100644 frontend/src/lib/components/PersonTypeSelector.svelte.spec.ts diff --git a/frontend/src/lib/components/PersonTypeSelector.svelte b/frontend/src/lib/components/PersonTypeSelector.svelte index 2ddf58cb..fe879f4e 100644 --- a/frontend/src/lib/components/PersonTypeSelector.svelte +++ b/frontend/src/lib/components/PersonTypeSelector.svelte @@ -37,9 +37,9 @@ function select(type: PersonType) { aria-checked={selected === type} tabindex={selected === type ? 0 : -1} onclick={() => select(type)} - class="min-h-[48px] cursor-pointer rounded-sm border px-3 py-2 text-sm font-medium transition-colors focus-visible:ring-2 focus-visible:ring-brand-navy focus-visible:outline-none {selected === type - ? 'border-brand-navy bg-brand-navy text-white' - : 'border-brand-sand bg-white text-brand-navy hover:border-brand-navy/50'}" + class="min-h-[48px] cursor-pointer rounded-sm border px-3 py-2 text-sm font-medium transition-colors focus-visible:ring-2 focus-visible:ring-focus-ring focus-visible:outline-none {selected === type + ? 'border-primary bg-primary text-primary-fg' + : 'border-line bg-surface text-ink hover:border-primary/50'}" > {labels[type]()} diff --git a/frontend/src/lib/components/PersonTypeSelector.svelte.spec.ts b/frontend/src/lib/components/PersonTypeSelector.svelte.spec.ts new file mode 100644 index 00000000..8602b3a3 --- /dev/null +++ b/frontend/src/lib/components/PersonTypeSelector.svelte.spec.ts @@ -0,0 +1,37 @@ +import { describe, it, expect, afterEach } from 'vitest'; +import { cleanup, render } from 'vitest-browser-svelte'; + +import PersonTypeSelector from './PersonTypeSelector.svelte'; + +afterEach(() => cleanup()); + +describe('PersonTypeSelector', () => { + it('selected button uses semantic bg-primary and text-primary-fg classes', () => { + const { container } = render(PersonTypeSelector, { value: 'PERSON' }); + const buttons = container.querySelectorAll('[role="radio"]'); + const selected = Array.from(buttons).find((b) => b.getAttribute('aria-checked') === 'true'); + expect(selected).not.toBeNull(); + expect(selected!.className).toContain('bg-primary'); + expect(selected!.className).toContain('text-primary-fg'); + }); + + it('unselected buttons use semantic bg-surface, text-ink, border-line classes', () => { + const { container } = render(PersonTypeSelector, { value: 'PERSON' }); + const buttons = container.querySelectorAll('[role="radio"]'); + const unselected = Array.from(buttons).filter((b) => b.getAttribute('aria-checked') !== 'true'); + expect(unselected.length).toBeGreaterThan(0); + for (const btn of unselected) { + expect(btn.className).toContain('bg-surface'); + expect(btn.className).toContain('text-ink'); + expect(btn.className).toContain('border-line'); + } + }); + + it('focus ring uses semantic ring-focus-ring class', () => { + const { container } = render(PersonTypeSelector, { value: 'PERSON' }); + const buttons = container.querySelectorAll('[role="radio"]'); + for (const btn of buttons) { + expect(btn.className).toContain('ring-focus-ring'); + } + }); +});