@transferwise/components 46.17.2 → 46.18.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 (99) hide show
  1. package/build/index.esm.js +28 -20
  2. package/build/index.esm.js.map +1 -1
  3. package/build/index.js +27 -19
  4. package/build/index.js.map +1 -1
  5. package/build/main.css +4 -0
  6. package/build/styles/instructionsList/InstructionsList.css +4 -0
  7. package/build/styles/main.css +4 -0
  8. package/build/types/accordion/Accordion.d.ts +3 -7
  9. package/build/types/accordion/Accordion.d.ts.map +1 -1
  10. package/build/types/accordion/index.d.ts +1 -0
  11. package/build/types/accordion/index.d.ts.map +1 -1
  12. package/build/types/body/Body.d.ts +1 -1
  13. package/build/types/chips/Chips.d.ts +2 -2
  14. package/build/types/chips/Chips.d.ts.map +1 -1
  15. package/build/types/dateLookup/DateLookup.d.ts +1 -0
  16. package/build/types/dateLookup/DateLookup.d.ts.map +1 -1
  17. package/build/types/dateLookup/getFocusableTime/getFocusableTime.d.ts +1 -1
  18. package/build/types/dateLookup/getFocusableTime/getFocusableTime.d.ts.map +1 -1
  19. package/build/types/decision/Decision.d.ts +1 -1
  20. package/build/types/decision/Decision.d.ts.map +1 -1
  21. package/build/types/flowNavigation/FlowNavigation.d.ts +1 -1
  22. package/build/types/flowNavigation/FlowNavigation.d.ts.map +1 -1
  23. package/build/types/flowNavigation/animatedLabel/AnimatedLabel.d.ts +1 -1
  24. package/build/types/flowNavigation/animatedLabel/AnimatedLabel.d.ts.map +1 -1
  25. package/build/types/index.d.ts +2 -1
  26. package/build/types/index.d.ts.map +1 -1
  27. package/build/types/instructionsList/InstructionsList.d.ts +4 -4
  28. package/build/types/instructionsList/InstructionsList.d.ts.map +1 -1
  29. package/build/types/markdown/Markdown.d.ts +2 -2
  30. package/build/types/markdown/Markdown.d.ts.map +1 -1
  31. package/build/types/phoneNumberInput/PhoneNumberInput.d.ts +1 -1
  32. package/build/types/phoneNumberInput/PhoneNumberInput.d.ts.map +1 -1
  33. package/build/types/phoneNumberInput/utils/excludeCountries/excludeCountries.d.ts +1 -1
  34. package/build/types/phoneNumberInput/utils/excludeCountries/excludeCountries.d.ts.map +1 -1
  35. package/build/types/phoneNumberInput/utils/groupCountriesByPrefix/groupCountriesByPrefix.d.ts +1 -1
  36. package/build/types/phoneNumberInput/utils/groupCountriesByPrefix/groupCountriesByPrefix.d.ts.map +1 -1
  37. package/build/types/phoneNumberInput/utils/longestMatchingPrefix/index.d.ts +1 -1
  38. package/build/types/phoneNumberInput/utils/longestMatchingPrefix/index.d.ts.map +1 -1
  39. package/build/types/phoneNumberInput/utils/sortArrayByProperty/sortArrayByProperty.d.ts +1 -1
  40. package/build/types/phoneNumberInput/utils/sortArrayByProperty/sortArrayByProperty.d.ts.map +1 -1
  41. package/build/types/radioGroup/RadioGroup.d.ts +2 -1
  42. package/build/types/radioGroup/RadioGroup.d.ts.map +1 -1
  43. package/build/types/radioGroup/index.d.ts +1 -1
  44. package/build/types/radioGroup/index.d.ts.map +1 -1
  45. package/build/types/segmentedControl/SegmentedControl.d.ts +3 -3
  46. package/build/types/segmentedControl/SegmentedControl.d.ts.map +1 -1
  47. package/build/types/slidingPanel/SlidingPanel.d.ts.map +1 -1
  48. package/build/types/stepper/Stepper.d.ts +1 -1
  49. package/build/types/stepper/Stepper.d.ts.map +1 -1
  50. package/build/types/typeahead/Typeahead.d.ts +8 -6
  51. package/build/types/typeahead/Typeahead.d.ts.map +1 -1
  52. package/build/types/typeahead/typeaheadInput/TypeaheadInput.d.ts +1 -1
  53. package/build/types/typeahead/typeaheadInput/TypeaheadInput.d.ts.map +1 -1
  54. package/build/types/uploadInput/UploadInput.d.ts +1 -1
  55. package/build/types/uploadInput/UploadInput.d.ts.map +1 -1
  56. package/build/types/uploadInput/uploadButton/UploadButton.d.ts +1 -1
  57. package/build/types/uploadInput/uploadButton/UploadButton.d.ts.map +1 -1
  58. package/build/types/uploadInput/uploadButton/getAllowedFileTypes.d.ts +1 -1
  59. package/build/types/uploadInput/uploadButton/getAllowedFileTypes.d.ts.map +1 -1
  60. package/package.json +1 -1
  61. package/src/accordion/Accordion.tsx +6 -7
  62. package/src/accordion/index.ts +1 -0
  63. package/src/chips/Chips.story.tsx +2 -2
  64. package/src/chips/Chips.tsx +2 -2
  65. package/src/dateLookup/DateLookup.js +2 -0
  66. package/src/dateLookup/DateLookup.story.js +3 -0
  67. package/src/dateLookup/DateLookup.view.spec.js +5 -0
  68. package/src/dateLookup/dateTrigger/DateTrigger.js +1 -1
  69. package/src/dateLookup/dateTrigger/DateTrigger.spec.js +11 -3
  70. package/src/dateLookup/getFocusableTime/getFocusableTime.tsx +1 -1
  71. package/src/decision/Decision.tsx +1 -1
  72. package/src/flowNavigation/FlowNavigation.tsx +1 -1
  73. package/src/flowNavigation/__snapshots__/FlowNavigation.spec.js.snap +6 -0
  74. package/src/flowNavigation/animatedLabel/AnimatedLabel.tsx +1 -1
  75. package/src/index.ts +2 -1
  76. package/src/instructionsList/InstructionsList.css +4 -0
  77. package/src/instructionsList/InstructionsList.less +5 -0
  78. package/src/instructionsList/InstructionsList.tsx +7 -7
  79. package/src/main.css +4 -0
  80. package/src/markdown/Markdown.tsx +3 -3
  81. package/src/moneyInput/MoneyInput.tsx +3 -3
  82. package/src/phoneNumberInput/PhoneNumberInput.tsx +1 -1
  83. package/src/phoneNumberInput/utils/excludeCountries/excludeCountries.ts +5 -2
  84. package/src/phoneNumberInput/utils/groupCountriesByPrefix/groupCountriesByPrefix.ts +1 -1
  85. package/src/phoneNumberInput/utils/longestMatchingPrefix/index.ts +1 -1
  86. package/src/phoneNumberInput/utils/sortArrayByProperty/sortArrayByProperty.ts +1 -1
  87. package/src/promoCard/PromoCardGroup.tsx +1 -1
  88. package/src/radioGroup/RadioGroup.tsx +6 -1
  89. package/src/radioGroup/index.ts +1 -1
  90. package/src/segmentedControl/SegmentedControl.tsx +3 -3
  91. package/src/slidingPanel/SlidingPanel.js +1 -0
  92. package/src/stepper/Stepper.spec.js +16 -0
  93. package/src/stepper/Stepper.tsx +2 -1
  94. package/src/typeahead/Typeahead.story.tsx +109 -0
  95. package/src/typeahead/Typeahead.tsx +18 -9
  96. package/src/typeahead/typeaheadInput/TypeaheadInput.tsx +1 -1
  97. package/src/uploadInput/UploadInput.tsx +6 -6
  98. package/src/uploadInput/uploadButton/UploadButton.tsx +5 -7
  99. package/src/uploadInput/uploadButton/getAllowedFileTypes.ts +1 -1
