import React, { createContext, useContext, useState, useEffect } from 'react'; import PropTypes from 'prop-types'; const AccessibilityContext = createContext(); export const useAccessibility = () => { const context = useContext(AccessibilityContext); if (!context) { throw new Error('useAccessibility must be used within AccessibilityProvider'); } return context; }; const AccessibilityProvider = ({ children }) => { const [reducedMotion, setReducedMotion] = useState(false); const [highContrast, setHighContrast] = useState(false); const [fontSize, setFontSize] = useState('medium'); const [announcements, setAnnouncements] = useState([]); // Detect user preferences useEffect(() => { // Check for reduced motion preference const mediaQuery = window.matchMedia('(prefers-reduced-motion: reduce)'); setReducedMotion(mediaQuery.matches); const handleChange = (e) => setReducedMotion(e.matches); mediaQuery.addEventListener('change', handleChange); return () => mediaQuery.removeEventListener('change', handleChange); }, []); useEffect(() => { // Check for high contrast preference const mediaQuery = window.matchMedia('(prefers-contrast: high)'); setHighContrast(mediaQuery.matches); const handleChange = (e) => setHighContrast(e.matches); mediaQuery.addEventListener('change', handleChange); return () => mediaQuery.removeEventListener('change', handleChange); }, []); // Announce to screen readers const announce = (message, priority = 'polite') => { const id = Date.now(); const announcement = { id, message, priority }; setAnnouncements(prev => [...prev, announcement]); // Remove announcement after it's been read setTimeout(() => { setAnnouncements(prev => prev.filter(a => a.id !== id)); }, 1000); }; // Skip to main content const skipToMain = () => { const main = document.querySelector('main'); if (main) { main.focus(); main.scrollIntoView(); } }; // Focus management const focusElement = (selector) => { const element = document.querySelector(selector); if (element) { element.focus(); return true; } return false; }; const value = { reducedMotion, highContrast, fontSize, setFontSize, announce, skipToMain, focusElement, announcements, }; return ( {children} {/* Screen reader announcements */}
{announcements .filter(a => a.priority === 'polite') .map(a => (
{a.message}
))}
{announcements .filter(a => a.priority === 'assertive') .map(a => (
{a.message}
))}
{/* Skip to main content link */}
); }; AccessibilityProvider.propTypes = { children: PropTypes.node.isRequired, }; export default AccessibilityProvider;