brf/client/shared/components/select_factory.tsx

119 lines
2.9 KiB
TypeScript

import { h, type FunctionComponent } from 'preact'
import { useCallback, useEffect, useRef, useState } from 'preact/hooks'
import cn from 'classnames'
import mergeStyles from '../utils/merge_styles.ts'
// interface Styles {
// base?: string
// touched?: string
// element?: string
// label?: string
// }
export default function selectFactory({ styles }): FunctionComponent<{
autoFocus?: boolean
className?: string
defaultValue?: string
disabled?: boolean
name: string
noEmpty?: boolean
label: string
placeholder?: string
required?: boolean
options: string[] | number[] | { label: string; value: string | number }
}> {
if (Array.isArray(styles)) {
styles = mergeStyles(styles)
}
const Select = ({
autoFocus,
className,
color,
defaultValue,
design = 'main',
disabled,
icon,
name,
label,
noEmpty,
onChange,
placeholder,
required,
size = 'medium',
options,
}) => {
const [touched, setTouched] = useState(false)
const selectRef = useRef<HTMLSelectElement>()
console.log(options)
const onBlur = useCallback(() => setTouched(true), [])
useEffect(() => {
const form = selectRef.current!.form
const resetTouched = setTouched.bind(null, false)
form!.addEventListener('reset', resetTouched)
return () => form!.removeEventListener('reset', resetTouched)
}, [])
useEffect(() => {
if (autoFocus) selectRef.current!.focus()
}, [autoFocus])
return (
<div
className={cn(
styles.base,
design && styles[design],
color && styles[color],
size && styles[size],
touched && styles.touched,
className,
)}
>
{icon && <div className={cn(styles.icon)} style={{ maskImage: icon }} />}
<select
autoFocus={autoFocus}
className={styles.element}
disabled={disabled}
name={name}
onBlur={onBlur}
onChange={onChange}
ref={selectRef}
defaultValue={defaultValue}
required={required}
>
{!noEmpty && <option value=''>{placeholder || label}</option>}
{options &&
options.map((option) => (
<option
key={option.value != null ? option.value : option}
value={option.value != null ? option.value : option}
>
{option.label || option}
</option>
))}
</select>
{/**
* <label> comes after <input> to be able to use next sibling CSS selector
* (see public input.module.scss). Use `flexbox` and `order` if <label> needs to be
* displayed before <input> (see admin input.module.scss)
*/}
{label && (
<label className={styles.label} htmlFor={name}>
{label}
</label>
)}
</div>
)
}
return Select
}