@transferwise/components 46.140.1 → 46.141.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 (134) hide show
  1. package/build/avatarWrapper/AvatarWrapper.js +3 -4
  2. package/build/avatarWrapper/AvatarWrapper.js.map +1 -1
  3. package/build/avatarWrapper/AvatarWrapper.mjs +4 -5
  4. package/build/avatarWrapper/AvatarWrapper.mjs.map +1 -1
  5. package/build/button/LegacyButton.js.map +1 -1
  6. package/build/button/LegacyButton.mjs.map +1 -1
  7. package/build/common/hooks/useHasIntersected/useHasIntersected.js +6 -4
  8. package/build/common/hooks/useHasIntersected/useHasIntersected.js.map +1 -1
  9. package/build/common/hooks/useHasIntersected/useHasIntersected.mjs +6 -4
  10. package/build/common/hooks/useHasIntersected/useHasIntersected.mjs.map +1 -1
  11. package/build/common/liveRegion/LiveRegion.js +4 -1
  12. package/build/common/liveRegion/LiveRegion.js.map +1 -1
  13. package/build/common/liveRegion/LiveRegion.mjs +4 -1
  14. package/build/common/liveRegion/LiveRegion.mjs.map +1 -1
  15. package/build/dateInput/DateInput.js +10 -10
  16. package/build/dateInput/DateInput.js.map +1 -1
  17. package/build/dateInput/DateInput.mjs +10 -10
  18. package/build/dateInput/DateInput.mjs.map +1 -1
  19. package/build/dateLookup/monthCalendar/table/MonthCalendarTable.js +1 -1
  20. package/build/dateLookup/monthCalendar/table/MonthCalendarTable.js.map +1 -1
  21. package/build/dateLookup/monthCalendar/table/MonthCalendarTable.mjs +1 -1
  22. package/build/dateLookup/monthCalendar/table/MonthCalendarTable.mjs.map +1 -1
  23. package/build/dateLookup/yearCalendar/table/YearCalendarTable.js +1 -1
  24. package/build/dateLookup/yearCalendar/table/YearCalendarTable.js.map +1 -1
  25. package/build/dateLookup/yearCalendar/table/YearCalendarTable.mjs +1 -1
  26. package/build/dateLookup/yearCalendar/table/YearCalendarTable.mjs.map +1 -1
  27. package/build/expressiveMoneyInput/ExpressiveMoneyInput.js.map +1 -1
  28. package/build/expressiveMoneyInput/ExpressiveMoneyInput.mjs.map +1 -1
  29. package/build/expressiveMoneyInput/amountInput/AmountInput.js +17 -11
  30. package/build/expressiveMoneyInput/amountInput/AmountInput.js.map +1 -1
  31. package/build/expressiveMoneyInput/amountInput/AmountInput.mjs +18 -12
  32. package/build/expressiveMoneyInput/amountInput/AmountInput.mjs.map +1 -1
  33. package/build/expressiveMoneyInput/hooks/useInputStyle.js +8 -6
  34. package/build/expressiveMoneyInput/hooks/useInputStyle.js.map +1 -1
  35. package/build/expressiveMoneyInput/hooks/useInputStyle.mjs +9 -7
  36. package/build/expressiveMoneyInput/hooks/useInputStyle.mjs.map +1 -1
  37. package/build/header/Header.js +1 -1
  38. package/build/header/Header.js.map +1 -1
  39. package/build/header/Header.mjs +1 -1
  40. package/build/header/Header.mjs.map +1 -1
  41. package/build/inputs/SelectInput/BottomSheet/SelectInputBottomSheet.js.map +1 -1
  42. package/build/inputs/SelectInput/BottomSheet/SelectInputBottomSheet.mjs.map +1 -1
  43. package/build/inputs/SelectInput/Options/SelectInputOptions.js +34 -22
  44. package/build/inputs/SelectInput/Options/SelectInputOptions.js.map +1 -1
  45. package/build/inputs/SelectInput/Options/SelectInputOptions.mjs +35 -23
  46. package/build/inputs/SelectInput/Options/SelectInputOptions.mjs.map +1 -1
  47. package/build/inputs/SelectInput/Popover/SelectInputPopover.js.map +1 -1
  48. package/build/inputs/SelectInput/Popover/SelectInputPopover.mjs.map +1 -1
  49. package/build/inputs/SelectInput/SelectInput.js +8 -6
  50. package/build/inputs/SelectInput/SelectInput.js.map +1 -1
  51. package/build/inputs/SelectInput/SelectInput.mjs +9 -7
  52. package/build/inputs/SelectInput/SelectInput.mjs.map +1 -1
  53. package/build/inputs/SelectInput/TriggerButton/SelectInputTriggerButton.js.map +1 -1
  54. package/build/inputs/SelectInput/TriggerButton/SelectInputTriggerButton.mjs.map +1 -1
  55. package/build/nudge/Nudge.js +31 -15
  56. package/build/nudge/Nudge.js.map +1 -1
  57. package/build/nudge/Nudge.mjs +32 -16
  58. package/build/nudge/Nudge.mjs.map +1 -1
  59. package/build/phoneNumberInput/PhoneNumberInput.js +9 -12
  60. package/build/phoneNumberInput/PhoneNumberInput.js.map +1 -1
  61. package/build/phoneNumberInput/PhoneNumberInput.mjs +9 -12
  62. package/build/phoneNumberInput/PhoneNumberInput.mjs.map +1 -1
  63. package/build/promoCard/PromoCardGroup.js +34 -16
  64. package/build/promoCard/PromoCardGroup.js.map +1 -1
  65. package/build/promoCard/PromoCardGroup.mjs +35 -17
  66. package/build/promoCard/PromoCardGroup.mjs.map +1 -1
  67. package/build/segmentedControl/SegmentedControl.js +6 -1
  68. package/build/segmentedControl/SegmentedControl.js.map +1 -1
  69. package/build/segmentedControl/SegmentedControl.mjs +7 -2
  70. package/build/segmentedControl/SegmentedControl.mjs.map +1 -1
  71. package/build/tabs/Tabs.js +1 -1
  72. package/build/tabs/Tabs.js.map +1 -1
  73. package/build/tabs/Tabs.mjs +1 -1
  74. package/build/tabs/Tabs.mjs.map +1 -1
  75. package/build/tooltip/Tooltip.js +6 -3
  76. package/build/tooltip/Tooltip.js.map +1 -1
  77. package/build/tooltip/Tooltip.mjs +6 -3
  78. package/build/tooltip/Tooltip.mjs.map +1 -1
  79. package/build/types/avatarWrapper/AvatarWrapper.d.ts.map +1 -1
  80. package/build/types/common/hooks/useHasIntersected/useHasIntersected.d.ts.map +1 -1
  81. package/build/types/common/liveRegion/LiveRegion.d.ts.map +1 -1
  82. package/build/types/dateLookup/monthCalendar/table/MonthCalendarTable.d.ts.map +1 -1
  83. package/build/types/expressiveMoneyInput/ExpressiveMoneyInput.d.ts.map +1 -1
  84. package/build/types/expressiveMoneyInput/amountInput/AmountInput.d.ts.map +1 -1
  85. package/build/types/expressiveMoneyInput/hooks/useInputStyle.d.ts +2 -2
  86. package/build/types/expressiveMoneyInput/hooks/useInputStyle.d.ts.map +1 -1
  87. package/build/types/expressiveMoneyInput/hooks/useSelectionRange.d.ts.map +1 -1
  88. package/build/types/inputs/SelectInput/BottomSheet/SelectInputBottomSheet.d.ts.map +1 -1
  89. package/build/types/inputs/SelectInput/Options/SelectInputOptions.d.ts.map +1 -1
  90. package/build/types/inputs/SelectInput/Popover/SelectInputPopover.d.ts.map +1 -1
  91. package/build/types/inputs/SelectInput/SelectInput.d.ts.map +1 -1
  92. package/build/types/nudge/Nudge.d.ts.map +1 -1
  93. package/build/types/phoneNumberInput/PhoneNumberInput.d.ts.map +1 -1
  94. package/build/types/promoCard/PromoCardGroup.d.ts.map +1 -1
  95. package/build/types/segmentedControl/SegmentedControl.d.ts.map +1 -1
  96. package/build/types/tooltip/Tooltip.d.ts.map +1 -1
  97. package/build/types/uploadInput/UploadInput.d.ts.map +1 -1
  98. package/build/uploadInput/UploadInput.js +29 -25
  99. package/build/uploadInput/UploadInput.js.map +1 -1
  100. package/build/uploadInput/UploadInput.mjs +29 -25
  101. package/build/uploadInput/UploadInput.mjs.map +1 -1
  102. package/package.json +2 -2
  103. package/src/avatarWrapper/AvatarWrapper.test.tsx +33 -3
  104. package/src/avatarWrapper/AvatarWrapper.tsx +5 -6
  105. package/src/button/LegacyButton.tsx +1 -1
  106. package/src/button/_stories/Button.test.story.tsx +3 -3
  107. package/src/common/hooks/useContainerSize.test.tsx +1 -1
  108. package/src/common/hooks/useHasIntersected/useHasIntersected.ts +12 -4
  109. package/src/common/liveRegion/LiveRegion.tsx +5 -2
  110. package/src/dateInput/DateInput.tsx +10 -10
  111. package/src/dateLookup/monthCalendar/table/MonthCalendarTable.tsx +1 -5
  112. package/src/dateLookup/yearCalendar/table/YearCalendarTable.tsx +1 -1
  113. package/src/expressiveMoneyInput/ExpressiveMoneyInput.tsx +1 -1
  114. package/src/expressiveMoneyInput/amountInput/AmountInput.tsx +22 -15
  115. package/src/expressiveMoneyInput/hooks/useInputStyle.ts +20 -8
  116. package/src/expressiveMoneyInput/hooks/useSelectionRange.ts +2 -0
  117. package/src/header/Header.tsx +2 -2
  118. package/src/inputs/SelectInput/BottomSheet/SelectInputBottomSheet.tsx +4 -0
  119. package/src/inputs/SelectInput/Options/SelectInputOptions.tsx +43 -27
  120. package/src/inputs/SelectInput/Popover/SelectInputPopover.tsx +4 -0
  121. package/src/inputs/SelectInput/SelectInput.tsx +21 -15
  122. package/src/inputs/SelectInput/TriggerButton/SelectInputTriggerButton.tsx +1 -1
  123. package/src/nudge/Nudge.tsx +29 -20
  124. package/src/phoneNumberInput/PhoneNumberInput.test.tsx +16 -0
  125. package/src/phoneNumberInput/PhoneNumberInput.tsx +11 -13
  126. package/src/promoCard/PromoCard.story.tsx +3 -3
  127. package/src/promoCard/PromoCardGroup.tsx +39 -21
  128. package/src/segmentedControl/SegmentedControl.test.tsx +25 -0
  129. package/src/segmentedControl/SegmentedControl.tsx +7 -1
  130. package/src/select/Select.story.tsx +1 -1
  131. package/src/tabs/Tabs.tsx +1 -1
  132. package/src/tooltip/Tooltip.tsx +3 -0
  133. package/src/uploadInput/UploadInput.test.tsx +19 -0
  134. package/src/uploadInput/UploadInput.tsx +28 -24
