UNPKG

aura-glass

Version:

A comprehensive glassmorphism design system for React applications with 142+ production-ready components

289 lines (286 loc) 8.86 kB
'use client'; import { useState, useRef, useCallback, useEffect } from 'react'; const DEFAULT_SETTINGS = { reducedMotion: false, highContrast: false, largeText: false, colorScheme: 'auto', forcedColors: false, screenReader: false, keyboardNavigation: false }; /** * Enhanced accessibility settings hook with comprehensive device detection */ function useAccessibilitySettings(options = {}) { const { enableStorage = true, storageKey = 'aura-glass-a11y-settings', detectScreenReader = true } = options; const [settings, setSettings] = useState(DEFAULT_SETTINGS); const [isLoading, setIsLoading] = useState(true); const mediaQueriesRef = useRef([]); const keyboardDetectionRef = useRef(false); // Detect screen reader usage const detectScreenReaderUsage = useCallback(() => { if (!detectScreenReader || typeof window === 'undefined') return false; // Check for common screen reader indicators const indicators = [ // NVDA () => 'speechSynthesis' in window && window.speechSynthesis.getVoices().length > 0, // JAWS, NVDA, others () => navigator.userAgent.includes('NVDA') || navigator.userAgent.includes('JAWS'), // VoiceOver (macOS/iOS) () => 'webkitSpeechRecognition' in window, // General screen reader detection () => window.navigator.userAgent.includes('Screenreader')]; return indicators.some(indicator => { try { return indicator(); } catch { return false; } }); }, [detectScreenReader]); // Detect keyboard navigation preference const detectKeyboardNavigation = useCallback(() => { let keyboardUsed = false; const handleKeyDown = e => { if (e.key === 'Tab' || e.key === 'Enter' || e.key === ' ' || e.key.startsWith('Arrow')) { keyboardUsed = true; keyboardDetectionRef.current = true; setSettings(prev => ({ ...prev, keyboardNavigation: true })); } }; const handleMouseDown = () => { if (keyboardUsed) { keyboardDetectionRef.current = false; setSettings(prev => ({ ...prev, keyboardNavigation: false })); } }; document.addEventListener('keydown', handleKeyDown, { passive: true }); document.addEventListener('mousedown', handleMouseDown, { passive: true }); return () => { document.removeEventListener('keydown', handleKeyDown); document.removeEventListener('mousedown', handleMouseDown); }; }, []); // Load settings from storage const loadStoredSettings = useCallback(() => { if (!enableStorage || typeof window === 'undefined') return {}; try { const stored = localStorage.getItem(storageKey); return stored ? JSON.parse(stored) : {}; } catch (error) { console.warn('Failed to load accessibility settings from storage:', error); return {}; } }, [enableStorage, storageKey]); // Save settings to storage const saveSettings = useCallback(newSettings => { if (!enableStorage || typeof window === 'undefined') return; try { localStorage.setItem(storageKey, JSON.stringify(newSettings)); } catch (error) { console.warn('Failed to save accessibility settings to storage:', error); } }, [enableStorage, storageKey]); // Update individual setting const updateSetting = useCallback((key, value) => { setSettings(prev => { const newSettings = { ...prev, [key]: value }; saveSettings(newSettings); return newSettings; }); }, [saveSettings]); // Reset to defaults const resetSettings = useCallback(() => { const detectedSettings = detectSystemSettings(); const newSettings = { ...DEFAULT_SETTINGS, ...detectedSettings }; setSettings(newSettings); saveSettings(newSettings); }, [saveSettings]); // Detect system accessibility preferences const detectSystemSettings = useCallback(() => { if (typeof window === 'undefined') return {}; const queries = [{ key: 'reducedMotion', query: '(prefers-reduced-motion: reduce)' }, { key: 'highContrast', query: '(prefers-contrast: high)' }, { key: 'forcedColors', query: '(forced-colors: active)' }]; const colorSchemeQuery = window.matchMedia('(prefers-color-scheme: dark)'); const detected = { screenReader: detectScreenReaderUsage(), colorScheme: colorSchemeQuery.matches ? 'dark' : 'light' }; queries.forEach(({ key, query }) => { try { const mediaQuery = window.matchMedia(query); detected[key] = mediaQuery.matches; } catch (error) { console.warn(`Failed to detect ${key} preference:`, error); } }); return detected; }, [detectScreenReaderUsage]); // Initialize settings useEffect(() => { if (typeof window === 'undefined') { setIsLoading(false); return; } const initializeSettings = async () => { try { // Detect system preferences const systemSettings = detectSystemSettings(); // Load stored preferences const storedSettings = loadStoredSettings(); // Merge with priority: stored > system > defaults const initialSettings = { ...DEFAULT_SETTINGS, ...systemSettings, ...storedSettings }; setSettings(initialSettings); // Set up media query listeners const queries = [{ key: 'reducedMotion', query: '(prefers-reduced-motion: reduce)' }, { key: 'highContrast', query: '(prefers-contrast: high)' }, { key: 'forcedColors', query: '(forced-colors: active)' }]; const colorSchemeQuery = window.matchMedia('(prefers-color-scheme: dark)'); const updateFromMediaQuery = () => { const updates = { colorScheme: colorSchemeQuery.matches ? 'dark' : 'light' }; queries.forEach(({ key, query }) => { try { const mediaQuery = window.matchMedia(query); updates[key] = mediaQuery.matches; } catch (error) { console.warn(`Failed to update ${key} from media query:`, error); } }); setSettings(prev => { const newSettings = { ...prev, ...updates }; saveSettings(newSettings); return newSettings; }); }; // Add listeners const mediaQueries = [...queries.map(({ query }) => window.matchMedia(query)), colorSchemeQuery]; mediaQueries.forEach(mq => { mq.addEventListener('change', updateFromMediaQuery); }); mediaQueriesRef.current = mediaQueries; // Set up keyboard detection const cleanupKeyboardDetection = detectKeyboardNavigation(); // Cleanup function return () => { mediaQueries.forEach(mq => { mq.removeEventListener('change', updateFromMediaQuery); }); cleanupKeyboardDetection(); }; } catch (error) { console.error('Failed to initialize accessibility settings:', error); } finally { setIsLoading(false); } }; const cleanup = initializeSettings(); return () => { cleanup?.then(fn => fn?.()); }; }, [detectSystemSettings, loadStoredSettings, saveSettings, detectKeyboardNavigation]); // Cleanup on unmount useEffect(() => { return () => { mediaQueriesRef.current = []; }; }, []); return { settings, updateSetting, resetSettings, isLoading }; } /** * Hook for specific accessibility feature detection */ function useAccessibilityFeature(feature) { const { settings } = useAccessibilitySettings(); return settings[feature]; } /** * Hook for accessibility-aware animations */ function useAccessibleAnimation(defaultEnabled = true) { const { settings } = useAccessibilitySettings(); const shouldAnimate = defaultEnabled && !settings.reducedMotion; const animationDuration = settings.reducedMotion ? 0 : 300; const transitionDuration = settings.reducedMotion ? 0 : 200; return { shouldAnimate, animationDuration, transitionDuration }; } /** * Hook for accessibility-aware colors */ function useAccessibleColors() { const { settings } = useAccessibilitySettings(); return { shouldUseHighContrast: settings.highContrast, colorScheme: settings.colorScheme, forcedColors: settings.forcedColors }; } export { useAccessibilityFeature, useAccessibilitySettings, useAccessibleAnimation, useAccessibleColors }; //# sourceMappingURL=useAccessibilitySettings.js.map