fix(web): address review of the dialog, toast and async primitives
Two of these were real defects in the previous two commits. friendlyMessage discarded exactly the messages it claimed to keep: the "is this a bare reason phrase" test was a shape regex, and "Default steps cannot be edited" has the same shape as "Not Found". It is an exact-match set of reason phrases now. Modal depended on onKeyDown, which is rebuilt whenever onClose changes identity — and onClose is an inline arrow at every call site, so any parent re-render (a 30s poll, a mutation flipping to pending) tore the effect down and rebuilt it: cleanup restored focus to the trigger, setup then moved it to the top of the dialog, mid-typing. onClose is held in a ref and the effect is keyed on `open` alone. Also in Modal: initial focus takes the first control in the body rather than the panel, since the header comes first in DOM order and every dialog was opening on its own dismiss button; the Tab trap pulls focus back when it has escaped the panel entirely rather than only handling the two ends; the scroll lock is refcounted, because per-instance save/restore released the page when an outer dialog unmounted under an open inner one; and the whole thing is portalled to the body so a nested confirm is not clipped by its parent's overflow box. The fleet's filtered-empty state keyed on the search alone, so a tag filter matching nothing told a customer with a full fleet to add their first server. Remaining: pending mutation errors are reset when a confirm dialog closes, so one member's failure no longer greets the next; ConfirmDialog clears typed confirmation when the target changes, not only when it reopens; deleting a workflow closes its dialogs before navigating rather than carrying a scroll lock onto the next page; toasts split into a polite and an assertive region, since one polite wrapper demotes the role="alert" children inside it; and a custom skeleton gets a live "Loading" beside it, having been aria-hidden with nothing else to announce.
This commit is contained in:
@@ -142,6 +142,7 @@ function SecretRow({ group, secret }: { group: string; secret: Secret }) {
|
||||
mutate: remove,
|
||||
isPending: removing,
|
||||
error: removeError,
|
||||
reset: resetRemove,
|
||||
} = useMutation({
|
||||
mutationFn: () => api.deleteSecret(group, secret.key),
|
||||
onSuccess: () => {
|
||||
@@ -199,7 +200,10 @@ function SecretRow({ group, secret }: { group: string; secret: Secret }) {
|
||||
confirmLabel="Delete key"
|
||||
loading={removing}
|
||||
error={removeError ? friendlyMessage(removeError) : null}
|
||||
onClose={() => setConfirming(false)}
|
||||
onClose={() => {
|
||||
resetRemove();
|
||||
setConfirming(false);
|
||||
}}
|
||||
onConfirm={() => remove()}
|
||||
body={
|
||||
<>
|
||||
|
||||
@@ -228,11 +228,25 @@ function ServersPageBody() {
|
||||
skeleton={<TableSkeleton columns={6} />}
|
||||
isEmpty={visible.length === 0}
|
||||
empty={
|
||||
search.trim() ? (
|
||||
/* Narrowed to nothing is not the same as owning nothing. Telling a
|
||||
customer with a full fleet to "add your first server" because a
|
||||
tag filter matched none of it is the version of this that gets
|
||||
screenshotted. */
|
||||
search.trim() || Object.keys(selected).length > 0 ? (
|
||||
<EmptyState
|
||||
title="No servers match that search."
|
||||
description="Clear the search to see the rest of the fleet."
|
||||
action={{ label: "Clear search", onClick: () => setSearch("") }}
|
||||
title="No servers match those filters."
|
||||
description={
|
||||
Object.keys(selected).length > 0 && search.trim()
|
||||
? "Nothing matches both the tag filter and the search."
|
||||
: Object.keys(selected).length > 0
|
||||
? "No server carries every tag selected above."
|
||||
: "Clear the search to see the rest of the fleet."
|
||||
}
|
||||
action={
|
||||
search.trim()
|
||||
? { label: "Clear search", onClick: () => setSearch("") }
|
||||
: { label: "Clear filters", onClick: () => setSelected({}) }
|
||||
}
|
||||
/>
|
||||
) : (
|
||||
<EmptyState
|
||||
|
||||
Reference in New Issue
Block a user