Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .Jules/palette.md
Original file line number Diff line number Diff line change
@@ -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.
2 changes: 1 addition & 1 deletion src/components/ExportButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 ? (
<div className="h-4 w-4 animate-spin rounded-full border-2 border-[#6C63FF] border-t-transparent" />
Expand Down Expand Up @@ -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"
Expand Down
4 changes: 4 additions & 0 deletions src/components/dashboard/ExportRoiRail.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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' ? (
<Loader2 className="size-3.5 animate-spin" />
Expand All @@ -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' ? (
<Loader2 className="size-3.5 animate-spin" />
Expand All @@ -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' ? (
<Loader2 className="size-3.5 animate-spin" />
Expand All @@ -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 ? (
<Loader2 className="size-3.5 animate-spin" />
Expand Down
2 changes: 2 additions & 0 deletions src/components/dashboard/IntegrationHealthRail.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 ? (
<Loader2 className="size-3.5 animate-spin" />
Expand Down Expand Up @@ -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] ? (
<Loader2 className="size-3.5 animate-spin" />
Expand Down
1 change: 1 addition & 0 deletions src/components/dashboard/dashboard-header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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}
>
<RefreshCcw className={`size-4 ${isLoading ? 'animate-spin' : ''}`} />
</button>
Expand Down
Loading