@@ -5,6 +5,7 @@ import { Search as SearchIcon } from '@transferwise/icons';
5
5
  import { useState } from 'react';
6
6
 
7
7
  import { Sentiment } from '../common';
8
+ import { Input } from '../inputs/Input';
8
9
 
9
10
  import Typeahead, { type TypeaheadOption } from './Typeahead';
10
11
 
@@ -120,3 +121,111 @@ Basic.play = async ({ canvasElement }: StoryContext) => {
120
121
  const canvas = within(canvasElement);
121
122
  await userEvent.type(canvas.getByRole('combobox'), 'abc{ArrowDown}');
122
123
  };
124
+
125
+ type Result =
126
+ | {
127
+ type: 'action';
128
+ value: string;
129
+ }
130
+ | {
131
+ type: 'search';
132
+ value: string;
133
+ };
134
+
135
+ type SearchState = 'success' | 'idle' | 'error' | 'loading';
136
+
137
+ export const Search = () => {
138
+ const [results, setResults] = useState<Result[]>([]);
139
+ const [state, setState] = useState<SearchState>('idle');
140
+ const [filledValue, setFilledValue] = useState<string | null>(null);
141
+
142
+ const onChange = (query: string) => {
143
+ if (query === 'loading') {
144
+ setState('loading');
145
+ setResults([]);
146
+ return;
147
+ }
148
+ if (query === 'error') {
149
+ setState('error');
150
+ setResults([]);
151
+ return;
152
+ }
153
+ if (query === 'nothing') {
154
+ setState('success');
155
+ setResults([]);
156
+ return;
157
+ }
158
+
159
+ setState('success');
160
+ setResults(getResults(query));
161
+ };
162
+
163
+ const onResultSelected = (option: Result) => {
164
+ if (option.type === 'search') {
165
+ setResults([
166
+ { type: 'action', value: `${option.value} Result #1` },
167
+ { type: 'action', value: `${option.value} Result #2` },
168
+ { type: 'action', value: `${option.value} Result #3` },
169
+ ]);
170
+ }
171
+ if (option.type === 'action') {
172
+ setFilledValue(option.value);
173
+ }
174
+ };
175
+
176
+ const getResults = (query: string): Result[] => {
177
+ return [
178
+ { type: 'action', value: `${query} Result #1` },
179
+ { type: 'action', value: `${query} Result #2` },
180
+ { type: 'action', value: `${query} Result #3` },
181
+ { type: 'search', value: `Search for more: '${query}'` },
182
+ ];
183
+ };
184
+
185
+ return (
186
+ <>
187
+ <Typeahead<Result>
188
+ id="typeahead-input-id"
189
+ name="typeahead-input-name"
190
+ size="md"
191
+ maxHeight={100}
192
+ footer={<SearchFooter options={results} state={state} />}
193
+ multiple={false}
194
+ clearable={false}
195
+ addon={<SearchIcon />}
196
+ options={results.map((option) => ({
197
+ value: option,
198
+ label: option.value,
199
+ keepFocusOnSelect: option.type === 'search',
200
+ clearQueryOnSelect: option.type === 'action',
201
+ }))}
202
+ onChange={(values) => {
203
+ if (values.length > 0) {
204
+ const [updatedValue] = values;
205
+ if (updatedValue.value) {
206
+ onResultSelected(updatedValue.value);
207
+ }
208
+ }
209
+ }}
210
+ onInputChange={onChange}
211
+ />
212
+ {filledValue != null ? <Input value={filledValue} /> : null}
213
+ </>
214
+ );
215
+ };
216
+
217
+ function SearchFooter({ options, state }: { options: Result[]; state: SearchState }) {
218
+ if (state === 'loading') {
219
+ return <p className="m-y-2 m-x-2">Loading...</p>;
220
+ }
221
+
222
+ if (state === 'success' && options.length === 0) {
223
+ return <p className="m-y-2 m-x-2">No results found</p>;
224
+ }
225
+
226
+ if (state === 'error' && options.length === 0) {
227
+ return <div className="m-y-2 m-x-2">Something went wrong</div>;
228
+ }
229
+
230
+ return null;
231
+ }
@@ -30,6 +30,8 @@ export type TypeaheadOption<T = string> = {
30
30
  note?: string;
31
31
  secondary?: string;
32
32
  value?: T;
33
+ clearQueryOnSelect?: boolean;
34
+ keepFocusOnSelect?: boolean;
33
35
  };
