@transferwise/components 0.0.0-experimental-0a5396f → 0.0.0-experimental-81a961c

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 (37) hide show
  1. package/build/inputs/SelectInput.js +4 -0
  2. package/build/inputs/SelectInput.js.map +1 -1
  3. package/build/inputs/SelectInput.mjs +4 -0
  4. package/build/inputs/SelectInput.mjs.map +1 -1
  5. package/build/moneyInput/MoneyInput.js +1 -1
  6. package/build/moneyInput/MoneyInput.js.map +1 -1
  7. package/build/moneyInput/MoneyInput.mjs +1 -1
  8. package/build/moneyInput/MoneyInput.mjs.map +1 -1
  9. package/build/typeahead/Typeahead.js +8 -20
  10. package/build/typeahead/Typeahead.js.map +1 -1
  11. package/build/typeahead/Typeahead.mjs +9 -20
  12. package/build/typeahead/Typeahead.mjs.map +1 -1
  13. package/build/typeahead/typeaheadInput/TypeaheadInput.js +1 -3
  14. package/build/typeahead/typeaheadInput/TypeaheadInput.js.map +1 -1
  15. package/build/typeahead/typeaheadInput/TypeaheadInput.mjs +1 -3
  16. package/build/typeahead/typeaheadInput/TypeaheadInput.mjs.map +1 -1
  17. package/build/typeahead/typeaheadOption/TypeaheadOption.js +5 -7
  18. package/build/typeahead/typeaheadOption/TypeaheadOption.js.map +1 -1
  19. package/build/typeahead/typeaheadOption/TypeaheadOption.mjs +5 -7
  20. package/build/typeahead/typeaheadOption/TypeaheadOption.mjs.map +1 -1
  21. package/build/types/inputs/SelectInput.d.ts.map +1 -1
  22. package/build/types/typeahead/Typeahead.d.ts +1 -1
  23. package/build/types/typeahead/Typeahead.d.ts.map +1 -1
  24. package/build/types/typeahead/typeaheadInput/TypeaheadInput.d.ts +0 -1
  25. package/build/types/typeahead/typeaheadInput/TypeaheadInput.d.ts.map +1 -1
  26. package/build/types/typeahead/typeaheadOption/TypeaheadOption.d.ts +1 -1
  27. package/build/types/typeahead/typeaheadOption/TypeaheadOption.d.ts.map +1 -1
  28. package/package.json +3 -3
  29. package/src/inputs/SelectInput.tsx +5 -2
  30. package/src/moneyInput/MoneyInput.docs.mdx +40 -0
  31. package/src/moneyInput/MoneyInput.rtl.spec.tsx +38 -0
  32. package/src/moneyInput/MoneyInput.spec.js +0 -11
  33. package/src/moneyInput/MoneyInput.story.tsx +10 -7
  34. package/src/moneyInput/MoneyInput.tsx +1 -1
  35. package/src/typeahead/Typeahead.tsx +13 -23
  36. package/src/typeahead/typeaheadInput/TypeaheadInput.tsx +0 -3
  37. package/src/typeahead/typeaheadOption/TypeaheadOption.tsx +5 -7
