Skip to content

Commit 033ecc7

Browse files
imp-dancetw15egan
andauthored
chore: Types for FileUploader, FileUploaderButton, FileUploaderDropContainer, FileUploaderItem, FileUploaderSkeleton, Filename, Upload and ButtonSkeleton (#13992)
* chore: renamed fileuploader files to tsx * chore: added types for Loading component * chore: added types for FileUploader components and ButtonSkeleton * chore: tab index prop takes boths string & number, test snapshot updated * chore: removed field size usage in button skeleton --------- Co-authored-by: TJ Egan <tw15egan@gmail.com>
1 parent 2ee33d7 commit 033ecc7

8 files changed

Lines changed: 439 additions & 35 deletions

File tree

packages/react/__tests__/__snapshots__/PublicAPI-test.js.snap

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3440,7 +3440,17 @@ Map {
34403440
"type": "oneOf",
34413441
},
34423442
"tabIndex": Object {
3443-
"type": "string",
3443+
"args": Array [
3444+
Array [
3445+
Object {
3446+
"type": "number",
3447+
},
3448+
Object {
3449+
"type": "string",
3450+
},
3451+
],
3452+
],
3453+
"type": "oneOfType",
34443454
},
34453455
},
34463456
},

packages/react/src/components/FileUploader/FileUploader.Skeleton.js renamed to packages/react/src/components/FileUploader/FileUploader.Skeleton.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,14 @@ import cx from 'classnames';
1111
import SkeletonText from '../SkeletonText';
1212
import ButtonSkeleton from '../Button/Button.Skeleton';
1313
import { usePrefix } from '../../internal/usePrefix';
14+
import { ReactAttr } from '../../types/common';
15+
16+
export interface FileUploaderSkeletonProps extends ReactAttr<HTMLDivElement> {
17+
/**
18+
* Specify an optional className to add.
19+
*/
20+
className?: string;
21+
}
1422

