@transferwise/components 46.3.0 → 46.5.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 (41) hide show
  1. package/build/index.esm.js +119 -127
  2. package/build/index.esm.js.map +1 -1
  3. package/build/index.js +119 -127
  4. package/build/index.js.map +1 -1
  5. package/build/types/common/responsivePanel/ResponsivePanel.d.ts.map +1 -1
  6. package/build/types/dimmer/Dimmer.d.ts.map +1 -1
  7. package/build/types/index.d.ts +1 -0
  8. package/build/types/index.d.ts.map +1 -1
  9. package/build/types/inputs/SelectInput.d.ts +2 -2
  10. package/build/types/inputs/SelectInput.d.ts.map +1 -1
  11. package/build/types/inputs/_BottomSheet.d.ts.map +1 -1
  12. package/build/types/inputs/_Popover.d.ts.map +1 -1
  13. package/build/types/moneyInput/MoneyInput.d.ts +45 -31
  14. package/build/types/moneyInput/MoneyInput.d.ts.map +1 -1
  15. package/build/types/moneyInput/MoneyInput.messages.d.ts +6 -6
  16. package/build/types/moneyInput/MoneyInput.messages.d.ts.map +1 -1
  17. package/build/types/moneyInput/currencyFormatting.d.ts +2 -2
  18. package/build/types/moneyInput/currencyFormatting.d.ts.map +1 -1
  19. package/build/types/moneyInput/index.d.ts +2 -1
  20. package/build/types/moneyInput/index.d.ts.map +1 -1
  21. package/package.json +1 -1
  22. package/src/common/bottomSheet/__snapshots__/BottomSheet.spec.tsx.snap +1 -1
  23. package/src/common/responsivePanel/ResponsivePanel.tsx +1 -2
  24. package/src/dimmer/Dimmer.tsx +5 -1
  25. package/src/flowNavigation/FlowNavigation.story.js +1 -1
  26. package/src/index.ts +6 -0
  27. package/src/inputs/SelectInput.tsx +2 -2
  28. package/src/inputs/_BottomSheet.tsx +5 -1
  29. package/src/inputs/_Popover.tsx +5 -1
  30. package/src/moneyInput/{MoneyInput.rtl.spec.js → MoneyInput.rtl.spec.tsx} +4 -4
  31. package/src/moneyInput/MoneyInput.spec.js +109 -49
  32. package/src/moneyInput/MoneyInput.story.tsx +6 -14
  33. package/src/moneyInput/{MoneyInput.js → MoneyInput.tsx} +189 -173
  34. package/src/moneyInput/{currencyFormatting.spec.js → currencyFormatting.spec.ts} +2 -2
  35. package/src/moneyInput/{currencyFormatting.js → currencyFormatting.ts} +7 -10
  36. package/src/moneyInput/index.ts +7 -0
  37. package/src/popover/__snapshots__/Popover.spec.js.snap +1 -1
  38. package/src/radioGroup/RadioGroup.js +2 -2
  39. package/src/radioGroup/RadioGroup.rtl.spec.tsx +16 -0
  40. package/src/moneyInput/index.js +0 -1
  41. /package/src/moneyInput/{MoneyInput.messages.js → MoneyInput.messages.ts} +0 -0
@@ -1,48 +1,79 @@
1
1
  import { isEmpty, isNumber, isNull } from '@transferwise/neptune-validation';
2
2
  import { Flag } from '@wise/art';
3
3
  import classNames from 'classnames';
4
- import PropTypes from 'prop-types';
5
4
  import { Component } from 'react';
6
- import { injectIntl } from 'react-intl';
5
+ import { injectIntl, WrappedComponentProps } from 'react-intl';
7
6
 
8
7
  import { Typography } from '../common';
9
8
  import { Key as keyValues } from '../common/key';
10
9
  import keyCodes from '../common/keyCodes';
11
- import { Size } from '../common/propsValues/size';
10
+ import { Size, SizeLarge, SizeMedium, SizeSmall } from '../common/propsValues/size';
12
11
  import { Input } from '../inputs/Input';
13
- import { SelectInput, SelectInputOptionContent } from '../inputs/SelectInput';
12
+ import {
13
+ SelectInput,
14
+ SelectInputItem,
15
+ SelectInputOptionContent,
16
+ SelectInputOptionItem,
17
+ SelectInputProps,
18
+ } from '../inputs/SelectInput';
14
19
  import Title from '../title';
