refactor(web): shared ConfirmDialog, FormControls, and Modal a11y

Extract reusable confirm/modal shells and bordered form controls, adopt them across list pages and editors, and improve icon-button labels plus KvPage keyboard rows.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-08-21 22:08:23 +07:00
co-authored by Cursor
parent 9985ff3440
commit 3d25d8a614
27 changed files with 606 additions and 626 deletions
+27 -38
View File
@@ -10,6 +10,7 @@ import {
useWorkflows,
} from "../api/hooks.js";
import { DuplicateWorkflowDialog } from "../components/DuplicateWorkflowDialog.jsx";
import { ConfirmDialog } from "../components/ConfirmDialog.jsx";
import { WorkflowFileIcon } from "../components/WorkflowFileIcon.jsx";
import { formatTime, WorkflowStatusBadge } from "../lib/format.jsx";
@@ -80,6 +81,7 @@ export function WorkflowsPage() {
type="button"
className="btn btn-ghost btn-sm"
title="Reregister workflows"
aria-label="Reregister workflows"
disabled={reregister.isPending}
onClick={() => reregister.mutate()}
>
@@ -153,7 +155,11 @@ export function WorkflowsPage() {
<td className="whitespace-nowrap">{formatTime(w.lastInvokedAt)}</td>
<td>{w.invocationCount}</td>
<td className="text-right whitespace-nowrap">
<label className="inline-flex items-center mr-1" title={w.enabled ? "Disable" : "Enable"}>
<label
className="inline-flex items-center mr-1"
title={w.enabled ? "Disable" : "Enable"}
aria-label={w.enabled ? "Disable" : "Enable"}
>
<input
type="checkbox"
className="toggle toggle-success toggle-xs"
@@ -172,6 +178,7 @@ export function WorkflowsPage() {
type="button"
className="btn btn-ghost btn-xs"
title="Run"
aria-label="Run"
disabled={Boolean(w.loadError) || runningKey === w.key}
onClick={() => onRun(w)}
>
@@ -184,6 +191,7 @@ export function WorkflowsPage() {
<Link
className="btn btn-ghost btn-xs"
title="Events"
aria-label="Events"
to={`/events?workflow=${encodeURIComponent(w.key)}`}
>
<LuActivity className="size-4" />
@@ -191,6 +199,7 @@ export function WorkflowsPage() {
<Link
className="btn btn-ghost btn-xs"
title="Edit"
aria-label="Edit"
to={`/workflows/${encodeURIComponent(w.owner)}/${encodeURIComponent(w.file)}/edit`}
>
<LuPencil className="size-4" />
@@ -199,6 +208,7 @@ export function WorkflowsPage() {
type="button"
className="btn btn-ghost btn-xs"
title="Duplicate"
aria-label="Duplicate"
disabled={Boolean(w.loadError)}
onClick={() => setDuplicateSource(w)}
>
@@ -208,6 +218,7 @@ export function WorkflowsPage() {
type="button"
className="btn btn-ghost btn-xs text-error"
title="Move to trash"
aria-label="Move to trash"
onClick={() => setConfirmDelete(w)}
>
<LuTrash2 className="size-4" />
@@ -233,43 +244,21 @@ export function WorkflowsPage() {
/>
) : null}
{confirmDelete ? (
<dialog className="modal modal-open">
<div className="modal-box">
<h3 className="font-bold">Move {confirmDelete.key} to trash?</h3>
<p className="mt-2 text-sm opacity-70">
The workflow is removed from the list but kept in trash for 7 days. Revision history
is preserved.
</p>
{del.isError ? (
<p className="text-error text-sm mt-2">{errorMessage(del.error)}</p>
) : null}
<div className="modal-action">
<button type="button" className="btn btn-ghost" onClick={() => setConfirmDelete(null)}>
Cancel
</button>
<button
type="button"
className="btn btn-error"
disabled={del.isPending}
onClick={() =>
del.mutate(
{ owner: confirmDelete.owner, file: confirmDelete.file },
{ onSuccess: () => setConfirmDelete(null) },
)
}
>
Move to trash
</button>
</div>
</div>
<form method="dialog" className="modal-backdrop">
<button type="button" onClick={() => setConfirmDelete(null)}>
close
</button>
</form>
</dialog>
) : null}
<ConfirmDialog
open={Boolean(confirmDelete)}
title={confirmDelete ? `Move ${confirmDelete.key} to trash?` : ""}
message="The workflow is removed from the list but kept in trash for 7 days. Revision history is preserved."
confirmLabel="Move to trash"
error={del.isError ? errorMessage(del.error) : null}
loading={del.isPending}
onCancel={() => setConfirmDelete(null)}
onConfirm={() =>
del.mutate(
{ owner: confirmDelete.owner, file: confirmDelete.file },
{ onSuccess: () => setConfirmDelete(null) },
)
}
/>
</div>
);
}