34
36
 
35
37
  export interface TypeaheadProps<T> {
@@ -43,16 +45,16 @@ export interface TypeaheadProps<T> {
43
45
  allowNew?: boolean;
44
46
  autoFillOnBlur?: boolean;
45
47
  autoFocus?: boolean;
46
- chipSeparators?: string[];
48
+ chipSeparators?: readonly string[];
47
49
  clearable?: boolean;
48
50
  footer?: ReactNode;
49
- initialValue?: TypeaheadOption<T>[];
51
+ initialValue?: readonly TypeaheadOption<T>[];
50
52
  inputAutoComplete?: string;
51
53
  maxHeight?: number;
52
54
  minQueryLength?: number;
53
55
  placeholder?: string;
54
56
  multiple?: boolean;
55
- options: TypeaheadOption<T>[];
57
+ options: readonly TypeaheadOption<T>[];
56
58
  searchDelay?: number;
57
59
  showSuggestions?: boolean;
58
60
  showNewEntry?: boolean;
@@ -67,7 +69,7 @@ export interface TypeaheadProps<T> {
67
69
  }
68
70
 
69
71
  type TypeaheadState<T> = {
70
- selected: TypeaheadOption<T>[];
72
+ selected: readonly TypeaheadOption<T>[];
71
73
  keyboardFocusedOptionIndex: number | null;
72
74
  errorState: boolean;
73
75
  query: string;
@@ -250,7 +252,15 @@ export default class Typeahead<T> extends Component<TypeaheadProps<T>, Typeahead
250
252
  }
251
253
 
252
254
  this.updateSelectedValue(selected);
253
- this.hideMenu();
255
+
256
+ if (!item.keepFocusOnSelect) {
257
+ this.hideMenu();
258
+ }
259
+
260
+ if (item.clearQueryOnSelect) {
261
+ query = '';
262
+ }
263
+
254
264
  this.setState({
255
265
  query,
256
266
  });
@@ -311,12 +321,12 @@ export default class Typeahead<T> extends Component<TypeaheadProps<T>, Typeahead
311
321
  );
312
322
  };