15
20
 
16
21
  import messages from './MoneyInput.messages';
17
22
  import { formatAmount, parseAmount } from './currencyFormatting';
18
23
 
19
- const Currency = PropTypes.shape({
20
- header: PropTypes.string,
21
- value: PropTypes.string,
22
- label: PropTypes.string,
23
- currency: PropTypes.string,
24
- note: PropTypes.string,
25
- searchable: PropTypes.string,
26
- });
24
+ export interface CurrencyOptionItem {
25
+ header?: never;
26
+ value: string;
27
+ label: string;
28
+ currency: string;
29
+ note?: string;
30
+ searchable?: string;
31
+ }
27
32
 
28
- const isNumberOrNull = (v) => isNumber(v) || isNull(v);
33
+ export interface CurrencyHeaderItem {
34
+ header: string;
35
+ }
29
36
 
30
- const formatAmountIfSet = (amount, currency, locale, maxLengthOverride) => {
37
+ export type CurrencyItem = CurrencyOptionItem | CurrencyHeaderItem;
38
+
39
+ const isNumberOrNull = (v: unknown): v is number | null => isNumber(v) || isNull(v);
40
+
41
+ const formatAmountIfSet = ({
42
+ amount,
43
+ currency,
44
+ locale,
45
+ maxLengthOverride,
46
+ }: {
47
+ amount: number | null | undefined;
48
+ currency: string;
49
+ locale: string;
50
+ maxLengthOverride?: number;
51
+ }) => {
31
52
  if (maxLengthOverride) {
32
- return amount || '';
53
+ return amount != null ? String(amount) : '';
33
54
  } else {
34
55
  return typeof amount === 'number' ? formatAmount(amount, currency, locale) : '';
35
56
  }
36
57
  };
37
58
 
38
- const parseNumber = (amount, currency, locale, maxLengthOverride) => {
59
+ const parseNumber = ({
60
+ amount,
61
+ currency,
62
+ locale,
63
+ maxLengthOverride,
64
+ }: {
65
+ amount: string;
66
+ currency: string;
67
+ locale: string;
68
+ maxLengthOverride?: number;
69
+ }) => {
39
70
  if (!maxLengthOverride) {
40
71
  return parseAmount(amount, currency, locale);
41
72
  }
42
73
  if (maxLengthOverride && amount.length > maxLengthOverride) {
43
74
  return 0;
44
75
  }
45
- return +amount;
76
+ return Number(amount);
46
77
  };
47
78
 
48
79
  const inputKeyCodeAllowlist = new Set([
@@ -61,39 +92,76 @@ const inputKeyCodeAllowlist = new Set([
61
92
 
62
93
  const inputKeyAllowlist = new Set([keyValues.PERIOD, keyValues.COMMA]);
63
94
 
64
- class MoneyInput extends Component {
65
- constructor(props) {
95
+ export interface MoneyInputProps extends WrappedComponentProps {
96
+ id?: string;
97
+ currencies: readonly CurrencyItem[];
98
+ selectedCurrency: CurrencyOptionItem;
99
+ onCurrencyChange?: (value: CurrencyOptionItem) => void;
100
+ placeholder?: number;
101
+ amount: number | null;
102
+ size?: SizeSmall | SizeMedium | SizeLarge;
103
+ onAmountChange?: (value: number | null) => void;
104
+ addon?: React.ReactNode;
105
+ searchPlaceholder?: string;
106
+ /**
107
+ * Allows the consumer to react to searching, while the search itself is handled internally.
108
+ */
109
+ onSearchChange?: (value: { searchQuery: string; filteredOptions: CurrencyItem[] }) => void;
110
+ customActionLabel?: React.ReactNode;
111
+ onCustomAction?: () => void;
112
+ classNames?: Record<string, string>;
113
+ selectProps?: Partial<SelectInputProps<CurrencyOptionItem>>;
114
+ maxLengthOverride?: number;
115
+ }
116
+
117
+ interface MoneyInputState {
118
+ searchQuery: string;
119
+ formattedAmount: string;
120
+ locale: string;
121
+ }
122
+
123
+ class MoneyInput extends Component<MoneyInputProps, MoneyInputState> {
124
+ declare props: MoneyInputProps &
125
+ Required<Pick<MoneyInputProps, keyof typeof MoneyInput.defaultProps>>;
126
+
127
+ static defaultProps = {
128
+ size: Size.LARGE,
129
+ classNames: {},
130
+ selectProps: {},
131
+ } satisfies Partial<MoneyInputProps>;
132
+
133
+ amountFocused = false;
134
+
135
+ constructor(props: MoneyInputProps) {
66
136
  super(props);
67
- const { locale } = this.props.intl;
68
- this.formatMessage = this.props.intl.formatMessage;
69
137
  this.state = {
70
138
  searchQuery: '',
71
- formattedAmount: formatAmountIfSet(
72
- props.amount,
73
- props.selectedCurrency.currency,
74
- locale,
75
- props.maxLengthOverride,
76
- ),
77
- locale,
139
+ formattedAmount: formatAmountIfSet({
140
+ amount: props.amount,
141
+ currency: props.selectedCurrency.currency,
142
+ locale: props.intl.locale,
143
+ maxLengthOverride: props.maxLengthOverride,
144
+ }),
145
+ locale: props.intl.locale,
78
146
  };
79
147
  }
80
148
 
81
- UNSAFE_componentWillReceiveProps(nextProps) {
82
- this.setState({ locale: nextProps?.intl?.locale });
149
+ UNSAFE_componentWillReceiveProps(nextProps: MoneyInputProps) {
150
+ this.setState({ locale: nextProps.intl.locale });
83
151
 
84
152
  if (!this.amountFocused) {
85
153
  this.setState({
86
- formattedAmount: formatAmountIfSet(
87
- nextProps.amount,
88
- nextProps.selectedCurrency.currency,
89
- nextProps?.intl?.locale,
90
- nextProps.maxLengthOverride,
91
- ),
154
+ formattedAmount: formatAmountIfSet({
155
+ amount: nextProps.amount,
156
+ currency: nextProps.selectedCurrency.currency,
157
+ locale: nextProps.intl.locale,
158
+ maxLengthOverride: nextProps.maxLengthOverride,
159
+ }),
92
160
  });
93
161
  }
94
162
  }
95
163
 
96
- isInputAllowedForKeyEvent = (event) => {
164
+ isInputAllowedForKeyEvent = (event: React.KeyboardEvent<HTMLInputElement>) => {
97
165
  const { keyCode, metaKey, key, ctrlKey } = event;
98
166
  const isNumberKey = isNumber(parseInt(key, 10));
99
167
 
@@ -106,54 +174,54 @@ class MoneyInput extends Component {
106
174
  );
107
175
  };
108
176
 
109
- handleKeyDown = (event) => {
177
+ handleKeyDown: React.KeyboardEventHandler<HTMLInputElement> = (event) => {
110
178
  if (!this.isInputAllowedForKeyEvent(event)) {
111
179
  event.preventDefault();
112
180
  }
113
181
  };
114
182
 
115
- handlePaste = (event) => {
116
- const paste = (event.clipboardData || window.clipboardData).getData('text');
183
+ handlePaste: React.ClipboardEventHandler<HTMLInputElement> = (event) => {
184
+ const paste = event.clipboardData.getData('text');
117
185
  const { locale } = this.state;
118
186
  const parsed = isEmpty(paste)
119
187
  ? null
120
- : parseNumber(
121
- paste,
122
- this.props.selectedCurrency.currency,
123
- locale,
124
- this.props.maxLengthOverride,
125
- );
188
+ : parseNumber({
189
+ amount: paste,
190
+ currency: this.props.selectedCurrency.currency,
191
+ locale: locale,
192
+ maxLengthOverride: this.props.maxLengthOverride,
193
+ });
126
194
 
127
195
  if (isNumberOrNull(parsed)) {
128
196
  this.setState({
129
- formattedAmount: formatAmountIfSet(
130
- parsed,
131
- this.props.selectedCurrency.currency,
132
- locale,
133
- this.props.maxLengthOverride,
134
- ),
197
+ formattedAmount: formatAmountIfSet({
198
+ amount: parsed,
199
+ currency: this.props.selectedCurrency.currency,
200
+ locale: locale,
201
+ maxLengthOverride: this.props.maxLengthOverride,
202
+ }),
135
203
  });
136
- this.props.onAmountChange(parsed);
204
+ this.props.onAmountChange?.(parsed);
137
205
  }
138
206
 
139
207
  event.preventDefault();
140
208
  };
141
209
 
142
- onAmountChange = (event) => {
210
+ onAmountChange: React.ChangeEventHandler<HTMLInputElement> = (event) => {
143
211
  const { value } = event.target;
144
212
  this.setState({
145
213
  formattedAmount: value,
146
214
  });
147
215
  const parsed = isEmpty(value)
148
216
  ? null
149
- : parseNumber(
150
- value,
151
- this.props.selectedCurrency.currency,
152
- this.state.locale,
153
- this.props.maxLengthOverride,
154
- );
217
+ : parseNumber({
218
+ amount: value,
219
+ currency: this.props.selectedCurrency.currency,
220
+ locale: this.state.locale,
221
+ maxLengthOverride: this.props.maxLengthOverride,
222
+ });
155
223
  if (isNumberOrNull(parsed)) {
156
- this.props.onAmountChange(parsed);
224
+ this.props.onAmountChange?.(parsed);
157
225
  }
158
226
  };
159
227
 
@@ -166,34 +234,26 @@ class MoneyInput extends Component {
166
234
  this.amountFocused = true;
167
235
  };
168
236
 
169
- mapOption = (item) => {
170
- return {
171
- type: 'option',
172
- value: item,
173
- filterMatchers: [item.value, item.label, item.note, item.searchable],
174
- };
175
- };
176
-
177
237
  getSelectOptions() {
178
- const selectOptions = [...filterOptionsForQuery(this.props.currencies, this.state.searchQuery)];
238
+ const selectOptions = filterCurrenciesForQuery(this.props.currencies, this.state.searchQuery);
179
239
 
180
- let formattedOptions = [];
181
- let groupIndex = null;
240
+ const formattedOptions: SelectInputItem<CurrencyOptionItem>[] = [];
241
+ let currentGroupOptions: SelectInputOptionItem<CurrencyOptionItem>[] | undefined;
182
242
 
183
243
  selectOptions.forEach((item) => {
184
- if (item.header) {
244
+ if (item.header != null) {
245
+ currentGroupOptions = [];
185
246
  formattedOptions.push({
186
247
  type: 'group',
187
248
  label: item.header,
188
- options: [],
249
+ options: currentGroupOptions,
189
250
  });
190
- groupIndex = formattedOptions.length - 1;
191
251
  } else {
192
- if (groupIndex === null) {
193
- formattedOptions.push(this.mapOption(item));
194
- } else {
195
- formattedOptions[groupIndex]?.options.push(this.mapOption(item));
196
- }
252
+ (currentGroupOptions ?? formattedOptions).push({
253
+ type: 'option',
254
+ value: item,
255
+ filterMatchers: [item.value, item.label, item.note ?? '', item.searchable ?? ''],
256
+ });
197
257
  }
198
258
  });
199
259
 
@@ -202,51 +262,47 @@ class MoneyInput extends Component {
202
262
 
203
263
  setAmount() {
204
264
  this.setState((previousState) => {
205
- const parsed = parseNumber(
206
- previousState.formattedAmount,
207
- this.props.selectedCurrency.currency,
208
- previousState.locale,
209
- this.props.maxLengthOverride,
210
- );
265
+ const parsed = parseNumber({
266
+ amount: previousState.formattedAmount,
267
+ currency: this.props.selectedCurrency.currency,
268
+ locale: previousState.locale,
269
+ maxLengthOverride: this.props.maxLengthOverride,
270
+ });
211
271
  if (!isNumberOrNull(parsed)) {
212
272
  return {
213
273
  formattedAmount: previousState.formattedAmount,
214
274
  };
215
275
  }
216
276
  return {
217
- formattedAmount: formatAmountIfSet(
218
- parsed,
219
- this.props.selectedCurrency.currency,
220
- previousState.locale,
221
- this.props.maxLengthOverride,
222
- ),
277
+ formattedAmount: formatAmountIfSet({
278
+ amount: parsed,
279
+ currency: this.props.selectedCurrency.currency,
280
+ locale: previousState.locale,
281
+ maxLengthOverride: this.props.maxLengthOverride,
282
+ }),
223
283
  };
224
284
  });
225
285
  }
226
286
 
227
- handleSelectChange = (value) => {
287
+ handleSelectChange = (value: CurrencyOptionItem) => {
228
288
  this.handleSearchChange('');
229
- this.props.onCurrencyChange(value);
289
+ this.props.onCurrencyChange?.(value);
230
290
  };
231
291
 
232
292
  handleCustomAction = () => {
233
293
  this.handleSearchChange('');
234
- if (this.props.onCustomAction) {
235
- this.props.onCustomAction();
236
- }
294
+ this.props.onCustomAction?.();
237
295
  };
238
296
 
239
- handleSearchChange = (searchQuery) => {
297
+ handleSearchChange = (searchQuery: string) => {
240
298
  this.setState({ searchQuery });
241
- if (this.props.onSearchChange) {
242
- this.props.onSearchChange({
243
- searchQuery,
244
- filteredOptions: filterOptionsForQuery(this.props.currencies, searchQuery),
245
- });
246
- }
299
+ this.props.onSearchChange?.({
300
+ searchQuery,
301
+ filteredOptions: filterCurrenciesForQuery(this.props.currencies, searchQuery),
302
+ });
247
303
  };
248
304
 
249
- style = (className) => this.props.classNames[className] || className;
305
+ style = (className: string) => this.props.classNames[className] || className;
250
306
 
251
307
  render() {
252
308
  const { selectedCurrency, onCurrencyChange, size, addon, id, selectProps, maxLengthOverride } =
@@ -292,12 +348,12 @@ class MoneyInput extends Component {
292
348
  inputMode="decimal"
293
349
  disabled={disabled}
294
350
  maxLength={maxLengthOverride}
295
- placeholder={formatAmountIfSet(
296
- this.props.placeholder,
297
- this.props.selectedCurrency.currency,
298
- this.state.locale,
299
- this.props.maxLengthOverride,
300
- )}
351
+ placeholder={formatAmountIfSet({
352
+ amount: this.props.placeholder,
353
+ currency: this.props.selectedCurrency.currency,
354
+ locale: this.state.locale,
355
+ maxLengthOverride: this.props.maxLengthOverride,
356
+ })}
301
357
  autoComplete="off"
302
358
  onKeyDown={this.handleKeyDown}
303
359
  onChange={this.onAmountChange}
@@ -364,13 +420,13 @@ class MoneyInput extends Component {
364
420
  this.props.onCustomAction
365
421
  ? () => (
366
422
  // eslint-disable-next-line jsx-a11y/click-events-have-key-events
367
- <div role="button" tabIndex="0" onClick={this.handleCustomAction}>
423
+ <div role="button" tabIndex={0} onClick={this.handleCustomAction}>
368
424
  {this.props.customActionLabel}
369
425
  </div>
370
426
  )
371
- : null
427
+ : undefined
372
428
  }
373
- placeholder={this.formatMessage(messages.selectPlaceholder)}
429
+ placeholder={this.props.intl.formatMessage(messages.selectPlaceholder)}
374
430
  filterable
375
431
  filterPlaceholder={this.props.searchPlaceholder}
376
432
  disabled={disabled}
@@ -388,33 +444,36 @@ class MoneyInput extends Component {
388
444
  }
389
445
  }
390
446
 
391
- function filterOptionsForQuery(options, query) {
447
+ function filterCurrenciesForQuery(
448
+ currencies: readonly CurrencyItem[],
449
+ query: string,
450
+ ): CurrencyItem[] {
392
451
  if (!query) {
393
- return options;
452
+ return [...currencies];
394
453
  }
395
454
 
455
+ const options = currencies.filter(
456
+ (option): option is CurrencyOptionItem => option.header == null,
457
+ );
396
458
  const filteredOptions = removeDuplicateValueOptions(options).filter((option) =>
397
- isCurrencyOptionAndFitsQuery(option, query),
459
+ currencyOptionFitsQuery(option, query),
398
460
  );
399
461
 
400
462
  return sortOptionsLabelsToFirst(filteredOptions, query);
401
463
  }
402
464
 
403
- function removeDuplicateValueOptions(options) {
404
- const result = [];
405
- const resultValues = [];
406
-
407
- options.forEach((option) => {
408
- if (option.value && !resultValues.includes(option.value)) {
409
- result.push(option);
410
- resultValues.push(option.value);
465
+ function removeDuplicateValueOptions(options: CurrencyOptionItem[]) {
466
+ const uniqueValues = new Set<string>();
467
+ return options.filter((option) => {
468
+ if (!uniqueValues.has(option.value)) {
469
+ uniqueValues.add(option.value);
470
+ return true;
411
471
  }
472
+ return false;
412
473
  });
413
-
414
- return result;
415
474
  }
416
475
 
417
- function isCurrencyOptionAndFitsQuery(option, query) {
476
+ function currencyOptionFitsQuery(option: CurrencyOptionItem, query: string) {
418
477
  if (!option.value) {
419
478
  return false;
420
479
  }
@@ -426,11 +485,11 @@ function isCurrencyOptionAndFitsQuery(option, query) {
426
485
  );
427
486
  }
428
487
 
429
- function contains(property, query) {
488
+ function contains(property: string | undefined, query: string) {
430
489
  return property && property.toLowerCase().includes(query.toLowerCase());
431
490
  }
432
491
 
433
- function sortOptionsLabelsToFirst(options, query) {
492
+ function sortOptionsLabelsToFirst(options: CurrencyOptionItem[], query: string) {
434
493
  return options.sort((first, second) => {
435
494
  const firstContains = contains(first.label, query);
436
495
  const secondContains = contains(second.label, query);
@@ -448,47 +507,4 @@ function sortOptionsLabelsToFirst(options, query) {
448
507
  });
449
508
  }
450
509
 
451
- MoneyInput.propTypes = {
452
- id: PropTypes.string,
453
- currencies: PropTypes.arrayOf(Currency).isRequired,
454
- selectedCurrency: Currency.isRequired,
455
- onCurrencyChange: PropTypes.func,
456
- placeholder: PropTypes.number,
457
- amount: PropTypes.number,
458
- size: PropTypes.oneOf(['sm', 'md', 'lg']),
459
- onAmountChange: PropTypes.func,
460
- addon: PropTypes.node,
461
- searchPlaceholder: PropTypes.string,
462
- /**
463
- * Allows the consumer to react to searching, while the search itself is handled internally. Called with `{ searchQuery: string, filteredOptions: Currency[] }`
464
- */
465
- onSearchChange: PropTypes.func,
466
- customActionLabel: PropTypes.node,
467
- onCustomAction: PropTypes.func,
468
- classNames: PropTypes.objectOf(PropTypes.string),
469
- selectProps: PropTypes.object,
470
- maxLengthOverride: PropTypes.number,
471
- };
472
-
473
- MoneyInput.defaultProps = {
474
- id: null,
475
- size: Size.LARGE,
476
- addon: null,
477
- searchPlaceholder: '',
478
- onSearchChange: undefined,
479
- onCurrencyChange: null,
480
- placeholder: null,
481
- amount: null,
482
- onAmountChange: null,
483
- customActionLabel: '',
484
- onCustomAction: null,
485
- classNames: {},
486
- selectProps: {},
487
- maxLengthOverride: null,
488
- };
489
-
490
- // this export is necessary for react-to-typescript-definitions
491
- // to be able to properly generate TS types, this is due to us wrapping this component in `injectIntl` before exporting
492
- export { MoneyInput };
493
-
494
510
  export default injectIntl(MoneyInput);
@@ -1,12 +1,12 @@
1
1
  import { formatAmount, parseAmount } from './currencyFormatting';
2
2
 
3
3
  jest.mock('@transferwise/formatting', () => ({
4
- formatAmount: (number) => `formatted ${number}`,
4
+ formatAmount: (number: number) => `formatted ${number}`,
5
5
  }));
6
6
 
7
7
  describe('Number formatting', () => {
8
8
  it('uses @transferwise/formatting for formatting numbers', () => {
9
- expect(formatAmount(100)).toBe('formatted 100');
9
+ expect(formatAmount(100, 'gbp')).toBe('formatted 100');
10
10
  });
11
11
 
12
12
  it('parses localized numbers', () => {
@@ -5,7 +5,7 @@ import { DEFAULT_LOCALE } from '../common/locale';
5
5
  export { formatAmount };
6
6
 
7
7
  // TODO: do not duplicate this between formatting and components
8
- const currencyDecimals = {
8
+ const currencyDecimals: Record<string, number> = {
9
9
  BIF: 0,
10
10
  BYR: 0,
11
11
  CLP: 0,
@@ -41,30 +41,27 @@ function isNumberLocaleSupported() {
41
41
  return numberString === '1,234';
42
42
  }
43
43
 
44
- function getValidLocale(locale) {
44
+ function getValidLocale(locale: string) {
45
45
  try {
46
46
  const noUnderscoreLocale = locale.replace(/_/, '-');
47
47
 
48
48
  Intl.NumberFormat(noUnderscoreLocale);
49
49
  return noUnderscoreLocale;
50
50
  } catch {
51
- return 'en-GB';
51
+ return DEFAULT_LOCALE;
52
52
  }
53
53
  }
54
54
 
55
- function getCurrencyDecimals(currency = '') {
55
+ function getCurrencyDecimals(currency: string) {
56
56
  const upperCaseCurrency = currency.toUpperCase();
57
- if (Object.prototype.hasOwnProperty.call(currencyDecimals, upperCaseCurrency)) {
58
- return currencyDecimals[upperCaseCurrency];
59
- }
60
- return DEFAULT_CURRENCY_DECIMALS;
57
+ return currencyDecimals[upperCaseCurrency] ?? DEFAULT_CURRENCY_DECIMALS;
61
58
  }
62
59
 
63
- function getDecimalSeparator(locale) {
60
+ function getDecimalSeparator(locale: string) {
64
61
  return isNumberLocaleSupported() ? (1.1).toLocaleString(locale)[1] : '.';
65
62
  }
66
63
 
67
- export function parseAmount(number, currency, locale) {
64
+ export function parseAmount(number: string, currency: string, locale = DEFAULT_LOCALE) {
68
65
  const validLocale = getValidLocale(locale);
69
66
 
70
67
  const precision = getCurrencyDecimals(currency);
@@ -0,0 +1,7 @@
1
+ export type {
2
+ CurrencyHeaderItem,
3
+ CurrencyItem,
4
+ CurrencyOptionItem,
5
+ MoneyInputProps,
6
+ } from './MoneyInput';
7
+ export { default } from './MoneyInput';
@@ -2,7 +2,7 @@
2
2
 
3
3
  exports[`Popover on desktop renders when is open 1`] = `
4
4
  <div
5
- class="np-panel np-panel--open np-theme-light np-popover__container"
5
+ class="np-panel np-panel--open np-popover__container"
6
6
  data-popper-escaped="true"
7
7
  data-popper-placement="right"
8
8
  data-popper-reference-hidden="true"
@@ -20,7 +20,7 @@ class RadioGroup extends Component {
20
20
  const { radios, name } = this.props;
21
21
  const { selectedValue } = this.state;
22
22
  return radios && radios.length > 0 ? (
23
- <>
23
+ <div role="radiogroup">
24
24
  {radios.map(({ id, avatar, value, label, disabled, secondary, readOnly }, index) => (
25
25
  <Radio
26
26
  // eslint-disable-next-line react/no-array-index-key
@@ -37,7 +37,7 @@ class RadioGroup extends Component {
37
37
  onChange={(value_) => this.handleOnChange(value_)}
38
38
  />
39
39
  ))}
40
- </>
40
+ </div>
41
41
  ) : null;
42
42
  }
43
43
  }
@@ -0,0 +1,16 @@
1
+ import { render, screen } from '@testing-library/react';
2
+
3
+ import RadioGroup from '.';
4
+
5
+ describe('RadioGroup', () => {
6
+ it('has accessible role', () => {
7
+ render(
8
+ <RadioGroup
9
+ name="currency"
10
+ radios={[{ label: 'USD' }, { label: 'EUR' }]}
11
+ onChange={() => {}}
12
+ />,
13
+ );
14
+ expect(screen.getByRole('radiogroup')).toBeInTheDocument();
15
+ });
16
+ });
@@ -1 +0,0 @@
1
- export { default } from './MoneyInput';