import { useContext, useMemo, useState, type KeyboardEvent, type ReactNode } from "react"; import type { FloatingPlacement } from "@nodedc/ui-core"; import { Dropdown } from "./Dropdown.js"; import { InspectorSelectPolicyContext } from "./InspectorContext.js"; import { cn } from "./cn.js"; export interface SelectOption { value: T; label: string; description?: string; icon?: ReactNode; disabled?: boolean; } export type SelectVariant = "integrated" | "split"; export interface SelectProps { value: T; options: Array>; label: string; onChange: (value: T, option: SelectOption) => void; searchable?: boolean; searchPlaceholder?: string; emptyLabel?: string; placement?: FloatingPlacement; minMenuWidth?: number; menuWidth?: number | "anchor"; disabled?: boolean; variant?: SelectVariant; className?: string; triggerClassName?: string; menuClassName?: string; } export function Select({ value, options, label, onChange, searchable = false, searchPlaceholder = "Поиск", emptyLabel = "Ничего не найдено", placement = "bottom-start", minMenuWidth = 180, menuWidth = "anchor", disabled = false, variant = "integrated", className, triggerClassName, menuClassName, }: SelectProps) { const inspectorSplitRequired = useContext(InspectorSelectPolicyContext); const resolvedVariant: SelectVariant = inspectorSplitRequired ? "split" : variant; const [query, setQuery] = useState(""); const selected = options.find((option) => option.value === value) ?? options[0]; const visibleOptions = useMemo(() => { const normalized = query.trim().toLocaleLowerCase(); if (!normalized) return options; return options.filter((option) => `${option.label} ${option.description ?? ""}`.toLocaleLowerCase().includes(normalized)); }, [options, query]); return ( { const handleKeyDown = (event: KeyboardEvent) => { if (event.key === "ArrowDown" || event.key === "Enter" || event.key === " ") { event.preventDefault(); if (!open) toggle(); } }; if (resolvedVariant === "split") { return (
{selected?.icon ? {selected.icon} : null} {selected?.label ?? "—"}
); } return ( ); }} > {({ close }) => ( <> {searchable ? ( ) : null} {visibleOptions.map((option) => ( ))} {visibleOptions.length === 0 ?
{emptyLabel}
: null} )}
); }