import * as React from 'react' ; import { ReactNodeNoStrings } from '../../types' ; import { useFieldIds } from '../../hooks' ; import { Box } from '../Box' ; import { VisuallyHidden } from '../VisuallyHidden' ; import { validateAccept } from './utils' ; type State = { droppable ? : boolean ; file ? : File ; focused ? : boolean ; name ? : string ; previewUrl ? : string ; type ? : string ; } ; const initialState = { } ; type NativeInputProps = React . AllHTMLAttributes < HTMLInputElement > ; export type Props = { accept ? : NativeInputProps [ 'accept' ] ; autoFocus ? : NativeInputProps [ 'autoFocus' ] ; children : ( context : State & { reset ( event ) : void ; } ) => ReactNodeNoStrings ; defaultValue ? : { name ? : string ; type : string ; url : string ; } ; disabled ? : NativeInputProps [ 'disabled' ] ; error ? : boolean | React . ReactNode ; id ? : NativeInputProps [ 'id' ] ; /** Size in megabytes */ maxSize ? : number ; name ? : string ; required ? : NativeInputProps [ 'required' ] ; tabIndex ? : NativeInputProps [ 'tabIndex' ] ; onBlur ? : NativeInputProps [ 'onBlur' ] ; onError ? ( error ) : void ; onChange ? ( file ) : void ; onFocus ? : NativeInputProps [ 'onFocus' ] ; onReset ? ( ) : void ; } ; export const FileInput = React . forwardRef ( ( { accept , autoFocus , children , defaultValue , disabled , error , id , maxSize , name , required , tabIndex , onBlur , onChange , onError , onFocus , onReset , } , ref ) => { const defaultRef = React . useRef < HTMLInputElement > ( null ) ; const inputRef = ( ref as React . RefObject < HTMLInputElement > ) || defaultRef ; const [ state , setState ] = React . useState < State > ( initialState ) ; const hasError = error ? true : undefined ; const ids = useFieldIds ( { id , error : hasError , } ) ; const handleFile = React . useCallback ( ( file , event ? ) => { if ( maxSize && file . size > maxSize * 1000000 ) { event ?. preventDefault ( ) ; onError && onError ( `File is ${ ( file . size / 1000000 ) . toFixed ( 2 ) } MB. Must be smaller than ${ maxSize } MB` ) ; return ; } setState ( ( x ) => ( { ... x , file , name : file . name , type : file . type , } ) ) ; onChange && onChange ( file ) ; } , [ maxSize , onChange , onError ] ) ; const handleChange = React . useCallback ( ( event ) => { const files = event . target . files ; if ( ! files ?. length ) return ; handleFile ( files [ 0 ] , event ) ; } , [ handleFile ] ) ; const handleDragOver = React . useCallback ( ( event ) => { event . preventDefault ( ) ; setState ( ( x ) => ( { ... x , droppable : true } ) ) ; } , [ ] ) ; const handleDragLeave = React . useCallback ( ( event ) => { event . preventDefault ( ) ; setState ( ( x ) => ( { ... x , droppable : false } ) ) ; } , [ ] ) ; const handleDrop = React . useCallback ( ( event ) => { event . preventDefault ( ) ; setState ( ( x ) => ( { ... x , droppable : false } ) ) ; let file ; if ( event . dataTransfer . items ) { const files = event . dataTransfer . items ; if ( files ?. [ 0 ] . kind !== 'file' ) return ; file = files [ 0 ] . getAsFile ( ) ; if ( ! file ) return ; } else { const files = event . dataTransfer . files ; if ( ! files ?. length ) return ; file = files [ 0 ] ; } if ( ! validateAccept ( file . type , accept ) ) return ; handleFile ( file , event ) ; } , [ handleFile , accept ] ) ; const handleFocus = React . useCallback ( ( event ) => { setState ( ( x ) => ( { ... x , focused : true } ) ) ; onFocus && onFocus ( event ) ; } , [ onFocus ] ) ; const handleBlur = React . useCallback ( ( event ) => { setState ( ( x ) => ( { ... x , focused : false } ) ) ; onBlur && onBlur ( event ) ; } , [ onBlur ] ) ; const reset = React . useCallback ( ( event ) => { event . preventDefault ( ) ; setState ( initialState ) ; if ( inputRef . current ) inputRef . current . value = '' ; onReset && onReset ( ) ; } , [ inputRef , onReset ] ) ; React . useEffect ( ( ) => { if ( ! defaultValue ) return ; setState ( { previewUrl : defaultValue . url , name : defaultValue . name , type : defaultValue . type , } ) ; } , [ ] ) ; React . useEffect ( ( ) => { if ( ! state . file ) return ; const previewUrl = URL . createObjectURL ( state . file ) ; setState ( ( x ) => ( { ... x , previewUrl } ) ) ; return ( ) => URL . revokeObjectURL ( previewUrl ) ; } , [ state . file ] ) ; return ( < Box ref = { ref } > < VisuallyHidden > < Box accept = { accept } aria - invalid = { hasError } as = "input" autoFocus = { autoFocus } disabled = { disabled } name = { name } ref = { inputRef } required = { required } tabIndex = { tabIndex } type = "file" onBlur = { handleBlur } onChange = { handleChange } onFocus = { handleFocus } { ... ids . content } /> < /VisuallyHidden> < Box as = "label" { ... ids . label } onDragLeave = { handleDragLeave } onDragOver = { handleDragOver } onDrop = { handleDrop } > { children ( { ... state , reset } ) } < /Box> < /Box>); } ) ; FileInput . displayName = 'FileInput' ;