♿️(frontend) add global screen reader announcer
centralize live region rendering with a shared announce hook.
This commit is contained in:
15
src/frontend/src/hooks/useScreenReaderAnnounce.ts
Normal file
15
src/frontend/src/hooks/useScreenReaderAnnounce.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import { useCallback } from 'react'
|
||||||
|
import {
|
||||||
|
announceToScreenReader,
|
||||||
|
type Politeness,
|
||||||
|
} from '@/stores/screenReaderAnnouncer'
|
||||||
|
|
||||||
|
export const useScreenReaderAnnounce = () => {
|
||||||
|
return useCallback(
|
||||||
|
(message: string, politeness: Politeness = 'polite') => {
|
||||||
|
announceToScreenReader(message, politeness)
|
||||||
|
},
|
||||||
|
[]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
@@ -4,6 +4,7 @@ import { Header } from './Header'
|
|||||||
import { layoutStore } from '@/stores/layout'
|
import { layoutStore } from '@/stores/layout'
|
||||||
import { useSnapshot } from 'valtio'
|
import { useSnapshot } from 'valtio'
|
||||||
import { Footer } from '@/layout/Footer'
|
import { Footer } from '@/layout/Footer'
|
||||||
|
import { ScreenReaderAnnouncer } from '@/primitives'
|
||||||
|
|
||||||
export type Layout = 'fullpage' | 'centered'
|
export type Layout = 'fullpage' | 'centered'
|
||||||
|
|
||||||
@@ -41,6 +42,7 @@ export const Layout = ({ children }: { children: ReactNode }) => {
|
|||||||
flexDirection: 'column',
|
flexDirection: 'column',
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
|
<ScreenReaderAnnouncer />
|
||||||
{children}
|
{children}
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
20
src/frontend/src/primitives/ScreenReaderAnnouncer.tsx
Normal file
20
src/frontend/src/primitives/ScreenReaderAnnouncer.tsx
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import { useSnapshot } from 'valtio'
|
||||||
|
import { screenReaderAnnouncerStore } from '@/stores/screenReaderAnnouncer'
|
||||||
|
|
||||||
|
export const ScreenReaderAnnouncer = () => {
|
||||||
|
const { announcement } = useSnapshot(screenReaderAnnouncerStore)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={announcement.id}
|
||||||
|
role="status"
|
||||||
|
aria-live={announcement.politeness}
|
||||||
|
aria-atomic="true"
|
||||||
|
className="sr-only"
|
||||||
|
data-announce-id={announcement.id}
|
||||||
|
>
|
||||||
|
{announcement.message}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
@@ -24,6 +24,7 @@ export { Menu } from './Menu'
|
|||||||
export { MenuList } from './MenuList'
|
export { MenuList } from './MenuList'
|
||||||
export { P } from './P'
|
export { P } from './P'
|
||||||
export { Popover } from './Popover'
|
export { Popover } from './Popover'
|
||||||
|
export { ScreenReaderAnnouncer } from './ScreenReaderAnnouncer'
|
||||||
export { Text } from './Text'
|
export { Text } from './Text'
|
||||||
export { ToggleButton } from './ToggleButton'
|
export { ToggleButton } from './ToggleButton'
|
||||||
export { Ul } from './Ul'
|
export { Ul } from './Ul'
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { proxy } from 'valtio'
|
import { proxy } from 'valtio'
|
||||||
|
|
||||||
type Politeness = 'polite' | 'assertive'
|
export type Politeness = 'polite' | 'assertive'
|
||||||
|
|
||||||
type ScreenReaderAnnouncement = {
|
type ScreenReaderAnnouncement = {
|
||||||
message: string
|
message: string
|
||||||
|
|||||||
Reference in New Issue
Block a user