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:
@@ -10,6 +10,7 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
||||
({ className, type, label, error, id, ...props }, ref) => {
|
||||
const generatedId = React.useId()
|
||||
const inputId = id || generatedId
|
||||
const errorId = `${inputId}-error`
|
||||
|
||||
return (
|
||||
<div className="w-full">
|
||||
@@ -34,11 +35,15 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
||||
className
|
||||
)}
|
||||
ref={ref}
|
||||
aria-required={props.required ? "true" : undefined}
|
||||
aria-invalid={error ? "true" : "false"}
|
||||
aria-describedby={error ? errorId : undefined}
|
||||
{...props}
|
||||
/>
|
||||
{error && (
|
||||
<p className="mt-1 text-sm text-[#C41E3A]">{error}</p>
|
||||
<p id={errorId} className="mt-1 text-sm text-[#C41E3A]" role="alert">
|
||||
{error}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
export function LoadingSkeleton({ className = '' }: { className?: string }) {
|
||||
return (
|
||||
<div className={`animate-pulse bg-gray-200 rounded ${className}`} />
|
||||
);
|
||||
}
|
||||
|
||||
export function FormSkeleton() {
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<LoadingSkeleton className="h-12 w-full" />
|
||||
<LoadingSkeleton className="h-12 w-full" />
|
||||
<LoadingSkeleton className="h-12 w-full" />
|
||||
<LoadingSkeleton className="h-32 w-full" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function SectionSkeleton() {
|
||||
return (
|
||||
<div className="py-24">
|
||||
<div className="container-wide">
|
||||
<LoadingSkeleton className="h-12 w-1/3 mb-8" />
|
||||
<LoadingSkeleton className="h-6 w-2/3 mb-4" />
|
||||
<LoadingSkeleton className="h-6 w-1/2" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -10,6 +10,7 @@ const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
|
||||
({ className, label, error, id, ...props }, ref) => {
|
||||
const generatedId = React.useId()
|
||||
const textareaId = id || generatedId
|
||||
const errorId = `${textareaId}-error`
|
||||
|
||||
return (
|
||||
<div className="w-full">
|
||||
@@ -33,11 +34,15 @@ const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
|
||||
className
|
||||
)}
|
||||
ref={ref}
|
||||
aria-required={props.required ? "true" : undefined}
|
||||
aria-invalid={error ? "true" : "false"}
|
||||
aria-describedby={error ? errorId : undefined}
|
||||
{...props}
|
||||
/>
|
||||
{error && (
|
||||
<p className="mt-1 text-sm text-[#C41E3A]">{error}</p>
|
||||
<p id={errorId} className="mt-1 text-sm text-[#C41E3A]" role="alert">
|
||||
{error}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user