@@ -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,114 @@ 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+ let statusText = "" ;
179+ if ( uploadState . isUploading ) {
180+ statusText = t ( `Importing ${ file ?. name } ` ) ;
181+ } else if ( uploadState . isError ) {
182+ statusText = t ( "There was an error in the upload" ) ;
183+ } else {
184+ statusText = t ( "Upload Complete!" ) ;
185+ }
186+ return (
187+ < div
188+ className = "upload-progress-section"
189+ aria-label = { t ( "File upload progress" ) }
190+ data-testid = "file-upload-progress"
191+ >
192+ < FileUploadIcon aria-hidden = "true" />
193+
194+ < div
195+ className = "upload-progress-bar"
196+ aria-label = { t ( "Upload progress bar" ) }
197+ data-testid = "file-upload-progress-bar"
198+ >
199+ < CustomProgressBar
200+ progress = { progress }
201+ color = { uploadState . isError ? "error" : undefined }
202+ />
203+ </ div >
204+ < p className = "upload-status" aria-live = "polite" >
205+ { statusText }
206+ </ p >
207+ </ div >
208+ ) ;
209+ } , [ uploadState , file , progress , t ] ) ;
195210
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 ;
211+ const containerClassName = useMemo (
212+ ( ) =>
213+ buildClassNames (
214+ "file-upload" ,
215+ uploadState . hasFile && "file-upload-progress" ,
216+ isDragOver && "file-upload-dragover" ,
217+ className
218+ ) ,
219+ [ uploadState . hasFile , isDragOver , className ]
220+ ) ;
205221
206222 return (
207223 < div
208- className = "upload-progress"
209- aria-label = { t ( "File upload progress" ) }
210- data-testid = "file-upload-progress"
224+ ref = { ref }
225+ role = "button"
226+ data-testid = { dataTestId || "file-upload-container" }
227+ className = { containerClassName }
228+ tabIndex = { 0 }
229+ onDragOver = { handleDragOver }
230+ onDragLeave = { handleDragLeave }
231+ onDrop = { handleDrop }
232+ onClick = { handleAreaClick }
233+ onKeyDown = { handleKeyDown }
234+ aria-label = { ariaLabel || t ( "Upload file area" ) }
235+ { ...restProps }
211236 >
212- < FileUploadIcon aria-hidden = "true" />
237+ { ! uploadState . hasFile ? (
238+ renderUploadPrompt ( )
239+ ) : (
240+ < div className = "upload-status-section" >
241+ { renderUploadStatus ( ) }
213242
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 >
224-
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- ) }
243+ { ( uploadState . isUploading ||
244+ uploadState . isCompleted ||
245+ uploadState . isError ) && (
246+ < div className = "upload-action-row" >
235247
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- />
248+ { uploadState . isError && file && (
249+ < V8CustomButton
250+ className = "file-upload-action-btn"
251+ label = { t ( "Try Again" ) }
252+ onClick = { ( ) => onRetry ?.( file ) }
253+ ariaLabel = { t ( "Try Again" ) }
254+ dataTestId = "file-upload-retry-btn"
255+ variant = "primary"
256+ />
257+ ) }
258+ { uploadState . isCompleted && (
259+ < V8CustomButton
260+ className = "file-upload-action-btn"
261+ label = { t ( primaryButtonText ) }
262+ onClick = { onDone }
263+ ariaLabel = { t ( primaryButtonText ) }
264+ dataTestId = "file-upload-action-btn"
265+ variant = "primary"
266+ />
267+ ) }
268+ { ( uploadState . isUploading || uploadState . isCompleted ) && ( < V8CustomButton
269+ className = "file-upload-action-btn"
270+ label = "Cancel"
271+ onClick = { onCancel }
272+ ariaLabel = "Cancel File Upload"
273+ dataTestId = "file-upload-cancel-btn"
274+ variant = "secondary"
275+ /> ) }
276+ </ div >
277+ ) }
278+ </ div >
246279 ) }
247280 </ div >
248281 ) ;
249- } , [ uploadState , file , progress , t , onCancel , onRetry , onDone ] ) ;
282+ }
283+ ) ;
250284
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- } ) ;
278-
279- // Set display name for better debugging
280285FileUploadAreaComponent . displayName = "FileUploadArea" ;
281-
282- // Export memoized component for performance optimization
283286export const FileUploadArea = memo ( FileUploadAreaComponent ) ;
284-
285- // Export types for consumers
286287export type { FileUploadAreaProps } ;
0 commit comments