UNPKG

@restnfeel/agentc-starter-kit

Version:

한국어 기업용 CMS 모듈 - Task Master AI와 함께 빠르게 웹사이트를 구현할 수 있는 재사용 가능한 컴포넌트 시스템

286 lines (279 loc) 11.3 kB
"use client"; import { jsx } from 'react/jsx-runtime'; import { useEffect, useCallback, useState } from 'react'; // Hook for detecting accessibility preferences function useAccessibilityPreferences() { const [preferences, setPreferences] = useState({ prefersReducedMotion: false, prefersHighContrast: false, prefersLargeText: false, supportsTouchOnly: false, screenReaderActive: false, }); useEffect(() => { if (typeof window === "undefined") return; const checkPreferences = () => { // Reduced motion preference const reducedMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches; // High contrast preference const highContrast = window.matchMedia("(prefers-contrast: high)").matches; // Large text preference (approximate detection) const largeText = window.devicePixelRatio > 1.5 && window.innerWidth < 768; // Touch-only device detection const touchOnly = "ontouchstart" in window && !window.matchMedia("(hover: hover)").matches; // Screen reader detection (heuristic) const screenReader = !window.speechSynthesis || window.navigator.userAgent.includes("NVDA") || window.navigator.userAgent.includes("JAWS") || window.navigator.userAgent.includes("VoiceOver"); setPreferences({ prefersReducedMotion: reducedMotion, prefersHighContrast: highContrast, prefersLargeText: largeText, supportsTouchOnly: touchOnly, screenReaderActive: screenReader, }); }; checkPreferences(); // Listen for preference changes const mediaQueries = [ window.matchMedia("(prefers-reduced-motion: reduce)"), window.matchMedia("(prefers-contrast: high)"), window.matchMedia("(hover: hover)"), ]; const handleChange = () => checkPreferences(); mediaQueries.forEach((mq) => { if (mq.addEventListener) { mq.addEventListener("change", handleChange); } else { mq.addListener(handleChange); } }); return () => { mediaQueries.forEach((mq) => { if (mq.removeEventListener) { mq.removeEventListener("change", handleChange); } else { mq.removeListener(handleChange); } }); }; }, []); return preferences; } // Hook for haptic feedback function useHapticFeedback(enabled = true) { const triggerHaptic = useCallback((type = "light") => { if (!enabled || typeof navigator === "undefined") return; const vibrationPatterns = { light: [10], medium: [20], heavy: [50], error: [100, 50, 100], }; if ("vibrate" in navigator) { navigator.vibrate(vibrationPatterns[type]); } }, [enabled]); return triggerHaptic; } // Hook for voice announcements function useVoiceAnnouncements(enabled = true) { const announce = useCallback((message, priority = "polite") => { if (!enabled || typeof window === "undefined") return; // Create or get existing live region let liveRegion = document.getElementById("mobile-chat-announcements"); if (!liveRegion) { liveRegion = document.createElement("div"); liveRegion.id = "mobile-chat-announcements"; liveRegion.setAttribute("aria-live", priority); liveRegion.setAttribute("aria-atomic", "true"); liveRegion.style.position = "absolute"; liveRegion.style.left = "-10000px"; liveRegion.style.width = "1px"; liveRegion.style.height = "1px"; liveRegion.style.overflow = "hidden"; document.body.appendChild(liveRegion); } // Clear previous content and set new message liveRegion.textContent = ""; setTimeout(() => { liveRegion.textContent = message; }, 100); // Clear message after a delay setTimeout(() => { if (liveRegion) { liveRegion.textContent = ""; } }, 3000); }, [enabled]); return announce; } function TouchButton({ children, onClick, disabled = false, variant = "primary", size = "md", className = "", ariaLabel, hapticFeedback = true, }) { const preferences = useAccessibilityPreferences(); const triggerHaptic = useHapticFeedback(hapticFeedback); const announce = useVoiceAnnouncements(); const handleClick = useCallback(() => { if (disabled) return; triggerHaptic("light"); onClick(); if (ariaLabel) { announce(`${ariaLabel} activated`, "polite"); } }, [disabled, triggerHaptic, onClick, ariaLabel, announce]); const getSizeStyles = () => { const sizes = { sm: { minHeight: 44, minWidth: 44, padding: "8px 12px", fontSize: "14px", }, md: { minHeight: 48, minWidth: 48, padding: "12px 16px", fontSize: "16px", }, lg: { minHeight: 56, minWidth: 56, padding: "16px 20px", fontSize: "18px", }, }; const baseSize = sizes[size]; // Increase size for large text preference if (preferences.prefersLargeText) { return { ...baseSize, minHeight: baseSize.minHeight + 8, minWidth: baseSize.minWidth + 8, fontSize: `calc(${baseSize.fontSize} * 1.2)`, }; } return baseSize; }; const getVariantStyles = () => { const variants = { primary: { background: preferences.prefersHighContrast ? "#000" : "linear-gradient(135deg, #667eea 0%, #764ba2 100%)", color: "#fff", border: preferences.prefersHighContrast ? "2px solid #fff" : "none", }, secondary: { background: preferences.prefersHighContrast ? "#fff" : "#f8f9fa", color: preferences.prefersHighContrast ? "#000" : "#333", border: preferences.prefersHighContrast ? "2px solid #000" : "1px solid #e1e5e9", }, ghost: { background: "transparent", color: preferences.prefersHighContrast ? "#000" : "#667eea", border: preferences.prefersHighContrast ? "2px solid #000" : "1px solid transparent", }, }; return variants[variant]; }; const sizeStyles = getSizeStyles(); const variantStyles = getVariantStyles(); return (jsx("button", { onClick: handleClick, disabled: disabled, className: `touch-button ${className}`, "aria-label": ariaLabel, style: { ...sizeStyles, ...variantStyles, borderRadius: "8px", cursor: disabled ? "not-allowed" : "pointer", opacity: disabled ? 0.6 : 1, transition: preferences.prefersReducedMotion ? "none" : "all 0.2s ease", touchAction: "manipulation", userSelect: "none", WebkitTapHighlightColor: "transparent", }, children: children })); } function TouchInput({ value, onChange, onSubmit, placeholder, disabled = false, className = "", ariaLabel, autoFocus = false, }) { const preferences = useAccessibilityPreferences(); const handleKeyPress = useCallback((e) => { if (e.key === "Enter" && onSubmit) { e.preventDefault(); onSubmit(); } }, [onSubmit]); const getInputStyles = () => { const baseStyles = { minHeight: preferences.prefersLargeText ? 56 : 48, padding: preferences.prefersLargeText ? "16px 20px" : "12px 16px", fontSize: preferences.prefersLargeText ? "18px" : "16px", borderRadius: "24px", border: preferences.prefersHighContrast ? "2px solid #000" : "2px solid #e1e5e9", background: preferences.prefersHighContrast ? "#fff" : "#f8f9fa", color: preferences.prefersHighContrast ? "#000" : "#333", outline: "none", width: "100%", touchAction: "manipulation", }; return baseStyles; }; return (jsx("input", { type: "text", value: value, onChange: (e) => onChange(e.target.value), onKeyPress: handleKeyPress, placeholder: placeholder, disabled: disabled, className: className, "aria-label": ariaLabel, autoFocus: autoFocus, style: getInputStyles() })); } // Main accessibility enhancer component function MobileAccessibilityEnhancer({ children, enableHapticFeedback = true, enableVoiceAnnouncements = true, touchTargetSize = { minSize: 48, spacing: 8, feedbackDuration: 200 }, className = "", }) { const preferences = useAccessibilityPreferences(); useEffect(() => { // Apply global accessibility styles const style = document.createElement("style"); style.textContent = ` .mobile-accessibility-enhanced { ${preferences.prefersReducedMotion ? "animation-duration: 0.01ms !important; animation-iteration-count: 1 !important; transition-duration: 0.01ms !important;" : ""} ${preferences.prefersHighContrast ? "filter: contrast(150%);" : ""} } .mobile-accessibility-enhanced * { -webkit-tap-highlight-color: transparent; touch-action: manipulation; } .mobile-accessibility-enhanced button, .mobile-accessibility-enhanced input, .mobile-accessibility-enhanced [role="button"] { min-height: ${touchTargetSize.minSize}px; min-width: ${touchTargetSize.minSize}px; margin: ${touchTargetSize.spacing}px; } .mobile-accessibility-enhanced input, .mobile-accessibility-enhanced textarea { font-size: 16px !important; /* Prevent zoom on iOS */ } ${preferences.prefersLargeText ? ` .mobile-accessibility-enhanced { font-size: 1.2em; } .mobile-accessibility-enhanced button, .mobile-accessibility-enhanced input { padding: 16px; } ` : ""} `; document.head.appendChild(style); return () => { document.head.removeChild(style); }; }, [preferences, touchTargetSize]); return (jsx("div", { className: `mobile-accessibility-enhanced ${className}`, children: children })); } export { MobileAccessibilityEnhancer, TouchButton, TouchInput, useAccessibilityPreferences, useHapticFeedback, useVoiceAnnouncements }; //# sourceMappingURL=mobile-accessibility-enhancer.js.map