From 947a5c1bbf3d250500d6b53d137441d5f2cbe04b Mon Sep 17 00:00:00 2001 From: Z8MB1E Date: Thu, 17 Sep 2026 16:28:17 -0400 Subject: [PATCH] feat(ui): searchable combobox across data-driven dropdowns - new ui/combobox primitive (popover plus cmdk): search input, scrolling list, trigger-width popover, per-option disabled and hint lines - converts every data-driven single-value select site-wide (blueprints, factions, accounts, assignments, NPCs, structures, locker items, seats, missions, ranks, resources, mod lists, event targets, sprints) - long multi-select checkbox menus (event log types, kanban labels) gain search inputs following the resource picker pattern - static short enum selects stay as plain selects --- .../account/RequestTransferDialog.tsx | 80 +++++----- .../banking/BankTransactionDialog.tsx | 24 +-- .../frontend/banking/CreateAccountDialog.tsx | 23 ++- .../baseManagement/HireStaffDialog.tsx | 112 ++++++------- .../frontend/events/EventLogTable.tsx | 33 ++-- .../frontend/helpdesk/TicketDetail.tsx | 27 ++-- .../intelligence/CreateSideOpDialog.tsx | 95 ++++++----- .../frontend/locker/AddLockerItemDialog.tsx | 31 ++-- .../frontend/locker/EquipmentEditorDialog.tsx | 76 ++++----- .../frontend/locker/LoadoutEditor.tsx | 26 ++- .../frontend/locker/LockerCharacterPanel.tsx | 30 ++-- .../frontend/locker/WardrobePanel.tsx | 36 ++--- .../frontend/map/PlacementDialog.tsx | 85 +++++----- .../frontend/market/CreateListingDialog.tsx | 35 ++--- .../minigames/poker/PokerDevPanel.tsx | 84 +++++----- .../profile/LeadershipEvaluationDialog.tsx | 29 ++-- .../frontend/profile/PromoteButton.tsx | 31 ++-- .../frontend/projects/KanbanFilters.tsx | 68 ++++---- .../frontend/storage/EventLedger.tsx | 53 +++++-- .../frontend/storage/ManageStorageDialog.tsx | 148 ++++++++---------- src/components/ui/combobox.tsx | 122 +++++++++++++++ 21 files changed, 651 insertions(+), 597 deletions(-) create mode 100644 src/components/ui/combobox.tsx diff --git a/src/components/frontend/account/RequestTransferDialog.tsx b/src/components/frontend/account/RequestTransferDialog.tsx index 5703556..d9346d7 100644 --- a/src/components/frontend/account/RequestTransferDialog.tsx +++ b/src/components/frontend/account/RequestTransferDialog.tsx @@ -5,6 +5,7 @@ import { useRouter } from "next/navigation"; import { toast } from "sonner"; import { ArrowLeftRightIcon, Loader2Icon } from "lucide-react"; import { Alert, AlertDescription } from "@/components/ui/alert"; +import { Combobox } from "@/components/ui/combobox"; import { Button } from "@/components/ui/button"; import { Dialog, @@ -16,7 +17,14 @@ import { DialogTrigger, } from "@/components/ui/dialog"; import { Label } from "@/components/ui/label"; -import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; +import { + Select, + SelectContent, + SelectGroup, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; import { Skeleton } from "@/components/ui/skeleton"; import { Textarea } from "@/components/ui/textarea"; import { requestTransfer } from "@/app/(frontend)/transfers/actions"; @@ -199,22 +207,20 @@ export function RequestTransferDialog({
- + ({ + value: String(assignment.id), + label: assignment.callsign + ? `${assignment.name} · ${assignment.callsign}` + : assignment.name, + }))} + />
@@ -222,26 +228,20 @@ export function RequestTransferDialog({ {loadingDestinations ? ( ) : destinations.length > 0 ? ( - + placeholder="Select destination assignment" + searchPlaceholder="Search assignments..." + options={destinations.map((assignment) => ({ + value: String(assignment.id), + label: assignment.callsign + ? `${assignment.name} · ${assignment.callsign}` + : assignment.name, + }))} + /> ) : ( @@ -271,10 +271,18 @@ export function RequestTransferDialog({ )} - - diff --git a/src/components/frontend/banking/BankTransactionDialog.tsx b/src/components/frontend/banking/BankTransactionDialog.tsx index 354d6d2..61db382 100644 --- a/src/components/frontend/banking/BankTransactionDialog.tsx +++ b/src/components/frontend/banking/BankTransactionDialog.tsx @@ -5,6 +5,7 @@ import { useRouter } from "next/navigation"; import { ArrowDownLeftIcon, ArrowRightLeftIcon, ArrowUpRightIcon } from "lucide-react"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; +import { Combobox } from "@/components/ui/combobox"; import { Label } from "@/components/ui/label"; import { Textarea } from "@/components/ui/textarea"; import { @@ -164,18 +165,17 @@ export function BankTransactionDialog({ {mode === "transfer" && (
- + ({ + value: String(a.id), + label: a.name, + }))} + />
)}
diff --git a/src/components/frontend/banking/CreateAccountDialog.tsx b/src/components/frontend/banking/CreateAccountDialog.tsx index bef6939..cb71326 100644 --- a/src/components/frontend/banking/CreateAccountDialog.tsx +++ b/src/components/frontend/banking/CreateAccountDialog.tsx @@ -5,6 +5,7 @@ import { useRouter } from "next/navigation"; import { PlusIcon } from "lucide-react"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; +import { Combobox } from "@/components/ui/combobox"; import { Label } from "@/components/ui/label"; import { Select, @@ -117,18 +118,16 @@ export function CreateAccountDialog({ {accountType === "faction" && (
- + ({ + value: String(f.id), + label: f.name, + }))} + />
)} {accountType === "personal" && !isManager && ( diff --git a/src/components/frontend/baseManagement/HireStaffDialog.tsx b/src/components/frontend/baseManagement/HireStaffDialog.tsx index a6862a1..7937ed9 100644 --- a/src/components/frontend/baseManagement/HireStaffDialog.tsx +++ b/src/components/frontend/baseManagement/HireStaffDialog.tsx @@ -15,15 +15,8 @@ import { DialogTrigger, } from "@/components/ui/dialog"; import { Input } from "@/components/ui/input"; +import { Combobox } from "@/components/ui/combobox"; import { Label } from "@/components/ui/label"; -import { - Select, - SelectContent, - SelectGroup, - SelectItem, - SelectTrigger, - SelectValue, -} from "@/components/ui/select"; import { useRouter } from "next/navigation"; import type { LaborClassificationOption, StructureStaffingOption } from "@/lib/base"; import { currencyLabel, type CurrencyConfig } from "@/lib/banking/format"; @@ -353,61 +346,51 @@ export function HireStaffDialog({ {fixedNpc?.name ?? `NPC #${fixedNpcId}`}

) : ( - + ({ + value: String(npc.id), + label: npc.name, + }))} + /> )}
)}
- + ({ + value: String(structure.id), + label: structure.name, + }))} + />
- + 0 + ? [{ value: "labor", label: "Labor" }] + : []), + ...approvedPositionTypes.map((position) => ({ + value: position.value, + label: position.label, + })), + ]} + /> {selectedStructure && approvedClassifications.length === 0 && approvedPositionTypes.length === 0 && ( @@ -421,20 +404,15 @@ export function HireStaffDialog({ <>
- + ({ + value: String(classification.id), + label: classification.name, + }))} + /> {selectedStructure && approvedClassifications.length === 0 && (

This structure has no approved labor positions. diff --git a/src/components/frontend/events/EventLogTable.tsx b/src/components/frontend/events/EventLogTable.tsx index ff13655..c0435b4 100644 --- a/src/components/frontend/events/EventLogTable.tsx +++ b/src/components/frontend/events/EventLogTable.tsx @@ -4,6 +4,7 @@ import * as React from "react"; import { ChevronLeft, ChevronRight, RotateCcw, Search } from "lucide-react"; import { Badge } from "@/components/ui/badge"; +import { Combobox } from "@/components/ui/combobox"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { @@ -305,19 +306,20 @@ export default function EventLogTable() { - + ({ + value, + label, + })), + ]} + /> setPageSize(Number(v))} - > + - - - - - Unassigned - {assigneeOptions.map((option) => ( - - {option.label} - - ))} - - + searchPlaceholder="Search members..." + options={[ + { value: "unassigned", label: "Unassigned" }, + ...assigneeOptions.map((option) => ({ + value: String(option.id), + label: option.label, + })), + ]} + />