@carbon/react 1.97.0 → 1.99.0-rc.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.playwright/INTERNAL_AVT_REPORT_DO_NOT_USE.json +1002 -967
- package/es/components/ButtonSet/ButtonSet.d.ts +5 -0
- package/es/components/ButtonSet/ButtonSet.js +68 -4
- package/es/components/CodeSnippet/CodeSnippet.d.ts +1 -1
- package/es/components/Copy/Copy.d.ts +1 -1
- package/es/components/CopyButton/CopyButton.d.ts +1 -1
- package/es/components/DataTable/DataTable.d.ts +2 -1
- package/es/components/DataTable/TableContainer.d.ts +10 -2
- package/es/components/DataTable/TableContainer.js +15 -3
- package/es/components/DataTable/state/sorting.d.ts +2 -4
- package/es/components/DatePicker/DatePicker.d.ts +3 -2
- package/es/components/DatePicker/DatePicker.js +18 -135
- package/es/components/DatePicker/DatePickerLocales.d.ts +12 -0
- package/es/components/DatePicker/DatePickerLocales.js +135 -0
- package/es/components/DatePickerInput/DatePickerInput.js +50 -28
- package/es/components/Dropdown/Dropdown.js +13 -1
- package/es/components/FileUploader/FileUploader.d.ts +23 -8
- package/es/components/FileUploader/FileUploader.js +53 -33
- package/es/components/FileUploader/FileUploaderButton.js +2 -2
- package/es/components/FileUploader/FileUploaderDropContainer.d.ts +13 -2
- package/es/components/FileUploader/FileUploaderDropContainer.js +15 -6
- package/es/components/FileUploader/FileUploaderItem.js +9 -6
- package/es/components/Link/Link.js +1 -1
- package/es/components/Menu/index.d.ts +4 -3
- package/es/components/MultiSelect/FilterableMultiSelect.js +1 -0
- package/es/components/MultiSelect/MultiSelect.js +1 -0
- package/es/components/NumberInput/NumberInput.d.ts +6 -2
- package/es/components/NumberInput/NumberInput.js +17 -5
- package/es/components/Pagination/Pagination.js +5 -5
- package/es/components/Popover/index.d.ts +1 -1
- package/es/components/Popover/index.js +27 -13
- package/es/components/Select/Select.js +27 -33
- package/es/components/Tabs/Tabs.js +1 -1
- package/es/components/TextArea/TextArea.js +14 -4
- package/es/components/TextInput/PasswordInput.js +3 -2
- package/es/components/Toggletip/index.d.ts +1 -0
- package/es/components/Toggletip/index.js +1 -1
- package/es/components/Tooltip/index.d.ts +3 -2
- package/es/internal/FloatingMenu.js +6 -5
- package/es/internal/OptimizedResize.js +4 -5
- package/es/tools/events.d.ts +1 -1
- package/lib/components/ButtonSet/ButtonSet.d.ts +5 -0
- package/lib/components/ButtonSet/ButtonSet.js +67 -3
- package/lib/components/CodeSnippet/CodeSnippet.d.ts +1 -1
- package/lib/components/Copy/Copy.d.ts +1 -1
- package/lib/components/CopyButton/CopyButton.d.ts +1 -1
- package/lib/components/DataTable/DataTable.d.ts +2 -1
- package/lib/components/DataTable/TableContainer.d.ts +10 -2
- package/lib/components/DataTable/TableContainer.js +15 -3
- package/lib/components/DataTable/state/sorting.d.ts +2 -4
- package/lib/components/DatePicker/DatePicker.d.ts +3 -2
- package/lib/components/DatePicker/DatePicker.js +18 -135
- package/lib/components/DatePicker/DatePickerLocales.d.ts +12 -0
- package/lib/components/DatePicker/DatePickerLocales.js +137 -0
- package/lib/components/DatePickerInput/DatePickerInput.js +49 -27
- package/lib/components/Dropdown/Dropdown.js +13 -1
- package/lib/components/FileUploader/FileUploader.d.ts +23 -8
- package/lib/components/FileUploader/FileUploader.js +53 -33
- package/lib/components/FileUploader/FileUploaderButton.js +2 -2
- package/lib/components/FileUploader/FileUploaderDropContainer.d.ts +13 -2
- package/lib/components/FileUploader/FileUploaderDropContainer.js +15 -6
- package/lib/components/FileUploader/FileUploaderItem.js +9 -6
- package/lib/components/Link/Link.js +1 -1
- package/lib/components/Menu/index.d.ts +4 -3
- package/lib/components/MultiSelect/FilterableMultiSelect.js +1 -0
- package/lib/components/MultiSelect/MultiSelect.js +1 -0
- package/lib/components/NumberInput/NumberInput.d.ts +6 -2
- package/lib/components/NumberInput/NumberInput.js +17 -5
- package/lib/components/Pagination/Pagination.js +5 -5
- package/lib/components/Popover/index.d.ts +1 -1
- package/lib/components/Popover/index.js +27 -13
- package/lib/components/Select/Select.js +27 -33
- package/lib/components/Tabs/Tabs.js +1 -1
- package/lib/components/TextArea/TextArea.js +14 -4
- package/lib/components/TextInput/PasswordInput.js +3 -2
- package/lib/components/Toggletip/index.d.ts +1 -0
- package/lib/components/Toggletip/index.js +3 -0
- package/lib/components/Tooltip/index.d.ts +3 -2
- package/lib/internal/FloatingMenu.js +6 -5
- package/lib/internal/OptimizedResize.js +4 -5
- package/lib/tools/events.d.ts +1 -1
- package/package.json +21 -29
- package/telemetry.yml +3 -0
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* LICENSE file in the root directory of this source tree.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import { WarningFilled, WarningAltFilled
|
|
8
|
+
import { Calendar, WarningFilled, WarningAltFilled } from '@carbon/icons-react';
|
|
9
9
|
import { warning } from '../../internal/warning.js';
|
|
10
10
|
import cx from 'classnames';
|
|
11
11
|
import PropTypes from 'prop-types';
|
|
@@ -19,6 +19,7 @@ import '../Text/TextDirection.js';
|
|
|
19
19
|
import { deprecate } from '../../prop-types/deprecate.js';
|
|
20
20
|
import { AILabel } from '../AILabel/index.js';
|
|
21
21
|
import { isComponentElement } from '../../internal/utils.js';
|
|
22
|
+
import { useNormalizedInputProps } from '../../internal/useNormalizedInputProps.js';
|
|
22
23
|
|
|
23
24
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- https://github.com/carbon-design-system/carbon/issues/20452
|
|
24
25
|
|
|
@@ -41,8 +42,9 @@ const DatePickerInput = /*#__PURE__*/React.forwardRef(function DatePickerInput(p
|
|
|
41
42
|
size = 'md',
|
|
42
43
|
slug,
|
|
43
44
|
type = 'text',
|
|
44
|
-
warn,
|
|
45
|
+
warn = false,
|
|
45
46
|
warnText,
|
|
47
|
+
readOnly,
|
|
46
48
|
...rest
|
|
47
49
|
} = props;
|
|
48
50
|
const prefix = usePrefix();
|
|
@@ -50,6 +52,15 @@ const DatePickerInput = /*#__PURE__*/React.forwardRef(function DatePickerInput(p
|
|
|
50
52
|
isFluid
|
|
51
53
|
} = useContext(FormContext);
|
|
52
54
|
const datePickerInputInstanceId = useId();
|
|
55
|
+
const normalizedProps = useNormalizedInputProps({
|
|
56
|
+
id,
|
|
57
|
+
readOnly,
|
|
58
|
+
disabled,
|
|
59
|
+
invalid,
|
|
60
|
+
invalidText,
|
|
61
|
+
warn,
|
|
62
|
+
warnText
|
|
63
|
+
});
|
|
53
64
|
const datePickerInputProps = {
|
|
54
65
|
id,
|
|
55
66
|
onChange: event => {
|
|
@@ -71,28 +82,28 @@ const DatePickerInput = /*#__PURE__*/React.forwardRef(function DatePickerInput(p
|
|
|
71
82
|
didWarnAboutDatePickerInputValue = true;
|
|
72
83
|
}
|
|
73
84
|
const wrapperClasses = cx(`${prefix}--date-picker-input__wrapper`, {
|
|
74
|
-
[`${prefix}--date-picker-input__wrapper--invalid`]: invalid,
|
|
75
|
-
[`${prefix}--date-picker-input__wrapper--warn`]: warn,
|
|
85
|
+
[`${prefix}--date-picker-input__wrapper--invalid`]: normalizedProps.invalid,
|
|
86
|
+
[`${prefix}--date-picker-input__wrapper--warn`]: normalizedProps.warn,
|
|
76
87
|
[`${prefix}--date-picker-input__wrapper--slug`]: slug,
|
|
77
88
|
[`${prefix}--date-picker-input__wrapper--decorator`]: decorator
|
|
78
89
|
});
|
|
79
90
|
const labelClasses = cx(`${prefix}--label`, {
|
|
80
91
|
[`${prefix}--visually-hidden`]: hideLabel,
|
|
81
|
-
[`${prefix}--label--disabled`]: disabled,
|
|
82
|
-
[`${prefix}--label--readonly`]:
|
|
92
|
+
[`${prefix}--label--disabled`]: normalizedProps.disabled,
|
|
93
|
+
[`${prefix}--label--readonly`]: readOnly
|
|
83
94
|
});
|
|
84
95
|
const helperTextClasses = cx(`${prefix}--form__helper-text`, {
|
|
85
|
-
[`${prefix}--form__helper-text--disabled`]: disabled
|
|
96
|
+
[`${prefix}--form__helper-text--disabled`]: normalizedProps.disabled
|
|
86
97
|
});
|
|
87
98
|
const inputClasses = cx(`${prefix}--date-picker__input`, {
|
|
88
99
|
[`${prefix}--date-picker__input--${size}`]: size,
|
|
89
|
-
[`${prefix}--date-picker__input--invalid`]: invalid,
|
|
90
|
-
[`${prefix}--date-picker__input--warn`]: warn
|
|
100
|
+
[`${prefix}--date-picker__input--invalid`]: normalizedProps.invalid,
|
|
101
|
+
[`${prefix}--date-picker__input--warn`]: normalizedProps.warn
|
|
91
102
|
});
|
|
92
103
|
const containerClasses = cx(`${prefix}--date-picker-container`, {
|
|
93
104
|
[`${prefix}--date-picker--nolabel`]: !labelText,
|
|
94
|
-
[`${prefix}--date-picker--fluid--invalid`]: isFluid && invalid,
|
|
95
|
-
[`${prefix}--date-picker--fluid--warn`]: isFluid && warn
|
|
105
|
+
[`${prefix}--date-picker--fluid--invalid`]: isFluid && normalizedProps.invalid,
|
|
106
|
+
[`${prefix}--date-picker--fluid--warn`]: isFluid && normalizedProps.warn
|
|
96
107
|
});
|
|
97
108
|
const datePickerInputHelperId = !helperText ? undefined : `datepicker-input-helper-text-${datePickerInputInstanceId}`;
|
|
98
109
|
|
|
@@ -101,11 +112,11 @@ const DatePickerInput = /*#__PURE__*/React.forwardRef(function DatePickerInput(p
|
|
|
101
112
|
...rest,
|
|
102
113
|
...datePickerInputProps,
|
|
103
114
|
className: inputClasses,
|
|
104
|
-
disabled,
|
|
115
|
+
disabled: normalizedProps.disabled,
|
|
105
116
|
ref,
|
|
106
117
|
['aria-describedby']: helperText ? datePickerInputHelperId : undefined
|
|
107
118
|
};
|
|
108
|
-
if (invalid) {
|
|
119
|
+
if (normalizedProps.invalid) {
|
|
109
120
|
inputProps['data-invalid'] = true;
|
|
110
121
|
}
|
|
111
122
|
const input = /*#__PURE__*/React.createElement("input", inputProps);
|
|
@@ -130,19 +141,11 @@ const DatePickerInput = /*#__PURE__*/React.forwardRef(function DatePickerInput(p
|
|
|
130
141
|
datePickerType: datePickerType
|
|
131
142
|
}), /*#__PURE__*/React.createElement(DatePickerIcon, {
|
|
132
143
|
datePickerType: datePickerType,
|
|
133
|
-
invalid: invalid,
|
|
134
|
-
warn: warn
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
}), /*#__PURE__*/React.createElement(Text, {
|
|
138
|
-
as: "div",
|
|
139
|
-
className: `${prefix}--form-requirement`
|
|
140
|
-
}, invalidText)), warn && !invalid && /*#__PURE__*/React.createElement(React.Fragment, null, isFluid && /*#__PURE__*/React.createElement("hr", {
|
|
141
|
-
className: `${prefix}--date-picker__divider`
|
|
142
|
-
}), /*#__PURE__*/React.createElement(Text, {
|
|
143
|
-
as: "div",
|
|
144
|
-
className: `${prefix}--form-requirement`
|
|
145
|
-
}, warnText)), helperText && !invalid && /*#__PURE__*/React.createElement(Text, {
|
|
144
|
+
invalid: normalizedProps.invalid,
|
|
145
|
+
warn: normalizedProps.warn,
|
|
146
|
+
disabled: normalizedProps.disabled,
|
|
147
|
+
readOnly: readOnly
|
|
148
|
+
}))), normalizedProps.validation, helperText && !normalizedProps.invalid && !normalizedProps.warn && /*#__PURE__*/React.createElement(Text, {
|
|
146
149
|
as: "div",
|
|
147
150
|
id: datePickerInputHelperId,
|
|
148
151
|
className: helperTextClasses
|
|
@@ -243,7 +246,9 @@ DatePickerInput.propTypes = {
|
|
|
243
246
|
function DatePickerIcon({
|
|
244
247
|
datePickerType,
|
|
245
248
|
invalid,
|
|
246
|
-
warn
|
|
249
|
+
warn,
|
|
250
|
+
disabled,
|
|
251
|
+
readOnly
|
|
247
252
|
}) {
|
|
248
253
|
const prefix = usePrefix();
|
|
249
254
|
const {
|
|
@@ -254,6 +259,15 @@ function DatePickerIcon({
|
|
|
254
259
|
return null;
|
|
255
260
|
}
|
|
256
261
|
}
|
|
262
|
+
|
|
263
|
+
// Don't show invalid/warn icons when disabled or readonly
|
|
264
|
+
if (disabled || readOnly) {
|
|
265
|
+
return /*#__PURE__*/React.createElement(Calendar, {
|
|
266
|
+
className: `${prefix}--date-picker__icon`,
|
|
267
|
+
role: "img",
|
|
268
|
+
"aria-hidden": "true"
|
|
269
|
+
});
|
|
270
|
+
}
|
|
257
271
|
if (invalid) {
|
|
258
272
|
return /*#__PURE__*/React.createElement(WarningFilled, {
|
|
259
273
|
className: `${prefix}--date-picker__icon ${prefix}--date-picker__icon--invalid`
|
|
@@ -286,7 +300,15 @@ DatePickerIcon.propTypes = {
|
|
|
286
300
|
/**
|
|
287
301
|
* Specify whether the control is currently in warning state
|
|
288
302
|
*/
|
|
289
|
-
warn: PropTypes.bool
|
|
303
|
+
warn: PropTypes.bool,
|
|
304
|
+
/**
|
|
305
|
+
* Specify whether or not the input should be disabled
|
|
306
|
+
*/
|
|
307
|
+
disabled: PropTypes.bool,
|
|
308
|
+
/**
|
|
309
|
+
* Specify whether the input is readonly
|
|
310
|
+
*/
|
|
311
|
+
readOnly: PropTypes.bool
|
|
290
312
|
};
|
|
291
313
|
|
|
292
314
|
export { DatePickerInput as default };
|
|
@@ -27,7 +27,9 @@ import { ListBoxTypePropType, ListBoxSizePropType } from '../ListBox/ListBoxProp
|
|
|
27
27
|
|
|
28
28
|
const {
|
|
29
29
|
ItemMouseMove,
|
|
30
|
-
MenuMouseLeave
|
|
30
|
+
MenuMouseLeave,
|
|
31
|
+
ToggleButtonBlur,
|
|
32
|
+
FunctionCloseMenu
|
|
31
33
|
} = useSelect.stateChangeTypes;
|
|
32
34
|
/**
|
|
33
35
|
* Custom state reducer for `useSelect` in Downshift, providing control over
|
|
@@ -61,6 +63,12 @@ function stateReducer(state, actionAndChanges) {
|
|
|
61
63
|
return state;
|
|
62
64
|
}
|
|
63
65
|
return changes;
|
|
66
|
+
case ToggleButtonBlur:
|
|
67
|
+
case FunctionCloseMenu:
|
|
68
|
+
return {
|
|
69
|
+
...changes,
|
|
70
|
+
selectedItem: state.selectedItem
|
|
71
|
+
};
|
|
64
72
|
default:
|
|
65
73
|
return changes;
|
|
66
74
|
}
|
|
@@ -334,7 +342,11 @@ const Dropdown = /*#__PURE__*/React.forwardRef(({
|
|
|
334
342
|
size: size$1,
|
|
335
343
|
className: className,
|
|
336
344
|
invalid: normalizedProps.invalid,
|
|
345
|
+
invalidText: isFluid ? invalidText : undefined,
|
|
346
|
+
invalidTextId: normalizedProps.invalidId,
|
|
337
347
|
warn: normalizedProps.warn,
|
|
348
|
+
warnText: isFluid ? warnText : undefined,
|
|
349
|
+
warnTextId: normalizedProps.warnId,
|
|
338
350
|
light: light,
|
|
339
351
|
isOpen: isOpen,
|
|
340
352
|
ref: enableFloatingStyles || autoAlign ? refs.setReference : null,
|
|
@@ -8,7 +8,9 @@ import React, { type HTMLAttributes } from 'react';
|
|
|
8
8
|
interface FileItem {
|
|
9
9
|
name: string;
|
|
10
10
|
uuid: string;
|
|
11
|
-
file: File
|
|
11
|
+
file: File & {
|
|
12
|
+
invalidFileType?: boolean;
|
|
13
|
+
};
|
|
12
14
|
}
|
|
13
15
|
export interface FileChangeData {
|
|
14
16
|
addedFiles: FileItem[];
|
|
@@ -58,6 +60,10 @@ export interface FileUploaderProps extends HTMLAttributes<HTMLSpanElement> {
|
|
|
58
60
|
* Specify the title text of this `<FileUploader>`
|
|
59
61
|
*/
|
|
60
62
|
labelTitle?: string;
|
|
63
|
+
/**
|
|
64
|
+
* Maximum file size allowed in bytes. Files larger than this will be marked invalid
|
|
65
|
+
*/
|
|
66
|
+
maxFileSize?: number;
|
|
61
67
|
/**
|
|
62
68
|
* Specify if the component should accept multiple files to upload
|
|
63
69
|
*/
|
|
@@ -66,24 +72,32 @@ export interface FileUploaderProps extends HTMLAttributes<HTMLSpanElement> {
|
|
|
66
72
|
* Provide a name for the underlying `<input>` node
|
|
67
73
|
*/
|
|
68
74
|
name?: string;
|
|
75
|
+
/**
|
|
76
|
+
* Event handler that is called after files are added to the uploader
|
|
77
|
+
*/
|
|
78
|
+
onAddFiles?: (event: React.SyntheticEvent<HTMLElement>, content: {
|
|
79
|
+
addedFiles: Array<File & {
|
|
80
|
+
invalidFileType?: boolean;
|
|
81
|
+
}>;
|
|
82
|
+
}) => void;
|
|
69
83
|
/**
|
|
70
84
|
* Provide an optional `onChange` hook that is called each time the input is changed.
|
|
71
85
|
* When 'enable-enhanced-file-uploader' feature flag is enabled:
|
|
72
86
|
* - Also fires for file deletions and clearFiles operations
|
|
73
87
|
* - Event includes enhanced file information in event.target
|
|
74
88
|
*/
|
|
75
|
-
onChange?: (event:
|
|
89
|
+
onChange?: (event: React.SyntheticEvent<HTMLElement>, data?: FileChangeData) => void;
|
|
76
90
|
/**
|
|
77
91
|
* Provide an optional `onClick` hook that is called each time the
|
|
78
92
|
* FileUploader is clicked
|
|
79
93
|
*/
|
|
80
|
-
onClick?: (event:
|
|
94
|
+
onClick?: (event: React.SyntheticEvent<HTMLElement>) => void;
|
|
81
95
|
/**
|
|
82
96
|
* Provide an optional `onDelete` hook that is called when an uploaded item is removed.
|
|
83
97
|
* When 'enable-enhanced-file-uploader' feature flag is enabled:
|
|
84
98
|
* - Event includes deleted file information in event.target
|
|
85
99
|
*/
|
|
86
|
-
onDelete?: (event:
|
|
100
|
+
onDelete?: (event: React.SyntheticEvent<HTMLElement>, data?: FileDeleteData) => void;
|
|
87
101
|
/**
|
|
88
102
|
* Specify the size of the FileUploaderButton, from a list of available
|
|
89
103
|
* sizes.
|
|
@@ -101,9 +115,10 @@ export interface FileUploaderHandle {
|
|
|
101
115
|
getCurrentFiles?: () => FileItem[];
|
|
102
116
|
}
|
|
103
117
|
declare const FileUploader: {
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
118
|
+
(props: FileUploaderProps): React.ReactElement;
|
|
119
|
+
displayName?: string;
|
|
120
|
+
propTypes?: unknown;
|
|
121
|
+
contextTypes?: unknown;
|
|
122
|
+
defaultProps?: unknown;
|
|
108
123
|
};
|
|
109
124
|
export default FileUploader;
|
|
@@ -20,7 +20,6 @@ import '../Text/TextDirection.js';
|
|
|
20
20
|
import { useId } from '../../internal/useId.js';
|
|
21
21
|
import { useFeatureFlag } from '../FeatureFlags/index.js';
|
|
22
22
|
|
|
23
|
-
// eslint-disable-next-line react/display-name -- https://github.com/carbon-design-system/carbon/issues/20452
|
|
24
23
|
const FileUploader = /*#__PURE__*/React.forwardRef(({
|
|
25
24
|
accept,
|
|
26
25
|
buttonKind,
|
|
@@ -31,8 +30,10 @@ const FileUploader = /*#__PURE__*/React.forwardRef(({
|
|
|
31
30
|
iconDescription,
|
|
32
31
|
labelDescription,
|
|
33
32
|
labelTitle,
|
|
33
|
+
maxFileSize,
|
|
34
34
|
multiple,
|
|
35
35
|
name,
|
|
36
|
+
onAddFiles,
|
|
36
37
|
onChange,
|
|
37
38
|
onClick,
|
|
38
39
|
onDelete,
|
|
@@ -44,20 +45,51 @@ const FileUploader = /*#__PURE__*/React.forwardRef(({
|
|
|
44
45
|
const enhancedFileUploaderEnabled = useFeatureFlag('enable-enhanced-file-uploader');
|
|
45
46
|
const [fileItems, setFileItems] = useState([]);
|
|
46
47
|
const [legacyFileNames, setLegacyFileNames] = useState([]);
|
|
47
|
-
|
|
48
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars -- https://github.com/carbon-design-system/carbon/issues/20452
|
|
49
|
-
const [fileObjects, setFileObjects] = useState(new Map());
|
|
48
|
+
const uploaderButton = /*#__PURE__*/React.createRef();
|
|
50
49
|
const nodes = [];
|
|
51
|
-
const createFileItem = file => ({
|
|
50
|
+
const createFileItem = useCallback(file => ({
|
|
52
51
|
name: file.name,
|
|
53
52
|
uuid: `${fileUploaderInstanceId}-${Date.now()}-${Array.from(crypto.getRandomValues(new Uint8Array(8))).map(b => b.toString(36)).join('')}`,
|
|
54
53
|
file
|
|
55
|
-
});
|
|
54
|
+
}), [fileUploaderInstanceId]);
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Validates files based on file size restrictions.
|
|
58
|
+
* Marks invalid files with `invalidFileType: true` but includes them in the result.
|
|
59
|
+
*
|
|
60
|
+
* Note: The `accept` prop is passed to the native HTML input element (`FileUploaderButton`),
|
|
61
|
+
* which provides UI-level filtering in the file picker dialog, but there is no JavaScript validation
|
|
62
|
+
* for file types - users can bypass this by changing the file type filter in the dialog.
|
|
63
|
+
* https://github.com/carbon-design-system/carbon/issues/21166
|
|
64
|
+
*/
|
|
65
|
+
const validateFiles = useCallback(files => {
|
|
66
|
+
return files.map(file => {
|
|
67
|
+
if (maxFileSize && file.size > maxFileSize) {
|
|
68
|
+
file.invalidFileType = true;
|
|
69
|
+
}
|
|
70
|
+
return file;
|
|
71
|
+
});
|
|
72
|
+
}, [maxFileSize]);
|
|
56
73
|
const handleChange = useCallback(evt => {
|
|
57
74
|
evt.stopPropagation();
|
|
58
|
-
const
|
|
75
|
+
const incomingFiles = Array.from(evt.target.files);
|
|
76
|
+
const filesToValidate = multiple ? incomingFiles : [incomingFiles[0]];
|
|
77
|
+
const validatedFiles = validateFiles(filesToValidate);
|
|
78
|
+
if (onAddFiles) {
|
|
79
|
+
onAddFiles(evt, {
|
|
80
|
+
addedFiles: validatedFiles
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Filter out invalid files since FileUploader cannot display them
|
|
85
|
+
// (FileUploaderDropContainer returns all files because parent uses FileUploaderItem to display errors)
|
|
86
|
+
// https://github.com/carbon-design-system/carbon/issues/21166
|
|
87
|
+
const validFiles = validatedFiles.filter(file => !file.invalidFileType);
|
|
88
|
+
if (validFiles.length === 0) {
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
59
91
|
if (enhancedFileUploaderEnabled) {
|
|
60
|
-
const newFileItems =
|
|
92
|
+
const newFileItems = validFiles.map(createFileItem);
|
|
61
93
|
let updatedFileItems;
|
|
62
94
|
if (multiple) {
|
|
63
95
|
const existingNames = new Set(fileItems.map(item => item.name));
|
|
@@ -84,23 +116,14 @@ const FileUploader = /*#__PURE__*/React.forwardRef(({
|
|
|
84
116
|
onChange(enhancedEvent);
|
|
85
117
|
}
|
|
86
118
|
} else {
|
|
87
|
-
const filenames =
|
|
119
|
+
const filenames = validFiles.map(file => file.name);
|
|
88
120
|
const updatedFileNames = multiple ? [...new Set([...legacyFileNames, ...filenames])] : filenames;
|
|
89
121
|
setLegacyFileNames(updatedFileNames);
|
|
90
|
-
setFileObjects(prevMap => {
|
|
91
|
-
const newMap = multiple ? new Map(prevMap) : new Map();
|
|
92
|
-
newFiles.forEach(file => {
|
|
93
|
-
newMap.set(file.name, file);
|
|
94
|
-
});
|
|
95
|
-
return newMap;
|
|
96
|
-
});
|
|
97
122
|
if (onChange) {
|
|
98
123
|
onChange(evt);
|
|
99
124
|
}
|
|
100
125
|
}
|
|
101
|
-
},
|
|
102
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps -- https://github.com/carbon-design-system/carbon/issues/20452
|
|
103
|
-
[enhancedFileUploaderEnabled, fileItems, legacyFileNames, multiple, onChange]);
|
|
126
|
+
}, [enhancedFileUploaderEnabled, fileItems, legacyFileNames, multiple, onAddFiles, onChange, createFileItem, validateFiles]);
|
|
104
127
|
const handleClick = useCallback((evt, {
|
|
105
128
|
index,
|
|
106
129
|
filenameStatus
|
|
@@ -137,15 +160,6 @@ const FileUploader = /*#__PURE__*/React.forwardRef(({
|
|
|
137
160
|
const deletedFileName = legacyFileNames[index];
|
|
138
161
|
const filteredArray = legacyFileNames.filter(filename => filename !== deletedFileName);
|
|
139
162
|
setLegacyFileNames(filteredArray);
|
|
140
|
-
|
|
141
|
-
// Update File objects
|
|
142
|
-
setFileObjects(prevMap => {
|
|
143
|
-
const newMap = new Map(prevMap);
|
|
144
|
-
if (deletedFileName) {
|
|
145
|
-
newMap.delete(deletedFileName);
|
|
146
|
-
}
|
|
147
|
-
return newMap;
|
|
148
|
-
});
|
|
149
163
|
if (onDelete) {
|
|
150
164
|
onDelete(evt);
|
|
151
165
|
}
|
|
@@ -155,9 +169,7 @@ const FileUploader = /*#__PURE__*/React.forwardRef(({
|
|
|
155
169
|
}
|
|
156
170
|
uploaderButton.current?.focus?.();
|
|
157
171
|
}
|
|
158
|
-
},
|
|
159
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps -- https://github.com/carbon-design-system/carbon/issues/20452
|
|
160
|
-
[enhancedFileUploaderEnabled, fileItems, legacyFileNames, onDelete, onChange, onClick]);
|
|
172
|
+
}, [enhancedFileUploaderEnabled, fileItems, legacyFileNames, onDelete, onChange, onClick, uploaderButton]);
|
|
161
173
|
useImperativeHandle(ref, () => ({
|
|
162
174
|
clearFiles() {
|
|
163
175
|
if (enhancedFileUploaderEnabled) {
|
|
@@ -180,7 +192,6 @@ const FileUploader = /*#__PURE__*/React.forwardRef(({
|
|
|
180
192
|
}
|
|
181
193
|
} else {
|
|
182
194
|
setLegacyFileNames([]);
|
|
183
|
-
setFileObjects(new Map());
|
|
184
195
|
}
|
|
185
196
|
},
|
|
186
197
|
...(enhancedFileUploaderEnabled && {
|
|
@@ -189,7 +200,6 @@ const FileUploader = /*#__PURE__*/React.forwardRef(({
|
|
|
189
200
|
}
|
|
190
201
|
})
|
|
191
202
|
}), [enhancedFileUploaderEnabled, fileItems, onChange]);
|
|
192
|
-
const uploaderButton = /*#__PURE__*/React.createRef();
|
|
193
203
|
const classes = cx({
|
|
194
204
|
[`${prefix}--form-item`]: true,
|
|
195
205
|
[className]: className
|
|
@@ -263,6 +273,7 @@ const FileUploader = /*#__PURE__*/React.forwardRef(({
|
|
|
263
273
|
})
|
|
264
274
|
}))))));
|
|
265
275
|
});
|
|
276
|
+
FileUploader.displayName = 'FileUploader';
|
|
266
277
|
FileUploader.propTypes = {
|
|
267
278
|
/**
|
|
268
279
|
* Specify the types of files that this input should be able to receive
|
|
@@ -301,6 +312,10 @@ FileUploader.propTypes = {
|
|
|
301
312
|
* Specify the title text of this `<FileUploader>`
|
|
302
313
|
*/
|
|
303
314
|
labelTitle: PropTypes.string,
|
|
315
|
+
/**
|
|
316
|
+
* Maximum file size allowed in bytes. Files larger than this will be marked invalid
|
|
317
|
+
*/
|
|
318
|
+
maxFileSize: PropTypes.number,
|
|
304
319
|
/**
|
|
305
320
|
* Specify if the component should accept multiple files to upload
|
|
306
321
|
*/
|
|
@@ -309,6 +324,11 @@ FileUploader.propTypes = {
|
|
|
309
324
|
* Provide a name for the underlying `<input>` node
|
|
310
325
|
*/
|
|
311
326
|
name: PropTypes.string,
|
|
327
|
+
/**
|
|
328
|
+
* Event handler that is called after files are added to the uploader
|
|
329
|
+
* The event handler signature looks like `onAddFiles(evt, { addedFiles })`
|
|
330
|
+
*/
|
|
331
|
+
onAddFiles: PropTypes.func,
|
|
312
332
|
/**
|
|
313
333
|
* Provide an optional `onChange` hook that is called each time the input is
|
|
314
334
|
* changed
|
|
@@ -36,10 +36,10 @@ function FileUploaderButton({
|
|
|
36
36
|
const prefix = usePrefix();
|
|
37
37
|
const [labelText, setLabelText] = useState(ownerLabelText);
|
|
38
38
|
const [prevOwnerLabelText, setPrevOwnerLabelText] = useState(ownerLabelText);
|
|
39
|
-
|
|
39
|
+
const generatedId = useId();
|
|
40
40
|
const {
|
|
41
41
|
current: inputId
|
|
42
|
-
} = useRef(id ||
|
|
42
|
+
} = useRef(id || generatedId);
|
|
43
43
|
const inputNode = useRef(null);
|
|
44
44
|
const classes = cx(`${prefix}--btn`, className, {
|
|
45
45
|
[`${prefix}--btn--${buttonKind}`]: buttonKind,
|
|
@@ -28,6 +28,10 @@ export interface FileUploaderDropContainerProps extends Omit<HTMLAttributes<HTML
|
|
|
28
28
|
* this control
|
|
29
29
|
*/
|
|
30
30
|
labelText?: string;
|
|
31
|
+
/**
|
|
32
|
+
* Maximum file size allowed in bytes. Files larger than this will be marked invalid
|
|
33
|
+
*/
|
|
34
|
+
maxFileSize?: number;
|
|
31
35
|
/**
|
|
32
36
|
* Specify if the component should accept multiple files to upload
|
|
33
37
|
*/
|
|
@@ -40,7 +44,7 @@ export interface FileUploaderDropContainerProps extends Omit<HTMLAttributes<HTML
|
|
|
40
44
|
* Event handler that is called after files are added to the uploader
|
|
41
45
|
*/
|
|
42
46
|
onAddFiles?: (event: React.SyntheticEvent<HTMLElement>, content: {
|
|
43
|
-
addedFiles:
|
|
47
|
+
addedFiles: AddedFile[];
|
|
44
48
|
}) => void;
|
|
45
49
|
/**
|
|
46
50
|
* Provide an optional function to be called when the button element
|
|
@@ -64,7 +68,10 @@ export interface FileUploaderDropContainerProps extends Omit<HTMLAttributes<HTML
|
|
|
64
68
|
*/
|
|
65
69
|
tabIndex?: number | string;
|
|
66
70
|
}
|
|
67
|
-
|
|
71
|
+
interface AddedFile extends File {
|
|
72
|
+
invalidFileType?: boolean;
|
|
73
|
+
}
|
|
74
|
+
declare function FileUploaderDropContainer({ accept, className, id, disabled, labelText, maxFileSize, multiple, name, onAddFiles, onClick, pattern, innerRef, ...rest }: FileUploaderDropContainerProps): import("react/jsx-runtime").JSX.Element;
|
|
68
75
|
declare namespace FileUploaderDropContainer {
|
|
69
76
|
var propTypes: {
|
|
70
77
|
/**
|
|
@@ -88,6 +95,10 @@ declare namespace FileUploaderDropContainer {
|
|
|
88
95
|
* this control
|
|
89
96
|
*/
|
|
90
97
|
labelText: PropTypes.Validator<string>;
|
|
98
|
+
/**
|
|
99
|
+
* Maximum file size allowed in bytes. Files larger than this will be marked invalid
|
|
100
|
+
*/
|
|
101
|
+
maxFileSize: PropTypes.Requireable<number>;
|
|
91
102
|
/**
|
|
92
103
|
* Specify if the component should accept multiple files to upload
|
|
93
104
|
*/
|
|
@@ -23,6 +23,7 @@ function FileUploaderDropContainer({
|
|
|
23
23
|
id,
|
|
24
24
|
disabled,
|
|
25
25
|
labelText = 'Add file',
|
|
26
|
+
maxFileSize,
|
|
26
27
|
multiple = false,
|
|
27
28
|
name,
|
|
28
29
|
onAddFiles = noopFn,
|
|
@@ -33,10 +34,10 @@ function FileUploaderDropContainer({
|
|
|
33
34
|
}) {
|
|
34
35
|
const prefix = usePrefix();
|
|
35
36
|
const inputRef = useRef(null);
|
|
36
|
-
|
|
37
|
+
const generatedId = useId();
|
|
37
38
|
const {
|
|
38
39
|
current: uid
|
|
39
|
-
} = useRef(id ||
|
|
40
|
+
} = useRef(id || generatedId);
|
|
40
41
|
const [isActive, setActive] = useState(false);
|
|
41
42
|
const dropareaClasses = cx(`${prefix}--file__drop-container`, `${prefix}--file-browse-btn`, {
|
|
42
43
|
[`${prefix}--file__drop-container--drag-over`]: isActive,
|
|
@@ -44,12 +45,9 @@ function FileUploaderDropContainer({
|
|
|
44
45
|
}, className);
|
|
45
46
|
|
|
46
47
|
/**
|
|
47
|
-
* Filters the array of added files based on file type restrictions
|
|
48
|
+
* Filters the array of added files based on file type and size restrictions
|
|
48
49
|
*/
|
|
49
50
|
function validateFiles(transferredFiles) {
|
|
50
|
-
if (!accept.length) {
|
|
51
|
-
return transferredFiles;
|
|
52
|
-
}
|
|
53
51
|
const acceptedTypes = new Set(accept);
|
|
54
52
|
return transferredFiles.reduce((acc, curr) => {
|
|
55
53
|
const {
|
|
@@ -58,6 +56,13 @@ function FileUploaderDropContainer({
|
|
|
58
56
|
} = curr;
|
|
59
57
|
const fileExtensionRegExp = new RegExp(pattern, 'i');
|
|
60
58
|
const [fileExtension] = name.match(fileExtensionRegExp) ?? [];
|
|
59
|
+
if (maxFileSize && curr.size > maxFileSize) {
|
|
60
|
+
curr.invalidFileType = true;
|
|
61
|
+
return acc.concat([curr]);
|
|
62
|
+
}
|
|
63
|
+
if (!accept.length) {
|
|
64
|
+
return acc.concat([curr]);
|
|
65
|
+
}
|
|
61
66
|
if (fileExtension === undefined) {
|
|
62
67
|
return acc;
|
|
63
68
|
}
|
|
@@ -170,6 +175,10 @@ FileUploaderDropContainer.propTypes = {
|
|
|
170
175
|
* this control
|
|
171
176
|
*/
|
|
172
177
|
labelText: PropTypes.string.isRequired,
|
|
178
|
+
/**
|
|
179
|
+
* Maximum file size allowed in bytes. Files larger than this will be marked invalid
|
|
180
|
+
*/
|
|
181
|
+
maxFileSize: PropTypes.number,
|
|
173
182
|
/**
|
|
174
183
|
* Specify if the component should accept multiple files to upload
|
|
175
184
|
*/
|
|
@@ -36,10 +36,10 @@ function FileUploaderItem({
|
|
|
36
36
|
const textRef = useRef(null);
|
|
37
37
|
const [isEllipsisApplied, setIsEllipsisApplied] = useState(false);
|
|
38
38
|
const prefix = usePrefix();
|
|
39
|
-
|
|
39
|
+
const generatedId = useId();
|
|
40
40
|
const {
|
|
41
41
|
current: id
|
|
42
|
-
} = useRef(uuid ||
|
|
42
|
+
} = useRef(uuid || generatedId);
|
|
43
43
|
const classes = cx(`${prefix}--file__selected-file`, className, {
|
|
44
44
|
[`${prefix}--file__selected-file--invalid`]: invalid,
|
|
45
45
|
[`${prefix}--file__selected-file--md`]: size === 'md',
|
|
@@ -49,11 +49,14 @@ function FileUploaderItem({
|
|
|
49
49
|
const filterSpaceName = name => {
|
|
50
50
|
return name?.replace(/\s+/g, '');
|
|
51
51
|
};
|
|
52
|
-
|
|
53
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- https://github.com/carbon-design-system/carbon/issues/20452
|
|
54
52
|
const isEllipsisActive = element => {
|
|
55
|
-
|
|
56
|
-
|
|
53
|
+
if (!element) {
|
|
54
|
+
setIsEllipsisApplied(false);
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
const isActive = element.offsetWidth < element.scrollWidth;
|
|
58
|
+
setIsEllipsisApplied(isActive);
|
|
59
|
+
return isActive;
|
|
57
60
|
};
|
|
58
61
|
useLayoutEffect(() => {
|
|
59
62
|
isEllipsisActive(textRef.current);
|
|
@@ -67,7 +67,7 @@ const LinkBase = /*#__PURE__*/React.forwardRef(({
|
|
|
67
67
|
ref: ref
|
|
68
68
|
}, linkProps, rest, {
|
|
69
69
|
onClick: handleOnClick
|
|
70
|
-
}), children, !inline && Icon && /*#__PURE__*/React.createElement("
|
|
70
|
+
}), children, !inline && Icon && /*#__PURE__*/React.createElement("span", {
|
|
71
71
|
className: `${prefix}--link__icon`
|
|
72
72
|
}, /*#__PURE__*/React.createElement(Icon, null)));
|
|
73
73
|
});
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* This source code is licensed under the Apache-2.0 license found in the
|
|
5
5
|
* LICENSE file in the root directory of this source tree.
|
|
6
6
|
*/
|
|
7
|
-
import { Menu } from './Menu';
|
|
8
|
-
import { MenuItem, MenuItemDivider, MenuItemGroup, MenuItemRadioGroup, MenuItemSelectable } from './MenuItem';
|
|
9
|
-
export { Menu, MenuItem, MenuItemDivider, MenuItemGroup, MenuItemRadioGroup, MenuItemSelectable, };
|
|
7
|
+
import { Menu, type MenuProps } from './Menu';
|
|
8
|
+
import { MenuItem, MenuItemDivider, MenuItemGroup, MenuItemRadioGroup, MenuItemSelectable, type MenuItemProps, type MenuItemDividerProps, type MenuItemGroupProps, type MenuItemRadioGroupProps, type MenuItemSelectableProps } from './MenuItem';
|
|
9
|
+
export { Menu, MenuItem, MenuItemDivider, MenuItemGroup, MenuItemRadioGroup, MenuItemSelectable, type MenuProps, type MenuItemProps, type MenuItemDividerProps, type MenuItemGroupProps, type MenuItemRadioGroupProps, type MenuItemSelectableProps, };
|
|
10
|
+
export default Menu;
|
|
@@ -724,6 +724,7 @@ const FilterableMultiSelect = /*#__PURE__*/forwardRef(function FilterableMultiSe
|
|
|
724
724
|
return /*#__PURE__*/React.createElement(ListBox.MenuItem, _extends({
|
|
725
725
|
key: itemProps.id,
|
|
726
726
|
"aria-label": itemText,
|
|
727
|
+
"aria-checked": isIndeterminate ? 'mixed' : isChecked,
|
|
727
728
|
isActive: isChecked && !item['isSelectAll'],
|
|
728
729
|
isHighlighted: highlightedIndex === index,
|
|
729
730
|
title: itemText,
|
|
@@ -509,6 +509,7 @@ const MultiSelect = /*#__PURE__*/React.forwardRef(({
|
|
|
509
509
|
key: itemProps.id,
|
|
510
510
|
isActive: isChecked && !item['isSelectAll'],
|
|
511
511
|
"aria-label": itemText,
|
|
512
|
+
"aria-checked": isIndeterminate ? 'mixed' : isChecked,
|
|
512
513
|
isHighlighted: highlightedIndex === index,
|
|
513
514
|
title: itemText,
|
|
514
515
|
disabled: itemProps['aria-disabled']
|
|
@@ -135,10 +135,14 @@ export interface NumberInputProps extends Omit<React.InputHTMLAttributes<HTMLInp
|
|
|
135
135
|
*/
|
|
136
136
|
min?: number;
|
|
137
137
|
/**
|
|
138
|
-
* Provide an optional handler that is called when the input
|
|
138
|
+
* Provide an optional handler that is called when the input is blurred.
|
|
139
|
+
*/
|
|
140
|
+
onBlur?: (event: React.FocusEvent<HTMLInputElement>, value?: string | number) => void;
|
|
141
|
+
/**
|
|
142
|
+
* Provide an optional handler that is called when the stepper
|
|
139
143
|
* buttons are blurred.
|
|
140
144
|
*/
|
|
141
|
-
|
|
145
|
+
onStepperBlur?: (event: React.FocusEvent<HTMLButtonElement>) => void;
|
|
142
146
|
/**
|
|
143
147
|
* Provide an optional handler that is called when the internal state of
|
|
144
148
|
* NumberInput changes. This handler is called with event and state info.
|