@@ -51,6 +51,7 @@ describe('MoneyInput', () => {
51
51
  const props = {
52
52
  currencies,
53
53
  selectedCurrency: currencies[1],
54
+ searchPlaceholder: 'Type a currency / country',
54
55
  amount: 1000,
55
56
  onAmountChange: jest.fn(),
56
57
  onCurrencyChange: jest.fn(),
@@ -90,4 +91,41 @@ describe('MoneyInput', () => {
90
91
  );
91
92
  expect(screen.getAllByRole('group')[0]).toHaveAccessibleName(/^Recipient gets/);
92
93
  });
94
+
95
+ describe('ids', () => {
96
+ it('should guarantee id and connect the input with the selected currency via withId HoC', () => {
97
+ render(<MoneyInput {...props} />);
98
+ const input = screen.getByRole('textbox');
99
+ const button = screen.getByRole('combobox');
100
+ expect(input.getAttribute('id')).toBeTruthy();
101
+ expect(input).toHaveAttribute('aria-describedby', button.getAttribute('id'));
102
+ });
103
+
104
+ it('should have unique id for the select filter with predefined id', async () => {
105
+ const fieldId = 'myFieldId';
106
+ render(
107
+ <Field label="Multiple currencies" id={fieldId}>
108
+ <MoneyInput {...props} />
109
+ </Field>,
110
+ );
111
+ await userEvent.click(screen.getByRole('combobox'));
112
+ expect(screen.getByLabelText(props.searchPlaceholder)).toHaveAttribute(
113
+ 'id',
114
+ `${fieldId}SelectedCurrencySearch`,
115
+ );
116
+ });
117
+
118
+ it('should have unique id for the select filter without predefined id', async () => {
119
+ render(
120
+ <Field label="Multiple currencies">
121
+ <MoneyInput {...props} />
122
+ </Field>,
123
+ );
124
+ await userEvent.click(screen.getByRole('combobox'));
125
+ expect(screen.getByLabelText(props.searchPlaceholder)).toHaveAttribute(
126
+ 'id',
127
+ expect.stringMatching(/^:.*?:SelectedCurrencySearch$/),
128
+ );
129
+ });
130
+ });
93
131
  });
@@ -1,5 +1,4 @@
1
1
  import { shallow } from 'enzyme';
2
- import { render, screen } from '@testing-library/react';
3
2
 
4
3
  import { MoneyInput, Title, Input, SelectInput } from '..';
5
4
  import { mockMatchMedia, mockResizeObserver } from '../test-utils';
@@ -818,14 +817,4 @@ describe('Money Input', () => {
818
817
  });
819
818
  });
820
819
  });
821
-
822
- describe('withId', () => {
823
- it('should guarantee id and connect the input with the selected currency', () => {
824
- render(<MoneyInput {...props} />);
825
- const input = screen.getByRole('textbox');
826
- const button = screen.getByRole('combobox');
827
- expect(input.getAttribute('id')).toBeTruthy();
828
- expect(input).toHaveAttribute('aria-describedby', button.getAttribute('id'));
829
- });
830
- });
831
820
  });
