From 1b15aa737fa0acf956a8c29ce0f54b958a295679 Mon Sep 17 00:00:00 2001 From: ghzhost Date: Thu, 10 Sep 2026 21:56:59 +0000 Subject: [PATCH] fix(ui): make interactive data-table rows keyboard accessible - Add tabIndex=0, role="button", and Enter/Space onKeyDown handler when onRowClick is present - Keep non-interactive rows unchanged - Ensure clicks/keypresses on nested interactive controls are not intercepted - Add visible focus styling for keyboard navigation Closes forthfate/openorbit#16 --- frontend/src/components/ui/data-table.tsx | 19 ++++++++++++++++++- frontend/src/theme-overrides.css | 1 + 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/frontend/src/components/ui/data-table.tsx b/frontend/src/components/ui/data-table.tsx index cd0f076..3a1b6a1 100644 --- a/frontend/src/components/ui/data-table.tsx +++ b/frontend/src/components/ui/data-table.tsx @@ -57,10 +57,27 @@ export function DataTable({ className={`tr${onRowClick ? " tr--interactive" : ""}`} style={{ gridTemplateColumns: template }} key={row.id} + tabIndex={onRowClick ? 0 : undefined} + role={onRowClick ? "button" : undefined} + onKeyDown={ + onRowClick + ? (event) => { + if ( + (event.key === "Enter" || event.key === " ") && + !(event.target as HTMLElement).closest( + "button,input,select,textarea,a", + ) + ) { + event.preventDefault(); + onRowClick(row); + } + } + : undefined + } onClick={(event) => { if ( !(event.target as HTMLElement).closest( - "button,input,select,textarea", + "button,input,select,textarea,a", ) ) onRowClick?.(row); diff --git a/frontend/src/theme-overrides.css b/frontend/src/theme-overrides.css index 8758911..b269e81 100644 --- a/frontend/src/theme-overrides.css +++ b/frontend/src/theme-overrides.css @@ -102,6 +102,7 @@ footer { display:flex; align-items:center; justify-content:space-between; margin .table-sort:hover { color:var(--accent); } .build-panel,.active-evaluation-panel { padding:18px; } .tr--interactive { cursor:pointer; } +.tr--interactive:focus-visible { outline:2px solid var(--accent); outline-offset:-2px; background:var(--surface-raised); } .build-table .tr--interactive:hover,.active-evaluation-table .tr--interactive:hover { background:var(--surface-raised); } .active-evaluation-table { display:block; max-width:100%; overflow-x:auto; overflow-y:hidden; overscroll-behavior-inline:contain; -webkit-overflow-scrolling:touch; }.active-evaluation-table .tr { min-width:1240px; width:100%; }.active-evaluation-table .tr > span { min-width:0; padding-inline:6px; }.active-evaluation-table .tr > span:nth-child(n+3) { justify-content:center; } .modal--run-detail { width:min(1080px,100%); overflow:hidden; }.modal--run-detail .modal-header { flex:none; }.run-detail-evaluation { display:flex; align-items:baseline; gap:9px; min-width:0; margin:0 0 14px; }.run-detail-evaluation strong { overflow:hidden; min-width:0; text-overflow:ellipsis; white-space:nowrap; font-size:13px; }.run-detail-evaluation code { flex:none; color:var(--muted); font:10px 'DM Mono',monospace; }.run-detail-summary { display:grid; grid-template-columns:repeat(5,minmax(0,1fr)); gap:10px; margin-bottom:20px; }.run-detail-summary > div { display:grid; gap:5px; min-width:0; padding:11px; border:1px solid var(--line); border-radius:8px; background:var(--bg); }.run-detail-summary small,.evaluation-summary small { color:var(--muted); font-size:10px; }.run-detail-summary strong { overflow:hidden; text-overflow:ellipsis; white-space:nowrap; font-size:12px; }.run-phase { font:600 11px 'DM Mono',monospace; }.content .tr span.run-phase--queued,.content .tr span.run-phase--awaiting_approval { color:#f1d292; }.content .tr span.run-phase--running { color:#8cc8ff; }.content .tr span.run-phase--succeeded { color:#bce989; }.content .tr span.run-phase--failed,.content .tr span.run-phase--cancelled { color:#eaa89f; }.console-output { max-height:min(510px,calc(100vh - 310px)); overflow:auto; padding-right:8px; }.run-result { display:grid; gap:16px; max-height:min(510px,calc(100vh - 310px)); overflow:auto; padding:18px 8px 0 0; }.run-result h3 { margin:0 0 10px; font-size:13px; }.result-filter-trigger { position:relative; display:flex; justify-content:flex-start; }.result-translation-action { margin-left:auto; }.result-filter-popover { right:auto; left:0; }.result-filter-range { display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); gap:8px; }.result-filter-range label { display:grid; gap:7px; color:var(--muted); font-size:10px; }.result-filter-range input { min-width:0; min-height:36px; padding:8px 10px; border-radius:7px; }.result-iterations { display:grid; gap:14px; }.result-iteration { display:grid; gap:16px; padding:14px; border:1px solid var(--line); border-radius:9px; background:var(--surface-raised); }.result-iteration > header { display:flex; align-items:center; justify-content:space-between; gap:12px; padding-bottom:12px; border-bottom:1px solid var(--line); }.result-iteration > header > strong { font:600 12px 'DM Mono',monospace; }.result-iteration > header > span { display:flex; align-items:center; gap:8px; }.result-iteration > header small { color:var(--muted); font-size:10px; }.result-iteration > header > span > b:last-child { font:11px 'DM Mono',monospace; }.evaluation-summary { display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); gap:12px; padding:14px; border:1px solid var(--line); border-radius:8px; background:var(--bg); }.evaluation-summary > div { display:grid; gap:4px; }.evaluation-summary strong { font-size:18px; }.evaluation-summary span,.evaluation-summary p,.result-items p,.result-items small { color:var(--muted); font-size:11px; }.evaluation-summary p { grid-column:1/-1; margin:0; line-height:1.6; }.result-items { display:grid; gap:8px; }.result-items article { padding:12px; border:1px solid var(--line); border-radius:8px; background:var(--bg); }.result-row { display:grid; grid-template-columns:124px minmax(0,1fr) auto; gap:14px; align-items:start; }.result-row__time { color:var(--muted); font:10px/1.45 'DM Mono',monospace; }.result-row__body { min-width:0; }.result-row__body strong { display:block; font-size:12px; }.result-items p { margin:7px 0 0; line-height:1.55; }.result-row__metrics { display:flex; gap:13px; align-items:start; }.result-row__metrics > span { display:grid; gap:4px; min-width:52px; }.result-row__metrics small { font:9px 'DM Mono',monospace; text-transform:uppercase; }.result-row__metrics b:not(.decision) { font:11px 'DM Mono',monospace; }.decision { width:max-content; border-radius:999px; padding:3px 7px; color:var(--text); background:var(--surface-raised); font:500 9px 'DM Mono',monospace; text-transform:uppercase; }.decision--adopted,.decision--approved,.decision--low { color:#bce989; background:#283d29; }.decision--rejected,.decision--critical { color:#eaa89f; background:#402624; }.decision--medium,.decision--pending { color:#f1d292; background:#403420; } @media(max-width:720px){.result-row { grid-template-columns:1fr; gap:8px; }.result-row__metrics { flex-wrap:wrap; }.result-row__time { order:2; }.result-row__metrics { order:3; }.result-iteration > header { align-items:flex-start; flex-direction:column; }.result-filter-popover { right:auto; left:0; }}