fix(ux): resolve tickets #55 #56 — resource modal stability and success feedback

#55: Add SuccessToast after new resource is created. ResourceModal gains an
optional onSuccess(displayName) prop; ResourcesClient wires it to a toast
that auto-dismisses after 2.5 s.

#56: Fix useFocusTrap stale-closure bug. Focusable elements are now queried
dynamically inside handleKeyDown (not captured once at mount), so Tab
navigation stays correct as the form re-renders. Initial focus is deferred
via requestAnimationFrame so the browser layout is stable before focus() fires.

Co-Authored-By: claude-flow <ruv@ruv.net>
This commit is contained in:
2026-04-03 15:43:12 +02:00
parent 0d0707264d
commit 65db330a4d
3 changed files with 39 additions and 10 deletions
@@ -35,6 +35,7 @@ import { useViewPrefs } from "~/hooks/useViewPrefs.js";
import { useRowOrder } from "~/hooks/useRowOrder.js";
import { useAnchoredOverlay } from "~/hooks/useAnchoredOverlay.js";
import { DraggableTableRow } from "~/components/ui/DraggableTableRow.js";
import { SuccessToast } from "~/components/ui/SuccessToast.js";
type ModalState =
| { type: "closed" }
@@ -158,6 +159,7 @@ export function ResourcesClient() {
const [departedFilter, setDepartedFilter] = useState<BooleanFilter>(DEFAULT_BOOLEAN_FILTER);
const [modal, setModal] = useState<ModalState>({ type: "closed" });
const [confirm, setConfirm] = useState<ConfirmState>({ type: "closed" });
const [successToast, setSuccessToast] = useState<string | null>(null);
const selection = useSelection();
const utils = trpc.useUtils();
@@ -1427,7 +1429,13 @@ export function ResourcesClient() {
]}
/>
{modal.type === "create" && <ResourceModal mode="create" onClose={closeModal} />}
{modal.type === "create" && (
<ResourceModal
mode="create"
onClose={closeModal}
onSuccess={(name) => setSuccessToast(`Resource "${name}" created successfully.`)}
/>
)}
{modal.type === "edit" && (
<ResourceModal mode="edit" resource={modal.resource} onClose={closeModal} />
)}
@@ -1461,6 +1469,12 @@ export function ResourcesClient() {
onCancel={() => setConfirm({ type: "closed" })}
/>
)}
<SuccessToast
show={successToast !== null}
message={successToast ?? ""}
onDone={() => setSuccessToast(null)}
/>
</div>
);
}