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:
张翔
2026-02-24 00:40:19 +08:00
parent 44ba75e4d1
commit 016b7cfb91
22 changed files with 2479 additions and 7 deletions
+6 -1
View File
@@ -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>
)
+28
View File
@@ -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>
);
}
+6 -1
View File
@@ -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>
)