Added accessibility support
This commit is contained in:
@@ -0,0 +1,171 @@
|
||||
# Dostępność — widget a11y (WCAG)
|
||||
|
||||
Widget dostępności: pływający przycisk otwierający panel z opcjami dla osób z
|
||||
niepełnosprawnościami (rozmiar tekstu, kontrast, skala szarości, podkreślone
|
||||
linki, czytelna czcionka, wyłączenie animacji, duży kursor).
|
||||
|
||||
> **OPCJONALNY — nie dodawaj domyślnie.** Widget a11y jest wymagany prawnie
|
||||
> TYLKO dla niektórych stron (podmioty publiczne, część e-commerce/usług objętych
|
||||
> European Accessibility Act). Dla większości stron komercyjnych to OPCJA, nie
|
||||
> obowiązek. Dodawaj GDY klient/projekt tego wymaga — nie na każdej stronie z
|
||||
> automatu. W razie wątpliwości: zapytaj, czy strona podlega wymogom dostępności.
|
||||
|
||||
Wzorzec jak CookieBanner: provider + widget, wpinasz raz. Preferencje w cookie
|
||||
(bez flash), stosowane jako atrybuty `data-a11y-*` na `<html>`.
|
||||
|
||||
> **Granica plugin/projekt:** plugin dostarcza MECHANIZM (widget, stan, cookie,
|
||||
> atrybuty na html). Projekt dostarcza CSS reagujący na atrybuty — bo style
|
||||
> zależą od designu projektu (kolory, czcionki, Tailwind). Plugin NIE narzuca
|
||||
> stylów, żeby nie kolidować. Gotowy CSS do skopiowania niżej.
|
||||
|
||||
---
|
||||
|
||||
## 1. Wpięcie — Provider + Widget
|
||||
|
||||
```tsx
|
||||
// app/(frontend)/[locale]/layout.tsx
|
||||
import { AccessibilityProvider, AccessibilityWidget } from '@intecion/ipal-kit/client'
|
||||
|
||||
<AccessibilityProvider>
|
||||
<body>
|
||||
{children}
|
||||
<AccessibilityWidget
|
||||
classNames={{
|
||||
button: 'a11y-button',
|
||||
panel: 'a11y-panel',
|
||||
row: 'a11y-row',
|
||||
label: 'a11y-label',
|
||||
control: 'a11y-control',
|
||||
active: 'a11y-active',
|
||||
resetButton: 'a11y-reset',
|
||||
closeButton: 'a11y-close',
|
||||
}}
|
||||
texts={{ title: 'Dostępność', reset: 'Resetuj' }} // opcjonalne, PL domyślnie
|
||||
/>
|
||||
</body>
|
||||
</AccessibilityProvider>
|
||||
```
|
||||
|
||||
Widget jest bez stylów (jak CookieBanner) — classNames dopasowujesz do designu.
|
||||
|
||||
---
|
||||
|
||||
## 2. CSS reagujący na atrybuty (OBOWIĄZKOWE — projekt)
|
||||
|
||||
Widget ustawia atrybuty na `<html>`. Bez tego CSS nic się nie dzieje. Skopiuj do
|
||||
globalnego CSS projektu (dostosuj do designu):
|
||||
|
||||
```css
|
||||
/* Rozmiar tekstu */
|
||||
html[data-a11y-text="1"] { font-size: 112.5%; }
|
||||
html[data-a11y-text="2"] { font-size: 125%; }
|
||||
html[data-a11y-text="3"] { font-size: 150%; }
|
||||
|
||||
/* Odstęp między liniami */
|
||||
html[data-a11y-line="1"] * { line-height: 1.8 !important; }
|
||||
html[data-a11y-line="2"] * { line-height: 2.2 !important; }
|
||||
|
||||
/* Kontrast wysoki */
|
||||
html[data-a11y-contrast="high"] {
|
||||
filter: contrast(1.4);
|
||||
}
|
||||
/* Kontrast odwrócony */
|
||||
html[data-a11y-contrast="inverted"] {
|
||||
filter: invert(1) hue-rotate(180deg);
|
||||
}
|
||||
html[data-a11y-contrast="inverted"] img,
|
||||
html[data-a11y-contrast="inverted"] video {
|
||||
filter: invert(1) hue-rotate(180deg); /* przywróć media */
|
||||
}
|
||||
|
||||
/* Skala szarości */
|
||||
html[data-a11y-grayscale="on"] { filter: grayscale(1); }
|
||||
/* Uwaga: filter na html nie kumuluje się — jeśli łączysz kontrast+szarość,
|
||||
zastosuj na body albo połącz w jednej regule. */
|
||||
|
||||
/* Podkreślone linki */
|
||||
html[data-a11y-underline="on"] a { text-decoration: underline !important; }
|
||||
|
||||
/* Czytelna czcionka (podmień na swoją dyslexia-friendly / prostą) */
|
||||
html[data-a11y-font="readable"] * {
|
||||
font-family: Verdana, Tahoma, sans-serif !important;
|
||||
letter-spacing: 0.02em;
|
||||
}
|
||||
|
||||
/* Wyłączenie animacji */
|
||||
html[data-a11y-motion="reduce"] *,
|
||||
html[data-a11y-motion="reduce"] *::before,
|
||||
html[data-a11y-motion="reduce"] *::after {
|
||||
animation-duration: 0.001ms !important;
|
||||
transition-duration: 0.001ms !important;
|
||||
scroll-behavior: auto !important;
|
||||
}
|
||||
|
||||
/* Duży kursor */
|
||||
html[data-a11y-cursor="big"] * {
|
||||
cursor: url('/cursors/big.svg') 4 4, auto !important;
|
||||
}
|
||||
```
|
||||
|
||||
Dostosuj wartości do projektu (kolory kontrastu, czcionka, kursor). To Twój CSS —
|
||||
plugin tylko ustawia atrybuty.
|
||||
|
||||
> **Kontrast + filter:** wiele opcji używa `filter` na `<html>`. CSS `filter` na
|
||||
> jednym elemencie NIE kumuluje wielu wartości z różnych reguł — ostatnia wygrywa.
|
||||
> Jeśli chcesz łączyć (np. szarość + kontrast), zastosuj filtry na `body` z
|
||||
> pełną wartością, albo zbuduj reguły kombinowane. Dla pojedynczych opcji działa
|
||||
> bez problemu.
|
||||
|
||||
---
|
||||
|
||||
## 3. Bez flash (SSR) — opcjonalne
|
||||
|
||||
Domyślnie widget stosuje atrybuty po hydratacji (krótki flash przy ładowaniu,
|
||||
jeśli użytkownik miał ustawienia). Żeby tego uniknąć, odczytaj cookie server-side
|
||||
i ustaw atrybuty na `<html>` w SSR:
|
||||
|
||||
```tsx
|
||||
// layout.tsx (server) — odczytaj cookie i ustaw atrybuty od razu
|
||||
import { cookies } from 'next/headers'
|
||||
import { A11Y_COOKIE, parseA11y, a11yAttributes } from '@intecion/ipal-kit'
|
||||
|
||||
const raw = (await cookies()).get(A11Y_COOKIE)?.value
|
||||
const attrs = a11yAttributes(parseA11y(raw))
|
||||
const htmlAttrs = Object.fromEntries(
|
||||
Object.entries(attrs).filter(([, v]) => v !== null),
|
||||
)
|
||||
|
||||
return <html lang={locale} {...htmlAttrs}>...</html>
|
||||
```
|
||||
|
||||
Provider i tak re-aplikuje na kliencie i synchronizuje. To tylko eliminuje flash.
|
||||
|
||||
---
|
||||
|
||||
## 4. Osobny przycisk otwierający (opcjonalnie)
|
||||
|
||||
Widget ma wbudowany pływający przycisk. Jeśli chcesz otwierać panel z innego
|
||||
miejsca (np. stopka „Dostępność"), użyj hooka:
|
||||
|
||||
```tsx
|
||||
'use client'
|
||||
import { useAccessibility } from '@intecion/ipal-kit/client'
|
||||
// stan otwarcia trzymaj sam, albo rozbuduj widget — hook daje state/set/reset
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. Compliance — kiedy dostępność jest wymagana
|
||||
|
||||
Dostępność (WCAG) jest wymagana prawnie TYLKO dla części stron:
|
||||
- **Podmioty publiczne** (urzędy, szkoły, instytucje) — ustawa o dostępności cyfrowej
|
||||
- **Duże e-commerce / usługi** objęte European Accessibility Act (2019/882, od 2025)
|
||||
- Strony, gdzie klient sam tego wymaga (polityka firmy, przetarg)
|
||||
|
||||
Dla **większości stron komercyjnych** (wizytówka, mała firma, katalog) widget a11y
|
||||
to **opcja, nie obowiązek** — dodawaj gdy klient wymaga, nie z automatu.
|
||||
|
||||
Gdy dodajesz: widget sam w sobie NIE czyni strony w pełni dostępną — to pomoc dla
|
||||
użytkownika. Pełna dostępność to też semantyczny HTML, alt teksty, nawigacja
|
||||
klawiaturą, kontrast bazowy. Widget uzupełnia, nie zastępuje. Nie sprzedawaj
|
||||
klientowi „mamy widget = jesteśmy zgodni z WCAG". Patrz wymagania-prawne.md.
|
||||
Reference in New Issue
Block a user