logipedia/src/components/combobox.tsx
mr-shortman 1597d4f113
Some checks failed
Gitea Actions Demo / Explore-Gitea-Actions (push) Failing after 0s
fixed editor missing peaces
2025-03-15 23:48:44 +01:00

141 lines
4.0 KiB
TypeScript

"use client";
import * as React from "react";
import { Check, ChevronsUpDown, LucideIcon } from "lucide-react";
import { cn } from "@/lib/utils";
import { Button, ButtonProps } from "@/components/ui/button";
import {
Command,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
} from "@/components/ui/command";
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover";
export type ComboboxProps = {
data: {
label: string;
value: string;
Icon?: LucideIcon;
}[];
onSelect: (value?: string) => void;
messageUi?: {
select?: string;
selectIcon?: LucideIcon;
empty?: string;
placeholder?: string;
};
initialValue?: string;
className?: string;
hideSearch?: boolean;
buttonProps?: ButtonProps;
};
export function Combobox({
data,
initialValue,
messageUi,
className,
hideSearch,
buttonProps,
onSelect,
}: ComboboxProps) {
const [open, setOpen] = React.useState(false);
const [value, setValue] = React.useState(initialValue ?? "");
const selectedItem = data.find((item) => item.value === initialValue)!;
return (
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger asChild>
<Button
variant="outline"
role="combobox"
aria-expanded={open}
className={cn(
"w-[200px] justify-between shadow-none hover:bg-background",
className,
)}
{...buttonProps}
>
<div
className={cn(
"flex items-center gap-2 text-muted-foreground",
selectedItem && "text-foreground",
)}
>
{selectedItem?.Icon ? (
<selectedItem.Icon className="size-4" />
) : messageUi?.selectIcon ? (
<messageUi.selectIcon className="size-4" />
) : null}
<span>
{selectedItem?.label
? selectedItem.label
: (messageUi?.select ?? "Select...")}
</span>
</div>
<ChevronsUpDown className="opacity-50" />
</Button>
</PopoverTrigger>
<PopoverContent className="popover-content-width-same-as-its-trigger w-full p-0">
<Command
filter={(value, search) => {
if (!search.trim()) return 1; // Show all when no search input
const entry = data.find((item) => item.value === value); // Assuming dataset is an array of objects with id and name
if (!entry) return 0; // If no matching entry is found, exclude it
return entry.label.toLowerCase().includes(search.toLowerCase())
? 1
: 0;
}}
>
{!hideSearch && (
<CommandInput
placeholder={messageUi?.placeholder ?? "Search..."}
className="h-9"
/>
)}
<CommandList>
<CommandEmpty>{messageUi?.empty ?? "Nothing found."}</CommandEmpty>
<CommandGroup>
{data.map((item) => (
<CommandItem
key={item.value}
value={item.value}
onSelect={(currentValue) => {
const newValue = currentValue === value ? "" : currentValue;
setValue(newValue);
onSelect(newValue);
setOpen(false);
}}
>
<div className="flex items-center gap-2">
{item?.Icon && <item.Icon className="ml-auto opacity-50" />}
<span>{item.label}</span>
</div>
<Check
className={cn(
"ml-auto",
value === item.value ? "opacity-100" : "opacity-0",
)}
/>
</CommandItem>
))}
</CommandGroup>
</CommandList>
</Command>
</PopoverContent>
</Popover>
);
}