forked from molecule-ai/molecule-core
fix(canvas/a11y): add type=button to tab toolbar and settings buttons
WCAG 4.1.2 / bug #1669 follow-up — fixing remaining buttons missing type="button" across tab components and settings. Files changed: - FilesTab/FilesToolbar.tsx (5 buttons): +New, Upload, Export, Clear, ↻ (all had onClick, no type=button) - config/secrets-section.tsx (7 buttons): Remove, Edit/Update/Cancel across 2 SecretRow variants + add-variable form - config/form-inputs.tsx (2 buttons): tag remove ×, section collapse toggle - ActivityTab.tsx (1 button): row expand toggle - TracesTab.tsx (1 button): Refresh - settings/UnsavedChangesGuard.tsx (2 buttons): Keep editing, Discard (Radix AlertDialog asChild wrappers — type=button prevents form submit) Total: 18 buttons fixed across 6 files. 934/934 tests pass. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
6b62391e5d
commit
1126d7b66d
@ -31,12 +31,12 @@ export function UnsavedChangesGuard({
|
||||
</AlertDialog.Title>
|
||||
<div className="guard-dialog__actions">
|
||||
<AlertDialog.Cancel asChild>
|
||||
<button className="guard-dialog__keep-btn" onClick={onKeepEditing}>
|
||||
<button type="button" className="guard-dialog__keep-btn">
|
||||
Keep editing
|
||||
</button>
|
||||
</AlertDialog.Cancel>
|
||||
<AlertDialog.Action asChild>
|
||||
<button className="guard-dialog__discard-btn" onClick={onDiscard}>
|
||||
<button type="button" className="guard-dialog__discard-btn">
|
||||
Discard
|
||||
</button>
|
||||
</AlertDialog.Action>
|
||||
|
||||
@ -186,7 +186,7 @@ function ActivityRow({
|
||||
: "bg-zinc-800/60 border-zinc-700/40"
|
||||
}`}
|
||||
>
|
||||
<button onClick={onToggle} className="w-full text-left px-3 py-2">
|
||||
<button type="button" onClick={onToggle} className="w-full text-left px-3 py-2">
|
||||
{/* Top row: type badge + method + time */}
|
||||
<div className="flex items-center gap-2">
|
||||
<span className={`text-[8px] font-mono px-1.5 py-0.5 rounded ${typeStyle.text} ${typeStyle.bg} border ${typeStyle.border}`}>
|
||||
|
||||
@ -44,7 +44,7 @@ export function FilesToolbar({
|
||||
<div className="flex gap-1.5">
|
||||
{root === "/configs" && (
|
||||
<>
|
||||
<button onClick={onNewFile} aria-label="Create new file" className="text-[10px] text-blue-400 hover:text-blue-300" title="Create new file">
|
||||
<button type="button" onClick={onNewFile} aria-label="Create new file" className="text-[10px] text-blue-400 hover:text-blue-300" title="Create new file">
|
||||
+ New
|
||||
</button>
|
||||
<input
|
||||
@ -57,20 +57,20 @@ export function FilesToolbar({
|
||||
className="hidden"
|
||||
onChange={(e) => e.target.files && onUpload(e.target.files)}
|
||||
/>
|
||||
<button onClick={() => uploadRef.current?.click()} aria-label="Upload folder" className="text-[10px] text-blue-400 hover:text-blue-300" title="Upload folder">
|
||||
<button type="button" onClick={() => uploadRef.current?.click()} aria-label="Upload folder" className="text-[10px] text-blue-400 hover:text-blue-300" title="Upload folder">
|
||||
Upload
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
<button onClick={onDownloadAll} aria-label="Download all files" className="text-[10px] text-zinc-500 hover:text-zinc-300" title="Download all files">
|
||||
<button type="button" onClick={onDownloadAll} aria-label="Download all files" className="text-[10px] text-zinc-500 hover:text-zinc-300" title="Download all files">
|
||||
Export
|
||||
</button>
|
||||
{root === "/configs" && (
|
||||
<button onClick={onClearAll} aria-label="Delete all files" className="text-[10px] text-red-400/60 hover:text-red-400" title="Delete all files">
|
||||
<button type="button" onClick={onClearAll} aria-label="Delete all files" className="text-[10px] text-red-400/60 hover:text-red-400" title="Delete all files">
|
||||
Clear
|
||||
</button>
|
||||
)}
|
||||
<button onClick={onRefresh} aria-label="Refresh file list" className="text-[10px] text-zinc-500 hover:text-zinc-300" title="Refresh">
|
||||
<button type="button" onClick={onRefresh} aria-label="Refresh file list" className="text-[10px] text-zinc-500 hover:text-zinc-300" title="Refresh">
|
||||
↻
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@ -55,7 +55,7 @@ export function TracesTab({ workspaceId }: Props) {
|
||||
<div className="p-4 space-y-2">
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<span className="text-xs text-zinc-400">{traces.length} traces</span>
|
||||
<button onClick={loadTraces} className="text-[10px] text-zinc-500 hover:text-zinc-300">
|
||||
<button type="button" onClick={loadTraces} className="text-[10px] text-zinc-500 hover:text-zinc-300">
|
||||
Refresh
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@ -104,7 +104,7 @@ export function TagList({ label, values, onChange, placeholder }: { label: strin
|
||||
{values.map((v, i) => (
|
||||
<span key={i} className="inline-flex items-center gap-1 px-1.5 py-0.5 bg-zinc-800 border border-zinc-700 rounded text-[10px] text-zinc-300 font-mono">
|
||||
{v}
|
||||
<button aria-label={`Remove tag ${v}`} onClick={() => onChange(values.filter((_, j) => j !== i))} className="text-zinc-500 hover:text-red-400">×</button>
|
||||
<button type="button" aria-label={`Remove tag ${v}`} onClick={() => onChange(values.filter((_, j) => j !== i))} className="text-zinc-500 hover:text-red-400">×</button>
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
@ -131,7 +131,7 @@ export function Section({ title, children, defaultOpen = true }: { title: string
|
||||
const [open, setOpen] = useState(defaultOpen);
|
||||
return (
|
||||
<div className="border border-zinc-800 rounded mb-2">
|
||||
<button onClick={() => setOpen(!open)} className="w-full flex items-center justify-between px-3 py-1.5 text-[10px] text-zinc-400 hover:text-zinc-200 bg-zinc-900/50">
|
||||
<button type="button" onClick={() => setOpen(!open)} className="w-full flex items-center justify-between px-3 py-1.5 text-[10px] text-zinc-400 hover:text-zinc-200 bg-zinc-900/50">
|
||||
<span className="font-medium uppercase tracking-wider">{title}</span>
|
||||
<span>{open ? "▾" : "▸"}</span>
|
||||
</button>
|
||||
|
||||
@ -113,9 +113,9 @@ function SecretRow({ label, secretKey, isSet, scope, globalMode, onSave, onDelet
|
||||
{isSet && <span className="text-[10px] text-green-500 bg-green-900/30 px-1.5 py-0.5 rounded">Set</span>}
|
||||
{scope && <ScopeBadge scope={scope} />}
|
||||
{!editing && isSet && (globalMode || scope !== "global") && (
|
||||
<button onClick={onDelete} className="text-[11px] text-red-400 hover:text-red-300">Remove</button>
|
||||
<button type="button" onClick={onDelete} className="text-[11px] text-red-400 hover:text-red-300">Remove</button>
|
||||
)}
|
||||
<button onClick={() => setEditing(!editing)} className="text-[11px] text-blue-400 hover:text-blue-300">
|
||||
<button type="button" onClick={() => setEditing(!editing)} className="text-[11px] text-blue-400 hover:text-blue-300">
|
||||
{actionLabel()}
|
||||
</button>
|
||||
</div>
|
||||
@ -128,7 +128,7 @@ function SecretRow({ label, secretKey, isSet, scope, globalMode, onSave, onDelet
|
||||
type={isPlaintext ? "text" : "password"} autoFocus
|
||||
className="flex-1 bg-zinc-900 border border-zinc-600 rounded px-2 py-1 text-[10px] text-zinc-100 font-mono focus:outline-none focus:border-blue-500"
|
||||
/>
|
||||
<button
|
||||
<button type="button"
|
||||
onClick={() => { onSave(value); setEditing(false); setValue(""); }}
|
||||
disabled={!value}
|
||||
className="px-2 py-1 bg-blue-600 hover:bg-blue-500 text-[10px] rounded text-white disabled:opacity-30"
|
||||
@ -165,10 +165,10 @@ function CustomSecretRow({ secretKey, scope, globalMode, onSave, onDelete }: {
|
||||
<span className="text-[10px] text-green-500">Set</span>
|
||||
{!globalMode && <ScopeBadge scope={scope} />}
|
||||
{canDelete && !editing && (
|
||||
<button onClick={onDelete} className="text-[11px] text-red-400 hover:text-red-300">Remove</button>
|
||||
<button type="button" onClick={onDelete} className="text-[11px] text-red-400 hover:text-red-300">Remove</button>
|
||||
)}
|
||||
{(canDelete || showOverride) && (
|
||||
<button onClick={() => setEditing(!editing)} className="text-[11px] text-blue-400 hover:text-blue-300">
|
||||
<button type="button" onClick={() => setEditing(!editing)} className="text-[11px] text-blue-400 hover:text-blue-300">
|
||||
{editing ? "Cancel" : showOverride ? "Override" : "Update"}
|
||||
</button>
|
||||
)}
|
||||
@ -181,7 +181,7 @@ function CustomSecretRow({ secretKey, scope, globalMode, onSave, onDelete }: {
|
||||
placeholder="New value" type="password" autoFocus
|
||||
className="flex-1 bg-zinc-900 border border-zinc-600 rounded px-2 py-1 text-[10px] text-zinc-100 font-mono focus:outline-none focus:border-blue-500"
|
||||
/>
|
||||
<button
|
||||
<button type="button"
|
||||
onClick={() => { onSave(value); setEditing(false); setValue(""); }}
|
||||
disabled={!value}
|
||||
className="px-2 py-1 bg-blue-600 hover:bg-blue-500 text-[10px] rounded text-white disabled:opacity-30"
|
||||
@ -355,16 +355,16 @@ export function SecretsSection({ workspaceId, requiredEnv }: { workspaceId: stri
|
||||
<input value={newValue} onChange={(e) => setNewValue(e.target.value)} placeholder="Value" type="password"
|
||||
className="w-full bg-zinc-900 border border-zinc-600 rounded px-2 py-1 text-[10px] text-zinc-100 focus:outline-none focus:border-blue-500" />
|
||||
<div className="flex gap-2">
|
||||
<button onClick={() => { if (newKey && newValue) handleSave(newKey, newValue); }} disabled={!newKey || !newValue}
|
||||
<button type="button" onClick={() => { if (newKey && newValue) handleSave(newKey, newValue); }} disabled={!newKey || !newValue}
|
||||
className="px-2 py-1 bg-blue-600 hover:bg-blue-500 text-[10px] rounded text-white disabled:opacity-30">
|
||||
Save{globalMode ? " (Global)" : ""}
|
||||
</button>
|
||||
<button onClick={() => { setShowAdd(false); setNewKey(""); setNewValue(""); }}
|
||||
<button type="button" onClick={() => { setShowAdd(false); setNewKey(""); setNewValue(""); }}
|
||||
className="px-2 py-1 bg-zinc-700 hover:bg-zinc-600 text-[10px] rounded text-zinc-300">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<button onClick={() => setShowAdd(true)} className="text-[10px] text-blue-400 hover:text-blue-300">
|
||||
<button type="button" onClick={() => setShowAdd(true)} className="text-[10px] text-blue-400 hover:text-blue-300">
|
||||
+ Add {globalMode ? "Global " : ""}Variable
|
||||
</button>
|
||||
)}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user