From f093351e5b65fb3c5e3d8989d6ff3ed2d3d96a24 Mon Sep 17 00:00:00 2001 From: Ian Boyes Date: Wed, 8 Jul 2026 10:11:41 -0700 Subject: [PATCH 1/4] refactor: control create-reference dialog with local state instead of url Move the create-reference dialog's open state and empty/import tab selection off the /refs URL search params and into local React state, since it's transient UI state rather than something that needs to be shareable or bookmarkable. The empty/import tab switch now uses the local-state Tabs component instead of the routing-based NavTab, with both panes kept mounted so in-progress input isn't lost when switching tabs. Also fixes a duplicate DOM id (name/description fields) that surfaced once both forms could be mounted simultaneously. --- .../references/components/CreateReference.tsx | 75 ++++++++++--------- .../references/components/EmptyReference.tsx | 7 +- .../references/components/ImportReference.tsx | 24 ++++-- .../references/components/ReferenceForm.tsx | 17 +++-- .../references/components/ReferenceList.tsx | 16 ++-- .../components/ReferenceToolbar.tsx | 6 +- .../__tests__/CreateReference.test.tsx | 49 ++++++++++++ .../__tests__/ReferenceList.test.tsx | 1 - .../src/routes/_authenticated/refs/index.tsx | 2 - 9 files changed, 134 insertions(+), 63 deletions(-) create mode 100644 apps/web/src/references/components/__tests__/CreateReference.test.tsx diff --git a/apps/web/src/references/components/CreateReference.tsx b/apps/web/src/references/components/CreateReference.tsx index bb821f733..fab4ee37f 100644 --- a/apps/web/src/references/components/CreateReference.tsx +++ b/apps/web/src/references/components/CreateReference.tsx @@ -1,52 +1,59 @@ import { Dialog, DialogContent, DialogTitle } from "@base/Dialog"; -import NavTab from "@base/NavTab"; -import NavTabs from "@base/NavTabs"; +import { Tabs, TabsContent, TabsList, TabsTrigger } from "@base/Tabs"; +import { useState } from "react"; import EmptyReference from "./EmptyReference"; import ImportReference from "./ImportReference"; type CreateReferenceProps = { - createReferenceType?: string; - setCreateReferenceType: (type?: string) => void; + open: boolean; + onOpenChange: (open: boolean) => void; }; /** * The create reference view with options to create an empty reference or import a reference */ -export function CreateReference({ - createReferenceType, - setCreateReferenceType, -}: CreateReferenceProps) { +export function CreateReference({ open, onOpenChange }: CreateReferenceProps) { + const [tab, setTab] = useState<"empty" | "import">("empty"); + + function handleOpenChange(open: boolean) { + onOpenChange(open); + if (!open) { + setTab("empty"); + } + } + + function handleSuccess() { + onOpenChange(false); + setTab("empty"); + } + return ( - { - setCreateReferenceType(undefined); - }} - > + Create Reference - - setTab(value as "empty" | "import")} + > + + Empty + Import + + - Empty - - + + - Import - - - - {createReferenceType === "import" ? ( - - ) : ( - - )} + + + ); diff --git a/apps/web/src/references/components/EmptyReference.tsx b/apps/web/src/references/components/EmptyReference.tsx index 3dbded2b2..911315c46 100644 --- a/apps/web/src/references/components/EmptyReference.tsx +++ b/apps/web/src/references/components/EmptyReference.tsx @@ -11,10 +11,14 @@ type FormValues = { organism: string; }; +type EmptyReferenceProps = { + onSuccess?: () => void; +}; + /** * A form for creating an empty reference */ -export default function EmptyReference() { +export default function EmptyReference({ onSuccess }: EmptyReferenceProps) { const navigate = useNavigate(); const mutation = useCreateReference(); @@ -37,6 +41,7 @@ export default function EmptyReference() { mutation.mutate(values, { onSuccess: () => { navigate({ to: "/refs" }); + onSuccess?.(); }, }), )} diff --git a/apps/web/src/references/components/ImportReference.tsx b/apps/web/src/references/components/ImportReference.tsx index 527f428d2..8f9b6bd89 100644 --- a/apps/web/src/references/components/ImportReference.tsx +++ b/apps/web/src/references/components/ImportReference.tsx @@ -7,12 +7,19 @@ import InputSimple from "@base/InputSimple"; import ProgressBarAffixed from "@base/ProgressBarAffixed"; import SaveButton from "@base/SaveButton"; import { useNavigate } from "@tanstack/react-router"; +import { useId } from "react"; import { Controller, useForm } from "react-hook-form"; import { UploadBar } from "@/uploads/components/UploadBar"; import { useImportReference, useUploadReference } from "../queries"; -export default function ImportReference() { +type ImportReferenceProps = { + onSuccess?: () => void; +}; + +export default function ImportReference({ onSuccess }: ImportReferenceProps) { const navigate = useNavigate(); + const nameId = useId(); + const descriptionId = useId(); const importMutation = useImportReference(); const { uploadMutation, fileName, uploadId, progress } = useUploadReference(); @@ -85,20 +92,25 @@ export default function ImportReference() { } importMutation.mutate( { ...values, importFrom: uploadId }, - { onSuccess: () => navigate({ to: "/refs", replace: true }) }, + { + onSuccess: () => { + navigate({ to: "/refs", replace: true }); + onSuccess?.(); + }, + }, ); })} > - Name - + Name + {errors.name?.type && "A name is required."} - Description + Description diff --git a/apps/web/src/references/components/ReferenceForm.tsx b/apps/web/src/references/components/ReferenceForm.tsx index 95040c8fa..9f101c5f3 100644 --- a/apps/web/src/references/components/ReferenceForm.tsx +++ b/apps/web/src/references/components/ReferenceForm.tsx @@ -3,6 +3,7 @@ import InputGroup from "@base/InputGroup"; import InputLabel from "@base/InputLabel"; import InputSimple from "@base/InputSimple"; import TextArea from "@base/TextArea"; +import { useId } from "react"; import type { FieldErrors, UseFormRegister } from "react-hook-form"; export type ReferenceFormMode = "edit" | "empty"; @@ -26,20 +27,24 @@ type ReferenceFormProps = { * Form input fields for organism, name and description */ export function ReferenceForm({ errors, mode, register }: ReferenceFormProps) { + const nameId = useId(); + const organismId = useId(); + const descriptionId = useId(); + const organismComponent = mode === "empty" || mode === "edit" ? ( - Organism - + Organism + ) : null; return ( <> - Name + Name {errors.name?.message} @@ -48,8 +53,8 @@ export function ReferenceForm({ errors, mode, register }: ReferenceFormProps) { {organismComponent} - Description -