Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 8 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -272,7 +272,9 @@ sidemeny. Begge komponentene får landlisten, valgt landkode og en
favoritter har en stjerne (★) med skjult tekst «(favoritt)» for
skjermlesere. Ved tomt utvalg vises «Ingen land i utvalget.».
- **`CountryNavigation`:** «Forrige», «Land 3 av 25» og «Neste» over
kortet.
kortet. Det tilgjengelige navnet på knappene inkluderer mållandet, for
eksempel «Forrige land: Norway» og «Neste land: Sweden». Synlig tekst
er uendret.
- **Oppsett:** Menyen ligger til venstre og valgt land til høyre. Under
641 px ligger menyen over kortet, og landlisten legges bak knappen
«Landliste (25)», se [Responsivt design](#responsivt-design).
Expand Down Expand Up @@ -536,7 +538,7 @@ Kontrollene ble kjørt manuelt i utviklingsserveren:

- `npm run lint` og `npm run build` besto, og de endrede filene besto
`prettier --check`.
- Manuell test i nettleseren gjenstår og skal gjøres før PR-en merges:
- Manuell test i nettleseren er gjennomført og besto:
forrige/neste, deaktiverte knapper ved første og siste land,
direktevalg (nå i sidemenyen, tidligere i select), samsvar mellom kort
og navigasjon, og bruk med bare tastatur.
Expand Down Expand Up @@ -721,8 +723,10 @@ siden av `CountryCard.tsx`), og bruker mockede API-svar og en isolert
- **`src/components/CountryNavigation.test.tsx`:** Med Norway, Sweden og
Denmark, og Sweden valgt, viser komponenten «Land 2 av 3». «Neste»
kaller `onSelect('DNK')`, og «Forrige» kaller `onSelect('NOR')`.
«Forrige» er deaktivert ved Norway, «Neste» ved Denmark. En valgt kode
som ikke finnes i utvalget, gir ingen rendret komponent.
Tilgjengelige navn er «Forrige land: Norway» og «Neste land: Denmark»,
mens synlig tekst forblir «Forrige» og «Neste». «Forrige» er deaktivert
ved Norway, «Neste» ved Denmark. En valgt kode som ikke finnes i
utvalget, gir ingen rendret komponent.
- **`src/components/FavoriteButton.test.tsx`:** Klikk kaller `onToggle`
én gang, og `aria-pressed` er `"true"`/`"false"` i tråd med props. En
snapshot-test fanger markeringen (☆/★) i begge tilstander.
Expand Down
60 changes: 60 additions & 0 deletions docs/accessibility.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# Tilgjengelighet

Sjekkliste for universell utforming i appen. Native HTML brukes der det gir riktig semantikk. ARIA legges bare til når native HTML ikke er nok.

## Tastaturnavigasjon

- Alle interaktive elementer er vanlige `<button>`, `<select>` eller `<input type="checkbox">`.
- Tab flytter fokus mellom kontrollene. Enter og mellomrom aktiverer knapper, og mellomrom veksler avkrysningsboksen.
- Sidemenyen og forrige/neste eier ingen egen tastaturlogikk utover native knapper.
- På smale skjermer har «Landliste» `aria-expanded`. Etter valg av land flyttes fokus tilbake til knappen, slik at den ikke blir værende i en skjult liste.

## Tilgjengelige navn

- Sortering og favorittfilter får navn fra synlig `<label>`: «Sorter etter» og «Vis bare favoritter».
- Favorittknappen har `aria-label` på formen «Favoritt: Bangladesh». Den synlige teksten «Favoritt» inngår i navnet.
- Forrige/neste har synlig tekst «Forrige» og «Neste». Det tilgjengelige navnet inkluderer mållandet, for eksempel «Forrige land: Norge» og «Neste land: Sverige». Deaktiverte knapper beholder synlig tekst som navn.
- Land i sidemenyen bruker landnavnet. Favoritter får skjult tekst «(favoritt)».
- Landemerker: `<nav aria-label="Bla mellom land">` og sidemenyen merket via overskriften «Land».

## Synlig fokus

Alle interaktive kontroller har `:focus-visible` med 2 px omriss i aksentfargen. Musklikk viser ikke omrisset, tastaturfokus gjør det.

## Semantisk HTML

- Siden ligger i `<main>`. Sidemenyen er `<aside>`, forrige/neste er `<nav>`, og landkortet er `<article>`.
- Faktaene i kortet ligger i `<dl>` med `<dt>`/`<dd>`.
- Landlisten er en `<ul>` med én knapp per land. Valgt land har `aria-current="true"`.
- Native `disabled` brukes på forrige/neste ved første og siste land. Unødvendig `role` eller `aria-*` er ikke lagt på knapper, select eller avkrysningsboks.

## Overskriftshierarki og dokumentspråk