@@ -67,8 +67,45 @@ const PromoCardGroup: FunctionComponent<PromoCardGroupProps> = ({
67
67
  onChange = () => {},
68
68
  testId,
69
69
  }) => {
70
- const [state, setState] = useState<string>(defaultChecked);
71
- const [containerRole, setContainerRole] = useState<string | null>(null);
70
+ const [promoCardState, setPromoCardState] = useState<{
71
+ defaultChecked: string;
72
+ state: string;
73
+ }>({
74
+ defaultChecked,
75
+ state: defaultChecked,
76
+ });
77
+
78
+ useEffect(() => {
79
+ if (promoCardState.defaultChecked !== defaultChecked) {
80
+ // eslint-disable-next-line react-hooks/set-state-in-effect -- Syncing defaultChecked prop to internal state
81
+ setPromoCardState({
82
+ defaultChecked,
83
+ state: defaultChecked,
84
+ });
85
+ }
86
+ }, [defaultChecked, promoCardState.defaultChecked]);
87
+
88
+ const { state } = promoCardState;
89
+ const setState = (newState: string) =>
90
+ setPromoCardState((prev) => ({ ...prev, state: newState }));
91
+
92
+ // Derive container role from children
93
+ const containerRole = useMemo(() => {
94
+ // Collect an array of types from the children PromoCard components
95
+ const types =
96
+ React.Children.map(children, (child) => {
97
+ if (React.isValidElement<PromoCardProps>(child) && child.props.type) {
98
+ return child.props.type;
99
+ }
100
+ return null;
101
+ })?.filter((type): type is 'radio' | 'checkbox' => type !== null && type !== undefined) ?? [];
102
+
103
+ // Check if all types are the same
104
+ const allTypesAreTheSame = types.every((type) => type === types[0]);
105
+
106
+ // If all types are the same and the type is 'radio', return 'radiogroup'
107
+ return allTypesAreTheSame && types[0] === 'radio' ? 'radiogroup' : null;
108
+ }, [children]);
72
109
 
73
110
  /**
74
111
  * The context value for the PromoCardGroup.
@@ -103,25 +140,6 @@ const PromoCardGroup: FunctionComponent<PromoCardGroupProps> = ({
103
140
  role: containerRole as AriaRoleRadioGroup | undefined, // Add the role attribute here
104
141
  };
105
142
 
106
- useEffect(() => {
107
- setState(defaultChecked);
108
-
109
- // Collect an array of types from the children PromoCard components
110
- const types =
111
- React.Children.map(children, (child) => {
112
- if (React.isValidElement<PromoCardProps>(child) && child.props.type) {
113
- return child.props.type;
114
- }
115
- return null;
116
- })?.filter((type): type is 'radio' | 'checkbox' => type !== null && type !== undefined) ?? [];
117
-
118
- // Check if all types are the same
119
- const allTypesAreTheSame = types.every((type) => type === types[0]);
120
-
121
- // If all types are the same and the type is 'radio', set the container role
122
- setContainerRole(allTypesAreTheSame && types[0] === 'radio' ? 'radiogroup' : null);
123
- }, [defaultChecked, children]);
124
-
125
143
  return (
126
144
  <PromoCardContext.Provider value={contextValue}>
127
145
  <div {...commonProps}>{children}</div>
@@ -188,4 +188,29 @@ describe('SegmentedControl', () => {
188
188
  'SegmentedControl only supports up to 3 segments. Please refer to: https://wise.design/components/segmented-control',
189
189
  );
190
190
  });
191
+
192
+ describe('animation behavior', () => {
193
+ it('skips animation on initial render', () => {
194
+ renderSegmentedControl();
195
+
196
+ // On initial render, the component should render without triggering animation
197
+ // The isMountedRef ensures animation is skipped on first render
198
+ const segmentedControl = screen.getByTestId('segmented-control');
199
+
200
+ // Verify the component renders successfully
201
+ expect(segmentedControl).toBeInTheDocument();
202
+ });
203
+
204
+ it('enables animation after value change', async () => {
205
+ renderSegmentedControl();
206
+
207
+ // Change value by clicking on a different segment
208
+ const payroll = screen.getByRole('radio', { name: 'Payroll' });
209
+ await userEvent.click(payroll);
210
+
211
+ // After a value change, the component should have completed at least one update cycle
212
+ // verifying that the isMountedRef tracking works correctly
213
+ expect(onChange).toHaveBeenCalledWith('payroll');
214
+ });
215
+ });
191
216
  });
@@ -38,6 +38,7 @@ const SegmentedControl = ({
38
38
  segments,
39
39
  onChange,
40
40
  }: SegmentedControlProps) => {
41
+ const isMountedRef = useRef(false);
41
42
  const [animate, setAnimate] = useState(false);
42
43
 
43
44
  const segmentsRef = useRef<HTMLDivElement>(null);
@@ -67,7 +68,12 @@ const SegmentedControl = ({
67
68
  };
68
69
 
69
70
  useEffect(() => {
70
- setAnimate(true);
71
+ if (isMountedRef.current) {
72
+ setAnimate(true);
73
+ } else {
74
+ isMountedRef.current = true;
75
+ }
76
+
71
77
  updateSegmentPosition();
72
78
 
73
79
  const handleWindowSizeChange = () => {
@@ -43,7 +43,7 @@ const ImageIcon = () => (
43
43
  );
44
44
 
45
45
  const isSelectOptionItem = (option: SelectItem | null): option is SelectOptionItem => {
46
- return option !== null && typeof option.value !== 'undefined';
46
+ return typeof option?.value !== 'undefined';
47
47
  };
48
48
 
49
49
  export const Basic: Story = {
package/src/tabs/Tabs.tsx CHANGED
@@ -254,7 +254,7 @@ export default class Tabs extends Component<TabsProps, TabsState> {
254
254
  };
255
255
 
256
256
  onKeyDown = (index: number) => (event: React.KeyboardEvent<HTMLLIElement>) => {
257
- if (event && event.key === 'Enter') {
257
+ if (event?.key === 'Enter') {
258
258
  this.switchTab(index);
259
259
  }
260
260
  };
@@ -51,6 +51,7 @@ const Tooltip = ({
51
51
  middleware: [
52
52
  offset(16),
53
53
  flip({ fallbackPlacements: [Position.TOP] }),
54
+ // eslint-disable-next-line react-hooks/refs -- arrowRef is passed to floating-ui middleware, legitimate API usage
54
55
  arrowMiddleware({ element: arrowRef, padding: 8 }),
55
56
  ],
56
57
  whileElementsMounted: open ? autoUpdate : undefined,
@@ -63,6 +64,7 @@ const Tooltip = ({
63
64
  };
64
65
 
65
66
  return (
67
+ /* eslint-disable react-hooks/refs -- setReference and setFloating are callback refs from floating-ui, safe to pass during render */
66
68
  <span
67
69
  ref={refs.setReference}
68
70
  className="tw-tooltip-container"
@@ -96,6 +98,7 @@ const Tooltip = ({
96
98
  </div>
97
99
  </div>
98
100
  </span>
101
+ /* eslint-enable react-hooks/refs */
99
102
  );
100
103
  };
101
104
 
@@ -66,6 +66,25 @@ describe('UploadInput', () => {
66
66
  jest.useRealTimers();
67
67
  });
68
68
 
69
+ describe('onFilesChange mount behavior', () => {
70
+ it('should not call onFilesChange on initial mount', () => {
71
+ const onFilesChange = jest.fn();
72
+ renderComponent({
73
+ ...props,
74
+ files: [
75
+ {
76
+ id: 1,
77
+ filename: 'existing-file.pdf',
78
+ status: Status.SUCCEEDED,
79
+ },
80
+ ],
81
+ onFilesChange,
82
+ });
83
+
84
+ expect(onFilesChange).not.toHaveBeenCalled();
85
+ });
86
+ });
87
+
69
88
  describe('single file upload', () => {
70
89
  it('should trigger onUploadFiles & onFilesChange with a single FormData entry containing `file` field', async () => {
71
90
  const date = Date.now();
@@ -170,10 +170,10 @@ const UploadInput = ({
170
170
  const inputAttributes = useInputAttributes({ nonLabelable: true });
171
171
  const [markedFileForDelete, setMarkedFileForDelete] = useState<UploadedFile | null>(null);
172
172
  const [lastAttemptedDeleteId, setLastAttemptedDeleteId] = useState<string | number | null>(null);
173
- const [mounted, setMounted] = useState(false);
173
+ const mountedRef = useRef(false);
174
174
  const { formatMessage } = useIntl();
175
175
  const uploadInputRef = useRef<HTMLInputElement | null>(null);
176
- let fileRefs: (HTMLDivElement | UploadItemRef | null)[] = [];
176
+ const fileRefs = useRef<(HTMLDivElement | UploadItemRef | null)[]>([]);
177
177
 
178
178
  const PROGRESS_STATUSES = new Set([Status.PENDING, Status.PROCESSING]);
179
179
 
@@ -196,7 +196,7 @@ const UploadInput = ({
196
196
  updateFileList((list) =>
197
197
  list.filter((fileInList) => file !== fileInList && file.id !== fileInList.id),
198
198
  );
199
- fileRefs = fileRefs.filter((ref) => ref && ref.id !== file.id);
199
+ fileRefs.current = fileRefs.current.filter((ref) => ref && ref.id !== file.id);
200
200
  }
201
201
 
202
202
  function modifyFileInList(file: UploadedFile, updates: Partial<UploadedFile>) {
@@ -208,18 +208,18 @@ const UploadInput = ({
208
208
  }
209
209
 
210
210
  const removeFile = async (file: UploadedFile) => {
211
- const { id, status } = file;
212
- fileRefs = fileRefs.filter((item) => item && item.id !== file.id);
211
+ const { id: fileId, status } = file;
212
+ fileRefs.current = fileRefs.current.filter((item) => item && item.id !== file.id);
213
213
 
214
214
  if (status === Status.FAILED) {
215
215
  removeFileFromList(file);
216
216
  return Promise.resolve();
217
217
  }
218
218
 
219
- if (onDeleteFile && id) {
219
+ if (onDeleteFile && fileId) {
220
220
  modifyFileInList(file, { status: Status.PROCESSING, error: undefined });
221
221
 
222
- return onDeleteFile(id)
222
+ return onDeleteFile(fileId)
223
223
  .then(() => {
224
224
  removeFileFromList(file);
225
225
  })
@@ -248,6 +248,7 @@ const UploadInput = ({
248
248
 
249
249
  function getNumberOfFilesUploaded() {
250
250
  const uploadInitiatedStatus = new Set([Status.SUCCEEDED, Status.PENDING]);
251
+
251
252
  const validFiles = uploadedFilesListReference.current.filter(
252
253
  (file) => file.status && uploadInitiatedStatus.has(file.status),
253
254
  );
@@ -321,14 +322,16 @@ const UploadInput = ({
321
322
  };
322
323
 
323
324
  useEffect(() => {
324
- setMounted(true);
325
- }, []);
325
+ // Skip onFilesChange call on initial mount, only call on updates
326
+ if (!mountedRef.current) {
327
+ mountedRef.current = true;
328
+ return;
329
+ }
326
330
 
327
- useEffect(() => {
328
- if (onFilesChange && mounted) {
331
+ if (onFilesChange) {
329
332
  onFilesChange([...uploadedFiles]);
330
333
  }
331
- }, [onFilesChange, uploadedFiles]); // eslint-disable-line react-hooks/exhaustive-deps
334
+ }, [onFilesChange, uploadedFiles]);
332
335
 
333
336
  type NextFocusable =
334
337
  | HTMLDivElement
@@ -336,14 +339,14 @@ const UploadInput = ({
336
339
  | { ref: HTMLDivElement | UploadItemRef; target: 'button' | 'link' }
337
340
  | null;
338
341
 
339
- const [nextFocusable, setNextFocusable] = useState<NextFocusable>(uploadInputRef.current);
342
+ const [nextFocusable, setNextFocusable] = useState<NextFocusable>(null);
340
343
 
341
344
  const handleFocus = (fileId: string | number) => {
342
- fileRefs = fileRefs.filter((ref) => {
345
+ fileRefs.current = fileRefs.current.filter((ref) => {
343
346
  return ref && ref.id !== markedFileForDelete?.id;
344
347
  });
345
348
 
346
- const filesCount = fileRefs.length;
349
+ const filesCount = fileRefs.current.length;
347
350
  let next: UploadItemRef | HTMLDivElement | null = uploadInputRef.current;
348
351
  let focusTarget: 'button' | 'link' = 'button';
349
352
 
@@ -355,15 +358,15 @@ const UploadInput = ({
355
358
  }
356
359
 
357
360
  if (filesCount > 1) {
358
- const currentFileIndex = fileRefs.findIndex((file) => file?.id === fileId);
359
- const currentFileId = fileRefs?.[currentFileIndex]?.id;
360
- const lastFileId = fileRefs?.[filesCount - 1]?.id;
361
+ const currentFileIndex = fileRefs.current.findIndex((file) => file?.id === fileId);
362
+ const currentFileId = fileRefs.current?.[currentFileIndex]?.id;
363
+ const lastFileId = fileRefs.current?.[filesCount - 1]?.id;
361
364
 
362
365
  // if last file, select a previous one
363
366
  if (currentFileId === lastFileId) {
364
- next = fileRefs[filesCount - 2];
367
+ next = fileRefs.current[filesCount - 2];
365
368
  } else {
366
- next = fileRefs[currentFileIndex + 1];
369
+ next = fileRefs.current[currentFileIndex + 1];
367
370
  }
368
371
 
369
372
  // If next is an UploadItemRef, check if it has a URL (succeeded)
@@ -401,7 +404,7 @@ const UploadInput = ({
401
404
  typeof uploadInputRef.current.focus === 'function'
402
405
  ) {
403
406
  setTimeout(() => {
404
- uploadInputRef.current!.focus();
407
+ uploadInputRef.current?.focus();
405
408
  }, 0);
406
409
  } else if (
407
410
  typeof focusTarget === 'object' &&
@@ -436,17 +439,17 @@ const UploadInput = ({
436
439
  aria-relevant="all"
437
440
  role="region"
438
441
  >
439
- {uploadedFiles.map((file, index) => (
442
+ {uploadedFiles.map((file) => (
440
443
  <UploadItem
441
444
  key={file.id}
442
445
  ref={(el: UploadItemRef | null) => {
443
446
  if (
444
447
  el &&
445
448
  el.id !== markedFileForDelete?.id &&
446
- !fileRefs.some((ref) => ref && ref.id === el.id) &&
449
+ !fileRefs.current.some((ref) => ref?.id === el.id) &&
447
450
  el.status !== 'processing'
448
451
  ) {
449
- fileRefs.push(el);
452
+ fileRefs.current.push(el);
450
453
  }
451
454
  }}
452
455
  file={file}
@@ -478,6 +481,7 @@ const UploadInput = ({
478
481
  ref={uploadInputRef}
479
482
  id={id}
480
483
  uploadButtonTitle={uploadButtonTitle}
484
+ // eslint-disable-next-line react-hooks/refs -- Function reads ref for file count check
481
485
  disabled={areMaximumFilesUploadedAlready() || disabled}
482
486
  multiple={multiple}
483
487
  fileTypes={fileTypes}