/** * @idealyst/components - Accessibility Utilities * * This module provides comprehensive accessibility support for building * WCAG 2.1 AA compliant React and React Native applications. * * @example * ```tsx * import { * AccessibilityProps, * getWebAriaProps, * useKeyboardNavigation, * useFocusTrap, * MENU_KEYS, * } from '@idealyst/components/utils/accessibility'; * ``` */ // ============================================================================= // TYPES // ============================================================================= export type { // Role types AriaRole, NativeAccessibilityRole, // Accessibility prop interfaces AccessibilityProps, InteractiveAccessibilityProps, FormAccessibilityProps, RangeAccessibilityProps, SelectionAccessibilityProps, LiveRegionAccessibilityProps, SortableAccessibilityProps, SelectableAccessibilityProps, HeadingAccessibilityProps, CurrentAccessibilityProps, // Native types NativeAccessibilityState, NativeAccessibilityValue, } from './types'; // ============================================================================= // ARIA HELPERS // ============================================================================= export { // Web ARIA mappers getWebAriaProps, getWebInteractiveAriaProps, getWebFormAriaProps, getWebRangeAriaProps, getWebSelectionAriaProps, getWebLiveRegionAriaProps, getWebSortableAriaProps, getWebSelectableAriaProps, getWebHeadingAriaProps, getWebCurrentAriaProps, // React Native mappers mapRoleToNative, getNativeAccessibilityProps, getNativeInteractiveAccessibilityProps, getNativeFormAccessibilityProps, getNativeRangeAccessibilityProps, getNativeSelectionAccessibilityProps, getNativeLiveRegionAccessibilityProps, getNativeSelectableAccessibilityProps, } from './ariaHelpers'; // ============================================================================= // HOOKS // ============================================================================= export { useKeyboardNavigation } from './useKeyboardNavigation'; export type { UseKeyboardNavigationOptions, UseKeyboardNavigationReturn, } from './useKeyboardNavigation'; export { useFocusTrap, useFocusTrapNative } from './useFocusTrap'; export type { UseFocusTrapOptions, UseFocusTrapReturn } from './useFocusTrap'; export { useAnnounce, useAnnounceNative } from './useAnnounce'; export type { AnnounceMode, UseAnnounceOptions, UseAnnounceReturn } from './useAnnounce'; // ============================================================================= // KEYBOARD PATTERNS // ============================================================================= export { // Key pattern constants BUTTON_KEYS, LINK_KEYS, MENU_KEYS, ACCORDION_KEYS, TAB_KEYS, SLIDER_KEYS, LISTBOX_KEYS, DIALOG_KEYS, CHECKBOX_KEYS, RADIO_KEYS, SWITCH_KEYS, TREE_KEYS, GRID_KEYS, COMBOBOX_KEYS, TOOLTIP_KEYS, // Helper functions matchesKey, matchesKeyPattern, } from './keyboardPatterns'; // ============================================================================= // UTILITIES // ============================================================================= /** * Counter for generating unique accessibility IDs. */ let idCounter = 0; /** * Generate a unique ID for accessibility attributes. * Use this for aria-labelledby, aria-describedby, aria-controls, etc. * * @param prefix - Optional prefix for the ID (default: 'a11y') * @returns A unique string ID * * @example * ```tsx * const inputId = generateAccessibilityId('input'); // 'input-1' * const errorId = generateAccessibilityId('error'); // 'error-2' * const helperId = generateAccessibilityId('helper'); // 'helper-3' * * * Enter your email * Invalid email format * ``` */ export function generateAccessibilityId(prefix: string = 'a11y'): string { return `${prefix}-${++idCounter}`; } /** * Reset the ID counter. Useful for testing. * @internal */ export function resetAccessibilityIdCounter(): void { idCounter = 0; } /** * Combine multiple accessibility IDs into a space-separated string. * Filters out undefined/null values. * * @param ids - IDs to combine (can include undefined/null) * @returns Space-separated string of IDs, or undefined if all are empty * * @example * ```tsx * const describedBy = combineIds(helperId, hasError ? errorId : undefined); * // Returns: "helper-1 error-2" or "helper-1" depending on hasError * ``` */ export function combineIds(...ids: (string | undefined | null)[]): string | undefined { const filtered = ids.filter((id): id is string => Boolean(id)); return filtered.length > 0 ? filtered.join(' ') : undefined; } /** * Check if the user prefers reduced motion. * Useful for disabling animations/transitions for accessibility. * * @returns true if the user prefers reduced motion * * @example * ```tsx * const prefersReducedMotion = checkReducedMotion(); * const transitionDuration = prefersReducedMotion ? 0 : 300; * ``` */ export function checkReducedMotion(): boolean { if (typeof window === 'undefined') return false; const mediaQuery = window.matchMedia?.('(prefers-reduced-motion: reduce)'); return mediaQuery?.matches ?? false; } /** * Minimum touch target size in pixels (WCAG 2.5.5 Target Size). * Interactive elements should be at least 44x44 pixels. */ export const MIN_TOUCH_TARGET_SIZE = 44; /** * Minimum contrast ratio for normal text (WCAG 2.1 AA). */ export const MIN_CONTRAST_RATIO_NORMAL = 4.5; /** * Minimum contrast ratio for large text (WCAG 2.1 AA). * Large text is 18pt+ or 14pt+ bold. */ export const MIN_CONTRAST_RATIO_LARGE = 3; /** * Minimum contrast ratio for UI components (WCAG 2.1 AA). */ export const MIN_CONTRAST_RATIO_UI = 3;