"use client"; import { useState } from "react"; import { CheckIcon, ChevronDownIcon, XIcon } from "lucide-react"; import { Button } from "@/components/ui/button"; import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"; import { Command, CommandGroup, CommandInput, CommandItem, CommandList, CommandSeparator, } from "@/components/ui/command"; import { cn } from "@/lib/utils"; /** * A free-text field that offers what you have already used. * * Not a select and not a tag. Location and work mode have to stay free text — * "Remote (US, PST overlap)" is a real answer and no enum survives it — but * free text with no memory is how one workspace ends up holding "Remote", * "remote" and "Remote " as three values that never group together. So the box * lists what is already on your applications, most-used first, and typing * something new is still just typing: the last row creates it and says so. * * Clearing is a first-class act rather than selecting all and pressing delete, * because "this one is not remote after all" is a thing that happens and an * empty string is the honest way to say it. */ export function ValuePicker({ value, options, onChange, placeholder = "Anything", ariaLabel, }: { value: string; /** What is already in use, with how many applications carry it. */ options: { value: string; count: number }[]; onChange: (value: string) => void; placeholder?: string; ariaLabel: string; }) { const [open, setOpen] = useState(false); const [query, setQuery] = useState(""); const typed = query.trim(); const matches = options.filter((option) => option.value.toLowerCase().includes(typed.toLowerCase()), ); // Only when it is genuinely new. Offering to create "Remote" while "Remote" // is the row above it is a menu arguing with itself. const isNew = typed.length > 0 && !options.some((option) => option.value.toLowerCase() === typed.toLowerCase()); const commit = (next: string) => { onChange(next); setQuery(""); setOpen(false); }; return (
Nothing used yet. Type one.
)} {matches.length > 0 && (