Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 | 'use client'; import { createContext, useContext, useState, useCallback, ReactNode } from 'react'; /** * LiveRegion Context Type * Provides methods for announcing messages to screen readers */ interface LiveRegionContextType { /** * Announce a message to screen readers * @param message - The message to announce * @param priority - 'polite' (default) waits for user to finish, 'assertive' interrupts immediately */ announce: (message: string, priority?: 'polite' | 'assertive') => void; } const LiveRegionContext = createContext<LiveRegionContextType | null>(null); interface LiveRegionProviderProps { children: ReactNode; } /** * LiveRegionProvider Component * * Provides a way to announce dynamic content changes to screen readers. * Wraps the application and provides the useAnnounce hook. * * @example * ```tsx * // In your layout or app root: * <LiveRegionProvider> * <App /> * </LiveRegionProvider> * * // In a component: * function AddToCartButton({ product }) { * const announce = useAnnounce(); * * const handleAddToCart = () => { * addToCart(product); * announce(`${product.name} added to cart`); * }; * * return <button onClick={handleAddToCart}>Add to Cart</button>; * } * ``` */ export function LiveRegionProvider({ children }: LiveRegionProviderProps) { const [politeMessage, setPoliteMessage] = useState(''); const [assertiveMessage, setAssertiveMessage] = useState(''); const announce = useCallback((message: string, priority: 'polite' | 'assertive' = 'polite') => { if (priority === 'assertive') { // Clear first, then set after a brief delay to ensure screen readers pick up the change setAssertiveMessage(''); setTimeout(() => setAssertiveMessage(message), 100); // Auto-clear after announcement setTimeout(() => setAssertiveMessage(''), 1000); } else { setPoliteMessage(''); setTimeout(() => setPoliteMessage(message), 100); setTimeout(() => setPoliteMessage(''), 1000); } }, []); return ( <LiveRegionContext.Provider value={{ announce }}> {children} {/* Polite announcements (non-interrupting) - used for status updates */} <div role="status" aria-live="polite" aria-atomic="true" className="sr-only" > {politeMessage} </div> {/* Assertive announcements (interrupting) - used for urgent messages */} <div role="alert" aria-live="assertive" aria-atomic="true" className="sr-only" > {assertiveMessage} </div> </LiveRegionContext.Provider> ); } /** * useAnnounce Hook * * Returns a function to announce messages to screen readers. * Must be used within a LiveRegionProvider. * * @returns announce function * @throws Error if used outside of LiveRegionProvider * * @example * ```tsx * const announce = useAnnounce(); * * // Polite announcement (default) - doesn't interrupt user * announce('Item added to cart'); * * // Assertive announcement - interrupts immediately for urgent messages * announce('Error: Payment failed', 'assertive'); * ``` */ export function useAnnounce() { const context = useContext(LiveRegionContext); if (!context) { throw new Error('useAnnounce must be used within a LiveRegionProvider'); } return context.announce; } export default LiveRegionProvider; |