import { Select as Kobalte } from "@kobalte/core/select" import { createMemo, onCleanup, splitProps, type ComponentProps, type JSX } from "solid-js" import { pipe, groupBy, entries, map } from "remeda" import { Button, ButtonProps } from "./button" import { Icon } from "./icon" export type SelectProps = Omit>, "value" | "onSelect" | "children"> & { placeholder?: string options: T[] current?: T value?: (x: T) => string label?: (x: T) => string groupBy?: (x: T) => string onSelect?: (value: T | undefined) => void onHighlight?: (value: T | undefined) => (() => void) | void class?: ComponentProps<"div">["class"] classList?: ComponentProps<"div">["classList"] children?: (item: T | undefined) => JSX.Element triggerStyle?: JSX.CSSProperties triggerVariant?: "settings" } export function Select(props: SelectProps & Omit) { const [local, others] = splitProps(props, [ "class", "classList", "placeholder", "options", "current", "value", "label", "groupBy", "onSelect", "onHighlight", "onOpenChange", "children", "triggerStyle", "triggerVariant", ]) const state = { key: undefined as string | undefined, cleanup: undefined as (() => void) | void, } const stop = () => { state.cleanup?.() state.cleanup = undefined state.key = undefined } const keyFor = (item: T) => (local.value ? local.value(item) : (item as string)) const move = (item: T | undefined) => { if (!local.onHighlight) return if (!item) { stop() return } const key = keyFor(item) if (state.key === key) return state.cleanup?.() state.cleanup = local.onHighlight(item) state.key = key } onCleanup(stop) const grouped = createMemo(() => { const result = pipe( local.options, groupBy((x) => (local.groupBy ? local.groupBy(x) : "")), // mapValues((x) => x.sort((a, b) => a.title.localeCompare(b.title))), entries(), map(([k, v]) => ({ category: k, options: v })), ) return result }) return ( // @ts-ignore {...others} data-component="select" data-trigger-style={local.triggerVariant} placement={local.triggerVariant === "settings" ? "bottom-end" : "bottom-start"} gutter={4} value={local.current} options={grouped()} optionValue={(x) => (local.value ? local.value(x) : (x as string))} optionTextValue={(x) => (local.label ? local.label(x) : (x as string))} optionGroupChildren="options" placeholder={local.placeholder} sectionComponent={(local) => ( {local.section.rawValue.category} )} itemComponent={(itemProps) => ( move(itemProps.item.rawValue)} onPointerMove={() => move(itemProps.item.rawValue)} onFocus={() => move(itemProps.item.rawValue)} > {local.children ? local.children(itemProps.item.rawValue) : local.label ? local.label(itemProps.item.rawValue) : (itemProps.item.rawValue as string)} )} onChange={(v) => { local.onSelect?.(v ?? undefined) stop() }} onOpenChange={(open) => { local.onOpenChange?.(open) if (!open) stop() }} > data-slot="select-select-trigger-value"> {(state) => { const selected = state.selectedOption() ?? local.current if (!selected) return local.placeholder || "" if (local.label) return local.label(selected) return selected as string }} ) }