313
323
 
314
- updateSelectedValue = (selected: TypeaheadOption<T>[]) => {
324
+ updateSelectedValue = (selected: readonly TypeaheadOption<T>[]) => {
315
325
  const { onChange, validateChip } = this.props;
316
326
 
317
327
  const errorState = selected.some((chip) => !validateChip(chip));
318
328
  this.setState({ selected, errorState }, () => {
319
- onChange(selected);
329
+ onChange([...selected]);
320
330
  });
321
331
  };
322
332
 
@@ -499,8 +509,7 @@ export default class Typeahead<T> extends Component<TypeaheadProps<T>, Typeahead
499
509
  </div>
500
510
  )}
501
511
  </div>
502
- {displayAlert && <InlineAlert type={alert.type}>{alert.message}</InlineAlert>}
503
- {menu}
512
+ {displayAlert ? <InlineAlert type={alert.type}>{alert.message}</InlineAlert> : menu}
504
513
  </div>
505
514
  </div>
506
515
  );
@@ -12,7 +12,7 @@ const DEFAULT_INPUT_MIN_WIDTH = 10;
12
12
  export type TypeaheadInputProps<T> = {
13
13
  typeaheadId: string;
14
14
  value: string;
15
- selected: TypeaheadOption<T>[];
15
+ selected: readonly TypeaheadOption<T>[];
16
16
  optionsShown?: boolean;
17
17
  autoComplete: string;
18
18
  onChange: React.ChangeEventHandler<HTMLInputElement>;
@@ -19,7 +19,7 @@ export type UploadInputProps = {
19
19
  /**
20
20
  * List of already existing, failed or in progress files
21
21
  */
22
- files?: UploadedFile[];
22
+ files?: readonly UploadedFile[];
23
23
 
24
24
  /**
25
25
  * The key of the file in the returned FormData object (default: file)
@@ -135,14 +135,14 @@ const UploadInput = ({
135
135
 
136
136
  const PROGRESS_STATUSES = new Set([Status.PENDING, Status.PROCESSING]);
137
137
 
138
- const [uploadedFiles, setUploadedFiles] = useState<UploadedFile[]>(
138
+ const [uploadedFiles, setUploadedFiles] = useState<readonly UploadedFile[]>(
139
139
  multiple || files.length === 0 ? files : [files[0]],
140
140
  );
141
141
 
142
142
  const uploadedFilesListReference = useRef(multiple || files.length === 0 ? files : [files[0]]);
143
143
 
144
144
  function addFileToList(recentUploadedFile: UploadedFile) {
145
- function addToList(listToAddTo: UploadedFile[]) {
145
+ function addToList(listToAddTo: readonly UploadedFile[]) {
146
146
  return [...listToAddTo, recentUploadedFile];
147
147
  }
148
148
 
@@ -151,7 +151,7 @@ const UploadInput = ({
151
151
  }
152
152
 
153
153
  const removeFileFromList = (file: UploadedFile) => {
154
- function filterOutFrom(listToFilterFrom: UploadedFile[]) {
154
+ function filterOutFrom(listToFilterFrom: readonly UploadedFile[]) {
155
155
  return listToFilterFrom.filter(
156
156
  (fileInList) => file !== fileInList && file.id !== fileInList.id,
157
157
  );
@@ -162,7 +162,7 @@ const UploadInput = ({
162
162
  };
163
163
 
164
164
  const modifyFileInList = (file: UploadedFile, updates: Partial<UploadedFile>) => {
165
- const updateListItem = (listToUpdate: UploadedFile[]) =>
165
+ const updateListItem = (listToUpdate: readonly UploadedFile[]) =>
166
166
  listToUpdate.map((fileInList) => {
167
167
  return fileInList === file || fileInList.id === file.id
168
168
  ? { ...file, ...updates }
@@ -295,7 +295,7 @@ const UploadInput = ({
295
295
 
296
296
  useEffect(() => {
297
297
  if (onFilesChange && mounted) {
298
- onFilesChange(uploadedFiles);
298
+ onFilesChange([...uploadedFiles]);
299
299
  }
300
300
  }, [onFilesChange, uploadedFiles]); // eslint-disable-line react-hooks/exhaustive-deps
301
301
 
@@ -12,7 +12,7 @@ import MESSAGES from './UploadButton.messages';
12
12
  import { DEFAULT_SIZE_LIMIT, imageFileTypes } from './defaults';
13
13
  import getAllowedFileTypes from './getAllowedFileTypes';
14
14
 
15
- type AllowedFileTypes = string | string[] | FileType[];
15
+ type AllowedFileTypes = string | readonly string[] | readonly FileType[];
16
16
  export type UploadButtonProps = {
17
17
  /**
18
18
  * Disable the upload button if your app is not yet ready to accept uploads
@@ -144,9 +144,7 @@ const UploadButton = ({
144
144
  return fileTypes;
145
145
  }
146
146
 
147
- return Array.isArray(fileTypes)
148
- ? getAllowedFileTypes(fileTypes).join(', ')
149
- : getAllowedFileTypes([fileTypes]).join(', ');
147
+ return getAllowedFileTypes(Array.isArray(fileTypes) ? fileTypes : [fileTypes]).join(', ');
150
148
  };
151
149
 
152
150
  function getDescription() {
@@ -165,18 +163,18 @@ const UploadButton = ({
165
163
  });
166
164
  }
167
165
 
168
- function getAcceptedTypes() {
166
+ function getAcceptedTypes(): Pick<React.ComponentPropsWithoutRef<'input'>, 'accept'> {
169
167
  const areAllFilesAllowed = getFileTypesDescription() === '*';
170
168
 
171
169
  if (areAllFilesAllowed) {
172
- return null; //file input by default allows all files
170
+ return {}; //file input by default allows all files
173
171
  }
174
172
 
175
173
  if (Array.isArray(fileTypes)) {
176
174
  return { accept: fileTypes.join(',') };
177
175
  }
178
176
 
179
- return { accept: fileTypes };
177
+ return { accept: fileTypes as string };
180
178
  }
181
179
 
182
180
  function renderDescription() {
@@ -1,6 +1,6 @@
1
1
  import { FileType } from '../../common';
2
2
 
3
- const getAllowedFileTypes = (fileTypes: FileType[] | string[]): string[] =>
3
+ const getAllowedFileTypes = (fileTypes: readonly FileType[] | readonly string[]): string[] =>
4
4
  fileTypes.map((fileTypeDefinition: string) =>
5
5
  fileTypeDefinition
6
6
  .split(',')