'use client'; import React, { useState, useEffect, useRef } from 'react'; import { motion, Variants } from 'framer-motion'; import { Icon, SearchNormal } from 'iconsax-react'; import clsx from 'clsx'; import { ChevronDown } from 'lucide-react'; import Image from 'next/image'; export interface ComboboxOption { id: string; title: string; icon?: Icon; imagePath?: string; label?: string; } type Props = { title: string; options: Array; expanded?: boolean; selectedId: string; searchable?: boolean; placeholder?: string; icon?: React.ElementType; onSelectionChange: (e: React.MouseEvent, index: number) => void, } & React.HTMLAttributes function Combobox({ title, options, placeholder, icon: Icon, expanded, selectedId, searchable = true, onSelectionChange, ...props }: Props) { const [expand, setExpand] = useState(expanded); const [searchValue, setSearchValue] = useState(''); const boxRef = useRef(null); useEffect(() => { const handleClickOutside = (event: MouseEvent) => { if (boxRef.current && !boxRef.current.contains(event.target as Node)) { setExpand(false); } }; if (expand) { document.addEventListener('mousedown', handleClickOutside); } return () => { document.removeEventListener('mousedown', handleClickOutside); }; }, [expand]); const toggleExpand = () => { setExpand((prev) => !prev); setSearchValue(''); }; const searchChanged = (e: React.ChangeEvent) => { setSearchValue(e.target.value); }; const setSelection = (e: React.MouseEvent, index: number) => { e.stopPropagation(); // prevent toggle onSelectionChange(e, index); setExpand(false); }; const selectedOption = options.find((x) => x.id === selectedId); const activeIcon = selectedOption?.icon && React.createElement(selectedOption.icon, { size: 16, color: "currentColor", className: "inline-block mr-1 text-primary dark:text-foreground" }); const activeImage = selectedOption?.imagePath && ( ); const variants: Variants = { collapse: { opacity: 0, scale: 0.95, pointerEvents: 'none', // disable clicks transition: { duration: 0.1, ease: 'easeInOut' } }, expand: { opacity: 1, scale: 1, pointerEvents: 'auto', // re-enable clicks transition: { duration: 0.1, ease: 'easeInOut' } } }; return (
{ activeImage ? ( activeImage ) : activeIcon ? ( activeIcon ) : ( Icon && ) } {selectedOption?.title ?? placeholder}
e.stopPropagation()} initial="collapse" animate={expand ? "expand" : "collapse"} variants={variants} > {searchable &&
}
{options .filter((v) => v.title?.includes(searchValue)) .map((v, i) => (
setSelection(e, i)} key={v.id} data-selected={v.id === selectedId} className="flex gap-3 items-center justify-end px-2 py-1 cursor-pointer hover:bg-current/10 rounded-md last:rounded-b-xl" tabIndex={0} role="option" aria-selected > { v?.imagePath ? ( ) : v?.icon && React.createElement(v.icon, { size: 16, color: "currentColor", className: "inline-block mr-1 text-primary dark:text-foreground" }) } {v.title}
))}
); } export default Combobox