ERP-node/frontend/components/common/SmartSelect.tsx

123 lines
3.5 KiB
TypeScript

"use client";
/**
* SmartSelect
*
* 옵션 개수에 따라 자동으로 검색 기능을 제공하는 셀렉트 컴포넌트.
* - 옵션 5개 미만: 기본 Select (드롭다운)
* - 옵션 5개 이상: Combobox (검색 + 드롭다운)
*/
import React, { useState, useMemo } from "react";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from "@/components/ui/command";
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
import { Button } from "@/components/ui/button";
import { Check, ChevronsUpDown } from "lucide-react";
import { cn } from "@/lib/utils";
const SEARCH_THRESHOLD = 5;
export interface SmartSelectOption {
code: string;
label: string;
}
interface SmartSelectProps {
options: SmartSelectOption[];
value: string;
onValueChange: (value: string) => void;
placeholder?: string;
disabled?: boolean;
className?: string;
}
export function SmartSelect({
options,
value,
onValueChange,
placeholder = "선택",
disabled = false,
className,
}: SmartSelectProps) {
const [open, setOpen] = useState(false);
const selectedLabel = useMemo(
() => options.find((o) => o.code === value)?.label,
[options, value],
);
if (options.length < SEARCH_THRESHOLD) {
return (
<Select value={value} onValueChange={onValueChange} disabled={disabled}>
<SelectTrigger className={cn("h-9", className)}>
<SelectValue placeholder={placeholder} />
</SelectTrigger>
<SelectContent>
{options.map((o) => (
<SelectItem key={o.code} value={o.code}>
{o.label}
</SelectItem>
))}
</SelectContent>
</Select>
);
}
return (
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger asChild>
<Button
variant="outline"
role="combobox"
aria-expanded={open}
disabled={disabled}
className={cn("h-9 w-full justify-between font-normal", className)}
>
<span className="truncate">
{selectedLabel || <span className="text-muted-foreground">{placeholder}</span>}
</span>
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
</Button>
</PopoverTrigger>
<PopoverContent
className="p-0"
style={{ width: "var(--radix-popover-trigger-width)" }}
align="start"
>
<Command
filter={(val, search) => {
if (!search) return 1;
return val.toLowerCase().includes(search.toLowerCase()) ? 1 : 0;
}}
>
<CommandInput placeholder="검색..." className="h-9" />
<CommandList>
<CommandEmpty> .</CommandEmpty>
<CommandGroup>
{options.map((o) => (
<CommandItem
key={o.code}
value={o.label}
onSelect={() => {
onValueChange(o.code);
setOpen(false);
}}
>
<Check
className={cn(
"mr-2 h-4 w-4",
value === o.code ? "opacity-100" : "opacity-0",
)}
/>
{o.label}
</CommandItem>
))}
</CommandGroup>
</CommandList>
</Command>
</PopoverContent>
</Popover>
);
}