feat(a11y,ux): implement comprehensive accessibility and UX optimizations
Phase 1: Accessibility Optimizations - Add proper label associations and ARIA attributes to form inputs - Implement aria-required, aria-invalid, aria-describedby for better form accessibility - Add role='alert' for error messages - Enhance keyboard navigation with aria-expanded, aria-controls - Add aria-label for mobile menu button - Implement aria-current for active navigation items - Add semantic HTML with aria-labelledby for sections Phase 2: UX Optimizations - Create loading skeleton components for better loading states - Add FormSkeleton, SectionSkeleton, and LoadingSkeleton components - Prepare for lazy loading implementation Files modified: - src/components/ui/input.tsx: Enhanced with ARIA attributes - src/components/ui/textarea.tsx: Enhanced with ARIA attributes - src/components/layout/header.tsx: Added navigation ARIA labels - src/components/sections/hero-section.tsx: Added section labels - src/components/sections/services-section.tsx: Added section labels - src/components/ui/loading-skeleton.tsx: New loading state components Impact: - WCAG 2.1 AA compliance improvements - Better screen reader support - Enhanced keyboard navigation - Improved user feedback during loading
This commit is contained in:
@@ -0,0 +1,78 @@
|
||||
'use client';
|
||||
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { gsap } from 'gsap';
|
||||
|
||||
interface GSAPAnimationProps {
|
||||
className?: string;
|
||||
color?: string;
|
||||
}
|
||||
|
||||
export function GSAPAnimation({
|
||||
className = '',
|
||||
color = '#C41E3A'
|
||||
}: GSAPAnimationProps) {
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const elementsRef = useRef<HTMLDivElement[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!containerRef.current) return;
|
||||
|
||||
const elements = elementsRef.current;
|
||||
|
||||
elements.forEach((el, i) => {
|
||||
gsap.fromTo(el,
|
||||
{
|
||||
opacity: 0,
|
||||
scale: 0,
|
||||
rotation: 0
|
||||
},
|
||||
{
|
||||
opacity: 0.15,
|
||||
scale: 1,
|
||||
rotation: 360,
|
||||
duration: 8,
|
||||
delay: i * 1.5,
|
||||
repeat: -1,
|
||||
yoyo: true,
|
||||
ease: 'power2.inOut'
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
return () => {
|
||||
gsap.killTweensOf(elements);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const shapes = [
|
||||
{ type: 'circle', size: 100, x: 15, y: 20 },
|
||||
{ type: 'square', size: 80, x: 75, y: 15 },
|
||||
{ type: 'circle', size: 60, x: 65, y: 65 },
|
||||
{ type: 'square', size: 50, x: 20, y: 70 }
|
||||
];
|
||||
|
||||
return (
|
||||
<div ref={containerRef} className={`absolute inset-0 pointer-events-none ${className}`}>
|
||||
{shapes.map((shape, index) => (
|
||||
<div
|
||||
key={index}
|
||||
ref={el => {
|
||||
if (el) elementsRef.current[index] = el;
|
||||
}}
|
||||
className="absolute border-2"
|
||||
style={{
|
||||
borderColor: `${color}20`,
|
||||
width: shape.size,
|
||||
height: shape.size,
|
||||
left: `${shape.x}%`,
|
||||
top: `${shape.y}%`,
|
||||
borderRadius: shape.type === 'circle' ? '50%' : '0'
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default GSAPAnimation;
|
||||
Reference in New Issue
Block a user