brf/client/shared/components/image_upload_factory.tsx
2025-12-13 21:12:08 +01:00

94 lines
2.4 KiB
TypeScript

import { h, type FunctionComponent } from 'preact'
import { useCallback, useRef, useState } from 'preact/hooks'
import cn from 'classnames'
// TODO when form resets after update the previous image flashes quickly after setImageSrc(null) and before new defaultValue propagates
type Styles = {
base?: string
element?: string
label?: string
noMargin?: string
touched?: string
image?: string
}
type Options = {
styles: Styles
}
type Props = {
autoFocus?: boolean
className?: string
defaultValue?: any
disabled?: boolean
label?: string
name?: string
noMargin?: boolean
placeholder?: string
required?: boolean
}
export default function fileUploadFactory({ styles }: Options) {
const FileUpload: FunctionComponent<Props> = ({
autoFocus,
className,
defaultValue,
disabled,
label,
name,
noMargin,
placeholder,
required,
}) => {
const [touched, setTouched] = useState(false)
const inputRef = useRef<HTMLInputElement>(null)
const imgRef = useRef<HTMLImageElement>(null)
const [imageSrc, setImageSrc] = useState<string | null>(null)
const onImageChange = useCallback((e: Event) => {
e.preventDefault()
const form = inputRef.current!.form!
const onReset = () => {
setTouched(false)
setImageSrc(null)
}
form.addEventListener('reset', onReset)
imgRef.current!.addEventListener('load', () => URL.revokeObjectURL(imgRef.current!.src), { once: true })
setImageSrc(URL.createObjectURL((e.currentTarget! as HTMLInputElement).files![0]))
return () => form.removeEventListerner('reset', onReset)
}, [])
return (
<div className={cn(styles.base, noMargin && styles.noMargin, touched && styles.touched, className)}>
<input
type='file'
accept='image/*'
name={name}
ref={inputRef}
className={styles.element}
onChange={onImageChange}
autoFocus={autoFocus}
disabled={disabled}
placeholder={placeholder}
required={required}
defaultValue={defaultValue}
/>
<label htmlFor={name} className={styles.label}>
{label}
</label>
<figure className={styles.image}>
<img className={styles.image} src={imageSrc || (defaultValue && `/uploads/${defaultValue}`)} ref={imgRef} />
</figure>
</div>
)
}
return FileUpload
}