mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-18 21:16:47 +00:00
fix(mobile): announce which Show and rating chip is chosen
MChip only showed its state through colour. A pressable option renders aria-pressed from active; the places filter's Show and rating floor chips use it, and the tests now check the chosen chip by its pressed state.
This commit is contained in:
@@ -10,16 +10,23 @@ interface MChipProps {
|
||||
* back what it can: the chips that start a search are the ones worth the height.
|
||||
*/
|
||||
size?: 'sm' | 'tap'
|
||||
/**
|
||||
* The chip is one choice of a picker: announce `active` as `aria-pressed`, so a
|
||||
* screen reader hears which one is on, not just its colour. Off by default, for
|
||||
* chips that only run an action.
|
||||
*/
|
||||
pressable?: boolean
|
||||
className?: string
|
||||
children: ReactNode
|
||||
}
|
||||
|
||||
/** Small pill chip: --m-act when active, neutral --m-ic surface otherwise. */
|
||||
export default function MChip({ active = false, onClick, size = 'sm', className = '', children }: MChipProps) {
|
||||
export default function MChip({ active = false, onClick, size = 'sm', pressable = false, className = '', children }: MChipProps) {
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
onClick={onClick}
|
||||
aria-pressed={pressable ? active : undefined}
|
||||
className={`inline-flex flex-none items-center gap-[6px] rounded-full font-semibold ${
|
||||
size === 'tap' ? 'px-[13px] py-[10px] text-[0.8125rem]' : 'px-3 py-[7px] text-[0.75rem]'
|
||||
} ${
|
||||
|
||||
@@ -94,7 +94,7 @@ export function MRatingFloorChips() {
|
||||
return (
|
||||
<div role="group" aria-label={t('places.filterByRating')} className="flex flex-wrap gap-[6px]">
|
||||
{RATING_FLOORS.map(floor => (
|
||||
<MChip key={String(floor)} size="tap" active={ratingFilter === floor} onClick={() => setRatingFilter(floor)}>
|
||||
<MChip key={String(floor)} size="tap" pressable active={ratingFilter === floor} onClick={() => setRatingFilter(floor)}>
|
||||
{floor === 'all' ? t('common.all') : (
|
||||
<>
|
||||
<Star size={12} strokeWidth={2.2} fill="currentColor" className="flex-none" />
|
||||
|
||||
@@ -61,7 +61,7 @@ export default function MPlacesFilterSheet({ open, onClose, places, categories }
|
||||
<SectionTitle>{t('places.filterShow')}</SectionTitle>
|
||||
<div role="group" aria-label={t('places.filterShow')} className="flex flex-wrap gap-[6px]">
|
||||
{pools.map(pool => (
|
||||
<MChip key={pool.id} size="tap" active={filter === pool.id} onClick={() => setFilter(pool.id)}>
|
||||
<MChip key={pool.id} size="tap" pressable active={filter === pool.id} onClick={() => setFilter(pool.id)}>
|
||||
{pool.label}
|
||||
</MChip>
|
||||
))}
|
||||
|
||||
@@ -178,6 +178,7 @@ describe('MPlacesBrowser', () => {
|
||||
const floors = screen.getByRole('group', { name: 'Filter by rating' })
|
||||
fireEvent.click(within(floors).getByRole('button', { name: '4+' }))
|
||||
expect(useTripStore.getState().placesRatingFilter).toBe(4)
|
||||
expect(within(floors).getByRole('button', { name: '4+' })).toHaveAttribute('aria-pressed', 'true')
|
||||
expect(screen.getByText('Louvre')).toBeInTheDocument()
|
||||
expect(screen.queryByText('Eiffel Tower')).not.toBeInTheDocument()
|
||||
expect(screen.queryByText('Seine Track')).not.toBeInTheDocument()
|
||||
|
||||
@@ -56,15 +56,23 @@ describe('MPlacesFilterSheet', () => {
|
||||
it('FE-MOB-PFSHEET-003: picking a pool writes it to the trip store', async () => {
|
||||
renderSheet()
|
||||
await screen.findByRole('dialog', { name: 'Filters' })
|
||||
fireEvent.click(within(group('Show')).getByRole('button', { name: 'Unplanned' }))
|
||||
const show = group('Show')
|
||||
expect(within(show).getByRole('button', { name: 'All' })).toHaveAttribute('aria-pressed', 'true')
|
||||
fireEvent.click(within(show).getByRole('button', { name: 'Unplanned' }))
|
||||
expect(useTripStore.getState().placesFilter).toBe('unplanned')
|
||||
// The chosen chip says so to a screen reader, and only that one.
|
||||
expect(within(show).getByRole('button', { name: 'Unplanned' })).toHaveAttribute('aria-pressed', 'true')
|
||||
expect(within(show).getByRole('button', { name: 'All' })).toHaveAttribute('aria-pressed', 'false')
|
||||
})
|
||||
|
||||
it('FE-MOB-PFSHEET-004: picking a floor writes the rating filter to the trip store', async () => {
|
||||
renderSheet()
|
||||
await screen.findByRole('dialog', { name: 'Filters' })
|
||||
fireEvent.click(within(group('Filter by rating')).getByRole('button', { name: '3+' }))
|
||||
const floors = group('Filter by rating')
|
||||
fireEvent.click(within(floors).getByRole('button', { name: '3+' }))
|
||||
expect(useTripStore.getState().placesRatingFilter).toBe(3)
|
||||
expect(within(floors).getByRole('button', { name: '3+' })).toHaveAttribute('aria-pressed', 'true')
|
||||
expect(within(floors).getByRole('button', { name: 'All' })).toHaveAttribute('aria-pressed', 'false')
|
||||
})
|
||||
|
||||
it('FE-MOB-PFSHEET-005: categories toggle in and out of the shared set, "no category" included', async () => {
|
||||
|
||||
Reference in New Issue
Block a user