浏览代码

@uppy/status-bar: refactor to typescript (#4839)

Co-authored-by: Merlijn Vos <merlijn@soverin.net>
Co-authored-by: Mikael Finstad <finstaden@gmail.com>
Antoine du Hamel 1 年之前
父节点
当前提交
6b30fff0c9

+ 145 - 40
packages/@uppy/status-bar/src/Components.jsx → packages/@uppy/status-bar/src/Components.tsx

@@ -1,14 +1,30 @@
+import type { Body, Meta } from '@uppy/utils/lib/UppyFile'
+import type { State, Uppy } from '@uppy/core/src/Uppy.ts'
+import type { FileProcessingInfo } from '@uppy/utils/lib/FileProgress'
+import type { I18n } from '@uppy/utils/lib/Translator'
 import { h } from 'preact'
 import classNames from 'classnames'
 import prettierBytes from '@transloadit/prettier-bytes'
 import prettyETA from '@uppy/utils/lib/prettyETA'
 
-import statusBarStates from './StatusBarStates.js'
+import statusBarStates from './StatusBarStates.ts'
 
 const DOT = `\u00B7`
-const renderDot = () => ` ${DOT} `
+const renderDot = (): string => ` ${DOT} `
+
+interface UploadBtnProps<M extends Meta, B extends Body> {
+  newFiles: number
+  isUploadStarted: boolean
+  recoveredState: null | State<M, B>
+  i18n: I18n
+  uploadState: string
+  isSomeGhost: boolean
+  startUpload: () => void
+}
 
-function UploadBtn (props) {
+function UploadBtn<M extends Meta, B extends Body>(
+  props: UploadBtnProps<M, B>,
+): JSX.Element {
   const {
     newFiles,
     isUploadStarted,
@@ -30,9 +46,10 @@ function UploadBtn (props) {
     { 'uppy-StatusBar-actionBtn--disabled': isSomeGhost },
   )
 
-  const uploadBtnText = newFiles && isUploadStarted && !recoveredState
-    ? i18n('uploadXNewFiles', { smart_count: newFiles })
-    : i18n('uploadXFiles', { smart_count: newFiles })
+  const uploadBtnText =
+    newFiles && isUploadStarted && !recoveredState
+      ? i18n('uploadXNewFiles', { smart_count: newFiles })
+      : i18n('uploadXFiles', { smart_count: newFiles })
 
   return (
     <button
@@ -48,7 +65,14 @@ function UploadBtn (props) {
   )
 }
 
-function RetryBtn (props) {
+interface RetryBtnProps<M extends Meta, B extends Body> {
+  i18n: I18n
+  uppy: Uppy<M, B>
+}
+
+function RetryBtn<M extends Meta, B extends Body>(
+  props: RetryBtnProps<M, B>,
+): JSX.Element {
   const { i18n, uppy } = props
 
   return (
@@ -56,7 +80,11 @@ function RetryBtn (props) {
       type="button"
       className="uppy-u-reset uppy-c-btn uppy-StatusBar-actionBtn uppy-StatusBar-actionBtn--retry"
       aria-label={i18n('retryUpload')}
-      onClick={() => uppy.retryAll().catch(() => { /* Error reported and handled via an event */ })}
+      onClick={() =>
+        uppy.retryAll().catch(() => {
+          /* Error reported and handled via an event */
+        })
+      }
       data-uppy-super-focusable
       data-cy="retry"
     >
@@ -75,7 +103,14 @@ function RetryBtn (props) {
   )
 }
 
-function CancelBtn (props) {
+interface CancelBtnProps<M extends Meta, B extends Body> {
+  i18n: I18n
+  uppy: Uppy<M, B>
+}
+
+function CancelBtn<M extends Meta, B extends Body>(
+  props: CancelBtnProps<M, B>,
+): JSX.Element {
   const { i18n, uppy } = props
 
   return (
@@ -84,7 +119,7 @@ function CancelBtn (props) {
       className="uppy-u-reset uppy-StatusBar-actionCircleBtn"
       title={i18n('cancel')}
       aria-label={i18n('cancel')}
-      onClick={() => uppy.cancelAll()}
+      onClick={(): void => uppy.cancelAll()}
       data-cy="cancel"
       data-uppy-super-focusable
     >
@@ -108,22 +143,34 @@ function CancelBtn (props) {
   )
 }
 
-function PauseResumeButton (props) {
+interface PauseResumeButtonProps<M extends Meta, B extends Body> {
+  i18n: I18n
+  uppy: Uppy<M, B>
+  isAllPaused: boolean
+  isAllComplete: boolean
+  resumableUploads: boolean
+}
+
+function PauseResumeButton<M extends Meta, B extends Body>(
+  props: PauseResumeButtonProps<M, B>,
+): JSX.Element {
   const { isAllPaused, i18n, isAllComplete, resumableUploads, uppy } = props
   const title = isAllPaused ? i18n('resume') : i18n('pause')
 
-  function togglePauseResume () {
-    if (isAllComplete) return null
+  function togglePauseResume(): void {
+    if (isAllComplete) return
 
     if (!resumableUploads) {
-      return uppy.cancelAll()
+      uppy.cancelAll()
+      return
     }
 
     if (isAllPaused) {
-      return uppy.resumeAll()
+      uppy.resumeAll()
+      return
     }
 
-    return uppy.pauseAll()
+    uppy.pauseAll()
   }
 
   return (
@@ -160,14 +207,19 @@ function PauseResumeButton (props) {
   )
 }
 
-function DoneBtn (props) {
+interface DoneBtnProps {
+  i18n: I18n
+  doneButtonHandler: (() => void) | null
+}
+
+function DoneBtn(props: DoneBtnProps): JSX.Element {
   const { i18n, doneButtonHandler } = props
 
   return (
     <button
       type="button"
       className="uppy-u-reset uppy-c-btn uppy-StatusBar-actionBtn uppy-StatusBar-actionBtn--done"
-      onClick={doneButtonHandler}
+      onClick={doneButtonHandler!}
       data-uppy-super-focusable
     >
       {i18n('done')}
@@ -175,7 +227,7 @@ function DoneBtn (props) {
   )
 }
 
-function LoadingSpinner () {
+function LoadingSpinner(): JSX.Element {
   return (
     <svg
       className="uppy-StatusBar-spinner"
@@ -192,10 +244,14 @@ function LoadingSpinner () {
   )
 }
 
-function ProgressBarProcessing (props) {
+interface ProgressBarProcessingProps {
+  progress: FileProcessingInfo
+}
+
+function ProgressBarProcessing(props: ProgressBarProcessingProps): JSX.Element {
   const { progress } = props
   const { value, mode, message } = progress
-  const roundedValue = Math.round(value * 100)
+  const roundedValue = Math.round(value! * 100)
   const dot = `\u00B7`
 
   return (
@@ -207,22 +263,25 @@ function ProgressBarProcessing (props) {
   )
 }
 
-function ProgressDetails (props) {
-  const {
-    numUploads,
-    complete,
-    totalUploadedSize,
-    totalSize,
-    totalETA,
-    i18n,
-  } = props
+interface ProgressDetailsProps {
+  i18n: I18n
+  numUploads: number
+  complete: number
+  totalUploadedSize: number
+  totalSize: number
+  totalETA: number
+}
+
+function ProgressDetails(props: ProgressDetailsProps): JSX.Element {
+  const { numUploads, complete, totalUploadedSize, totalSize, totalETA, i18n } =
+    props
 
   const ifShowFilesUploadedOfTotal = numUploads > 1
 
   return (
     <div className="uppy-StatusBar-statusSecondary">
-      {ifShowFilesUploadedOfTotal
-        && i18n('filesUploadedOfTotal', {
+      {ifShowFilesUploadedOfTotal &&
+        i18n('filesUploadedOfTotal', {
           complete,
           smart_count: numUploads,
         })}
@@ -248,7 +307,13 @@ function ProgressDetails (props) {
   )
 }
 
-function FileUploadCount (props) {
+interface FileUploadCountProps {
+  i18n: I18n
+  complete: number
+  numUploads: number
+}
+
+function FileUploadCount(props: FileUploadCountProps): JSX.Element {
   const { i18n, complete, numUploads } = props
 
   return (
@@ -258,7 +323,13 @@ function FileUploadCount (props) {
   )
 }
 
-function UploadNewlyAddedFiles (props) {
+interface UploadNewlyAddedFilesProps {
+  i18n: I18n
+  newFiles: number
+  startUpload: () => void
+}
+
+function UploadNewlyAddedFiles(props: UploadNewlyAddedFilesProps): JSX.Element {
   const { i18n, newFiles, startUpload } = props
   const uploadBtnClassNames = classNames(
     'uppy-u-reset',
@@ -284,7 +355,26 @@ function UploadNewlyAddedFiles (props) {
   )
 }
 
-function ProgressBarUploading (props) {
+interface ProgressBarUploadingProps {
+  i18n: I18n
+  supportsUploadProgress: boolean
+  totalProgress: number
+  showProgressDetails: boolean | undefined
+  isUploadStarted: boolean
+  isAllComplete: boolean
+  isAllPaused: boolean
+  newFiles: number
+  numUploads: number
+  complete: number
+  totalUploadedSize: number
+  totalSize: number
+  totalETA: number
+  startUpload: () => void
+}
+
+function ProgressBarUploading(
+  props: ProgressBarUploadingProps,
+): JSX.Element | null {
   const {
     i18n,
     supportsUploadProgress,
@@ -310,7 +400,7 @@ function ProgressBarUploading (props) {
 
   const title = isAllPaused ? i18n('paused') : i18n('uploading')
 
-  function renderProgressDetails () {
+  function renderProgressDetails(): JSX.Element | null {
     if (!isAllPaused && !showUploadNewlyAddedFiles && showProgressDetails) {
       if (supportsUploadProgress) {
         return (
@@ -357,7 +447,11 @@ function ProgressBarUploading (props) {
   )
 }
 
-function ProgressBarComplete (props) {
+interface ProgressBarCompleteProps {
+  i18n: I18n
+}
+
+function ProgressBarComplete(props: ProgressBarCompleteProps): JSX.Element {
   const { i18n } = props
 
   return (
@@ -385,10 +479,17 @@ function ProgressBarComplete (props) {
   )
 }
 
-function ProgressBarError (props) {
+interface ProgressBarErrorProps {
+  i18n: I18n
+  error: any
+  complete: number
+  numUploads: number
+}
+
+function ProgressBarError(props: ProgressBarErrorProps): JSX.Element {
   const { error, i18n, complete, numUploads } = props
 
-  function displayErrorAlert () {
+  function displayErrorAlert(): void {
     const errorMessage = `${i18n('uploadFailed')} \n\n ${error}`
     // eslint-disable-next-line no-alert
     alert(errorMessage) // TODO: move to custom alert implementation
@@ -422,7 +523,11 @@ function ProgressBarError (props) {
           </button>
         </div>
 
-        <FileUploadCount i18n={i18n} complete={complete} numUploads={numUploads} />
+        <FileUploadCount
+          i18n={i18n}
+          complete={complete}
+          numUploads={numUploads}
+        />
       </div>
     </div>
   )

+ 64 - 44
packages/@uppy/status-bar/src/StatusBar.jsx → packages/@uppy/status-bar/src/StatusBar.tsx

@@ -1,16 +1,25 @@
+import type { Body, Meta, UppyFile } from '@uppy/utils/lib/UppyFile'
+import type { Uppy, State } from '@uppy/core/src/Uppy.ts'
 import { UIPlugin } from '@uppy/core'
 import emaFilter from '@uppy/utils/lib/emaFilter'
 import getTextDirection from '@uppy/utils/lib/getTextDirection'
-import statusBarStates from './StatusBarStates.js'
-import StatusBarUI from './StatusBarUI.jsx'
-
+import statusBarStates from './StatusBarStates.ts'
+import StatusBarUI, { type StatusBarUIProps } from './StatusBarUI.tsx'
+// eslint-disable-next-line @typescript-eslint/ban-ts-comment
+// @ts-ignore We don't want TS to generate types for the package.json
 import packageJson from '../package.json'
-import locale from './locale.js'
+import locale from './locale.ts'
+import type { StatusBarOptions } from './StatusBarOptions.ts'
 
 const speedFilterHalfLife = 2000
 const ETAFilterHalfLife = 2000
 
-function getUploadingState (error, isAllComplete, recoveredState, files) {
+function getUploadingState(
+  error: unknown,
+  isAllComplete: boolean,
+  recoveredState: any,
+  files: Record<string, UppyFile<any, any>>,
+): StatusBarUIProps<any, any>['uploadState'] {
   if (error) {
     return statusBarStates.STATE_ERROR
   }
@@ -23,7 +32,8 @@ function getUploadingState (error, isAllComplete, recoveredState, files) {
     return statusBarStates.STATE_WAITING
   }
 
-  let state = statusBarStates.STATE_WAITING
+  let state: StatusBarUIProps<any, any>['uploadState'] =
+    statusBarStates.STATE_WAITING
   const fileIDs = Object.keys(files)
   for (let i = 0; i < fileIDs.length; i++) {
     const { progress } = files[fileIDs[i]]
@@ -33,16 +43,12 @@ function getUploadingState (error, isAllComplete, recoveredState, files) {
     }
     // If files are being preprocessed AND postprocessed at this time, we show the
     // preprocess state. If any files are being uploaded we show uploading.
-    if (progress.preprocess && state !== statusBarStates.STATE_UPLOADING) {
+    if (progress.preprocess) {
       state = statusBarStates.STATE_PREPROCESSING
     }
     // If NO files are being preprocessed or uploaded right now, but some files are
     // being postprocessed, show the postprocess state.
-    if (
-      progress.postprocess
-      && state !== statusBarStates.STATE_UPLOADING
-      && state !== statusBarStates.STATE_PREPROCESSING
-    ) {
+    if (progress.postprocess && state !== statusBarStates.STATE_PREPROCESSING) {
       state = statusBarStates.STATE_POSTPROCESSING
     }
   }
@@ -53,18 +59,22 @@ function getUploadingState (error, isAllComplete, recoveredState, files) {
  * StatusBar: renders a status bar with upload/pause/resume/cancel/retry buttons,
  * progress percentage and time remaining.
  */
-export default class StatusBar extends UIPlugin {
+export default class StatusBar<M extends Meta, B extends Body> extends UIPlugin<
+  StatusBarOptions,
+  M,
+  B
+> {
   static VERSION = packageJson.version
 
-  #lastUpdateTime
+  #lastUpdateTime: ReturnType<typeof performance.now>
 
-  #previousUploadedBytes
+  #previousUploadedBytes: number | null
 
-  #previousSpeed
+  #previousSpeed: number | null
 
-  #previousETA
+  #previousETA: number | null
 
-  constructor (uppy, opts) {
+  constructor(uppy: Uppy<M, B>, opts?: Partial<StatusBarOptions>) {
     super(uppy, opts)
     this.id = this.opts.id || 'StatusBar'
     this.title = 'StatusBar'
@@ -92,7 +102,11 @@ export default class StatusBar extends UIPlugin {
     this.install = this.install.bind(this)
   }
 
-  #computeSmoothETA (totalBytes) {
+  #computeSmoothETA(totalBytes: {
+    uploaded: number
+    total: number
+    remaining: number
+  }): number {
     if (totalBytes.total === 0 || totalBytes.remaining === 0) {
       return 0
     }
@@ -104,7 +118,8 @@ export default class StatusBar extends UIPlugin {
       return Math.round((this.#previousETA ?? 0) / 100) / 10
     }
 
-    const uploadedBytesSinceLastTick = totalBytes.uploaded - this.#previousUploadedBytes
+    const uploadedBytesSinceLastTick =
+      totalBytes.uploaded - this.#previousUploadedBytes!
     this.#previousUploadedBytes = totalBytes.uploaded
 
     // uploadedBytesSinceLastTick can be negative in some cases (packet loss?)
@@ -113,29 +128,31 @@ export default class StatusBar extends UIPlugin {
       return Math.round((this.#previousETA ?? 0) / 100) / 10
     }
     const currentSpeed = uploadedBytesSinceLastTick / dt
-    const filteredSpeed = this.#previousSpeed == null
-      ? currentSpeed
-      : emaFilter(currentSpeed, this.#previousSpeed, speedFilterHalfLife, dt)
+    const filteredSpeed =
+      this.#previousSpeed == null
+        ? currentSpeed
+        : emaFilter(currentSpeed, this.#previousSpeed, speedFilterHalfLife, dt)
     this.#previousSpeed = filteredSpeed
     const instantETA = totalBytes.remaining / filteredSpeed
 
-    const updatedPreviousETA = Math.max(this.#previousETA - dt, 0)
-    const filteredETA = this.#previousETA == null
-      ? instantETA
-      : emaFilter(instantETA, updatedPreviousETA, ETAFilterHalfLife, dt)
+    const updatedPreviousETA = Math.max(this.#previousETA! - dt, 0)
+    const filteredETA =
+      this.#previousETA == null
+        ? instantETA
+        : emaFilter(instantETA, updatedPreviousETA, ETAFilterHalfLife, dt)
     this.#previousETA = filteredETA
     this.#lastUpdateTime = performance.now()
 
     return Math.round(filteredETA / 100) / 10
   }
 
-  startUpload = () => {
-    return this.uppy.upload().catch(() => {
+  startUpload = (): ReturnType<Uppy<M, B>['upload']> => {
+    return this.uppy.upload().catch((() => {
       // Error logged in Core
-    })
+    }) as () => undefined)
   }
 
-  render (state) {
+  render(state: State<M, B>): JSX.Element {
     const {
       capabilities,
       files,
@@ -161,9 +178,7 @@ export default class StatusBar extends UIPlugin {
     // If some state was recovered, we want to show Upload button/counter
     // for all the files, because in this case it’s not an Upload button,
     // but “Confirm Restore Button”
-    const newFilesOrRecovered = recoveredState
-      ? Object.values(files)
-      : newFiles
+    const newFilesOrRecovered = recoveredState ? Object.values(files) : newFiles
     const resumableUploads = !!capabilities.resumableUploads
     const supportsUploadProgress = capabilities.uploadProgress !== false
 
@@ -194,6 +209,7 @@ export default class StatusBar extends UIPlugin {
       totalUploadedSize,
       isAllComplete: false,
       isAllPaused,
+      // @ts-expect-error TODO: remove this in 4.x branch
       isAllErrored,
       isUploadStarted,
       isUploadInProgress,
@@ -216,27 +232,30 @@ export default class StatusBar extends UIPlugin {
       hidePauseResumeButton: this.opts.hidePauseResumeButton,
       hideCancelButton: this.opts.hideCancelButton,
       hideAfterFinish: this.opts.hideAfterFinish,
+      // ts-expect-error TODO: remove this in 4.x branch
       isTargetDOMEl: this.isTargetDOMEl,
     })
   }
 
-  onMount () {
+  onMount(): void {
     // Set the text direction if the page has not defined one.
     const element = this.el
-    const direction = getTextDirection(element)
+    const direction = getTextDirection(element!)
     if (!direction) {
-      element.dir = 'ltr'
+      element!.dir = 'ltr'
     }
   }
 
-  #onUploadStart = () => {
+  #onUploadStart = (): void => {
     const { recoveredState } = this.uppy.getState()
 
     this.#previousSpeed = null
     this.#previousETA = null
     if (recoveredState) {
-      this.#previousUploadedBytes = Object.values(recoveredState.files)
-        .reduce((pv, { progress }) => pv + progress.bytesUploaded, 0)
+      this.#previousUploadedBytes = Object.values(recoveredState.files).reduce(
+        (pv, { progress }) => pv + (progress.bytesUploaded as number),
+        0,
+      )
 
       // We don't set `#lastUpdateTime` at this point because the upload won't
       // actually resume until the user asks for it.
@@ -248,7 +267,7 @@ export default class StatusBar extends UIPlugin {
     this.#previousUploadedBytes = 0
   }
 
-  install () {
+  install(): void {
     const { target } = this.opts
     if (target) {
       this.mount(target, this)
@@ -258,11 +277,12 @@ export default class StatusBar extends UIPlugin {
     // To cover the use case where the status bar is installed while the upload
     // has started, we set `lastUpdateTime` right away.
     this.#lastUpdateTime = performance.now()
-    this.#previousUploadedBytes = this.uppy.getFiles()
-      .reduce((pv, file) => pv + file.progress.bytesUploaded, 0)
+    this.#previousUploadedBytes = this.uppy
+      .getFiles()
+      .reduce((pv, file) => pv + (file.progress.bytesUploaded as number), 0)
   }
 
-  uninstall () {
+  uninstall(): void {
     this.unmount()
     this.uppy.off('upload', this.#onUploadStart)
   }

+ 14 - 0
packages/@uppy/status-bar/src/StatusBarOptions.ts

@@ -0,0 +1,14 @@
+import type { UIPluginOptions } from '@uppy/core/src/UIPlugin.ts'
+import type StatusBarLocale from './locale.ts'
+
+export interface StatusBarOptions extends UIPluginOptions {
+  target: HTMLElement | string
+  showProgressDetails: boolean
+  hideUploadButton: boolean
+  hideAfterFinish: boolean
+  hideRetryButton: boolean
+  hidePauseResumeButton: boolean
+  hideCancelButton: boolean
+  doneButtonHandler: (() => void) | null
+  locale?: typeof StatusBarLocale
+}

+ 0 - 8
packages/@uppy/status-bar/src/StatusBarStates.js

@@ -1,8 +0,0 @@
-export default {
-  STATE_ERROR: 'error',
-  STATE_WAITING: 'waiting',
-  STATE_PREPROCESSING: 'preprocessing',
-  STATE_UPLOADING: 'uploading',
-  STATE_POSTPROCESSING: 'postprocessing',
-  STATE_COMPLETE: 'complete',
-}

+ 8 - 0
packages/@uppy/status-bar/src/StatusBarStates.ts

@@ -0,0 +1,8 @@
+export default {
+  STATE_ERROR: 'error' as const,
+  STATE_WAITING: 'waiting' as const,
+  STATE_PREPROCESSING: 'preprocessing' as const,
+  STATE_UPLOADING: 'uploading' as const,
+  STATE_POSTPROCESSING: 'postprocessing' as const,
+  STATE_COMPLETE: 'complete' as const,
+}

+ 78 - 24
packages/@uppy/status-bar/src/StatusBarUI.jsx → packages/@uppy/status-bar/src/StatusBarUI.tsx

@@ -1,7 +1,10 @@
+import type { Body, Meta, UppyFile } from '@uppy/utils/lib/UppyFile'
+import type { I18n } from '@uppy/utils/lib/Translator'
+import type { Uppy, State } from '@uppy/core/src/Uppy.ts'
 import { h } from 'preact'
 import classNames from 'classnames'
-import statusBarStates from './StatusBarStates.js'
-import calculateProcessingProgress from './calculateProcessingProgress.js'
+import statusBarStates from './StatusBarStates.ts'
+import calculateProcessingProgress from './calculateProcessingProgress.ts'
 
 import {
   UploadBtn,
@@ -13,7 +16,7 @@ import {
   ProgressBarError,
   ProgressBarUploading,
   ProgressBarComplete,
-} from './Components.jsx'
+} from './Components.tsx'
 
 const {
   STATE_ERROR,
@@ -24,8 +27,42 @@ const {
   STATE_COMPLETE,
 } = statusBarStates
 
+export interface StatusBarUIProps<M extends Meta, B extends Body> {
+  newFiles: number
+  allowNewUpload: boolean
+  isUploadInProgress: boolean
+  isAllPaused: boolean
+  resumableUploads: boolean
+  error: any
+  hideUploadButton?: boolean
+  hidePauseResumeButton?: boolean
+  hideCancelButton?: boolean
+  hideRetryButton?: boolean
+  recoveredState: null | State<M, B>
+  uploadState: (typeof statusBarStates)[keyof typeof statusBarStates]
+  totalProgress: number
+  files: Record<string, UppyFile<M, B>>
+  supportsUploadProgress: boolean
+  hideAfterFinish?: boolean
+  isSomeGhost: boolean
+  doneButtonHandler?: (() => void) | null
+  isUploadStarted: boolean
+  i18n: I18n
+  startUpload: () => void
+  uppy: Uppy<M, B>
+  isAllComplete: boolean
+  showProgressDetails?: boolean
+  numUploads: number
+  complete: number
+  totalSize: number
+  totalETA: number
+  totalUploadedSize: number
+}
+
 // TODO: rename the function to StatusBarUI on the next major.
-export default function StatusBar (props) {
+export default function StatusBar<M extends Meta, B extends Body>(
+  props: StatusBarUIProps<M, B>,
+): JSX.Element {
   const {
     newFiles,
     allowNewUpload,
@@ -58,7 +95,7 @@ export default function StatusBar (props) {
     totalUploadedSize,
   } = props
 
-  function getProgressValue () {
+  function getProgressValue(): number | null {
     switch (uploadState) {
       case STATE_POSTPROCESSING:
       case STATE_PREPROCESSING: {
@@ -83,7 +120,7 @@ export default function StatusBar (props) {
     }
   }
 
-  function getIsIndeterminate () {
+  function getIsIndeterminate(): boolean {
     switch (uploadState) {
       case STATE_POSTPROCESSING:
       case STATE_PREPROCESSING: {
@@ -101,7 +138,7 @@ export default function StatusBar (props) {
     }
   }
 
-  function getIsHidden () {
+  function getIsHidden(): boolean | undefined {
     if (recoveredState) {
       return false
     }
@@ -122,20 +159,23 @@ export default function StatusBar (props) {
 
   const width = progressValue ?? 100
 
-  const showUploadBtn = !error
-    && newFiles
-    && !isUploadInProgress
-    && !isAllPaused
-    && allowNewUpload
-    && !hideUploadButton
+  const showUploadBtn =
+    !error &&
+    newFiles &&
+    !isUploadInProgress &&
+    !isAllPaused &&
+    allowNewUpload &&
+    !hideUploadButton
 
-  const showCancelBtn = !hideCancelButton
-    && uploadState !== STATE_WAITING
-    && uploadState !== STATE_COMPLETE
+  const showCancelBtn =
+    !hideCancelButton &&
+    uploadState !== STATE_WAITING &&
+    uploadState !== STATE_COMPLETE
 
-  const showPauseResumeBtn = resumableUploads
-    && !hidePauseResumeButton
-    && uploadState === STATE_UPLOADING
+  const showPauseResumeBtn =
+    resumableUploads &&
+    !hidePauseResumeButton &&
+    uploadState === STATE_UPLOADING
 
   const showRetryBtn = error && !isAllComplete && !hideRetryButton
 
@@ -159,16 +199,20 @@ export default function StatusBar (props) {
         role="progressbar"
         aria-label={`${width}%`}
         aria-valuetext={`${width}%`}
-        aria-valuemin="0"
-        aria-valuemax="100"
-        aria-valuenow={progressValue}
+        aria-valuemin={0}
+        aria-valuemax={100}
+        aria-valuenow={progressValue!}
       />
 
-      {(() => {
+      {((): JSX.Element | null => {
         switch (uploadState) {
           case STATE_PREPROCESSING:
           case STATE_POSTPROCESSING:
-            return <ProgressBarProcessing progress={calculateProcessingProgress(files)} />
+            return (
+              <ProgressBarProcessing
+                progress={calculateProcessingProgress(files)}
+              />
+            )
           case STATE_COMPLETE:
             return <ProgressBarComplete i18n={i18n} />
           case STATE_ERROR:
@@ -238,3 +282,13 @@ export default function StatusBar (props) {
     </div>
   )
 }
+
+StatusBar.defaultProps = {
+  doneButtonHandler: undefined,
+  hideAfterFinish: false,
+  hideCancelButton: false,
+  hidePauseResumeButton: false,
+  hideRetryButton: false,
+  hideUploadButton: undefined,
+  showProgressDetails: undefined,
+}

+ 11 - 6
packages/@uppy/status-bar/src/calculateProcessingProgress.js → packages/@uppy/status-bar/src/calculateProcessingProgress.ts

@@ -1,14 +1,19 @@
-export default function calculateProcessingProgress (files) {
-  const values = []
-  let mode
-  let message
+import type { FileProcessingInfo } from '@uppy/utils/lib/FileProgress'
+import type { UppyFile } from '@uppy/utils/lib/UppyFile'
+
+export default function calculateProcessingProgress(
+  files: Record<string, UppyFile<any, any>>,
+): FileProcessingInfo {
+  const values: number[] = []
+  let mode: FileProcessingInfo['mode'] = 'indeterminate'
+  let message: FileProcessingInfo['message']
 
   for (const { progress } of Object.values(files)) {
     const { preprocess, postprocess } = progress
     // In the future we should probably do this differently. For now we'll take the
     // mode and message from the first file…
     if (message == null && (preprocess || postprocess)) {
-      ({ mode, message } = preprocess || postprocess)
+      ;({ mode, message } = preprocess || postprocess!) // eslint-disable-line @typescript-eslint/no-non-null-assertion
     }
     if (preprocess?.mode === 'determinate') values.push(preprocess.value)
     if (postprocess?.mode === 'determinate') values.push(postprocess.value)
@@ -22,5 +27,5 @@ export default function calculateProcessingProgress (files) {
     mode,
     message,
     value,
-  }
+  } as FileProcessingInfo
 }

+ 0 - 1
packages/@uppy/status-bar/src/index.js

@@ -1 +0,0 @@
-export { default } from './StatusBar.jsx'

+ 2 - 0
packages/@uppy/status-bar/src/index.ts

@@ -0,0 +1,2 @@
+export { default } from './StatusBar.tsx'
+export type { StatusBarOptions } from './StatusBarOptions.ts'

+ 4 - 2
packages/@uppy/status-bar/src/locale.js → packages/@uppy/status-bar/src/locale.ts

@@ -1,3 +1,5 @@
+import type { Locale } from '@uppy/utils/lib/Translator'
+
 export default {
   strings: {
     // Shown in the status bar while files are being uploaded.
@@ -45,5 +47,5 @@ export default {
       1: '%{smart_count} more files added',
     },
     showErrorDetails: 'Show error details',
-  },
-}
+  } as Locale<0 | 1>['strings'],
+} as any as Locale

+ 25 - 0
packages/@uppy/status-bar/tsconfig.build.json

@@ -0,0 +1,25 @@
+{
+  "extends": "../../../tsconfig.shared",
+  "compilerOptions": {
+    "outDir": "./lib",
+    "rootDir": "./src",
+    "resolveJsonModule": false,
+    "noImplicitAny": false,
+    "skipLibCheck": true,
+    "paths": {
+      "@uppy/core": ["../core/src/index.js"],
+      "@uppy/core/lib/*": ["../core/src/*"],
+      "@uppy/utils/lib/*": ["../utils/src/*"]
+    }
+  },
+  "include": ["./src/**/*.*"],
+  "exclude": ["./src/**/*.test.ts"],
+  "references": [
+    {
+      "path": "../utils/tsconfig.build.json"
+    },
+    {
+      "path": "../core/tsconfig.build.json"
+    }
+  ]
+}

+ 21 - 0
packages/@uppy/status-bar/tsconfig.json

@@ -0,0 +1,21 @@
+{
+  "extends": "../../../tsconfig.shared",
+  "compilerOptions": {
+    "emitDeclarationOnly": false,
+    "paths": {
+      "@uppy/core": ["../core/src/index.js"],
+      "@uppy/core/lib/*": ["../core/src/*"],
+      "@uppy/utils/lib/*": ["../utils/src/*"]
+    },
+    "noEmit": true
+  },
+  "include": ["./package.json", "./src/**/*.*"],
+  "references": [
+    {
+      "path": "../utils/tsconfig.build.json"
+    },
+    {
+      "path": "../core/tsconfig.build.json"
+    }
+  ]
+}