You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
68 lines
2.1 KiB
TypeScript
68 lines
2.1 KiB
TypeScript
import { type ComponentChildren } from 'preact'
|
|
import { useState, useRef, useEffect } from 'preact/hooks'
|
|
import { clsx, isMobile } from './lib/util'
|
|
import { PhosphorIcon } from './Icon'
|
|
|
|
export const ComboBox = ({
|
|
value,
|
|
setValue,
|
|
children,
|
|
}: {
|
|
value: string
|
|
setValue: (s: string) => void
|
|
children: Record<string, ComponentChildren>
|
|
}) => {
|
|
const [cloak, setCloak] = useState(true)
|
|
const [open, setOpen] = useState(true)
|
|
const comboRef = useRef<HTMLDivElement>(null)
|
|
|
|
useEffect(() => {
|
|
const handleClick = (e: MouseEvent) => {
|
|
if (comboRef.current && !comboRef.current.contains(e.target as Node)) {
|
|
setOpen(false)
|
|
}
|
|
}
|
|
document.addEventListener('mousedown', handleClick)
|
|
return () => document.removeEventListener('mousedown', handleClick)
|
|
}, [])
|
|
|
|
const [itemWidth, setItemWidth] = useState<number>(200)
|
|
|
|
useEffect(() => {
|
|
setOpen(false)
|
|
setCloak(false)
|
|
}, [])
|
|
|
|
return (
|
|
<div
|
|
class="combobox"
|
|
ref={comboRef}
|
|
style={{ width: isMobile() ? undefined : itemWidth + 48 + 'px' }}
|
|
>
|
|
<div class="selected" onClick={() => setOpen(!open)}>
|
|
<div class="content">{children[value]}</div>
|
|
{/* <span class="material-symbols-outlined">expand_more</span> */}
|
|
<PhosphorIcon name="caret-down" />
|
|
</div>
|
|
{open && (
|
|
<div
|
|
class={clsx('dropdown', cloak && 'invisible')}
|
|
ref={el => el && setItemWidth(el.offsetWidth)}
|
|
>
|
|
{Object.keys(children).map(key => (
|
|
<div
|
|
class="option"
|
|
onClick={() => {
|
|
setValue(key)
|
|
setOpen(false)
|
|
}}
|
|
>
|
|
{children[key]}
|
|
</div>
|
|
))}
|
|
</div>
|
|
)}
|
|
</div>
|
|
)
|
|
}
|