fix(map): announce the places filter control as opening a dialog

MapTogglePill always set aria-pressed, so the phone map's Filters control read
as a toggle although it opens a sheet. An opensDialog option drops aria-pressed
for aria-haspopup="dialog"; the other map toggles are unchanged.
This commit is contained in:
EvKoh
2026-10-03 15:50:30 +02:00
committed by Maurice
parent a9f7adf99b
commit 3951e8cc9a
3 changed files with 18 additions and 5 deletions
+9 -2
View File
@@ -6,7 +6,7 @@ import { MAP_CONTROL_SHADOW } from './mapControlShadow'
* One round on/off control in the frosted shell every map control wears, so a new * One round on/off control in the frosted shell every map control wears, so a new
* one lines up with the compass, the layer switcher and the overview toggle. * one lines up with the compass, the layer switcher and the overview toggle.
*/ */
export function MapTogglePill({ active, onToggle, label, icon, testId, tooltipPlacement = 'left', badge = 0 }: { export function MapTogglePill({ active, onToggle, label, icon, testId, tooltipPlacement = 'left', badge = 0, opensDialog = false }: {
active: boolean active: boolean
onToggle: () => void onToggle: () => void
label: string label: string
@@ -16,6 +16,12 @@ export function MapTogglePill({ active, onToggle, label, icon, testId, tooltipPl
tooltipPlacement?: 'left' | 'right' tooltipPlacement?: 'left' | 'right'
/** A count pinned to the top right corner while above zero (how many filters are on, say). */ /** A count pinned to the top right corner while above zero (how many filters are on, say). */
badge?: number badge?: number
/**
* The control opens a dialog instead of switching something on and off (the phone
* map's places filter): it then announces the dialog, not a pressed state it
* does not have. `active` still lights it.
*/
opensDialog?: boolean
}) { }) {
return ( return (
<div style={{ <div style={{
@@ -33,7 +39,8 @@ export function MapTogglePill({ active, onToggle, label, icon, testId, tooltipPl
type="button" type="button"
onClick={onToggle} onClick={onToggle}
aria-label={label} aria-label={label}
aria-pressed={active} aria-pressed={opensDialog ? undefined : active}
aria-haspopup={opensDialog ? 'dialog' : undefined}
data-testid={testId} data-testid={testId}
className={active ? 'text-accent' : 'text-content-muted'} className={active ? 'text-accent' : 'text-content-muted'}
style={{ style={{
@@ -7,7 +7,8 @@ import { useTranslation } from '../../../../i18n'
/** /**
* The phone map's way into the places filter: a round control in the right-hand * The phone map's way into the places filter: a round control in the right-hand
* stack, lit and badged with how many filters are narrowing the markers. The sheet * stack, lit and badged with how many filters are narrowing the markers. The sheet
* it opens is the shell's 'placesFilter' sheet. * it opens is the shell's 'placesFilter' sheet, so the button announces a dialog
* rather than a pressed state.
*/ */
export function MPlacesFilterPill({ onOpen }: { onOpen: () => void }) { export function MPlacesFilterPill({ onOpen }: { onOpen: () => void }) {
const { t } = useTranslation() const { t } = useTranslation()
@@ -23,6 +24,7 @@ export function MPlacesFilterPill({ onOpen }: { onOpen: () => void }) {
label={label} label={label}
badge={count} badge={count}
testId="places-filter-pill" testId="places-filter-pill"
opensDialog
icon={<SlidersHorizontal size={17} strokeWidth={2} />} icon={<SlidersHorizontal size={17} strokeWidth={2} />}
/> />
) )
@@ -958,7 +958,9 @@ describe('MMapArea — places filter', () => {
const { shell } = renderArea({ trTab: 'plan' }) const { shell } = renderArea({ trTab: 'plan' })
const pill = screen.getByTestId('places-filter-pill') const pill = screen.getByTestId('places-filter-pill')
expect(pill).toHaveAccessibleName('Filters') expect(pill).toHaveAccessibleName('Filters')
expect(pill).toHaveAttribute('aria-pressed', 'false') // It opens a sheet: a dialog to announce, not an on/off state.
expect(pill).toHaveAttribute('aria-haspopup', 'dialog')
expect(pill).not.toHaveAttribute('aria-pressed')
act(() => { pill.click() }) act(() => { pill.click() })
expect(shell.openSheet).toHaveBeenCalledWith('placesFilter') expect(shell.openSheet).toHaveBeenCalledWith('placesFilter')
}) })
@@ -967,7 +969,9 @@ describe('MMapArea — places filter', () => {
seedStore(useTripStore, { placesFilter: 'unplanned', placesRatingFilter: 4 }) seedStore(useTripStore, { placesFilter: 'unplanned', placesRatingFilter: 4 })
renderArea({ trTab: 'plan' }) renderArea({ trTab: 'plan' })
const pill = screen.getByTestId('places-filter-pill') const pill = screen.getByTestId('places-filter-pill')
expect(pill).toHaveAttribute('aria-pressed', 'true') expect(pill).toHaveClass('text-accent')
expect(pill).not.toHaveAttribute('aria-pressed')
expect(pill).toHaveAttribute('aria-haspopup', 'dialog')
expect(pill).toHaveAccessibleName('Filters (2)') expect(pill).toHaveAccessibleName('Filters (2)')
expect(screen.getByText('2')).toHaveAttribute('aria-hidden', 'true') expect(screen.getByText('2')).toHaveAttribute('aria-hidden', 'true')
}) })