mediahub-fe/components/ui/combobox.tsx

181 lines
4.8 KiB
TypeScript
Raw Normal View History

"use client";
import * as React from "react";
import { Check, ChevronsUpDown } from "lucide-react";
import { cn } from "@/lib/utils";
import { Button } from "@/components/ui/button";
import {
Command,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
} from "@/components/ui/command";
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover";
export interface ComboboxOption {
value: string;
label: string;
disabled?: boolean;
}
interface ComboboxProps {
options: ComboboxOption[];
value?: string;
onValueChange?: (value: string) => void;
placeholder?: string;
searchPlaceholder?: string;
emptyMessage?: string;
disabled?: boolean;
className?: string;
triggerClassName?: string;
contentClassName?: string;
}
export function Combobox({
options,
value,
onValueChange,
placeholder = "Select option...",
searchPlaceholder = "Search...",
emptyMessage = "No results found.",
disabled = false,
className,
triggerClassName,
contentClassName,
}: ComboboxProps) {
const [open, setOpen] = React.useState(false);
const selectedOption = options.find((option) => option.value === value);
return (
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger asChild>
<Button
variant="outline"
role="combobox"
aria-expanded={open}
className={cn(
"w-full justify-between",
triggerClassName
)}
disabled={disabled}
>
{selectedOption ? selectedOption.label : placeholder}
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
</Button>
</PopoverTrigger>
<PopoverContent className={cn("w-full p-0", contentClassName)}>
<Command>
<CommandInput placeholder={searchPlaceholder} />
<CommandList>
<CommandEmpty>{emptyMessage}</CommandEmpty>
<CommandGroup>
{options.map((option) => (
<CommandItem
key={option.value}
value={option.value}
disabled={option.disabled}
onSelect={(currentValue) => {
onValueChange?.(currentValue === value ? "" : currentValue);
setOpen(false);
}}
>
<Check
className={cn(
"mr-2 h-4 w-4",
value === option.value ? "opacity-100" : "opacity-0"
)}
/>
{option.label}
</CommandItem>
))}
</CommandGroup>
</CommandList>
</Command>
</PopoverContent>
</Popover>
);
}
// Specialized combobox for Google Places Autocomplete
interface PlacesComboboxProps {
value: string;
onValueChange: (value: string) => void;
onSelect: (address: string) => void;
suggestions: Array<{ place_id: string; description: string }>;
status: string;
disabled?: boolean;
placeholder?: string;
className?: string;
}
export function PlacesCombobox({
value,
onValueChange,
onSelect,
suggestions,
status,
disabled = false,
placeholder = "Cari Alamat",
className,
}: PlacesComboboxProps) {
const [open, setOpen] = React.useState(false);
const handleSelect = (address: string) => {
onSelect(address);
setOpen(false);
};
return (
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger asChild>
<Button
variant="outline"
role="combobox"
aria-expanded={open}
className={cn(
"w-full justify-between border",
className
)}
disabled={disabled}
>
<input
value={value}
onChange={(e) => onValueChange(e.target.value)}
placeholder={placeholder}
className="flex-1 bg-transparent outline-none placeholder:text-muted-foreground"
disabled={disabled}
/>
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
</Button>
</PopoverTrigger>
<PopoverContent className="w-full p-0">
<Command>
<CommandList>
{status === "OK" && suggestions.length > 0 ? (
<CommandGroup>
{suggestions.map(({ place_id, description }) => (
<CommandItem
key={place_id}
value={description}
onSelect={() => handleSelect(description)}
>
{description}
</CommandItem>
))}
</CommandGroup>
) : (
<CommandEmpty>No results found.</CommandEmpty>
)}
</CommandList>
</Command>
</PopoverContent>
</Popover>
);
}