fix: react-doctor control-has-associated-label 48→19 (toggles, empty th, action menus)
This commit is contained in:
@@ -280,7 +280,7 @@ export default function CampaignListPanel({ initialCampaigns, brandId = "6429430
|
||||
<th className="text-left px-3 sm:px-4 py-3 font-semibold text-[var(--admin-text-muted)]">Status</th>
|
||||
<th className="text-left px-3 sm:px-4 py-3 font-semibold text-[var(--admin-text-muted)]">Created</th>
|
||||
<th className="text-left px-3 sm:px-4 py-3 font-semibold text-[var(--admin-text-muted)]">Sent</th>
|
||||
<th className="text-right px-3 sm:px-4 py-3 font-semibold text-[var(--admin-text-muted)]"></th>
|
||||
<th className="text-right px-3 sm:px-4 py-3 font-semibold text-[var(--admin-text-muted)]" aria-label="Actions"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y divide-[var(--admin-border)]">
|
||||
|
||||
@@ -452,7 +452,7 @@ function TableView({
|
||||
<th className="text-left px-4 py-3 font-semibold text-[var(--admin-text-muted)] text-xs uppercase tracking-wider">Type</th>
|
||||
<th className="text-left px-4 py-3 font-semibold text-[var(--admin-text-muted)] text-xs uppercase tracking-wider">Price</th>
|
||||
<th className="text-left px-4 py-3 font-semibold text-[var(--admin-text-muted)] text-xs uppercase tracking-wider">Status</th>
|
||||
<th className="px-4 py-3" />
|
||||
<th className="px-4 py-3" aria-label="Actions" />
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y divide-[var(--admin-border)]">
|
||||
@@ -703,6 +703,7 @@ function CardView({
|
||||
Edit
|
||||
</AdminButton>
|
||||
<button type="button"
|
||||
aria-label={`Delete ${product.name}`}
|
||||
onClick={() => onDelete(product.id)}
|
||||
className="rounded-lg border border-[var(--admin-border)] px-3 py-2 text-xs font-semibold text-[var(--admin-text-muted)] hover:bg-[var(--admin-danger-soft)] hover:text-[var(--admin-danger)] hover:border-[var(--admin-danger)] transition-colors"
|
||||
>
|
||||
|
||||
@@ -253,7 +253,7 @@ export default function ScheduleImportModal({ brandId, onClose, onComplete }: Pr
|
||||
<th className="px-3 py-2.5 text-left text-[10px] font-bold uppercase tracking-wider text-stone-500">Location</th>
|
||||
<th className="px-3 py-2.5 text-left text-[10px] font-bold uppercase tracking-wider text-stone-500">Date</th>
|
||||
<th className="px-3 py-2.5 text-left text-[10px] font-bold uppercase tracking-wider text-stone-500">Time</th>
|
||||
<th className="w-8 px-3 py-2.5" />
|
||||
<th className="w-8 px-3 py-2.5" aria-label="Actions" />
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y divide-stone-50">
|
||||
|
||||
@@ -532,7 +532,7 @@ function TableView({
|
||||
<SortIcon field="status" sortField={sortField} sortDirection={sortDirection} />
|
||||
</span>
|
||||
</th>
|
||||
<th className="px-4 py-3" />
|
||||
<th className="px-4 py-3" aria-label="Actions" />
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y divide-[var(--admin-border)]">
|
||||
@@ -1042,6 +1042,7 @@ function StopCard({
|
||||
Edit
|
||||
</AdminButton>
|
||||
<button type="button"
|
||||
aria-label={`Delete stop in ${stop.city}, ${stop.state}`}
|
||||
onClick={() => setConfirmDelete(true)}
|
||||
className="rounded-lg border border-[var(--admin-border)] px-3 py-2 text-xs font-semibold text-[var(--admin-text-muted)] hover:bg-red-50 hover:text-red-600 hover:border-red-200 transition-colors"
|
||||
>
|
||||
|
||||
@@ -261,7 +261,7 @@ export function TemplateListPanel({ templates, brandId = "64294306-5f42-463d-a5e
|
||||
<th className="text-left px-3 sm:px-4 py-3 font-semibold text-[var(--admin-text-muted)]">Type</th>
|
||||
<th className="text-left px-3 sm:px-4 py-3 font-semibold text-[var(--admin-text-muted)]">Subject</th>
|
||||
<th className="text-left px-3 sm:px-4 py-3 font-semibold text-[var(--admin-text-muted)]">Updated</th>
|
||||
<th className="text-right px-3 sm:px-4 py-3 font-semibold text-[var(--admin-text-muted)]"></th>
|
||||
<th className="text-right px-3 sm:px-4 py-3 font-semibold text-[var(--admin-text-muted)]" aria-label="Actions"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y divide-[var(--admin-border)]">
|
||||
|
||||
@@ -611,7 +611,7 @@ function WorkersTab({ workers, onAdd, onEdit, brandId, onSave }: {
|
||||
<th className="px-5 py-3 text-left text-xs font-bold text-[var(--admin-text-muted)] uppercase">PIN</th>
|
||||
<th className="px-5 py-3 text-left text-xs font-bold text-[var(--admin-text-muted)] uppercase">Status</th>
|
||||
<th className="px-5 py-3 text-left text-xs font-bold text-[var(--admin-text-muted)] uppercase">Language</th>
|
||||
<th className="px-5 py-3"></th>
|
||||
<th className="px-5 py-3" aria-label="Actions"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -661,7 +661,7 @@ function TasksTab({ tasks, onAdd, onEdit, brandId, onSave }: {
|
||||
<th className="px-5 py-3 text-left text-xs font-bold text-[var(--admin-text-muted)] uppercase">Name</th>
|
||||
<th className="px-5 py-3 text-left text-xs font-bold text-[var(--admin-text-muted)] uppercase">Unit</th>
|
||||
<th className="px-5 py-3 text-left text-xs font-bold text-[var(--admin-text-muted)] uppercase">Status</th>
|
||||
<th className="px-5 py-3"></th>
|
||||
<th className="px-5 py-3" aria-label="Actions"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
||||
@@ -584,7 +584,7 @@ export default function TimeTrackingSettingsClient({ brandId }: TimeTrackingSett
|
||||
<p className="text-sm text-[var(--admin-text-secondary)] font-medium">Daily overtime alerts</p>
|
||||
<p className="text-xs text-[var(--admin-text-muted)]">Send notification when worker hits daily threshold</p>
|
||||
</div>
|
||||
<button type="button" onClick={() => setEnableDailyAlerts(!enableDailyAlerts)}
|
||||
<button type="button" aria-label="Toggle daily alerts" aria-pressed={enableDailyAlerts} onClick={() => setEnableDailyAlerts(!enableDailyAlerts)}
|
||||
className={`relative inline-flex h-5 w-9 items-center rounded-full transition-colors ${enableDailyAlerts ? "bg-[var(--admin-accent)]" : "bg-stone-300"}`}>
|
||||
<span className={`inline-block h-3.5 w-3.5 rounded-full bg-white transition-transform ${enableDailyAlerts ? "translate-x-4" : "translate-x-1"}`} />
|
||||
</button>
|
||||
@@ -594,7 +594,7 @@ export default function TimeTrackingSettingsClient({ brandId }: TimeTrackingSett
|
||||
<p className="text-sm text-[var(--admin-text-secondary)] font-medium">Weekly overtime alerts</p>
|
||||
<p className="text-xs text-[var(--admin-text-muted)]">Send notification when worker hits weekly threshold</p>
|
||||
</div>
|
||||
<button type="button" onClick={() => setEnableWeeklyAlerts(!enableWeeklyAlerts)}
|
||||
<button type="button" aria-label="Toggle weekly alerts" aria-pressed={enableWeeklyAlerts} onClick={() => setEnableWeeklyAlerts(!enableWeeklyAlerts)}
|
||||
className={`relative inline-flex h-5 w-9 items-center rounded-full transition-colors ${enableWeeklyAlerts ? "bg-[var(--admin-accent)]" : "bg-stone-300"}`}>
|
||||
<span className={`inline-block h-3.5 w-3.5 rounded-full bg-white transition-transform ${enableWeeklyAlerts ? "translate-x-4" : "translate-x-1"}`} />
|
||||
</button>
|
||||
@@ -604,7 +604,7 @@ export default function TimeTrackingSettingsClient({ brandId }: TimeTrackingSett
|
||||
<p className="text-sm text-[var(--admin-text-secondary)] font-medium">Show overtime warning on employee screen</p>
|
||||
<p className="text-xs text-[var(--admin-text-muted)]">Display alert when worker approaches thresholds</p>
|
||||
</div>
|
||||
<button type="button" onClick={() => setOvertimeNotifications(!overtimeNotifications)}
|
||||
<button type="button" aria-label="Toggle overtime notifications" aria-pressed={overtimeNotifications} onClick={() => setOvertimeNotifications(!overtimeNotifications)}
|
||||
className={`relative inline-flex h-5 w-9 items-center rounded-full transition-colors ${overtimeNotifications ? "bg-[var(--admin-accent)]" : "bg-stone-300"}`}>
|
||||
<span className={`inline-block h-3.5 w-3.5 rounded-full bg-white transition-transform ${overtimeNotifications ? "translate-x-4" : "translate-x-1"}`} />
|
||||
</button>
|
||||
|
||||
@@ -385,7 +385,7 @@ export default function UsersPage({ initialUsers, brands, currentUser, onUserCre
|
||||
<th className="px-5 py-4 font-semibold">Brand</th>
|
||||
<th className="px-5 py-4 font-semibold">Status</th>
|
||||
<th className="px-5 py-4 font-semibold">Created</th>
|
||||
{canManageUsers && <th className="px-5 py-4 font-semibold" />}
|
||||
{canManageUsers && <th className="px-5 py-4 font-semibold" aria-label="Actions" />}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y divide-stone-100">
|
||||
@@ -651,7 +651,7 @@ export default function UsersPage({ initialUsers, brands, currentUser, onUserCre
|
||||
<p className="font-medium text-stone-900">Active</p>
|
||||
<p className="text-xs text-stone-500">Deactivated users cannot log in</p>
|
||||
</div>
|
||||
<button type="button"
|
||||
<button type="button" aria-label="Toggle user active status" aria-pressed={editing.active}
|
||||
onClick={() => setActive(!editing.active)}
|
||||
className={`relative inline-flex h-6 w-11 items-center rounded-full transition-colors ${editing.active ? "bg-emerald-600" : "bg-stone-300"}`}
|
||||
>
|
||||
|
||||
@@ -860,7 +860,7 @@ export default function WaterLogAdminPanel({
|
||||
<th className="px-4 py-2.5 text-right">Gallons</th>
|
||||
<th className="px-4 py-2.5">Method</th>
|
||||
<th className="px-4 py-2.5">Via</th>
|
||||
<th className="px-4 py-2.5"></th>
|
||||
<th className="px-4 py-2.5" aria-label="Actions"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
||||
@@ -37,7 +37,7 @@ export default function AdminActionMenu({ actions, triggerLabel, className = ""
|
||||
|
||||
return (
|
||||
<div ref={menuRef} className={`relative inline-flex items-center justify-end ${className}`}>
|
||||
<button type="button"
|
||||
<button type="button" aria-label="Open action menu" aria-haspopup="true" aria-expanded={open}
|
||||
onClick={() => setOpen(!open)}
|
||||
className="rounded-lg px-2 py-1.5 text-xs text-[var(--admin-text-muted)] hover:text-[var(--admin-text-secondary)] hover:bg-[var(--admin-bg-subtle)] transition-colors"
|
||||
>
|
||||
|
||||
@@ -87,7 +87,7 @@ export default function AdminFilterBar({
|
||||
{/* View Toggle */}
|
||||
{onViewModeChange && viewMode && (
|
||||
<div className="flex gap-1 rounded-xl border border-[var(--admin-border)] bg-white p-1">
|
||||
<button type="button"
|
||||
<button type="button" aria-label="Table view" aria-pressed={viewMode === "table"}
|
||||
onClick={() => onViewModeChange("table")}
|
||||
className={`rounded-lg p-1.5 transition-all ${
|
||||
viewMode === "table"
|
||||
@@ -99,7 +99,7 @@ export default function AdminFilterBar({
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M3 10h18M3 14h18m-9-4v8m-7 0h14a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v8a2 2 0 002 2z" />
|
||||
</svg>
|
||||
</button>
|
||||
<button type="button"
|
||||
<button type="button" aria-label="Cards view" aria-pressed={viewMode === "cards"}
|
||||
onClick={() => onViewModeChange("cards")}
|
||||
className={`rounded-lg p-1.5 transition-all ${
|
||||
viewMode === "cards"
|
||||
|
||||
@@ -39,6 +39,7 @@ export function AdminToggle({
|
||||
<button
|
||||
type="button"
|
||||
role="switch"
|
||||
aria-label={label ?? "Toggle"}
|
||||
aria-checked={checked}
|
||||
disabled={disabled}
|
||||
onClick={() => !disabled && onChange(!checked)}
|
||||
|
||||
@@ -110,7 +110,7 @@ export default function CookieConsentBanner() {
|
||||
<h3 className="font-medium text-[#1a1a1a]">Analytics Cookies</h3>
|
||||
<p className="text-sm text-[#888]">Help us improve the site</p>
|
||||
</div>
|
||||
<button type="button"
|
||||
<button type="button" role="switch" aria-label="Analytics cookies" aria-checked={preferences.analytics}
|
||||
onClick={() => setPreferences({ ...preferences, analytics: !preferences.analytics })}
|
||||
className={`w-12 h-7 rounded-full relative transition-colors ${
|
||||
preferences.analytics ? "bg-[#1a4d2e]" : "bg-gray-300"
|
||||
@@ -128,7 +128,7 @@ export default function CookieConsentBanner() {
|
||||
<h3 className="font-medium text-[#1a1a1a]">Marketing Cookies</h3>
|
||||
<p className="text-sm text-[#888]">Personalized ads and content</p>
|
||||
</div>
|
||||
<button type="button"
|
||||
<button type="button" role="switch" aria-label="Marketing cookies" aria-checked={preferences.marketing}
|
||||
onClick={() => setPreferences({ ...preferences, marketing: !preferences.marketing })}
|
||||
className={`w-12 h-7 rounded-full relative transition-colors ${
|
||||
preferences.marketing ? "bg-[#1a4d2e]" : "bg-gray-300"
|
||||
|
||||
@@ -124,7 +124,7 @@ export default function LotCreateModal({ isOpen, onClose, brandId }: Props) {
|
||||
<p className="text-sm" style={{ color: "var(--admin-text-muted)" }}>Quick entry — scan or fill in the fields below</p>
|
||||
</div>
|
||||
</div>
|
||||
<button type="button"
|
||||
<button type="button" aria-label="Close"
|
||||
onClick={onClose}
|
||||
className="flex h-9 w-9 items-center justify-center rounded-xl border transition-colors hover:bg-stone-50"
|
||||
style={{ borderColor: "var(--admin-border)" }}
|
||||
|
||||
@@ -318,7 +318,7 @@ export default function LotListTable({
|
||||
{h}
|
||||
</th>
|
||||
))}
|
||||
<th className="px-5 py-3.5" />
|
||||
<th className="px-5 py-3.5" aria-label="Actions" />
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
||||
@@ -249,6 +249,7 @@ export default function QRScanModal({ onClose, onScanResult }: QRScanModalProps)
|
||||
<>
|
||||
<video
|
||||
ref={videoRef}
|
||||
aria-label="QR code scanner camera feed"
|
||||
className="w-full h-full object-cover"
|
||||
playsInline
|
||||
muted
|
||||
|
||||
@@ -158,6 +158,7 @@ export default function TuxedoVideoHero({
|
||||
{/* Background video with parallax */}
|
||||
<video
|
||||
ref={videoRef}
|
||||
aria-hidden="true"
|
||||
autoPlay
|
||||
muted
|
||||
loop
|
||||
|
||||
Reference in New Issue
Block a user