11import PropTypes from 'prop-types' ;
22import React , {
33 useContext ,
4+ useEffect ,
45 useState ,
56} from 'react' ;
67import { withGlobalProps } from '../../providers/globalProps' ;
@@ -24,6 +25,11 @@ export const FileInputField = React.forwardRef((props, ref) => {
2425 isLabelVisible,
2526 label,
2627 layout,
28+ onChange,
29+ onDragEnter,
30+ onDragLeave,
31+ onDragOver,
32+ onDrop,
2733 required,
2834 size,
2935 validationState,
@@ -36,10 +42,10 @@ export const FileInputField = React.forwardRef((props, ref) => {
3642 const translations = useContext ( TranslationsContext ) ;
3743
3844 const [ selectedFileNames , setSelectedFileNames ] = useState ( [ ] ) ;
45+ const [ isDragAndDropSupported , setIsDragAndDropSupported ] = useState ( false ) ;
46+ const [ isDragging , setIsDragging ] = useState ( false ) ;
3947
40- const handleFileChange = ( event ) => {
41- const { files } = event . target ;
42-
48+ const handleFileChange = ( files ) => {
4349 if ( files . length === 0 ) {
4450 setSelectedFileNames ( [ ] ) ;
4551 return ;
@@ -54,6 +60,53 @@ export const FileInputField = React.forwardRef((props, ref) => {
5460 setSelectedFileNames ( fileNames ) ;
5561 } ;
5662
63+ const handleInputChange = ( event ) => {
64+ handleFileChange ( event . target . files ) ;
65+
66+ if ( props ?. onChange ) {
67+ props . onChange ( event ) ;
68+ }
69+ } ;
70+
71+ const handleDrop = ( event ) => {
72+ event . preventDefault ( ) ;
73+ handleFileChange ( event . dataTransfer . files ) ;
74+ setIsDragging ( false ) ;
75+
76+ if ( props ?. onDrop ) {
77+ props . onDrop ( event ) ;
78+ }
79+ } ;
80+
81+ const handleDragOver = ( event ) => {
82+ event . preventDefault ( ) ;
83+ setIsDragging ( true ) ;
84+
85+ if ( props ?. onDragOver ) {
86+ props . onDragOver ( event ) ;
87+ }
88+ } ;
89+
90+ const handleDragEnter = ( event ) => {
91+ setIsDragging ( true ) ;
92+
93+ if ( props ?. onDragEnter ) {
94+ props . onDragEnter ( event ) ;
95+ }
96+ } ;
97+
98+ const handleDragLeave = ( event ) => {
99+ setIsDragging ( false ) ;
100+
101+ if ( props ?. onDragLeave ) {
102+ props . onDragLeave ( event ) ;
103+ }
104+ } ;
105+
106+ useEffect ( ( ) => {
107+ setIsDragAndDropSupported ( 'draggable' in document . createElement ( 'span' ) ) ;
108+ } , [ ] ) ;
109+
57110 return (
58111 < label
59112 className = { classNames (
@@ -65,6 +118,7 @@ export const FileInputField = React.forwardRef((props, ref) => {
65118 : styles . isRootLayoutVertical ,
66119 resolveContextOrProp ( inputGroupContext && inputGroupContext . disabled , disabled ) && styles . isRootDisabled ,
67120 inputGroupContext && styles . isRootGrouped ,
121+ isDragging && styles . isRootDragging ,
68122 required && styles . isRootRequired ,
69123 getRootSizeClassName (
70124 resolveContextOrProp ( inputGroupContext && inputGroupContext . size , size ) ,
@@ -74,6 +128,10 @@ export const FileInputField = React.forwardRef((props, ref) => {
74128 ) }
75129 htmlFor = { id }
76130 id = { id && `${ id } __label` }
131+ onDragEnter = { ! disabled && isDragAndDropSupported ? handleDragEnter : undefined }
132+ onDragLeave = { ! disabled && isDragAndDropSupported ? handleDragLeave : undefined }
133+ onDragOver = { ! disabled && isDragAndDropSupported ? handleDragOver : undefined }
134+ onDrop = { ! disabled && isDragAndDropSupported ? handleDrop : undefined }
77135 >
78136 < div
79137 className = { classNames (
@@ -91,7 +149,7 @@ export const FileInputField = React.forwardRef((props, ref) => {
91149 className = { styles . input }
92150 disabled = { resolveContextOrProp ( inputGroupContext && inputGroupContext . disabled , disabled ) }
93151 id = { id }
94- onChange = { handleFileChange }
152+ onChange = { handleInputChange }
95153 ref = { ref }
96154 required = { required }
97155 type = "file"
@@ -100,9 +158,8 @@ export const FileInputField = React.forwardRef((props, ref) => {
100158 < Text lines = { 1 } >
101159 { ! selectedFileNames . length && (
102160 < >
103- { translations . FileInputField . drop }
104- { ' ' }
105161 < span className = { styles . dropZoneLink } > { translations . FileInputField . browse } </ span >
162+ { isDragAndDropSupported && ` ${ translations . FileInputField . drop } ` }
106163 </ >
107164 ) }
108165 { selectedFileNames . length === 1 && selectedFileNames [ 0 ] }
@@ -144,6 +201,11 @@ FileInputField.defaultProps = {
144201 id : undefined ,
145202 isLabelVisible : true ,
146203 layout : 'vertical' ,
204+ onChange : ( ) => { } ,
205+ onDragEnter : ( ) => { } ,
206+ onDragLeave : ( ) => { } ,
207+ onDragOver : ( ) => { } ,
208+ onDrop : ( ) => { } ,
147209 required : false ,
148210 size : 'medium' ,
149211 validationState : null ,
@@ -190,6 +252,26 @@ FileInputField.propTypes = {
190252 *
191253 */
192254 layout : PropTypes . oneOf ( [ 'horizontal' , 'vertical' ] ) ,
255+ /**
256+ * Callback fired when the value of the input changes.
257+ */
258+ onChange : PropTypes . func ,
259+ /**
260+ * Callback fired when a drag event enters the field.
261+ */
262+ onDragEnter : PropTypes . func ,
263+ /**
264+ * Callback fired when a drag event leaves the field.
265+ */
266+ onDragLeave : PropTypes . func ,
267+ /**
268+ * Callback fired when a drag event is over the field.
269+ */
270+ onDragOver : PropTypes . func ,
271+ /**
272+ * Callback fired when a file is dropped onto the field.
273+ */
274+ onDrop : PropTypes . func ,
193275 /**
194276 * If `true`, the input will be required.
195277 */
0 commit comments