From 2a9a400cd439fcf9d7bc77c9146ef2668eb5e8ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hartmut=20N=C3=B6renberg?= Date: Tue, 17 Mar 2026 10:29:12 +0100 Subject: [PATCH] fix: rewrite combobox components to inline-input pattern with dark mode (Gitea #12) Replace button+dropdown pattern with single input that becomes editable on focus. Fixes "second bar appearing" UX bug, adds dark mode, fixes z-index overlap. Co-Authored-By: claude-flow --- .../web/src/components/ui/ProjectCombobox.tsx | 78 ++++++++--------- .../src/components/ui/ResourceCombobox.tsx | 86 +++++++++---------- 2 files changed, 78 insertions(+), 86 deletions(-) diff --git a/apps/web/src/components/ui/ProjectCombobox.tsx b/apps/web/src/components/ui/ProjectCombobox.tsx index 731c7cc..fe95453 100644 --- a/apps/web/src/components/ui/ProjectCombobox.tsx +++ b/apps/web/src/components/ui/ProjectCombobox.tsx @@ -17,7 +17,7 @@ interface ProjectComboboxProps { export function ProjectCombobox({ value, onChange, - placeholder = "Search project…", + placeholder = "Search project\u2026", disabled = false, status, className = "", @@ -43,9 +43,9 @@ export function ProjectCombobox({ const selectedLabel = useMemo(() => { if (!value) return ""; const fromOpen = projects.find((p) => p.id === value); - if (fromOpen) return `${fromOpen.shortCode} — ${fromOpen.name}`; + if (fromOpen) return `${fromOpen.shortCode} \u2014 ${fromOpen.name}`; const fromAll = allData?.projects.find((p) => p.id === value); - if (fromAll) return `${fromAll.shortCode} — ${fromAll.name}`; + if (fromAll) return `${fromAll.shortCode} \u2014 ${fromAll.name}`; return value; }, [value, projects, allData]); @@ -61,72 +61,68 @@ export function ProjectCombobox({ return () => document.removeEventListener("mousedown", handleClick); }, [open]); - function handleOpen() { + function handleFocus() { if (disabled) return; setOpen(true); setSearch(""); - setTimeout(() => inputRef.current?.focus(), 0); } function select(id: string | null) { onChange(id); setOpen(false); setSearch(""); + inputRef.current?.blur(); } return (
- )} - +
{open && ( -
-
- setSearch(e.target.value)} - placeholder="Type to search…" - className="w-full px-2 py-1 text-sm border-0 outline-none" - /> -
+
    {projects.length === 0 ? ( -
  • No results
  • +
  • No results
  • ) : ( projects.map((p) => (
  • diff --git a/apps/web/src/components/ui/ResourceCombobox.tsx b/apps/web/src/components/ui/ResourceCombobox.tsx index e57ba3e..82c3b29 100644 --- a/apps/web/src/components/ui/ResourceCombobox.tsx +++ b/apps/web/src/components/ui/ResourceCombobox.tsx @@ -16,7 +16,7 @@ interface ResourceComboboxProps { export function ResourceCombobox({ value, onChange, - placeholder = "Search resource…", + placeholder = "Search resource\u2026", disabled = false, isActive = true, className = "", @@ -32,22 +32,22 @@ export function ResourceCombobox({ { enabled: open, staleTime: 30_000 }, ); - const resources = data?.resources ?? []; + const resources = (data?.resources ?? []) as Array<{ id: string; displayName: string; eid: string }>; - // Resolve display name for currently selected value - const { data: selectedData } = trpc.resource.list.useQuery( - { search: undefined, limit: 500, isActive: undefined as unknown as boolean }, + const selectedQuery = trpc.resource.list.useQuery( + { limit: 500 }, { enabled: !!value && !open, staleTime: 60_000 }, ); + const selectedResources = (selectedQuery.data?.resources ?? []) as Array<{ id: string; displayName: string; eid: string }>; const selectedLabel = useMemo(() => { if (!value) return ""; const fromOpen = resources.find((r) => r.id === value); if (fromOpen) return `${fromOpen.displayName} (${fromOpen.eid})`; - const fromSelected = selectedData?.resources.find((r) => r.id === value); + const fromSelected = selectedResources.find((r) => r.id === value); if (fromSelected) return `${fromSelected.displayName} (${fromSelected.eid})`; return value; - }, [value, resources, selectedData]); + }, [value, resources, selectedResources]); useEffect(() => { if (!open) return; @@ -61,73 +61,69 @@ export function ResourceCombobox({ return () => document.removeEventListener("mousedown", handleClick); }, [open]); - function handleOpen() { + function handleFocus() { if (disabled) return; setOpen(true); setSearch(""); - setTimeout(() => inputRef.current?.focus(), 0); } function select(id: string | null) { onChange(id); setOpen(false); setSearch(""); + inputRef.current?.blur(); } return (
    - )} - +
    {open && ( -
    -
    - setSearch(e.target.value)} - placeholder="Type to search…" - className="w-full px-2 py-1 text-sm border-0 outline-none" - /> -
    +
      {resources.length === 0 ? ( -
    • No results
    • +
    • No results
    • ) : ( resources.map((r) => (
    • ))