fix(tabs): WCAG 2.4.7 focus-visible rings — ChatTab, ActivityTab, ChannelsTab

ChatTab (desktop):
- Enable button: added focus-visible ring + aria-label
- Retry button: added focus-visible ring + aria-label
- Restart button: added focus-visible ring + aria-label
- Attach button: added focus-visible ring
- Send button: added focus-visible ring + aria-label

ActivityTab:
- Filter buttons (3): added focus-visible ring
- Auto-refresh toggle: added focus-visible ring
- Full Trace button: added focus-visible ring + aria-label

ChannelsTab:
- "edit manually" button: added focus-visible ring + aria-label
- Test button: added focus-visible ring + aria-label
- On/Off toggle: added focus-visible ring
- Remove button: added focus-visible ring + aria-label

All changes preserve existing test behavior.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Molecule AI · core-uiux 2026-05-16 02:42:51 +00:00
parent 3c7f84acbc
commit 41c74df709
3 changed files with 21 additions and 13 deletions

View File

@ -139,7 +139,7 @@ export function ActivityTab({ workspaceId }: Props) {
key={f.id}
onClick={() => setFilter(f.id)}
aria-pressed={filter === f.id}
className={`px-2 py-1 text-[11px] rounded-md font-medium transition-all ${
className={`px-2 py-1 text-[11px] rounded-md font-medium transition-all focus:outline-none focus-visible:ring-2 focus-visible:ring-accent/40 ${
filter === f.id
? "bg-surface-card text-ink ring-1 ring-zinc-600"
: "text-ink-mid hover:text-ink-mid hover:bg-surface-card/60"
@ -152,7 +152,7 @@ export function ActivityTab({ workspaceId }: Props) {
<button
onClick={() => setAutoRefresh(!autoRefresh)}
aria-pressed={autoRefresh}
className={`text-[11px] px-1.5 py-0.5 rounded ${
className={`text-[11px] px-1.5 py-0.5 rounded focus:outline-none focus-visible:ring-2 focus-visible:ring-accent/40 ${
autoRefresh ? "text-good bg-emerald-950/30" : "text-ink-mid"
}`}
title={autoRefresh ? "Auto-refresh ON" : "Auto-refresh OFF"}
@ -161,8 +161,9 @@ export function ActivityTab({ workspaceId }: Props) {
</button>
<button
onClick={() => setTraceOpen(true)}
className="px-2 py-1 bg-blue-900/40 hover:bg-blue-800/50 text-[11px] rounded text-accent border border-blue-800/30"
title="View full conversation trace across all workspaces"
aria-label="Full trace"
className="px-2 py-1 bg-blue-900/40 hover:bg-blue-800/50 text-[11px] rounded text-accent border border-blue-800/30 focus:outline-none focus-visible:ring-2 focus-visible:ring-accent/40"
title="View full conversation trace"
>
Full Trace
</button>

View File

@ -331,8 +331,9 @@ export function ChannelsTab({ workspaceId }: Props) {
</label>
))}
<button
aria-label={showManualInput ? "Hide manual input" : "Show manual input"}
onClick={() => setShowManualInput(!showManualInput)}
className="text-[10px] text-accent hover:underline"
className="text-[10px] text-accent hover:underline focus:outline-none focus-visible:ring-2 focus-visible:ring-accent/40"
>
{showManualInput ? "hide manual input" : "edit manually"}
</button>
@ -408,15 +409,16 @@ export function ChannelsTab({ workspaceId }: Props) {
</div>
<div className="flex items-center gap-1.5">
<button
aria-label={testing === ch.id ? "Sent!" : "Test channel"}
onClick={() => handleTest(ch)}
disabled={testing === ch.id}
className="text-[10px] px-2 py-0.5 rounded bg-surface-card/50 text-ink-mid hover:text-ink transition disabled:opacity-50"
className="text-[10px] px-2 py-0.5 rounded bg-surface-card/50 text-ink-mid hover:text-ink transition disabled:opacity-50 focus:outline-none focus-visible:ring-2 focus-visible:ring-accent/40"
>
{testing === ch.id ? "Sent!" : "Test"}
</button>
<button
onClick={() => handleToggle(ch)}
className={`text-[10px] px-2 py-0.5 rounded transition ${
className={`text-[10px] px-2 py-0.5 rounded transition focus:outline-none focus-visible:ring-2 focus-visible:ring-accent/40 ${
ch.enabled
? "bg-emerald-900/30 text-good hover:bg-emerald-900/50"
: "bg-surface-card/50 text-ink-mid hover:text-ink-mid"
@ -425,8 +427,9 @@ export function ChannelsTab({ workspaceId }: Props) {
{ch.enabled ? "On" : "Off"}
</button>
<button
aria-label={`Remove ${ch.config.chat_id || ch.config.channel_id || "channel"}`}
onClick={() => setPendingDelete(ch)}
className="text-[10px] px-2 py-0.5 rounded bg-red-900/20 text-bad hover:bg-red-900/40 transition"
className="text-[10px] px-2 py-0.5 rounded bg-red-900/20 text-bad hover:bg-red-900/40 transition focus:outline-none focus-visible:ring-2 focus-visible:ring-red-400"
>
Remove
</button>

View File

@ -383,7 +383,8 @@ function MyChatPanel({ workspaceId, data }: Props) {
// ignore — user will see no change and can retry
}
}}
className="px-2 py-0.5 text-[10px] font-medium bg-accent/10 hover:bg-accent/20 text-accent rounded border border-accent/30 transition-colors shrink-0"
aria-label="Enable agent chat"
className="px-2 py-0.5 text-[10px] font-medium bg-accent/10 hover:bg-accent/20 text-accent rounded border border-accent/30 transition-colors shrink-0 focus:outline-none focus-visible:ring-2 focus-visible:ring-accent/40"
>
Enable
</button>
@ -403,8 +404,9 @@ function MyChatPanel({ workspaceId, data }: Props) {
Failed to load chat history: {history.loadError}
</p>
<button
aria-label="Retry loading chat history"
onClick={history.loadInitial}
className="text-[10px] px-2 py-0.5 rounded bg-red-800 text-red-200 hover:bg-red-700 transition-colors"
className="text-[10px] px-2 py-0.5 rounded bg-red-800 text-red-200 hover:bg-red-700 transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-red-400"
>
Retry
</button>
@ -599,8 +601,9 @@ function MyChatPanel({ workspaceId, data }: Props) {
<span className="text-[10px] text-red-300">{displayError}</span>
{!isOnline && (
<button
aria-label="Restart workspace"
onClick={() => setConfirmRestart(true)}
className="text-[11px] px-2 py-0.5 bg-red-800 text-red-200 rounded hover:bg-red-700"
className="text-[11px] px-2 py-0.5 bg-red-800 text-red-200 rounded hover:bg-red-700 focus:outline-none focus-visible:ring-2 focus-visible:ring-red-400"
>
Restart
</button>
@ -636,7 +639,7 @@ function MyChatPanel({ workspaceId, data }: Props) {
disabled={!agentReachable || sending || uploading}
aria-label="Attach file"
title="Attach file"
className="p-2 bg-surface-card hover:bg-surface-card border border-line rounded-lg text-ink-mid hover:text-ink transition-colors shrink-0 disabled:opacity-40"
className="p-2 bg-surface-card hover:bg-surface-card border border-line rounded-lg text-ink-mid hover:text-ink transition-colors shrink-0 disabled:opacity-40 focus:outline-none focus-visible:ring-2 focus-visible:ring-accent/40"
>
<svg width="14" height="14" viewBox="0 0 16 16" fill="none" aria-hidden="true">
<path d="M11 6.5 7 10.5a2 2 0 1 0 2.8 2.8l4-4a3.5 3.5 0 0 0-5-5l-4.5 4.5a5 5 0 0 0 7 7l4-4" stroke="currentColor" strokeWidth="1.4" strokeLinecap="round" strokeLinejoin="round" />
@ -674,9 +677,10 @@ function MyChatPanel({ workspaceId, data }: Props) {
className="flex-1 bg-surface-card border border-line rounded-lg px-3 py-2 text-xs text-ink placeholder-ink-soft dark:bg-zinc-800 dark:border-zinc-600 dark:placeholder-zinc-500 focus:outline-none focus:border-accent focus-visible:ring-2 focus-visible:ring-accent/40 resize-none disabled:opacity-50"
/>
<button
aria-label="Send message"
onClick={handleSend}
disabled={(!input.trim() && pendingFiles.length === 0) || !agentReachable || sending || uploading}
className="px-4 py-2 bg-accent-strong hover:bg-accent text-xs font-medium rounded-lg text-white disabled:opacity-30 transition-colors shrink-0"
className="px-4 py-2 bg-accent-strong hover:bg-accent text-xs font-medium rounded-lg text-white disabled:opacity-30 transition-colors shrink-0 focus:outline-none focus-visible:ring-2 focus-visible:ring-accent/40"
>
{uploading ? "Uploading…" : "Send"}
</button>