@restnfeel/agentc-starter-kit
Version:
한국어 기업용 CMS 모듈 - Task Master AI와 함께 빠르게 웹사이트를 구현할 수 있는 재사용 가능한 컴포넌트 시스템
286 lines (279 loc) • 11.3 kB
JavaScript
"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