- Ett `<h1>`: «Utforsk verden».
- `<h2>` brukes til sidemenyen («Land») og landnavnet i kortet. Kortets `article` er koblet til landnavnet med `aria-labelledby`.
- Dokumentspråk (`lang`) og dokumenttittel settes i `index.html` og dekkes av en egen issue.

## Bildetekst

- Flagget har `alt` på formen «Flagget til …».
- Mangler flagget, eller kan bildet ikke lastes, vises teksten «Flagg ikke tilgjengelig» i stedet for et bilde.
- Stjerner for favoritt er dekorative og skjult med `aria-hidden`.

## Favorittstatus

- Favorittknappen er en vanlig `<button>` med `aria-pressed`. Navnet er det samme i begge tilstander; `aria-pressed` forteller om den er på.
- I sidemenyen vises favoritt både visuelt (★) og for skjermlesere («(favoritt)»).

## Lasting og feil

- Lasting bruker `role="status"`: «Laster land …».
- Feil bruker `role="alert"` og knappen «Prøv igjen».
- Tomt favorittutvalg bruker `role="status"` og knappen «Vis alle land».
- Posisjonen «Land 3 av 25» ligger i en `aria-live="polite"`-region, slik at skjermlesere leser ny posisjon ved blaing.

## Responsivt reflow og zoom

- `viewport` er satt, og layouten bruker flexbox/grid med `max-width: 100%` og `minmax(0, 1fr)`, slik at innholdet ombrekkes i stedet for å gi horisontal scrolling.
- Lange navn og tall brytes med `overflow-wrap`. Flagget beholder proporsjonene.
- Under 641 px ligger menyen over kortet, og landlisten ligger bak knappen «Landliste».
- Tekst og kontroller følger relativ fontstørrelse, slik at zoom og smale skjermer ikke klipper innholdet.
34 changes: 30 additions & 4 deletions src/components/CountryNavigation.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@ describe('CountryNavigation', () => {

expect(screen.getByText('Land 2 av 3')).toBeInTheDocument();

await userEvent.click(screen.getByRole('button', { name: 'Neste' }));
await userEvent.click(
screen.getByRole('button', { name: 'Neste land: Denmark' }),
);

expect(onSelect).toHaveBeenCalledWith('DNK');
});
Expand All @@ -38,11 +40,31 @@ describe('CountryNavigation', () => {
/>,
);

await userEvent.click(screen.getByRole('button', { name: 'Forrige' }));
await userEvent.click(
screen.getByRole('button', { name: 'Forrige land: Norway' }),
);

expect(onSelect).toHaveBeenCalledWith('NOR');
});

it('gir forrige- og neste-knappene tilgjengelige navn med mållandet', () => {
render(
<CountryNavigation
countries={countries}
selectedCode="SWE"
onSelect={vi.fn()}
/>,
);

const previous = screen.getByRole('button', {
name: 'Forrige land: Norway',
});
const next = screen.getByRole('button', { name: 'Neste land: Denmark' });

expect(previous).toHaveTextContent('Forrige');
expect(next).toHaveTextContent('Neste');
});

it('deaktiverer «Forrige» ved første og «Neste» ved siste land', () => {
const { rerender } = render(
<CountryNavigation
Expand All @@ -52,7 +74,9 @@ describe('CountryNavigation', () => {
/>,
);
expect(screen.getByRole('button', { name: 'Forrige' })).toBeDisabled();
expect(screen.getByRole('button', { name: 'Neste' })).toBeEnabled();
expect(
screen.getByRole('button', { name: 'Neste land: Sweden' }),
).toBeEnabled();

rerender(
<CountryNavigation
Expand All @@ -61,7 +85,9 @@ describe('CountryNavigation', () => {
onSelect={vi.fn()}
/>,
);
expect(screen.getByRole('button', { name: 'Forrige' })).toBeEnabled();
expect(
screen.getByRole('button', { name: 'Forrige land: Sweden' }),
).toBeEnabled();
expect(screen.getByRole('button', { name: 'Neste' })).toBeDisabled();
});

Expand Down
4 changes: 4 additions & 0 deletions src/components/CountryNavigation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ type CountryNavigationProps = {

// Forrige/neste mellom land i utvalget. Valgt land identifiseres med landkode,
// og komponenten eier ingen state selv. Direktevalg ligger i CountryMenu.
// Synlig tekst er «Forrige»/«Neste». aria-label forteller skjermlesere
// hvilket land knappen går til. Deaktiverte knapper har ingen nabo.
function CountryNavigation({
countries,
selectedCode,
Expand All @@ -31,6 +33,7 @@ function CountryNavigation({
className="country-navigation__button"
onClick={() => previous && onSelect(previous.code)}
disabled={!previous}
aria-label={previous ? `Forrige land: ${previous.name}` : undefined}
>
Forrige
</button>
Expand All @@ -42,6 +45,7 @@ function CountryNavigation({
className="country-navigation__button"
onClick={() => next && onSelect(next.code)}
disabled={!next}
aria-label={next ? `Neste land: ${next.name}` : undefined}
>
Neste
</button>
Expand Down