diff options
| author | Adam <[email protected]> | 2025-10-14 07:14:26 -0500 |
|---|---|---|
| committer | Adam <[email protected]> | 2025-10-14 07:16:24 -0500 |
| commit | 37e6c8342f1f63a95918a71ac78130b9d10d1103 (patch) | |
| tree | f5baccd7d4d97a0171eb224c9f7d88ec62b14aac /packages/ui/src/components/select.tsx | |
| parent | c04e8929911be2433063e9a53cccf8d2dfd232b8 (diff) | |
| download | opencode-37e6c8342f1f63a95918a71ac78130b9d10d1103.tar.gz opencode-37e6c8342f1f63a95918a71ac78130b9d10d1103.zip | |
wip: css and ui packages
Diffstat (limited to 'packages/ui/src/components/select.tsx')
| -rw-r--r-- | packages/ui/src/components/select.tsx | 92 |
1 files changed, 92 insertions, 0 deletions
diff --git a/packages/ui/src/components/select.tsx b/packages/ui/src/components/select.tsx new file mode 100644 index 000000000..c4d5443c1 --- /dev/null +++ b/packages/ui/src/components/select.tsx @@ -0,0 +1,92 @@ +import { Select as Kobalte } from "@kobalte/core/select" +import { createMemo, type ComponentProps } from "solid-js" +import { Icon, Button, type ButtonProps } from "@opencode-ai/ui" +import { pipe, groupBy, entries, map } from "remeda" + +export interface SelectProps<T> { + placeholder?: string + options: T[] + current?: T + value?: (x: T) => string + label?: (x: T) => string + groupBy?: (x: T) => string + onSelect?: (value: T | undefined) => void + class?: ComponentProps<"div">["class"] + classList?: ComponentProps<"div">["classList"] +} + +export function Select<T>(props: SelectProps<T> & ButtonProps) { + const grouped = createMemo(() => { + const result = pipe( + props.options, + groupBy((x) => (props.groupBy ? props.groupBy(x) : "")), + // mapValues((x) => x.sort((a, b) => a.title.localeCompare(b.title))), + entries(), + map(([k, v]) => ({ category: k, options: v })), + ) + return result + }) + + return ( + <Kobalte<T, { category: string; options: T[] }> + data-component="select" + value={props.current} + options={grouped()} + optionValue={(x) => (props.value ? props.value(x) : (x as string))} + optionTextValue={(x) => (props.label ? props.label(x) : (x as string))} + optionGroupChildren="options" + placeholder={props.placeholder} + sectionComponent={(props) => ( + <Kobalte.Section data-slot="section">{props.section.rawValue.category}</Kobalte.Section> + )} + itemComponent={(itemProps) => ( + <Kobalte.Item + data-slot="item" + classList={{ + ...(props.classList ?? {}), + [props.class ?? ""]: !!props.class, + }} + {...itemProps} + > + <Kobalte.ItemLabel data-slot="item-label"> + {props.label ? props.label(itemProps.item.rawValue) : (itemProps.item.rawValue as string)} + </Kobalte.ItemLabel> + <Kobalte.ItemIndicator data-slot="item-indicator"> + <Icon name="checkmark" size={16} /> + </Kobalte.ItemIndicator> + </Kobalte.Item> + )} + onChange={(v) => { + props.onSelect?.(v ?? undefined) + }} + > + <Kobalte.Trigger + data-slot="trigger" + as={Button} + size={props.size} + variant={props.variant} + classList={{ + ...(props.classList ?? {}), + [props.class ?? ""]: !!props.class, + }} + > + <Kobalte.Value<T> data-slot="value"> + {(state) => { + const selected = state.selectedOption() ?? props.current + if (!selected) return props.placeholder || "" + if (props.label) return props.label(selected) + return selected as string + }} + </Kobalte.Value> + <Kobalte.Icon data-slot="icon"> + <Icon name="chevron-down" size={16} class="-my-2 group-data-[expanded]:rotate-180" /> + </Kobalte.Icon> + </Kobalte.Trigger> + <Kobalte.Portal> + <Kobalte.Content data-component="select-content"> + <Kobalte.Listbox data-slot="list" /> + </Kobalte.Content> + </Kobalte.Portal> + </Kobalte> + ) +} |
