@carbon/react 1.97.0 → 1.98.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.
Files changed (63) hide show
  1. package/.playwright/INTERNAL_AVT_REPORT_DO_NOT_USE.json +974 -939
  2. package/es/components/ButtonSet/ButtonSet.d.ts +5 -0
  3. package/es/components/ButtonSet/ButtonSet.js +68 -4
  4. package/es/components/CodeSnippet/CodeSnippet.d.ts +1 -1
  5. package/es/components/Copy/Copy.d.ts +1 -1
  6. package/es/components/CopyButton/CopyButton.d.ts +1 -1
  7. package/es/components/DataTable/DataTable.d.ts +2 -1
  8. package/es/components/DataTable/TableContainer.d.ts +10 -2
  9. package/es/components/DataTable/TableContainer.js +15 -3
  10. package/es/components/DataTable/state/sorting.d.ts +2 -4
  11. package/es/components/DatePicker/DatePicker.d.ts +3 -2
  12. package/es/components/DatePicker/DatePicker.js +18 -135
  13. package/es/components/DatePicker/DatePickerLocales.d.ts +12 -0
  14. package/es/components/DatePicker/DatePickerLocales.js +135 -0
  15. package/es/components/DatePickerInput/DatePickerInput.js +50 -28
  16. package/es/components/Dropdown/Dropdown.js +9 -1
  17. package/es/components/FileUploader/FileUploader.d.ts +23 -8
  18. package/es/components/FileUploader/FileUploader.js +53 -33
  19. package/es/components/FileUploader/FileUploaderButton.js +2 -2
  20. package/es/components/FileUploader/FileUploaderDropContainer.d.ts +13 -2
  21. package/es/components/FileUploader/FileUploaderDropContainer.js +15 -6
  22. package/es/components/FileUploader/FileUploaderItem.js +9 -6
  23. package/es/components/Menu/index.d.ts +4 -3
  24. package/es/components/Pagination/Pagination.js +5 -5
  25. package/es/components/Select/Select.js +27 -33
  26. package/es/components/Toggletip/index.d.ts +1 -0
  27. package/es/components/Toggletip/index.js +1 -1
  28. package/es/components/Tooltip/index.d.ts +3 -2
  29. package/es/internal/FloatingMenu.js +6 -5
  30. package/es/internal/OptimizedResize.js +4 -5
  31. package/es/tools/events.d.ts +1 -1
  32. package/lib/components/ButtonSet/ButtonSet.d.ts +5 -0
  33. package/lib/components/ButtonSet/ButtonSet.js +67 -3
  34. package/lib/components/CodeSnippet/CodeSnippet.d.ts +1 -1
  35. package/lib/components/Copy/Copy.d.ts +1 -1
  36. package/lib/components/CopyButton/CopyButton.d.ts +1 -1
  37. package/lib/components/DataTable/DataTable.d.ts +2 -1
  38. package/lib/components/DataTable/TableContainer.d.ts +10 -2
  39. package/lib/components/DataTable/TableContainer.js +15 -3
  40. package/lib/components/DataTable/state/sorting.d.ts +2 -4
  41. package/lib/components/DatePicker/DatePicker.d.ts +3 -2
  42. package/lib/components/DatePicker/DatePicker.js +18 -135
  43. package/lib/components/DatePicker/DatePickerLocales.d.ts +12 -0
  44. package/lib/components/DatePicker/DatePickerLocales.js +137 -0
  45. package/lib/components/DatePickerInput/DatePickerInput.js +49 -27
  46. package/lib/components/Dropdown/Dropdown.js +9 -1
  47. package/lib/components/FileUploader/FileUploader.d.ts +23 -8
  48. package/lib/components/FileUploader/FileUploader.js +53 -33
  49. package/lib/components/FileUploader/FileUploaderButton.js +2 -2
  50. package/lib/components/FileUploader/FileUploaderDropContainer.d.ts +13 -2
  51. package/lib/components/FileUploader/FileUploaderDropContainer.js +15 -6
  52. package/lib/components/FileUploader/FileUploaderItem.js +9 -6
  53. package/lib/components/Menu/index.d.ts +4 -3
  54. package/lib/components/Pagination/Pagination.js +5 -5
  55. package/lib/components/Select/Select.js +27 -33
  56. package/lib/components/Toggletip/index.d.ts +1 -0
  57. package/lib/components/Toggletip/index.js +3 -0
  58. package/lib/components/Tooltip/index.d.ts +3 -2
  59. package/lib/internal/FloatingMenu.js +6 -5
  60. package/lib/internal/OptimizedResize.js +4 -5
  61. package/lib/tools/events.d.ts +1 -1
  62. package/package.json +19 -26
  63. package/telemetry.yml +2 -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, Calendar } from '@carbon/icons-react';
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`]: rest.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
- }))), invalid && /*#__PURE__*/React.createElement(React.Fragment, null, isFluid && /*#__PURE__*/React.createElement("hr", {
136
- className: `${prefix}--date-picker__divider`
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
  }
@@ -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: any, data?: FileChangeData) => void;
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: any) => void;
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: any, data?: FileDeleteData) => void;
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
- <ItemType>(props: FileUploaderProps): React.ReactElement<any>;
105
- propTypes?: any;
106
- contextTypes?: any;
107
- defaultProps?: any;
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 newFiles = Array.from(evt.target.files);
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 = newFiles.map(createFileItem);
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 = newFiles.map(file => file.name);
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
- // eslint-disable-next-line react-hooks/rules-of-hooks -- https://github.com/carbon-design-system/carbon/issues/20452
39
+ const generatedId = useId();
40
40
  const {
41
41
  current: inputId
42
- } = useRef(id || useId());
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: File[];
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
- declare function FileUploaderDropContainer({ accept, className, id, disabled, labelText, multiple, name, onAddFiles, onClick, pattern, innerRef, ...rest }: FileUploaderDropContainerProps): import("react/jsx-runtime").JSX.Element;
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
- // eslint-disable-next-line react-hooks/rules-of-hooks -- https://github.com/carbon-design-system/carbon/issues/20452
37
+ const generatedId = useId();
37
38
  const {
38
39
  current: uid
39
- } = useRef(id || useId());
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
- // eslint-disable-next-line react-hooks/rules-of-hooks -- https://github.com/carbon-design-system/carbon/issues/20452
39
+ const generatedId = useId();
40
40
  const {
41
41
  current: id
42
- } = useRef(uuid || useId());
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
- setIsEllipsisApplied(element.offsetWidth < element.scrollWidth);
56
- return element.offsetWidth < element.scrollWidth;
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);
@@ -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;
@@ -7,16 +7,16 @@
7
7
 
8
8
  import { extends as _extends } from '../../_virtual/_rollupPluginBabelHelpers.js';
9
9
  import { CaretLeft, CaretRight } from '@carbon/icons-react';
10
- import cx from 'classnames';
11
- import PropTypes from 'prop-types';
12
10
  import React, { useRef, useState, useEffect } from 'react';
11
+ import { IconButton } from '../IconButton/index.js';
12
+ import PropTypes from 'prop-types';
13
13
  import Select from '../Select/Select.js';
14
14
  import '../Select/Select.Skeleton.js';
15
15
  import SelectItem from '../SelectItem/SelectItem.js';
16
+ import cx from 'classnames';
17
+ import isEqual from 'react-fast-compare';
16
18
  import { useFallbackId } from '../../internal/useId.js';
17
19
  import { usePrefix } from '../../internal/usePrefix.js';
18
- import { IconButton } from '../IconButton/index.js';
19
- import isEqual from 'react-fast-compare';
20
20
 
21
21
  var _CaretLeft, _CaretRight;
22
22
  function mapPageSizesToObject(sizes) {
@@ -266,7 +266,7 @@ const Pagination = /*#__PURE__*/React.forwardRef(({
266
266
  onClick: decrementPage,
267
267
  ref: backBtnRef
268
268
  }, _CaretLeft || (_CaretLeft = /*#__PURE__*/React.createElement(CaretLeft, null))), /*#__PURE__*/React.createElement(IconButton, {
269
- align: "top-end",
269
+ align: "top",
270
270
  disabled: forwardButtonDisabled || isLastPage,
271
271
  kind: "ghost",
272
272
  className: forwardButtonClasses,