diff --git a/packages/react/__tests__/__snapshots__/PublicAPI-test.js.snap b/packages/react/__tests__/__snapshots__/PublicAPI-test.js.snap index 87835d163e55..9f09fffce1ec 100644 --- a/packages/react/__tests__/__snapshots__/PublicAPI-test.js.snap +++ b/packages/react/__tests__/__snapshots__/PublicAPI-test.js.snap @@ -3440,7 +3440,17 @@ Map { "type": "oneOf", }, "tabIndex": Object { - "type": "string", + "args": Array [ + Array [ + Object { + "type": "number", + }, + Object { + "type": "string", + }, + ], + ], + "type": "oneOfType", }, }, }, diff --git a/packages/react/src/components/FileUploader/FileUploader.Skeleton.js b/packages/react/src/components/FileUploader/FileUploader.Skeleton.tsx similarity index 82% rename from packages/react/src/components/FileUploader/FileUploader.Skeleton.js rename to packages/react/src/components/FileUploader/FileUploader.Skeleton.tsx index ba4da8537aba..87c535501fac 100644 --- a/packages/react/src/components/FileUploader/FileUploader.Skeleton.js +++ b/packages/react/src/components/FileUploader/FileUploader.Skeleton.tsx @@ -11,6 +11,14 @@ import cx from 'classnames'; import SkeletonText from '../SkeletonText'; import ButtonSkeleton from '../Button/Button.Skeleton'; import { usePrefix } from '../../internal/usePrefix'; +import { ReactAttr } from '../../types/common'; + +export interface FileUploaderSkeletonProps extends ReactAttr { + /** + * Specify an optional className to add. + */ + className?: string; +} function FileUploaderSkeleton({ className, ...rest }) { const prefix = usePrefix(); diff --git a/packages/react/src/components/FileUploader/FileUploader.js b/packages/react/src/components/FileUploader/FileUploader.tsx similarity index 70% rename from packages/react/src/components/FileUploader/FileUploader.js rename to packages/react/src/components/FileUploader/FileUploader.tsx index 78734e95acc9..179605f8a3d1 100644 --- a/packages/react/src/components/FileUploader/FileUploader.js +++ b/packages/react/src/components/FileUploader/FileUploader.tsx @@ -13,8 +13,102 @@ import FileUploaderButton from './FileUploaderButton'; import { ButtonKinds } from '../../prop-types/types'; import { keys, matches } from '../../internal/keyboard'; import { PrefixContext } from '../../internal/usePrefix'; +import { ReactAttr } from '../../types/common'; -export default class FileUploader extends React.Component { +export interface FileUploaderProps extends ReactAttr { + /** + * Specify the types of files that this input should be able to receive + */ + accept?: string[]; + + /** + * Specify the type of the `` + */ + buttonKind?: + | 'primary' + | 'secondary' + | 'danger' + | 'ghost' + | 'danger--primary' + | 'danger--ghost' + | 'danger--tertiary' + | 'tertiary'; + + /** + * Provide the label text to be read by screen readers when interacting with + * the `` + */ + buttonLabel?: string; + + /** + * Provide a custom className to be applied to the container node + */ + className?: string; + + /** + * Specify whether file input is disabled + */ + disabled?: boolean; + + /** + * Specify the status of the File Upload + */ + filenameStatus: 'edit' | 'complete' | 'uploading'; + + /** + * Provide a description for the complete/close icon that can be read by screen readers + */ + iconDescription: string; + + /** + * Specify the description text of this `` + */ + labelDescription?: string; + + /** + * Specify the title text of this `` + */ + labelTitle?: string; + + /** + * Specify if the component should accept multiple files to upload + */ + multiple?: boolean; + + /** + * Provide a name for the underlying `` node + */ + name?: string; + + /** + * Provide an optional `onChange` hook that is called each time the input is + * changed + */ + onChange?: (event: any) => void; + + /** + * Provide an optional `onClick` hook that is called each time the + * FileUploader is clicked + */ + onClick?: (event: any) => void; + + /** + * Provide an optional `onDelete` hook that is called when an uploaded item + * is removed + */ + onDelete?: (event: any) => void; + + /** + * Specify the size of the FileUploaderButton, from a list of available + * sizes. + */ + size?: 'sm' | 'small' | 'md' | 'field' | 'lg'; +} + +export default class FileUploader extends React.Component< + FileUploaderProps, + { filenames: string[] } +> { static propTypes = { /** * Specify the types of files that this input should be able to receive @@ -111,12 +205,12 @@ export default class FileUploader extends React.Component { }; state = { - filenames: [], + filenames: [] as string[], }; - nodes = []; + nodes: HTMLElement[] = []; - uploaderButton = React.createRef(); + uploaderButton = React.createRef(); static getDerivedStateFromProps({ filenameStatus }, state) { const { prevFilenameStatus } = state; @@ -133,7 +227,7 @@ export default class FileUploader extends React.Component { const filenames = Array.prototype.map.call( evt.target.files, (file) => file.name - ); + ) as string[]; this.setState({ filenames: this.props.multiple ? this.state.filenames.concat(filenames) @@ -153,9 +247,9 @@ export default class FileUploader extends React.Component { this.setState({ filenames: filteredArray }); if (this.props.onDelete) { this.props.onDelete(evt); - this.uploaderButton.current.focus(); + this.uploaderButton.current?.focus?.(); } - this.props.onClick(evt); + this.props.onClick?.(evt); } }; @@ -178,7 +272,7 @@ export default class FileUploader extends React.Component { accept, name, size = 'md', - onDelete, // eslint-disable-line no-unused-vars + onDelete, // eslint-disable-line ...other } = this.props; @@ -186,7 +280,7 @@ export default class FileUploader extends React.Component { const classes = classNames({ [`${prefix}--form-item`]: true, - [className]: className, + [className as string]: className, }); const getHelperLabelClasses = (baseClass) => @@ -228,7 +322,7 @@ export default class FileUploader extends React.Component { (this.nodes[index] = node)} // eslint-disable-line + ref={(node) => (this.nodes[index] = node as HTMLSpanElement)} // eslint-disable-line {...other}>

{name} @@ -239,7 +333,12 @@ export default class FileUploader extends React.Component { iconDescription={iconDescription} status={filenameStatus} onKeyDown={(evt) => { - if (matches(evt, [keys.Enter, keys.Space])) { + if ( + matches(evt as unknown as Event, [ + keys.Enter, + keys.Space, + ]) + ) { this.handleClick(evt, { index, filenameStatus }); } }} diff --git a/packages/react/src/components/FileUploader/FileUploaderButton.js b/packages/react/src/components/FileUploader/FileUploaderButton.tsx similarity index 63% rename from packages/react/src/components/FileUploader/FileUploaderButton.js rename to packages/react/src/components/FileUploader/FileUploaderButton.tsx index 6abea547b47a..9b9bc4312c83 100644 --- a/packages/react/src/components/FileUploader/FileUploaderButton.js +++ b/packages/react/src/components/FileUploader/FileUploaderButton.tsx @@ -13,9 +13,98 @@ import { ButtonKinds } from '../../prop-types/types'; import uid from '../../tools/uniqueId'; import { usePrefix } from '../../internal/usePrefix'; import deprecate from '../../prop-types/deprecate'; +import { ReactAttr } from '../../types/common'; function noop() {} +export interface FileUploaderButtonProps + extends Omit, 'onChange' | 'tabIndex'> { + /** + * Specify the types of files that this input should be able to receive + */ + accept?: string[]; + + /** + * Specify the type of underlying button + */ + buttonKind?: + | 'primary' + | 'secondary' + | 'danger' + | 'ghost' + | 'danger--primary' + | 'danger--ghost' + | 'danger--tertiary' + | 'tertiary'; + + /** + * Provide a custom className to be applied to the container node + */ + className?: string; + + /** + * Specify whether you want to disable any updates to the FileUploaderButton + * label + */ + disableLabelChanges?: boolean; + + /** + * Specify whether file input is disabled + */ + disabled?: boolean; + + /** + * Provide a unique id for the underlying `` node + */ + id?: string; + + /** + * Provide the label text to be read by screen readers when interacting with + * this control + */ + labelText?: React.ReactNode; + + /** + * Specify if the component should accept multiple files to upload + */ + multiple?: boolean; + + /** + * Provide a name for the underlying `` node + */ + name?: string; + + /** + * Provide an optional `onChange` hook that is called each time the `` + * value changes + */ + onChange?: (event: React.ChangeEvent) => void; + + /** + * Provide an optional `onClick` hook that is called each time the button is + * clicked + */ + onClick?: (event: React.MouseEvent) => void; + + /** + * Provide an accessibility role for the `` + */ + role?: string; + + /** + * Specify the size of the FileUploaderButton, from a list of available + * sizes. + */ + size?: 'sm' | 'small' | 'field' | 'md' | 'lg'; + + /** + * @deprecated The `tabIndex` prop for `FileUploaderButton` has been deprecated since it now renders a button element by default. + */ + tabIndex?: number | string; + + innerRef?: React.RefObject; +} + function FileUploaderButton({ accept, buttonKind = 'primary', @@ -31,12 +120,12 @@ function FileUploaderButton({ // eslint-disable-next-line react/prop-types innerRef, ...other -}) { +}: FileUploaderButtonProps) { const prefix = usePrefix(); const [labelText, setLabelText] = useState(ownerLabelText); const [prevOwnerLabelText, setPrevOwnerLabelText] = useState(ownerLabelText); const { current: inputId } = useRef(id || uid()); - const inputNode = useRef(null); + const inputNode = useRef(null); const classes = cx(`${prefix}--btn`, className, { [`${prefix}--btn--${buttonKind}`]: buttonKind, [`${prefix}--btn--disabled`]: disabled, @@ -54,20 +143,22 @@ function FileUploaderButton({ function onClick(event) { event.target.value = null; - inputNode.current.value = ''; - inputNode.current.click(); + if (inputNode.current) { + inputNode.current.value = ''; + inputNode.current.click(); + } } function onKeyDown(event) { - if (matches(event, [keys.Enter, keys.Space])) { + if (matches(event, [keys.Enter, keys.Space]) && inputNode.current) { inputNode.current.value = ''; inputNode.current.click(); } } - function handleOnChange(event) { + function handleOnChange(event: React.ChangeEvent) { const files = event.target.files; - const length = event.target.files.length; + const length = event.target.files?.length || 0; if (files && !disableLabelChanges) { if (length > 1) { setLabelText(`${length} files`); @@ -86,7 +177,12 @@ function FileUploaderButton({ className={classes} onClick={onClick} onKeyDown={onKeyDown} - {...other}> + {...other} + tabIndex={ + other.tabIndex !== undefined + ? parseInt(other.tabIndex as string) + : undefined + }> {labelText}