@@ -25,6 +25,7 @@ interface FileUploadAreaProps extends Omit<React.ComponentPropsWithoutRef<"div">
2525 onDone ?: ( ) => void ;
2626 /** Currently selected file */
2727 file : File | null ;
28+ primaryButtonText : string ;
2829 /** Upload progress (0-100) */
2930 progress : number ;
3031 /** Error message if upload fails */
@@ -62,6 +63,7 @@ const FileUploadAreaComponent = forwardRef<HTMLDivElement, FileUploadAreaProps>(
6263 dataTestId,
6364 className = "" ,
6465 maxFileSizeMB = 20 ,
66+ primaryButtonText = "Done" ,
6567 ...restProps
6668} , ref ) => {
6769 const { t } = useTranslation ( ) ;
@@ -172,115 +174,111 @@ const FileUploadAreaComponent = forwardRef<HTMLDivElement, FileUploadAreaProps>(
172174 </ >
173175 ) , [ fileInputId , handleChange , fileType , t , maxFileSizeMB ] ) ;
174176
175- // Memoized upload status component
176- const renderUploadStatus = useCallback ( ( ) => {
177- // Map states to config
178- const stateConfig = {
179- uploading : {
180- status : t ( `Importing ${ file ?. name } ` ) ,
181- label : t ( "Cancel" ) ,
182- onClick : ( ) => onCancel ?.( ) ,
183- } ,
184- error : {
185- status : t ( "There was an error in the upload" ) ,
186- label : t ( "Try Again" ) ,
187- onClick : ( ) => file && onRetry ?.( file ) ,
188- } ,
189- completed : {
190- status : t ( "Upload Complete!" ) ,
191- label : t ( "Done" ) ,
192- onClick : ( ) => onDone ?.( ) ,
193- } ,
194- } ;
177+ const renderUploadStatus = useCallback ( ( ) => {
178+ return (
179+ < div
180+ className = "upload-progress-section"
181+ aria-label = { t ( "File upload progress" ) }
182+ data-testid = "file-upload-progress"
183+ >
184+ < FileUploadIcon aria-hidden = "true" />
185+
186+ < div
187+ className = "upload-progress-bar"
188+ aria-label = { t ( "Upload progress bar" ) }
189+ data-testid = "file-upload-progress-bar"
190+ >
191+ < CustomProgressBar
192+ progress = { progress }
193+ color = { uploadState . isError ? "error" : undefined }
194+ />
195+ </ div >
196+
197+ < p className = "upload-status" aria-live = "polite" >
198+ { uploadState . isUploading
199+ ? t ( `Importing ${ file ?. name } ` )
200+ : uploadState . isError
201+ ? t ( "There was an error in the upload" )
202+ : t ( "Upload Complete!" ) }
203+ </ p >
204+ </ div >
205+ ) ;
206+ } , [ uploadState , file , progress , t ] ) ;
195207
196- // Determine current state
197- const current =
198- uploadState . isUploading
199- ? stateConfig . uploading
200- : uploadState . isError && file
201- ? stateConfig . error
202- : uploadState . isCompleted
203- ? stateConfig . completed
204- : null ;
208+ const containerClassName = useMemo (
209+ ( ) =>
210+ buildClassNames (
211+ "file-upload" ,
212+ uploadState . hasFile && "file-upload-progress" ,
213+ isDragOver && "file-upload-dragover" ,
214+ className
215+ ) ,
216+ [ uploadState . hasFile , isDragOver , className ]
217+ ) ;
205218
206219 return (
207220 < div
208- className = "upload-progress"
209- aria-label = { t ( "File upload progress" ) }
210- data-testid = "file-upload-progress"
221+ ref = { ref }
222+ role = "button"
223+ data-testid = { dataTestId || "file-upload-container" }
224+ className = { containerClassName }
225+ tabIndex = { 0 }
226+ onDragOver = { handleDragOver }
227+ onDragLeave = { handleDragLeave }
228+ onDrop = { handleDrop }
229+ onClick = { handleAreaClick }
230+ onKeyDown = { handleKeyDown }
231+ aria-label = { ariaLabel || t ( "Upload file area" ) }
232+ { ...restProps }
211233 >
212- < FileUploadIcon aria-hidden = "true" />
213-
214- < div
215- className = "upload-progress-bar"
216- aria-label = { t ( "Upload progress bar" ) }
217- data-testid = "file-upload-progress-bar"
218- >
219- < CustomProgressBar
220- progress = { progress }
221- color = { uploadState . isError ? "error" : undefined }
222- />
223- </ div >
234+ { ! uploadState . hasFile ? (
235+ renderUploadPrompt ( )
236+ ) : (
237+ < div className = "upload-status-section" >
238+ { renderUploadStatus ( ) }
224239
225- { /* Status text */ }
226- { current ?. status && (
227- < p
228- className = "upload-status"
229- aria-live = "polite"
230- data-testid = "file-upload-status"
231- >
232- { current . status }
233- </ p >
234- ) }
240+ { ( uploadState . isUploading ||
241+ uploadState . isCompleted ||
242+ uploadState . isError ) && (
243+ < div className = "upload-action-row" >
235244
236- { /* Action button */ }
237- { current && (
238- < V8CustomButton
239- className = "file-upload-action-btn"
240- label = { current . label }
241- onClick = { current . onClick }
242- ariaLabel = { current . label }
243- dataTestId = "file-upload-action-btn"
244- variant = "secondary"
245- />
245+ { uploadState . isError && file && (
246+ < V8CustomButton
247+ className = "file-upload-action-btn"
248+ label = { t ( "Try Again" ) }
249+ onClick = { ( ) => onRetry ?.( file ) }
250+ ariaLabel = { t ( "Try Again" ) }
251+ dataTestId = "file-upload-retry-btn"
252+ variant = "primary"
253+ />
254+ ) }
255+ { uploadState . isCompleted && (
256+ < V8CustomButton
257+ className = "file-upload-action-btn"
258+ label = { t ( primaryButtonText ) }
259+ onClick = { onDone }
260+ ariaLabel = { t ( primaryButtonText ) }
261+ dataTestId = "file-upload-action-btn"
262+ variant = "primary"
263+ />
264+ ) }
265+ { ( uploadState . isUploading || uploadState . isCompleted ) && ( < V8CustomButton
266+ className = "file-upload-action-btn"
267+ label = "Cancel"
268+ onClick = { onCancel }
269+ ariaLabel = "Cancel File Upload"
270+ dataTestId = "file-upload-cancel-btn"
271+ variant = "secondary"
272+ /> ) }
273+ </ div >
274+ ) }
275+ </ div >
246276 ) }
247277 </ div >
248278 ) ;
249- } , [ uploadState , file , progress , t , onCancel , onRetry , onDone ] ) ;
250-
251- // Memoized container className
252- const containerClassName = useMemo ( ( ) => buildClassNames (
253- "file-upload" ,
254- uploadState . hasFile && "file-upload-progress" ,
255- isDragOver && "file-upload-dragover" ,
256- className
257- ) , [ uploadState . hasFile , isDragOver , className ] ) ;
258-
259- return (
260- < div
261- ref = { ref }
262- role = "button"
263- data-testid = { dataTestId || "file-upload-container" }
264- className = { containerClassName }
265- tabIndex = { 0 }
266- onDragOver = { handleDragOver }
267- onDragLeave = { handleDragLeave }
268- onDrop = { handleDrop }
269- onClick = { handleAreaClick }
270- onKeyDown = { handleKeyDown }
271- aria-label = { ariaLabel || t ( "Upload file area" ) }
272- { ...restProps }
273- >
274- { ! uploadState . hasFile ? renderUploadPrompt ( ) : renderUploadStatus ( ) }
275- </ div >
276- ) ;
277- } ) ;
279+ }
280+ ) ;
278281
279- // Set display name for better debugging
280282FileUploadAreaComponent . displayName = "FileUploadArea" ;
281-
282- // Export memoized component for performance optimization
283283export const FileUploadArea = memo ( FileUploadAreaComponent ) ;
284-
285- // Export types for consumers
286284export type { FileUploadAreaProps } ;
0 commit comments