import { Loader2, Plus, Search } from "lucide-react"; import { useEffect, useRef, useState } from "react"; import { toast } from "sonner"; import { useImportSkill, useSkills } from "../hooks/useSkills"; interface SkillMultiSelectProps { selected: string[]; onChange: (skills: string[]) => void; } export function SkillMultiSelect({ selected, onChange }: SkillMultiSelectProps) { const { skills, loading: skillsLoading, refresh } = useSkills(); const importSkill = useImportSkill(); const [open, setOpen] = useState(false); const [search, setSearch] = useState(""); const containerRef = useRef(null); useEffect(() => { function handleClickOutside(e: MouseEvent) { if (containerRef.current && !containerRef.current.contains(e.target as Node)) { setOpen(false); } } document.addEventListener("mousedown", handleClickOutside); return () => document.removeEventListener("mousedown", handleClickOutside); }, []); const filtered = skills.filter( (skill) => skill.name.toLowerCase().includes(search.toLowerCase()) || skill.id.toLowerCase().includes(search.toLowerCase()), ); const available = filtered.filter((skill) => !selected.includes(skill.id)); const isGitHubRef = search.includes("@") && search.includes("/"); const handleToggle = (id: string) => { if (selected.includes(id)) { onChange(selected.filter((x) => x !== id)); } else { onChange([...selected, id]); } }; const handleImportAndAdd = async () => { const ref = search.trim(); if (!ref) return; try { const newSkill = await importSkill.mutateAsync(ref); toast.success(`Imported skill: ${newSkill.name}`); setSearch(""); refresh(); onChange([...selected, newSkill.id]); } catch (err: any) { toast.error(err.message || "Failed to import skill"); } }; return (
setOpen(!open)} className="flex min-h-9 w-full flex-wrap items-center gap-1.5 rounded-md border border-input bg-background px-3 py-1.5 text-sm cursor-pointer shadow-xs focus-within:ring-1 focus-within:ring-ring" > {selected.map((id) => { const skill = skills.find((s) => s.id === id); const displayName = skill ? skill.name : id.includes("@") ? id.split("@")[1] : id; return ( {displayName} ); })} {selected.length === 0 && Select skills...}
{open && (
setSearch(e.target.value)} className="flex-1 bg-transparent text-xs outline-none border-none text-content-primary placeholder:text-content-tertiary" />
{skillsLoading ? (
Loading...
) : available.length === 0 && !isGitHubRef ? (
No matching skills.
) : ( available.map((skill) => ( )) )}
{isGitHubRef && (
)}
)}
); }