From 971befe193d0b55e5725d50a97bd3c62337a1154 Mon Sep 17 00:00:00 2001 From: Bhavikprit Date: Wed, 4 Mar 2026 11:17:21 +0400 Subject: [PATCH] feat(#40): improve frontend accessibility (WCAG 2.1 AA) - Add reusable useFocusTrap hook for modal focus management with Escape key support and focus restoration on close - Task board: add role=region and aria-label to kanban columns, role=button and keyboard support (Enter/Space) to task cards, role=alert on error display, role=status on loading spinner - Modals: add role=dialog, aria-modal, aria-labelledby, focus trapping, backdrop click to close, aria-label on close buttons - Tab interface: add role=tablist/tab/tabpanel with aria-selected and aria-controls for task detail tabs - Forms: add htmlFor/id associations on all modal form labels - Layout: add skip-to-content link for keyboard navigation, id=main-content on main element Addresses #40 --- src/app/[[...panel]]/page.tsx | 5 +- src/components/panels/task-board-panel.tsx | 85 +++++++++++++++------- src/lib/use-focus-trap.ts | 77 ++++++++++++++++++++ 3 files changed, 138 insertions(+), 29 deletions(-) create mode 100644 src/lib/use-focus-trap.ts diff --git a/src/app/[[...panel]]/page.tsx b/src/app/[[...panel]]/page.tsx index ecfd9f0..e6358eb 100644 --- a/src/app/[[...panel]]/page.tsx +++ b/src/app/[[...panel]]/page.tsx @@ -140,6 +140,9 @@ export default function Home() { return (
+ + Skip to main content + {/* Left: Icon rail navigation (hidden on mobile, shown as bottom bar instead) */} @@ -149,7 +152,7 @@ export default function Home() { -
+
diff --git a/src/components/panels/task-board-panel.tsx b/src/components/panels/task-board-panel.tsx index 9aabc92..355b6ce 100644 --- a/src/components/panels/task-board-panel.tsx +++ b/src/components/panels/task-board-panel.tsx @@ -3,6 +3,7 @@ import { useState, useEffect, useCallback, useRef } from 'react' import { useMissionControl } from '@/store' import { useSmartPoll } from '@/lib/use-smart-poll' +import { useFocusTrap } from '@/lib/use-focus-trap' import { AgentAvatar } from '@/components/ui/agent-avatar' import { MarkdownRenderer } from '@/components/markdown-renderer' @@ -268,8 +269,8 @@ export function TaskBoardPanel() { if (loading) { return ( -
-
+
+ Loading tasks...
) @@ -298,11 +299,12 @@ export function TaskBoardPanel() { {/* Error Display */} {error && ( -
+
{error} @@ -310,10 +312,12 @@ export function TaskBoardPanel() { )} {/* Kanban Board */} -
+
{statusColumns.map(column => (
handleDragEnter(e, column.key)} onDragLeave={handleDragLeave} @@ -334,8 +338,12 @@ export function TaskBoardPanel() {
handleDragStart(e, task)} onClick={() => setSelectedTask(task)} + onKeyDown={(e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); setSelectedTask(task) } }} className={`bg-surface-1 rounded-lg p-3 cursor-pointer hover:bg-surface-2 transition-smooth border-l-4 ${priorityColors[task.priority]} ${ draggedTask?.id === task.id ? 'opacity-50' : '' }`} @@ -610,12 +618,14 @@ function TaskDetailModal({
) + const dialogRef = useFocusTrap(onClose) + return ( -
-
+
{ if (e.target === e.currentTarget) onClose() }}> +
-

{task.title}

+

{task.title}

{activeTab === 'details' && ( -
+
Status: {task.status} @@ -683,7 +697,7 @@ function TaskDetailModal({ )} {activeTab === 'comments' && ( -
+

Comments