89 lines
2.2 KiB
TypeScript
89 lines
2.2 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'
|
|
|
|
export default function textareaFactory(styles: {
|
|
base?: string
|
|
touched?: string
|
|
element?: string
|
|
label?: string
|
|
statusIcon?: string
|
|
}): FunctionComponent<{
|
|
autoFocus?: boolean
|
|
className?: string
|
|
cols?: number
|
|
defaultValue?: string
|
|
disabled?: boolean
|
|
name: string
|
|
label: string
|
|
placeholder?: string
|
|
required?: boolean
|
|
showValidity?: boolean
|
|
}> {
|
|
if (Array.isArray(styles)) {
|
|
styles = mergeStyles(styles)
|
|
}
|
|
|
|
const Textarea = ({
|
|
cols,
|
|
autoFocus,
|
|
className,
|
|
defaultValue,
|
|
disabled,
|
|
name,
|
|
label,
|
|
placeholder,
|
|
required,
|
|
rows,
|
|
showValidity = true,
|
|
}) => {
|
|
const [touched, setTouched] = useState(false)
|
|
const textareaRef = useRef<HTMLTextAreaElement>()
|
|
|
|
const onBlur = useCallback(() => setTouched(true), [])
|
|
|
|
useEffect(() => {
|
|
const form = textareaRef.current!.form
|
|
const resetTouched = setTouched.bind(null, false)
|
|
|
|
form.addEventListener('reset', resetTouched)
|
|
|
|
return () => form.removeEventListener('reset', resetTouched)
|
|
}, [])
|
|
|
|
return (
|
|
<div className={cn(styles.base, touched && styles.touched, className)}>
|
|
<textarea
|
|
autoFocus={autoFocus}
|
|
className={styles.element}
|
|
cols={cols}
|
|
disabled={disabled}
|
|
name={name}
|
|
onBlur={onBlur}
|
|
placeholder={placeholder ?? label}
|
|
ref={textareaRef}
|
|
defaultValue={defaultValue}
|
|
required={required}
|
|
rows={rows}
|
|
/>
|
|
|
|
{showValidity && <i className={styles.statusIcon} />}
|
|
|
|
{/**
|
|
* <label> comes after <textarea> 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 <textarea> (see admin input.module.scss)
|
|
*/}
|
|
{label && (
|
|
<label className={styles.label} htmlFor={name}>
|
|
{label}
|
|
</label>
|
|
)}
|
|
</div>
|
|
)
|
|
}
|
|
|
|
return Textarea
|
|
}
|