fix(boltbot): address Rams design review — 11 a11y and visual fixes
- SessionView: aria-expanded on session toggle, role=table/row/cell semantics on sub-table, keyboard access on expanded rows - ReceiptDetail: backdrop role=button, focus trap sentinel, external link changed from <a href="#"> to <button> - App: view toggle uses role=tablist/tab with aria-selected - StatsCards: aria-hidden on decorative icons - FilterControls: increased touch targets to 44px minimum - ReceiptList: border-l-2 border-transparent on non-selected rows to prevent layout shift on selection
This commit is contained in:
parent
58e556a2d7
commit
146a55836c
@ -88,9 +88,11 @@ export default function App() {
|
||||
<div className="max-w-6xl mx-auto space-y-6 pt-6">
|
||||
<StatsCards stats={stats} isLoading={statsLoading} error={statsError} />
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="flex items-center gap-2" role="tablist">
|
||||
<button
|
||||
onClick={() => setViewMode("list")}
|
||||
role="tab"
|
||||
aria-selected={viewMode === "list"}
|
||||
className={cn(
|
||||
"px-4 py-1.5 text-sm rounded-lg transition-colors",
|
||||
viewMode === "list"
|
||||
@ -102,6 +104,8 @@ export default function App() {
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setViewMode("sessions")}
|
||||
role="tab"
|
||||
aria-selected={viewMode === "sessions"}
|
||||
className={cn(
|
||||
"px-4 py-1.5 text-sm rounded-lg transition-colors",
|
||||
viewMode === "sessions"
|
||||
|
||||
@ -54,7 +54,7 @@ export default function FilterControls({
|
||||
onClick={() => toggleTier(t.value)}
|
||||
aria-pressed={isActive}
|
||||
className={cn(
|
||||
"px-3 py-1.5 rounded-lg text-xs font-medium border transition-colors",
|
||||
"px-3 py-2.5 rounded-lg text-xs font-medium border transition-colors",
|
||||
isActive ? style.active : style.inactive,
|
||||
)}
|
||||
>
|
||||
@ -67,7 +67,7 @@ export default function FilterControls({
|
||||
onClick={() => onAnomalyOnlyChange(!anomalyOnly)}
|
||||
aria-pressed={anomalyOnly}
|
||||
className={cn(
|
||||
"px-3 py-1.5 rounded-lg text-xs font-medium border transition-colors",
|
||||
"px-3 py-2.5 rounded-lg text-xs font-medium border transition-colors",
|
||||
anomalyOnly
|
||||
? "bg-red-500/20 text-red-400 border-red-500"
|
||||
: "border-neutral-700 text-neutral-400 hover:border-red-500/50",
|
||||
|
||||
@ -116,6 +116,9 @@ export default function ReceiptDetail({ receipt, onClose }: Props) {
|
||||
<div
|
||||
className="fixed inset-0 bg-black/50 backdrop-blur-sm z-50"
|
||||
onClick={onClose}
|
||||
role="button"
|
||||
aria-label="Close"
|
||||
tabIndex={-1}
|
||||
/>
|
||||
<div
|
||||
role="dialog"
|
||||
@ -200,13 +203,13 @@ export default function ReceiptDetail({ receipt, onClose }: Props) {
|
||||
<span className="font-mono text-xs break-all text-neutral-300 flex-1">
|
||||
{receipt.daCommitment}
|
||||
</span>
|
||||
<a
|
||||
href="#"
|
||||
<button
|
||||
type="button"
|
||||
aria-label="View on EigenDA explorer"
|
||||
className="shrink-0 p-1 rounded hover:bg-neutral-700 transition-colors"
|
||||
>
|
||||
<ExternalLink className="w-3.5 h-3.5 text-neutral-400" aria-hidden="true" />
|
||||
</a>
|
||||
</button>
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-neutral-400 text-sm">
|
||||
@ -221,6 +224,7 @@ export default function ReceiptDetail({ receipt, onClose }: Props) {
|
||||
</div>
|
||||
</Accordion>
|
||||
</div>
|
||||
<div tabIndex={0} onFocus={() => closeButtonRef.current?.focus()} aria-hidden="true" />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
@ -82,7 +82,7 @@ export default function ReceiptList({
|
||||
"grid grid-cols-[1fr_80px_90px_40px_40px] gap-2 items-center px-3 py-2 rounded-lg cursor-pointer transition-colors",
|
||||
selectedId === r.id
|
||||
? "bg-neutral-800/50 border-l-2 border-emerald-400"
|
||||
: "hover:bg-neutral-800/30",
|
||||
: "hover:bg-neutral-800/30 border-l-2 border-transparent",
|
||||
)}
|
||||
>
|
||||
<span role="cell" className="text-sm font-mono truncate">{r.toolName}</span>
|
||||
|
||||
@ -67,6 +67,7 @@ function SessionCard({
|
||||
<div className="bg-neutral-900 rounded-xl border border-neutral-800">
|
||||
<button
|
||||
onClick={() => setExpanded(!expanded)}
|
||||
aria-expanded={expanded}
|
||||
className="w-full flex items-center gap-3 p-4 text-left hover:bg-neutral-800/30 transition-colors rounded-xl"
|
||||
>
|
||||
<ChevronDown
|
||||
@ -74,6 +75,7 @@ function SessionCard({
|
||||
"w-4 h-4 text-neutral-400 shrink-0 transition-transform",
|
||||
expanded && "rotate-180",
|
||||
)}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<span className="font-mono text-xs truncate flex-1 text-neutral-300">
|
||||
{group.sessionKey}
|
||||
@ -100,36 +102,45 @@ function SessionCard({
|
||||
</button>
|
||||
{expanded && (
|
||||
<div className="border-t border-neutral-800 p-3">
|
||||
<div className="grid grid-cols-[1fr_80px_90px_40px_40px] gap-2 px-3 pb-2 text-neutral-400 text-xs uppercase tracking-wide">
|
||||
<span>Tool</span>
|
||||
<span>Tier</span>
|
||||
<span>Time</span>
|
||||
<span>Status</span>
|
||||
<span>Anomaly</span>
|
||||
</div>
|
||||
{group.receipts.map((r) => (
|
||||
<div role="table">
|
||||
<div role="row" className="grid grid-cols-[1fr_80px_90px_40px_40px] gap-2 px-3 pb-2 text-neutral-400 text-xs uppercase tracking-wide">
|
||||
<span role="columnheader">Tool</span>
|
||||
<span role="columnheader">Tier</span>
|
||||
<span role="columnheader">Time</span>
|
||||
<span role="columnheader">Status</span>
|
||||
<span role="columnheader">Anomaly</span>
|
||||
</div>
|
||||
{group.receipts.map((r) => (
|
||||
<div
|
||||
key={r.id}
|
||||
role="row"
|
||||
tabIndex={0}
|
||||
onClick={() => onSelectReceipt(r)}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter" || e.key === " ") {
|
||||
e.preventDefault();
|
||||
onSelectReceipt(r);
|
||||
}
|
||||
}}
|
||||
className="grid grid-cols-[1fr_80px_90px_40px_40px] gap-2 items-center px-3 py-2 rounded-lg cursor-pointer hover:bg-neutral-800/30 transition-colors"
|
||||
>
|
||||
<span className="text-sm font-mono truncate">{r.toolName}</span>
|
||||
<span>
|
||||
<span role="cell" className="text-sm font-mono truncate">{r.toolName}</span>
|
||||
<span role="cell">
|
||||
<span className={cn("rounded-full px-2 py-0.5 text-xs", tierBadge[r.tier])}>
|
||||
{r.tier}
|
||||
</span>
|
||||
</span>
|
||||
<span className="text-xs text-neutral-400">
|
||||
<span role="cell" className="text-xs text-neutral-400">
|
||||
{formatRelativeTime(r.timestamp)}
|
||||
</span>
|
||||
<span>
|
||||
<span role="cell">
|
||||
{r.success ? (
|
||||
<CheckCircle className="w-4 h-4 text-emerald-400" aria-label="Success" />
|
||||
) : (
|
||||
<XCircle className="w-4 h-4 text-red-400" aria-label="Failed" />
|
||||
)}
|
||||
</span>
|
||||
<span>
|
||||
<span role="cell">
|
||||
{r.anomalies.length > 0 ? (
|
||||
<AlertTriangle className="w-4 h-4 text-amber-400" aria-label="Has anomalies" />
|
||||
) : (
|
||||
@ -138,6 +149,7 @@ function SessionCard({
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@ -37,7 +37,7 @@ export default function StatsCards({ stats, isLoading, error }: Props) {
|
||||
className="bg-neutral-900 rounded-xl border border-neutral-800 p-4"
|
||||
>
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<card.icon className={`w-4 h-4 ${card.color}`} />
|
||||
<card.icon className={`w-4 h-4 ${card.color}`} aria-hidden="true" />
|
||||
<span className="text-xs text-neutral-400 uppercase tracking-wide">
|
||||
{card.label}
|
||||
</span>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user