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:
Satyam
2026-02-22 03:15:21 +05:30
parent b6f899afd2
commit 5fef584d7d
15 changed files with 123 additions and 55 deletions
+17 -7
View File
@@ -18,9 +18,9 @@ const StatusBadge = ({ status }) => {
);
};
const InputGroup = ({ label, children }) => (
const InputGroup = ({ label, children, htmlFor }) => (
<div className="space-y-1.5">
<label className="text-[10px] uppercase font-bold text-zinc-400 tracking-widest ml-0.5">{label}</label>
<label htmlFor={htmlFor} className="text-[10px] uppercase font-bold text-zinc-400 tracking-widest ml-0.5">{label}</label>
<div>{children}</div>
</div>
);
@@ -256,6 +256,8 @@ const PropertyDetailDrawer = ({ isOpen, onClose, data, newLocation, onSave, load
// Helper for Inputs
const RenderInput = ({ label, field, type = "text", placeholder, options, locked = false }) => {
const inputId = `prop-${field}`;
if (!isEditing) {
return (
<InputGroup label={label}>
@@ -277,9 +279,11 @@ const PropertyDetailDrawer = ({ isOpen, onClose, data, newLocation, onSave, load
}
return (
<InputGroup label={label}>
<InputGroup label={label} htmlFor={inputId}>
{options ? (
<select
id={inputId}
name={field}
value={formData[field]}
onChange={(e) => setFormData({ ...formData, [field]: e.target.value })}
className="w-full bg-zinc-50 dark:bg-black/20 border border-zinc-200 dark:border-white/10 rounded-lg py-2 px-2 text-sm text-zinc-900 dark:text-white focus:outline-none focus:ring-1 focus:ring-blue-500 appearance-none"
@@ -292,6 +296,8 @@ const PropertyDetailDrawer = ({ isOpen, onClose, data, newLocation, onSave, load
</select>
) : (
<input
id={inputId}
name={field}
type={type}
value={formData[field]}
onChange={(e) => setFormData({ ...formData, [field]: e.target.value })}
@@ -374,10 +380,10 @@ const PropertyDetailDrawer = ({ isOpen, onClose, data, newLocation, onSave, load
{/* Status Selector */}
{isEditing && (
<div className="space-y-2">
<label className="text-[10px] uppercase font-bold text-zinc-500 tracking-widest ml-1">Canvassing Status</label>
<div className="grid grid-cols-2 md:grid-cols-3 gap-2">
<span id="status-label" className="block text-[10px] uppercase font-bold text-zinc-500 tracking-widest ml-1">Canvassing Status</span>
<div role="group" aria-labelledby="status-label" className="grid grid-cols-2 md:grid-cols-3 gap-2">
{["Neutral", "Hot Lead", "Renovated", "Customer", "Not Interested"].map(s => (
<button key={s} onClick={() => setFormData({ ...formData, status: s })} className={`px-1 py-2 rounded text-[10px] font-bold border transition-all ${formData.status === s ? 'bg-zinc-900 dark:bg-white text-white dark:text-black' : 'bg-transparent border-zinc-200 dark:border-white/10 text-zinc-500'}`}>{s}</button>
<button key={s} aria-pressed={formData.status === s} onClick={() => setFormData({ ...formData, status: s })} className={`px-1 py-2 rounded text-[10px] font-bold border transition-all ${formData.status === s ? 'bg-zinc-900 dark:bg-white text-white dark:text-black' : 'bg-transparent border-zinc-200 dark:border-white/10 text-zinc-500'}`}>{s}</button>
))}
</div>
</div>
@@ -551,10 +557,12 @@ const PropertyDetailDrawer = ({ isOpen, onClose, data, newLocation, onSave, load
{/* SECTION 6: NOTES */}
<div className="space-y-4">
<div className="flex items-center gap-2 pb-2 border-b border-zinc-200 dark:border-white/5 text-zinc-500">
<Edit2 size={16} /> <h3 className="text-xs font-black uppercase tracking-widest">Field Notes</h3>
<Edit2 size={16} /> <h3 className="text-xs font-black uppercase tracking-widest"><label htmlFor="prop-notes">Field Notes</label></h3>
</div>
{isEditing ? (
<textarea
id="prop-notes"
name="notes"
className="w-full h-32 bg-zinc-50 dark:bg-black/20 border border-zinc-200 dark:border-white/10 rounded-xl p-3 text-sm text-zinc-900 dark:text-white focus:outline-none focus:ring-1 focus:ring-blue-500 resize-none"
value={formData.notes}
placeholder="Add notes..."
@@ -615,6 +623,8 @@ const PropertyDetailDrawer = ({ isOpen, onClose, data, newLocation, onSave, load
<span className="text-xl">+</span>
<span className="text-xs uppercase tracking-widest">Upload Photo</span>
<input
id="photo-upload"
name="photo-upload"
type="file"
accept="image/*"
className="hidden"