diff --git a/src/components/combobox/index.module.css b/src/components/combobox/index.module.css new file mode 100644 index 0000000..2592a1f --- /dev/null +++ b/src/components/combobox/index.module.css @@ -0,0 +1,76 @@ +.box { + display: contents; + inline-size: max-content; + + &:has(> :popover-open) > .button { + background-color: var(--surface-500); + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; + } +} + +.button { + display: grid; + grid-template-columns: inherit; + place-items: center start; + + inline-size: max-content; + + padding: var(--padding-m); + background-color: transparent; + border: none; + border-radius: var(--radii-m); + font-size: 1rem; + + cursor: pointer; +} + +.dialog { + display: none; + grid-template-columns: inherit; + + inset-inline-start: anchor(start); + inset-block-start: anchor(end); + position-try-fallbacks: flip-inline; + + inline-size: anchor-size(self-inline); + background-color: var(--surface-500); + padding: var(--padding-m); + border: none; + box-shadow: var(--shadow-2); + + &:popover-open { + display: grid; + } + + & > header { + display: grid; + grid-column: 1 / -1; + + gap: var(--padding-s); + } + + & > main { + display: grid; + grid-template-columns: subgrid; + grid-column: 1 / -1; + row-gap: var(--padding-s); + } +} + +.option { + display: grid; + grid-template-columns: subgrid; + grid-column: 1 / -1; + place-items: center start; + + border-radius: var(--radii-m); + padding: var(--padding-s); + margin-inline: calc(-1 * var(--padding-s)); + + cursor: pointer; + + &.selected { + background-color: oklch(from var(--info) l c h / .1); + } +} \ No newline at end of file diff --git a/src/components/combobox/index.tsx b/src/components/combobox/index.tsx new file mode 100644 index 0000000..9fae438 --- /dev/null +++ b/src/components/combobox/index.tsx @@ -0,0 +1,67 @@ +import { createMemo, createSignal, For, JSX, Setter, createEffect, Show } from "solid-js"; +import css from './index.module.css'; + +interface ComboBoxProps { + id: string; + class?: string; + value: K; + setValue?: Setter; + values: Record; + open?: boolean; + children: (key: K, value: T) => JSX.Element; + filter?: (query: string, key: K, value: T) => boolean; +} + +export function ComboBox(props: ComboBoxProps) { + const [dialog, setDialog] = createSignal(); + const [value, setValue] = createSignal(props.value); + const [open, setOpen] = createSignal(props.open ?? false); + const [query, setQuery] = createSignal(''); + + const values = createMemo(() => { + let entries = Object.entries(props.values) as [K, T][]; + const filter = props.filter; + const q = query(); + + if (filter) { + entries = entries.filter(([k, v]) => filter(q, k, v)); + } + + return entries; + }); + + createEffect(() => { + props.setValue?.(() => value()); + }); + + createEffect(() => { + dialog()?.[open() ? 'showPopover' : 'hidePopover'](); + }); + + return
+ + + setOpen(e.newState === 'open')}> + +
+ setQuery(e.target.value)} /> +
+
+ +
+ { + ([k, v]) => { + const selected = createMemo(() => value() === k); + + return { + setValue(() => k); + dialog()?.hidePopover(); + }}>{props.children(k, v)}; + } + } +
+
+
; +} \ No newline at end of file