feat(a11y): Implement ARIA accessibility sweep and keyboard navigation
- Added visually hidden labels, IDs, and names to form inputs across all key components and pages (modals, directories, chatbots) - Added tabIndex and onKeyDown handlers to clickable table rows in OwnerProjectDetail for keyboard accessibility - Validated existing ARIA roles and Escape key bindings on modal components
This commit is contained in:
@@ -129,12 +129,14 @@ const CustomerProfile = () => {
|
||||
{activeTab === 'details' ? (
|
||||
<SpotlightCard>
|
||||
<form onSubmit={handleSaveProfile} className="p-8 space-y-6">
|
||||
<FormInput label="Full Name" icon={User} value={formData.name} onChange={v => setFormData({ ...formData, name: v })} required />
|
||||
<FormInput label="Email Address" icon={null} prefix="@" type="email" value={formData.email} onChange={v => setFormData({ ...formData, email: v })} required />
|
||||
<FormInput label="Phone Number" value={formData.phone} onChange={v => setFormData({ ...formData, phone: v })} required />
|
||||
<FormInput id="customer-name" name="name" label="Full Name" icon={User} value={formData.name} onChange={v => setFormData({ ...formData, name: v })} required />
|
||||
<FormInput id="customer-email" name="email" label="Email Address" icon={null} prefix="@" type="email" value={formData.email} onChange={v => setFormData({ ...formData, email: v })} required />
|
||||
<FormInput id="customer-phone" name="phone" label="Phone Number" value={formData.phone} onChange={v => setFormData({ ...formData, phone: v })} required />
|
||||
<div className="space-y-2 md:col-span-2">
|
||||
<label className="text-xs font-bold uppercase text-zinc-500">Address</label>
|
||||
<label htmlFor="customer-address" className="text-xs font-bold uppercase text-zinc-500">Address</label>
|
||||
<textarea
|
||||
id="customer-address"
|
||||
name="address"
|
||||
value={formData.address}
|
||||
onChange={(e) => setFormData({ ...formData, address: e.target.value })}
|
||||
className="w-full px-4 py-2 bg-zinc-100 dark:bg-zinc-800/50 border border-zinc-200 dark:border-zinc-700 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent outline-none transition-all h-24 resize-none"
|
||||
@@ -168,16 +170,16 @@ const CustomerProfile = () => {
|
||||
</div>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<div className="md:col-span-2">
|
||||
<FormInput label="Address" value="2612 Dunwick Dr, Plano, TX 75023" onChange={() => { }} />
|
||||
<FormInput id="prop-address" name="prop-address" label="Address" value="2612 Dunwick Dr, Plano, TX 75023" onChange={() => { }} />
|
||||
</div>
|
||||
<FormInput label="Type" value="Residential" onChange={() => { }} />
|
||||
<FormInput label="Year Built" value="1971" onChange={() => { }} />
|
||||
<FormInput label="Built-Up Area (sqft)" value="2450" onChange={() => { }} />
|
||||
<FormInput label="Lot Size (sqft)" value="8500" onChange={() => { }} />
|
||||
<FormInput id="prop-type" name="prop-type" label="Type" value="Residential" onChange={() => { }} />
|
||||
<FormInput id="prop-year" name="prop-year" label="Year Built" value="1971" onChange={() => { }} />
|
||||
<FormInput id="prop-area-sqft" name="prop-area-sqft" label="Built-Up Area (sqft)" value="2450" onChange={() => { }} />
|
||||
<FormInput id="prop-lot-sqft" name="prop-lot-sqft" label="Lot Size (sqft)" value="8500" onChange={() => { }} />
|
||||
<div className="grid grid-cols-3 gap-4 md:col-span-2">
|
||||
<FormInput label="Beds" value="4" onChange={() => { }} />
|
||||
<FormInput label="Baths" value="3" onChange={() => { }} />
|
||||
<FormInput label="Parking" value="2" onChange={() => { }} />
|
||||
<FormInput id="prop-beds" name="prop-beds" label="Beds" value="4" onChange={() => { }} />
|
||||
<FormInput id="prop-baths" name="prop-baths" label="Baths" value="3" onChange={() => { }} />
|
||||
<FormInput id="prop-parking" name="prop-parking" label="Parking" value="2" onChange={() => { }} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -188,10 +190,10 @@ const CustomerProfile = () => {
|
||||
<ShieldCheck size={20} /> <h3 className="text-sm font-black uppercase tracking-widest">Insurance Information</h3>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<FormInput label="Insurance Company" value="Farmers" onChange={() => { }} />
|
||||
<FormInput label="Policy Number" value="POL-987654321" onChange={() => { }} />
|
||||
<FormInput label="Claim Filed?" value="No" onChange={() => { }} />
|
||||
<FormInput label="Adjuster Name" value="" placeholder="N/A" onChange={() => { }} />
|
||||
<FormInput id="ins-company" name="ins-company" label="Insurance Company" value="Farmers" onChange={() => { }} />
|
||||
<FormInput id="ins-policy" name="ins-policy" label="Policy Number" value="POL-987654321" onChange={() => { }} />
|
||||
<FormInput id="ins-claim-filed" name="ins-claim-filed" label="Claim Filed?" value="No" onChange={() => { }} />
|
||||
<FormInput id="ins-adjuster" name="ins-adjuster" label="Adjuster Name" value="" placeholder="N/A" onChange={() => { }} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -307,13 +309,15 @@ const ReadOnlyField = ({ label, value }) => (
|
||||
</div>
|
||||
);
|
||||
|
||||
const FormInput = ({ label, icon: Icon, prefix, value, onChange, type = "text", required }) => (
|
||||
const FormInput = ({ id, name, label, icon: Icon, prefix, value, onChange, type = "text", required }) => (
|
||||
<div className="space-y-2">
|
||||
<label className="text-xs font-bold uppercase text-zinc-500">{label} {required && <span className="text-red-500">*</span>}</label>
|
||||
<label htmlFor={id} className="text-xs font-bold uppercase text-zinc-500">{label} {required && <span className="text-red-500">*</span>}</label>
|
||||
<div className="relative">
|
||||
{Icon && <Icon className="absolute left-3 top-3 text-zinc-400" size={16} />}
|
||||
{prefix && <span className="absolute left-3 top-3 text-zinc-400 text-xs">{prefix}</span>}
|
||||
<input
|
||||
id={id}
|
||||
name={name || id}
|
||||
type={type}
|
||||
required={required}
|
||||
value={value}
|
||||
|
||||
Reference in New Issue
Block a user