@@ -9,13 +9,13 @@ import { Field } from '../field/Field';
9
9
  export default {
10
10
  component: MoneyInput,
11
11
  title: 'Forms/MoneyInput',
12
- render: function Render(args) {
12
+ render: function Render({ id, ...args }) {
13
13
  const [selectedCurrency, setSelectedCurrency] = useState(args.selectedCurrency);
14
14
 
15
15
  const handleOnCurrencyChange = (value: CurrencyOptionItem) => setSelectedCurrency(value);
16
16
 
17
17
  return (
18
- <Field label="Editable money input label" required>
18
+ <Field label="Editable money input label" required id={id}>
19
19
  <MoneyInput
20
20
  {...args}
21
21
  selectedCurrency={selectedCurrency}
@@ -129,6 +129,7 @@ export const MultipleCurrencies: Story = {
129
129
  },
130
130
  selectedCurrency: exampleCurrency.usd,
131
131
  searchPlaceholder: 'Type a currency / country',
132
+ id: 'moneyInput',
132
133
  },
133
134
  };
134
135
 
@@ -155,20 +156,22 @@ export const OpenedInput: Story = {
155
156
  };
156
157
 
157
158
  export const SmallInput: Story = {
158
- render: (args) => {
159
+ render: ({ id, ...args }) => {
159
160
  return (
160
161
  <>
161
- <Field id={args.id} label="Money inputs" required>
162
+ <Field label="Money inputs" required>
162
163
  <MoneyInput {...args} {...SingleCurrency.args} />
163
164
  </Field>
164
165
  <br />
165
- <MoneyInput {...args} {...MultipleCurrencies.args} />
166
+ <Field label="Multiple currencies">
167
+ <MoneyInput {...args} {...MultipleCurrencies.args} />
168
+ </Field>
166
169
  <hr />
167
- <Field id={args.id} label="Error states" sentiment="negative" required>
170
+ <Field label="Error state" sentiment="negative" required>
168
171
  <MoneyInput {...args} {...SingleCurrency.args} />
169
172
  </Field>
170
173
  <br />
171
- <Field sentiment="negative">
174
+ <Field label="Multiple currencies: error state" sentiment="negative">
172
175
  <MoneyInput {...args} {...MultipleCurrencies.args} />
173
176
  </Field>
174
177
  </>
@@ -314,7 +314,7 @@ class MoneyInput extends Component<MoneyInputPropsWithInputAttributes, MoneyInpu
314
314
 
315
315
  const isFixedCurrency = (!this.state.searchQuery && hasSingleCurrency()) || !onCurrencyChange;
316
316
  const disabled = !this.props.onAmountChange;
317
- const selectedCurrencyElementId = `${amountInputId}SelectedCurrency`;
317
+ const selectedCurrencyElementId = `${inputAttributes?.id ?? amountInputId}SelectedCurrency`;
318
318
 
319
319
  return (
320
320
  <div
@@ -3,7 +3,7 @@ import { clsx } from 'clsx';
3
3
  import { DebouncedFunc } from 'lodash';
4
4
  import clamp from 'lodash.clamp';
5
5
  import debounce from 'lodash.debounce';
6
- import React, { Component, ReactNode } from 'react';
6
+ import { Component, ReactNode } from 'react';
7
7
  import { injectIntl, WrappedComponentProps } from 'react-intl';
8
8
 
9
9
  import Chip from '../chips/Chip';
@@ -101,7 +101,6 @@ class Typeahead<T> extends Component<TypeaheadPropsWithInputAttributes<T>, Typea
101
101
  size: Size.MEDIUM,
102
102
  validateChip: () => true,
103
103
  } satisfies Partial<TypeaheadProps<unknown>>;
104
- optionRefs: (React.RefObject<HTMLLIElement> | null)[];
105
104
 
106
105
  constructor(props: TypeaheadPropsWithInputAttributes<T>) {
107
106
  super(props);
@@ -116,7 +115,6 @@ class Typeahead<T> extends Component<TypeaheadPropsWithInputAttributes<T>, Typea
116
115
  optionsShown: false,
117
116
  isFocused: false,
118
117
  };
119
- this.optionRefs = [] as (React.RefObject<HTMLLIElement> | null)[];
120
118
  }
121
119
 
122
120
  handleSearchDebounced: DebouncedFunc<Typeahead<T>['handleSearch']>;
@@ -230,6 +228,7 @@ class Typeahead<T> extends Component<TypeaheadPropsWithInputAttributes<T>, Typea
230
228
  }
231
229
  }
232
230
  };
231
+
233
232
  moveFocusedOption(offset: number) {
234
233
  this.setState((previousState) => {
235
234
  const { keyboardFocusedOptionIndex } = previousState;
@@ -238,10 +237,6 @@ class Typeahead<T> extends Component<TypeaheadPropsWithInputAttributes<T>, Typea
238
237
  if (keyboardFocusedOptionIndex !== null) {
239
238
  index = clamp(keyboardFocusedOptionIndex + offset, 0, options.length - 1);
240
239
  }
241
- const optionRef = this.optionRefs[index];
242
- if (optionRef?.current) {
243
- optionRef.current.focus(); // Set focus on the option element
244
- }
245
240
  return {
246
241
  keyboardFocusedOptionIndex: index,
247
242
  };
@@ -407,22 +402,17 @@ class Typeahead<T> extends Component<TypeaheadPropsWithInputAttributes<T>, Typea
407
402
  id={`menu-${id}`}
408
403
  >
409
404
  <ul className="dropdown-menu" role="menu" aria-labelledby="dropdownMenu1">
410
- {optionsToRender.map((option, idx) => {
411
- const ref = React.createRef<HTMLLIElement>();
412
- this.optionRefs[idx] = ref;
413
- return (
414
- <TypeaheadOption
415
- key={`${option.label}${idx.toString()}`}
416
- ref={ref}
417
- query={query}
418
- option={option}
419
- selected={keyboardFocusedOptionIndex === idx}
420
- onClick={(event) => {
421
- this.onOptionSelected(event, option);
422
- }}
423
- />
424
- );
425
- })}
405
+ {optionsToRender.map((option, idx) => (
406
+ <TypeaheadOption
407
+ key={`${option.label}${idx.toString()}`}
408
+ query={query}
409
+ option={option}
410
+ selected={keyboardFocusedOptionIndex === idx}
411
+ onClick={(event) => {
412
+ this.onOptionSelected(event, option);
413
+ }}
414
+ />
415
+ ))}
426
416
  {footer}
427
417
  </ul>
428
418
  </div>
@@ -24,7 +24,6 @@ export type TypeaheadInputProps<T> = {
24
24
 
25
25
  type TypeaheadInputState = {
26
26
  inputWidth: number;
27
- keyboardFocusedOptionIndex: number | null;
28
27
  };
29
28
 
30
29
  export default class TypeaheadInput<T> extends Component<
@@ -38,7 +37,6 @@ export default class TypeaheadInput<T> extends Component<
38
37
  super(props);
39
38
  this.state = {
40
39
  inputWidth: DEFAULT_INPUT_MIN_WIDTH,
41
- keyboardFocusedOptionIndex: null,
42
40
  };
43
41
  }
44
42
 
@@ -88,7 +86,6 @@ export default class TypeaheadInput<T> extends Component<
88
86
  className={clsx(multiple && 'typeahead__input')}
89
87
  name={name}
90
88
  id={`input-${typeaheadId}`}
91
- aria-activedescendant={`menu-${typeaheadId} ${this.state.keyboardFocusedOptionIndex !== null ? `option-${this.state.keyboardFocusedOptionIndex}` : ''}`}
92
89
  autoFocus={autoFocus}
93
90
  placeholder={hasPlaceholder ? placeholder : ''}
94
91
  aria-autocomplete="list"
@@ -1,5 +1,5 @@
1
+ /* eslint-disable jsx-a11y/anchor-is-valid */
1
2
  import { clsx } from 'clsx';
2
- import { forwardRef } from 'react';
3
3
 
4
4
  import { TypeaheadOption } from '../Typeahead';
5
5
  import highlight from '../util/highlight';
@@ -11,21 +11,19 @@ export type TypeaheadOptionProps<T> = {
11
11
  query?: string;
12
12
  };
13
13
 
14
- const Option = forwardRef<HTMLLIElement, TypeaheadOptionProps<any>>((props, ref) => {
14
+ const Option = <T,>(props: TypeaheadOptionProps<T>) => {
15
15
  const { option, selected = false, onClick = () => {}, query = '' } = props;
16
16
  const { label, note, secondary } = option;
17
17
 
18
18
  return (
19
19
  <li
20
- ref={ref}
21
20
  role="option"
22
- aria-selected={selected ? 'true' : 'false'}
23
- id={`option-${props.option.label}`}
21
+ aria-selected="false"
24
22
  className={clsx('typeahead__option tw-dropdown-item clickable', {
25
23
  'tw-dropdown-item--focused': selected,
26
24
  })}
27
25
  >
28
- <a className="dropdown-item" href="#" tabIndex={0} onClick={onClick}>
26
+ <a className="dropdown-item" href="#" tabIndex={-1} onClick={onClick}>
29
27
  <span>{highlight(label, query)}</span>
30
28
  {note && <span className="np-text-body-default m-l-1">{note}</span>}
31
29
  {secondary && (
@@ -34,6 +32,6 @@ const Option = forwardRef<HTMLLIElement, TypeaheadOptionProps<any>>((props, ref)
34
32
  </a>
35
33
  </li>
36
34
  );
37
- });
35
+ };
38
36
 
39
37
  export default Option;