@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.
- package/build/index.esm.js +28 -20
- package/build/index.esm.js.map +1 -1
- package/build/index.js +27 -19
- package/build/index.js.map +1 -1
- package/build/main.css +4 -0
- package/build/styles/instructionsList/InstructionsList.css +4 -0
- package/build/styles/main.css +4 -0
- package/build/types/accordion/Accordion.d.ts +3 -7
- package/build/types/accordion/Accordion.d.ts.map +1 -1
- package/build/types/accordion/index.d.ts +1 -0
- package/build/types/accordion/index.d.ts.map +1 -1
- package/build/types/body/Body.d.ts +1 -1
- package/build/types/chips/Chips.d.ts +2 -2
- package/build/types/chips/Chips.d.ts.map +1 -1
- package/build/types/dateLookup/DateLookup.d.ts +1 -0
- package/build/types/dateLookup/DateLookup.d.ts.map +1 -1
- package/build/types/dateLookup/getFocusableTime/getFocusableTime.d.ts +1 -1
- package/build/types/dateLookup/getFocusableTime/getFocusableTime.d.ts.map +1 -1
- package/build/types/decision/Decision.d.ts +1 -1
- package/build/types/decision/Decision.d.ts.map +1 -1
- package/build/types/flowNavigation/FlowNavigation.d.ts +1 -1
- package/build/types/flowNavigation/FlowNavigation.d.ts.map +1 -1
- package/build/types/flowNavigation/animatedLabel/AnimatedLabel.d.ts +1 -1
- package/build/types/flowNavigation/animatedLabel/AnimatedLabel.d.ts.map +1 -1
- package/build/types/index.d.ts +2 -1
- package/build/types/index.d.ts.map +1 -1
- package/build/types/instructionsList/InstructionsList.d.ts +4 -4
- package/build/types/instructionsList/InstructionsList.d.ts.map +1 -1
- package/build/types/markdown/Markdown.d.ts +2 -2
- package/build/types/markdown/Markdown.d.ts.map +1 -1
- package/build/types/phoneNumberInput/PhoneNumberInput.d.ts +1 -1
- package/build/types/phoneNumberInput/PhoneNumberInput.d.ts.map +1 -1
- package/build/types/phoneNumberInput/utils/excludeCountries/excludeCountries.d.ts +1 -1
- package/build/types/phoneNumberInput/utils/excludeCountries/excludeCountries.d.ts.map +1 -1
- package/build/types/phoneNumberInput/utils/groupCountriesByPrefix/groupCountriesByPrefix.d.ts +1 -1
- package/build/types/phoneNumberInput/utils/groupCountriesByPrefix/groupCountriesByPrefix.d.ts.map +1 -1
- package/build/types/phoneNumberInput/utils/longestMatchingPrefix/index.d.ts +1 -1
- package/build/types/phoneNumberInput/utils/longestMatchingPrefix/index.d.ts.map +1 -1
- package/build/types/phoneNumberInput/utils/sortArrayByProperty/sortArrayByProperty.d.ts +1 -1
- package/build/types/phoneNumberInput/utils/sortArrayByProperty/sortArrayByProperty.d.ts.map +1 -1
- package/build/types/radioGroup/RadioGroup.d.ts +2 -1
- package/build/types/radioGroup/RadioGroup.d.ts.map +1 -1
- package/build/types/radioGroup/index.d.ts +1 -1
- package/build/types/radioGroup/index.d.ts.map +1 -1
- package/build/types/segmentedControl/SegmentedControl.d.ts +3 -3
- package/build/types/segmentedControl/SegmentedControl.d.ts.map +1 -1
- package/build/types/slidingPanel/SlidingPanel.d.ts.map +1 -1
- package/build/types/stepper/Stepper.d.ts +1 -1
- package/build/types/stepper/Stepper.d.ts.map +1 -1
- package/build/types/typeahead/Typeahead.d.ts +8 -6
- package/build/types/typeahead/Typeahead.d.ts.map +1 -1
- package/build/types/typeahead/typeaheadInput/TypeaheadInput.d.ts +1 -1
- package/build/types/typeahead/typeaheadInput/TypeaheadInput.d.ts.map +1 -1
- package/build/types/uploadInput/UploadInput.d.ts +1 -1
- package/build/types/uploadInput/UploadInput.d.ts.map +1 -1
- package/build/types/uploadInput/uploadButton/UploadButton.d.ts +1 -1
- package/build/types/uploadInput/uploadButton/UploadButton.d.ts.map +1 -1
- package/build/types/uploadInput/uploadButton/getAllowedFileTypes.d.ts +1 -1
- package/build/types/uploadInput/uploadButton/getAllowedFileTypes.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/accordion/Accordion.tsx +6 -7
- package/src/accordion/index.ts +1 -0
- package/src/chips/Chips.story.tsx +2 -2
- package/src/chips/Chips.tsx +2 -2
- package/src/dateLookup/DateLookup.js +2 -0
- package/src/dateLookup/DateLookup.story.js +3 -0
- package/src/dateLookup/DateLookup.view.spec.js +5 -0
- package/src/dateLookup/dateTrigger/DateTrigger.js +1 -1
- package/src/dateLookup/dateTrigger/DateTrigger.spec.js +11 -3
- package/src/dateLookup/getFocusableTime/getFocusableTime.tsx +1 -1
- package/src/decision/Decision.tsx +1 -1
- package/src/flowNavigation/FlowNavigation.tsx +1 -1
- package/src/flowNavigation/__snapshots__/FlowNavigation.spec.js.snap +6 -0
- package/src/flowNavigation/animatedLabel/AnimatedLabel.tsx +1 -1
- package/src/index.ts +2 -1
- package/src/instructionsList/InstructionsList.css +4 -0
- package/src/instructionsList/InstructionsList.less +5 -0
- package/src/instructionsList/InstructionsList.tsx +7 -7
- package/src/main.css +4 -0
- package/src/markdown/Markdown.tsx +3 -3
- package/src/moneyInput/MoneyInput.tsx +3 -3
- package/src/phoneNumberInput/PhoneNumberInput.tsx +1 -1
- package/src/phoneNumberInput/utils/excludeCountries/excludeCountries.ts +5 -2
- package/src/phoneNumberInput/utils/groupCountriesByPrefix/groupCountriesByPrefix.ts +1 -1
- package/src/phoneNumberInput/utils/longestMatchingPrefix/index.ts +1 -1
- package/src/phoneNumberInput/utils/sortArrayByProperty/sortArrayByProperty.ts +1 -1
- package/src/promoCard/PromoCardGroup.tsx +1 -1
- package/src/radioGroup/RadioGroup.tsx +6 -1
- package/src/radioGroup/index.ts +1 -1
- package/src/segmentedControl/SegmentedControl.tsx +3 -3
- package/src/slidingPanel/SlidingPanel.js +1 -0
- package/src/stepper/Stepper.spec.js +16 -0
- package/src/stepper/Stepper.tsx +2 -1
- package/src/typeahead/Typeahead.story.tsx +109 -0
- package/src/typeahead/Typeahead.tsx +18 -9
- package/src/typeahead/typeaheadInput/TypeaheadInput.tsx +1 -1
- package/src/uploadInput/UploadInput.tsx +6 -6
- package/src/uploadInput/uploadButton/UploadButton.tsx +5 -7
- 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
|
-
|
|
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
|
|
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
|
|
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(',')
|