diff --git a/.Jules/palette.md b/.Jules/palette.md new file mode 100644 index 0000000..0b23f04 --- /dev/null +++ b/.Jules/palette.md @@ -0,0 +1,4 @@ +## 2025-03-05 - Missing aria-busy on loading buttons + +**Learning:** Found several buttons across the app (like refresh/sync/export buttons) that implement loading states with visual spinners or loading text, but fail to inform screen readers of their "busy" state via the \`aria-busy\` attribute. +**Action:** Always add \`aria-busy={isLoading ? 'true' : undefined}\` to any interactive element that displays a loading state, ensuring the loading process is exposed to assistive technologies. diff --git a/src/components/ExportButton.tsx b/src/components/ExportButton.tsx index f368c65..e0ebff2 100644 --- a/src/components/ExportButton.tsx +++ b/src/components/ExportButton.tsx @@ -121,6 +121,7 @@ function ExportButton({ variant = 'default', className = '' }: ExportButtonProps ? 'Export quota reached for this week.' : 'Export dashboard data (CSV, JSON, PDF)' } + aria-busy={isExporting ? 'true' : undefined} > {isExporting ? (
@@ -152,7 +153,6 @@ function ExportButton({ variant = 'default', className = '' }: ExportButtonProps stiffness: 300, damping: 20, }} - onClick={(e) => e.stopPropagation()} className="w-full max-w-md rounded-lg border border-[#E5E7EB] bg-white p-6 shadow-lg" role="dialog" diff --git a/src/components/dashboard/ExportRoiRail.tsx b/src/components/dashboard/ExportRoiRail.tsx index 374612e..1febaf8 100644 --- a/src/components/dashboard/ExportRoiRail.tsx +++ b/src/components/dashboard/ExportRoiRail.tsx @@ -166,6 +166,7 @@ export default function ExportRoiRail() { onClick={() => exportData('csv')} disabled={!!exporting || syncing || exportsDepleted} className="inline-flex items-center gap-1.5 rounded-md border border-[#E5E7EB] bg-white px-3 py-1.5 text-[11px] font-semibold text-[#374151] transition hover:bg-[#F3F4F6] disabled:opacity-60" + aria-busy={exporting === 'csv' ? 'true' : undefined} > {exporting === 'csv' ? ( @@ -178,6 +179,7 @@ export default function ExportRoiRail() { onClick={() => exportData('json')} disabled={!!exporting || syncing || exportsDepleted} className="inline-flex items-center gap-1.5 rounded-md border border-[#E5E7EB] bg-white px-3 py-1.5 text-[11px] font-semibold text-[#374151] transition hover:bg-[#F3F4F6] disabled:opacity-60" + aria-busy={exporting === 'json' ? 'true' : undefined} > {exporting === 'json' ? ( @@ -190,6 +192,7 @@ export default function ExportRoiRail() { onClick={() => exportData('pdf')} disabled={!!exporting || syncing || exportsDepleted} className="inline-flex items-center gap-1.5 rounded-md border border-[#E5E7EB] bg-white px-3 py-1.5 text-[11px] font-semibold text-[#374151] transition hover:bg-[#F3F4F6] disabled:opacity-60" + aria-busy={exporting === 'pdf' ? 'true' : undefined} > {exporting === 'pdf' ? ( @@ -202,6 +205,7 @@ export default function ExportRoiRail() { onClick={syncAll} disabled={syncing || !!exporting} className="inline-flex items-center gap-1.5 rounded-md border border-[#E5E7EB] bg-[#F8FAFC] px-3 py-1.5 text-[11px] font-semibold text-[#111827] transition hover:border-[#6C63FF] hover:bg-white disabled:opacity-60" + aria-busy={syncing ? 'true' : undefined} > {syncing ? ( diff --git a/src/components/dashboard/IntegrationHealthRail.tsx b/src/components/dashboard/IntegrationHealthRail.tsx index 6edce6b..bc976ab 100644 --- a/src/components/dashboard/IntegrationHealthRail.tsx +++ b/src/components/dashboard/IntegrationHealthRail.tsx @@ -234,6 +234,7 @@ export default function IntegrationHealthRail() { onClick={loadStatuses} className="inline-flex items-center gap-1 rounded-md border border-[#E5E7EB] bg-white px-3 py-1.5 text-xs font-semibold text-[#374151] hover:bg-[#F3F4F6] transition-colors disabled:opacity-60" disabled={loading} + aria-busy={loading ? 'true' : undefined} > {loading ? ( @@ -344,6 +345,7 @@ export default function IntegrationHealthRail() { onClick={() => handleSync(integration)} disabled={disabled || syncing[integration.type]} className="flex-1 inline-flex items-center justify-center gap-1.5 rounded-md border border-[#E5E7EB] bg-white px-3 py-1.5 text-[11px] font-semibold text-[#374151] transition-colors hover:bg-[#F3F4F6] disabled:cursor-not-allowed disabled:opacity-60" + aria-busy={syncing[integration.type] ? 'true' : undefined} > {syncing[integration.type] ? ( diff --git a/src/components/dashboard/dashboard-header.tsx b/src/components/dashboard/dashboard-header.tsx index fe86c11..e1e9b03 100644 --- a/src/components/dashboard/dashboard-header.tsx +++ b/src/components/dashboard/dashboard-header.tsx @@ -31,6 +31,7 @@ export function DashboardHeader({ disabled={isLoading} className="p-1.5 text-[#9CA3AF] hover:text-[#333333] transition-colors rounded-md hover:bg-[#FAFAFA]" aria-label="Refresh" + aria-busy={isLoading ? 'true' : undefined} >