1523
function FileUploaderSkeleton({ className, ...rest }) {
1624
const prefix = usePrefix();

packages/react/src/components/FileUploader/FileUploader.js renamed to packages/react/src/components/FileUploader/FileUploader.tsx

Lines changed: 110 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,102 @@ import FileUploaderButton from './FileUploaderButton';
1313
import { ButtonKinds } from '../../prop-types/types';
1414
import { keys, matches } from '../../internal/keyboard';
1515
import { PrefixContext } from '../../internal/usePrefix';
16+
import { ReactAttr } from '../../types/common';
1617

17-
export default class FileUploader extends React.Component {
18+
export interface FileUploaderProps extends ReactAttr<HTMLSpanElement> {
19+
/**
20+
* Specify the types of files that this input should be able to receive
21+
*/
22+
accept?: string[];
23+
24+
/**
25+
* Specify the type of the `<FileUploaderButton>`
26+
*/
27+
buttonKind?:
28+
| 'primary'
29+
| 'secondary'
30+
| 'danger'
31+
| 'ghost'
32+
| 'danger--primary'
33+
| 'danger--ghost'
34+
| 'danger--tertiary'
35+
| 'tertiary';
36+
37+
/**
38+
* Provide the label text to be read by screen readers when interacting with
39+
* the `<FileUploaderButton>`
40+
*/
41+
buttonLabel?: string;
42+
43+
/**
44+
* Provide a custom className to be applied to the container node
45+
*/
46+
className?: string;
47+
48+
/**
49+
* Specify whether file input is disabled
50+
*/
51+
disabled?: boolean;
52+
53+
/**
54+
* Specify the status of the File Upload
55+
*/
56+
filenameStatus: 'edit' | 'complete' | 'uploading';
57+
58+
/**
59+
* Provide a description for the complete/close icon that can be read by screen readers
60+
*/
61+
iconDescription: string;
62+
63+
/**
64+
* Specify the description text of this `<FileUploader>`
65+
*/
66+
labelDescription?: string;
67+
68+
/**
69+
* Specify the title text of this `<FileUploader>`
70+
*/
71+
labelTitle?: string;
72+
73+
/**
74+
* Specify if the component should accept multiple files to upload
75+
*/
76+
multiple?: boolean;
77+
78+
/**
79+
* Provide a name for the underlying `<input>` node
80+
*/
81+
name?: string;
82+
83+
/**
84+
* Provide an optional `onChange` hook that is called each time the input is
85+
* changed
86+
*/
87+
onChange?: (event: any) => void;
88+
89+
/**
90+
* Provide an optional `onClick` hook that is called each time the
91+
* FileUploader is clicked
92+
*/
93+
onClick?: (event: any) => void;
94+
95+
/**
96+
* Provide an optional `onDelete` hook that is called when an uploaded item
97+
* is removed
98+
*/
99+
onDelete?: (event: any) => void;
100+
101+
/**
102+
* Specify the size of the FileUploaderButton, from a list of available
103+
* sizes.
104+
*/
105+
size?: 'sm' | 'small' | 'md' | 'field' | 'lg';
106+
}
107+
108+
export default class FileUploader extends React.Component<
109+
FileUploaderProps,
110+
{ filenames: string[] }
111+
> {
18112
static propTypes = {
19113
/**
20114
* Specify the types of files that this input should be able to receive
@@ -111,12 +205,12 @@ export default class FileUploader extends React.Component {
111205
};
112206

113207
state = {
114-
filenames: [],
208+
filenames: [] as string[],
115209
};
116210

117-
nodes = [];
211+
nodes: HTMLElement[] = [];
118212

119-
uploaderButton = React.createRef();
213+
uploaderButton = React.createRef<HTMLLabelElement>();
120214

121215
static getDerivedStateFromProps({ filenameStatus }, state) {
122216
const { prevFilenameStatus } = state;
@@ -133,7 +227,7 @@ export default class FileUploader extends React.Component {
133227
const filenames = Array.prototype.map.call(
134228
evt.target.files,
135229
(file) => file.name
136-
);
230+
) as string[];
137231
this.setState({
138232
filenames: this.props.multiple
139233
? this.state.filenames.concat(filenames)
@@ -153,9 +247,9 @@ export default class FileUploader extends React.Component {
153247
this.setState({ filenames: filteredArray });
154248
if (this.props.onDelete) {
155249
this.props.onDelete(evt);
156-
this.uploaderButton.current.focus();
250+
this.uploaderButton.current?.focus?.();
157251
}
158-
this.props.onClick(evt);
252+
this.props.onClick?.(evt);
159253
}
160254
};
161255

@@ -178,15 +272,15 @@ export default class FileUploader extends React.Component {
178272
accept,
179273
name,
180274
size = 'md',
181-
onDelete, // eslint-disable-line no-unused-vars
275+
onDelete, // eslint-disable-line
182276
...other
183277
} = this.props;
184278

185279
const prefix = this.context;
186280

187281
const classes = classNames({
188282
[`${prefix}--form-item`]: true,
189-
[className]: className,
283+
[className as string]: className,
190284
});
191285

192286
const getHelperLabelClasses = (baseClass) =>
@@ -228,7 +322,7 @@ export default class FileUploader extends React.Component {
228322
<span
229323
key={index}
230324
className={selectedFileClasses}
231-
ref={(node) => (this.nodes[index] = node)} // eslint-disable-line
325+
ref={(node) => (this.nodes[index] = node as HTMLSpanElement)} // eslint-disable-line
232326
{...other}>
233327
<p className={`${prefix}--file-filename`} id={name}>
234328
{name}
@@ -239,7 +333,12 @@ export default class FileUploader extends React.Component {
239333
iconDescription={iconDescription}
240334
status={filenameStatus}
241335
onKeyDown={(evt) => {
242-
if (matches(evt, [keys.Enter, keys.Space])) {
336+
if (
337+
matches(evt as unknown as Event, [
338+
keys.Enter,
339+
keys.Space,
340+
])
341+
) {
243342
this.handleClick(evt, { index, filenameStatus });
244343
}
245344
}}

packages/react/src/components/FileUploader/FileUploaderButton.js renamed to packages/react/src/components/FileUploader/FileUploaderButton.tsx

Lines changed: 106 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,98 @@ import { ButtonKinds } from '../../prop-types/types';
1313
import uid from '../../tools/uniqueId';
1414
import { usePrefix } from '../../internal/usePrefix';
1515
import deprecate from '../../prop-types/deprecate';
16+
import { ReactAttr } from '../../types/common';
1617

1718
function noop() {}
1819

20+
export interface FileUploaderButtonProps
21+
extends Omit<ReactAttr<HTMLButtonElement>, 'onChange' | 'tabIndex'> {
22+
/**
23+
* Specify the types of files that this input should be able to receive
24+
*/
25+
accept?: string[];
26+
27+
/**
28+
* Specify the type of underlying button
29+
*/
30+
buttonKind?:
31+
| 'primary'
32+
| 'secondary'
33+
| 'danger'
34+
| 'ghost'
35+
| 'danger--primary'
36+
| 'danger--ghost'
37+
| 'danger--tertiary'
38+
| 'tertiary';
39+
40+
/**
41+
* Provide a custom className to be applied to the container node
42+
*/
43+
className?: string;
44+
45+
/**
46+
* Specify whether you want to disable any updates to the FileUploaderButton
47+
* label
48+
*/
49+
disableLabelChanges?: boolean;
50+
51+
/**
52+
* Specify whether file input is disabled
53+
*/
54+
disabled?: boolean;
55+
56+
/**
57+
* Provide a unique id for the underlying `<input>` node
58+
*/
59+
id?: string;
60+
61+
/**
62+
* Provide the label text to be read by screen readers when interacting with
63+
* this control
64+
*/
65+
labelText?: React.ReactNode;
66+
67+
/**
68+
* Specify if the component should accept multiple files to upload
69+
*/
70+
multiple?: boolean;
71+
72+
/**
73+
* Provide a name for the underlying `<input>` node
74+
*/
75+
name?: string;
76+
77+
/**
78+
* Provide an optional `onChange` hook that is called each time the `<input>`
79+
* value changes
80+
*/
81+
onChange?: (event: React.ChangeEvent<HTMLInputElement>) => void;
82+
83+
/**
84+
* Provide an optional `onClick` hook that is called each time the button is
85+
* clicked
86+
*/
87+
onClick?: (event: React.MouseEvent<HTMLButtonElement>) => void;
88+
89+
/**
90+
* Provide an accessibility role for the `<FileUploaderButton>`
91+
*/
92+
role?: string;
93+
94+
/**
95+
* Specify the size of the FileUploaderButton, from a list of available
96+
* sizes.
97+
*/
98+
size?: 'sm' | 'small' | 'field' | 'md' | 'lg';
99+
100+
/**
101+
* @deprecated The `tabIndex` prop for `FileUploaderButton` has been deprecated since it now renders a button element by default.
102+
*/
103+
tabIndex?: number | string;
104+
105+
innerRef?: React.RefObject<HTMLLabelElement>;
106+
}
107+
19108
function FileUploaderButton({
20109
accept,
21110
buttonKind = 'primary',
@@ -31,12 +120,12 @@ function FileUploaderButton({
31120
// eslint-disable-next-line react/prop-types
32121
innerRef,
33122
...other
34-
}) {
123+
}: FileUploaderButtonProps) {
35124
const prefix = usePrefix();
36125
const [labelText, setLabelText] = useState(ownerLabelText);
37126
const [prevOwnerLabelText, setPrevOwnerLabelText] = useState(ownerLabelText);
38127
const { current: inputId } = useRef(id || uid());
39-
const inputNode = useRef(null);
128+
const inputNode = useRef<HTMLInputElement>(null);
40129
const classes = cx(`${prefix}--btn`, className, {
41130
[`${prefix}--btn--${buttonKind}`]: buttonKind,
42131
[`${prefix}--btn--disabled`]: disabled,
@@ -54,20 +143,22 @@ function FileUploaderButton({
54143

55144
function onClick(event) {
56145
event.target.value = null;
57-
inputNode.current.value = '';
58-
inputNode.current.click();
146+
if (inputNode.current) {
147+
inputNode.current.value = '';
148+
inputNode.current.click();
149+
}
59150
}
60151

61152
function onKeyDown(event) {
62-
if (matches(event, [keys.Enter, keys.Space])) {
153+
if (matches(event, [keys.Enter, keys.Space]) && inputNode.current) {
63154
inputNode.current.value = '';
64155
inputNode.current.click();
65156
}
66157
}
67158

68-
function handleOnChange(event) {
159+
function handleOnChange(event: React.ChangeEvent<HTMLInputElement>) {
69160
const files = event.target.files;
70-
const length = event.target.files.length;
161+
const length = event.target.files?.length || 0;
71162
if (files && !disableLabelChanges) {
72163
if (length > 1) {
73164
setLabelText(`${length} files`);
@@ -86,7 +177,12 @@ function FileUploaderButton({
86177
className={classes}
87178
onClick={onClick}
88179
onKeyDown={onKeyDown}
89-
{...other}>
180+
{...other}
181+
tabIndex={
182+
other.tabIndex !== undefined
183+
? parseInt(other.tabIndex as string)
184+
: undefined
185+
}>
90186
{labelText}
91187
</button>
92188
<label
@@ -101,9 +197,9 @@ function FileUploaderButton({
101197
id={inputId}
102198
disabled={disabled}
103199
type="file"
104-
tabIndex="-1"
200+
tabIndex={-1}
105201
multiple={multiple}
106-
accept={accept}
202+
accept={accept?.toString()}
107203
name={name}
108204
onChange={handleOnChange}
109205
/>

0 commit comments

Comments
 (0)