From 33a0ca30bbbbd20d488292101f1f520b368f708b Mon Sep 17 00:00:00 2001 From: Knacky Date: Thu, 28 May 2026 07:02:34 +0200 Subject: [PATCH] =?UTF-8?q?fix(frontend):=20sprint=205=20post-code-review?= =?UTF-8?q?=20=E2=80=94=20dropdown=20close-on-outside=20+=20empty-state=20?= =?UTF-8?q?dropdown?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - useEffect pointerdown + Escape listeners when dropdown open (NIT 1) - empty state now renders NewSimulationDropdown instead of plain Link (NIT 2) - 3 new Vitest: close-on-outside, close-on-Escape, empty-state has dropdown Co-Authored-By: Claude Sonnet 4.6 --- frontend/src/components/SimulationList.tsx | 32 ++++++++++++------ frontend/tests/SimulationList.test.tsx | 38 ++++++++++++++++++++++ 2 files changed, 60 insertions(+), 10 deletions(-) diff --git a/frontend/src/components/SimulationList.tsx b/frontend/src/components/SimulationList.tsx index a158a23..2028e9d 100644 --- a/frontend/src/components/SimulationList.tsx +++ b/frontend/src/components/SimulationList.tsx @@ -1,4 +1,4 @@ -import { useRef, useState } from 'react'; +import { useEffect, useRef, useState } from 'react'; import { Link, useNavigate } from 'react-router-dom'; import { ChevronDown, Plus } from 'lucide-react'; import { extractApiError } from '@/api/client'; @@ -26,9 +26,27 @@ function NewSimulationDropdown({ engagementId }: { engagementId: number }): JSX. const { push } = useToast(); const [open, setOpen] = useState(false); const [showPicker, setShowPicker] = useState(false); - const btnRef = useRef(null); + const ref = useRef(null); const createMutation = useCreateSimulation(engagementId); + useEffect(() => { + if (!open) return; + const onPointerDown = (e: PointerEvent) => { + if (ref.current && !ref.current.contains(e.target as Node)) { + setOpen(false); + } + }; + const onKeyDown = (e: KeyboardEvent) => { + if (e.key === 'Escape') setOpen(false); + }; + document.addEventListener('pointerdown', onPointerDown); + document.addEventListener('keydown', onKeyDown); + return () => { + document.removeEventListener('pointerdown', onPointerDown); + document.removeEventListener('keydown', onKeyDown); + }; + }, [open]); + const handleBlank = () => { setOpen(false); navigate(`/engagements/${engagementId}/simulations/new`); @@ -51,7 +69,7 @@ function NewSimulationDropdown({ engagementId }: { engagementId: number }): JSX. }; return ( -
+