All files / components/shared/base AccessibilityProvider.tsx

93.54% Statements 29/31
75% Branches 6/8
100% Functions 9/9
100% Lines 28/28

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 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140                                                                12x                                               12x     16x 16x     16x 8x   8x 8x   8x 1x       8x   8x 8x         16x 14x   14x 3x   11x         16x 2x       16x 16x                 16x             12x                           17x   17x 2x         15x    
/* eslint-disable react-refresh/only-export-components */
/**
 * AccessibilityProvider - Context provider for accessibility settings
 *
 * Provides high contrast mode, reduced motion support, and other accessibility features
 * Respects user preferences from prefers-reduced-motion and prefers-contrast media queries
 *
 * @module components/base
 */
 
import React, {
  createContext,
  useContext,
  useState,
  useEffect,
  useMemo,
  useCallback,
} from "react";
 
/**
 * Accessibility context value interface
 */
export interface AccessibilityContextValue {
  readonly highContrast: boolean;
  readonly reducedMotion: boolean;
  readonly setHighContrast: (enabled: boolean) => void;
  readonly toggleHighContrast: () => void;
}
 
/**
 * Accessibility context
 */
const AccessibilityContext = createContext<AccessibilityContextValue | null>(
  null,
);
 
/**
 * Props for AccessibilityProvider
 */
export interface AccessibilityProviderProps {
  readonly children: React.ReactNode;
}
 
/**
 * AccessibilityProvider Component
 *
 * Provides accessibility settings throughout the component tree
 * Automatically detects user preferences for reduced motion
 *
 * @example
 * ```tsx
 * <AccessibilityProvider>
 *   <App />
 * </AccessibilityProvider>
 * ```
 */
export const AccessibilityProvider: React.FC<AccessibilityProviderProps> = ({
  children,
}) => {
  const [highContrast, setHighContrast] = useState(false);
  const [reducedMotion, setReducedMotion] = useState(false);
 
  // Detect user preference for reduced motion
  useEffect(() => {
    Iif (typeof window === "undefined") return;
 
    const mediaQuery = window.matchMedia("(prefers-reduced-motion: reduce)");
    setReducedMotion(mediaQuery.matches);
 
    const handleChange = (e: MediaQueryListEvent) => {
      setReducedMotion(e.matches);
    };
 
    // Modern browsers
    mediaQuery.addEventListener("change", handleChange);
 
    return () => {
      mediaQuery.removeEventListener("change", handleChange);
    };
  }, []);
 
  // Apply high contrast theme globally
  useEffect(() => {
    Iif (typeof document === "undefined") return;
 
    if (highContrast) {
      document.body.classList.add("high-contrast");
    } else {
      document.body.classList.remove("high-contrast");
    }
  }, [highContrast]);
 
  // Toggle high contrast mode
  const toggleHighContrast = useCallback(() => {
    setHighContrast((prev) => !prev);
  }, []);
 
  // Memoize context value to prevent unnecessary re-renders
  const value = useMemo<AccessibilityContextValue>(
    () => ({
      highContrast,
      reducedMotion,
      setHighContrast,
      toggleHighContrast,
    }),
    [highContrast, reducedMotion, toggleHighContrast],
  );
 
  return (
    <AccessibilityContext.Provider value={value}>
      {children}
    </AccessibilityContext.Provider>
  );
};
 
AccessibilityProvider.displayName = "AccessibilityProvider";
 
/**
 * Custom hook to access accessibility context
 *
 * @throws Error if used outside of AccessibilityProvider
 * @returns Accessibility context value
 *
 * @example
 * ```tsx
 * const { highContrast, reducedMotion, toggleHighContrast } = useAccessibility();
 * ```
 */
export function useAccessibility(): AccessibilityContextValue {
  const context = useContext(AccessibilityContext);
 
  if (!context) {
    throw new Error(
      "useAccessibility must be used within AccessibilityProvider",
    );
  }
 
  return context;
}