94 lines
2.4 KiB
TypeScript
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
|
|
}
|