@transferwise/components 46.162.1 → 46.163.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 (157) hide show
  1. package/build/Calendar/DateHeader/DateHeader.js +2 -6
  2. package/build/Calendar/DateHeader/DateHeader.js.map +1 -1
  3. package/build/Calendar/DateHeader/DateHeader.mjs +3 -7
  4. package/build/Calendar/DateHeader/DateHeader.mjs.map +1 -1
  5. package/build/Carousel/Carousel.js +2 -2
  6. package/build/Carousel/Carousel.js.map +1 -1
  7. package/build/Carousel/Carousel.mjs +3 -3
  8. package/build/Carousel/Carousel.mjs.map +1 -1
  9. package/build/Chips/Chip/Chip.js +11 -4
  10. package/build/Chips/Chip/Chip.js.map +1 -1
  11. package/build/Chips/Chip/Chip.mjs +11 -4
  12. package/build/Chips/Chip/Chip.mjs.map +1 -1
  13. package/build/Inputs/SelectInput/OptionContent/SelectInputOptionContent.js +4 -3
  14. package/build/Inputs/SelectInput/OptionContent/SelectInputOptionContent.js.map +1 -1
  15. package/build/Inputs/SelectInput/OptionContent/SelectInputOptionContent.mjs +4 -3
  16. package/build/Inputs/SelectInput/OptionContent/SelectInputOptionContent.mjs.map +1 -1
  17. package/build/ListItem/Navigation/ListItemNavigation.js +1 -1
  18. package/build/ListItem/Navigation/ListItemNavigation.js.map +1 -1
  19. package/build/ListItem/Navigation/ListItemNavigation.mjs +2 -2
  20. package/build/ListItem/Navigation/ListItemNavigation.mjs.map +1 -1
  21. package/build/PhoneNumberInput/PhoneNumberInput.js +2 -1
  22. package/build/PhoneNumberInput/PhoneNumberInput.js.map +1 -1
  23. package/build/PhoneNumberInput/PhoneNumberInput.mjs +2 -1
  24. package/build/PhoneNumberInput/PhoneNumberInput.mjs.map +1 -1
  25. package/build/PromoCard/Indicator/PromoCardIndicator.js +1 -1
  26. package/build/PromoCard/Indicator/PromoCardIndicator.js.map +1 -1
  27. package/build/PromoCard/Indicator/PromoCardIndicator.mjs +2 -2
  28. package/build/PromoCard/Indicator/PromoCardIndicator.mjs.map +1 -1
  29. package/build/Typeahead/Typeahead.js +17 -4
  30. package/build/Typeahead/Typeahead.js.map +1 -1
  31. package/build/Typeahead/Typeahead.mjs +17 -4
  32. package/build/Typeahead/Typeahead.mjs.map +1 -1
  33. package/build/Typeahead/TypeaheadInput/TypeaheadInput.js +2 -0
  34. package/build/Typeahead/TypeaheadInput/TypeaheadInput.js.map +1 -1
  35. package/build/Typeahead/TypeaheadInput/TypeaheadInput.mjs +2 -0
  36. package/build/Typeahead/TypeaheadInput/TypeaheadInput.mjs.map +1 -1
  37. package/build/i18n/es.json +1 -0
  38. package/build/i18n/es.json.js +1 -0
  39. package/build/i18n/es.json.js.map +1 -1
  40. package/build/i18n/es.json.mjs +1 -0
  41. package/build/i18n/es.json.mjs.map +1 -1
  42. package/build/i18n/hu.json +1 -0
  43. package/build/i18n/hu.json.js +1 -0
  44. package/build/i18n/hu.json.js.map +1 -1
  45. package/build/i18n/hu.json.mjs +1 -0
  46. package/build/i18n/hu.json.mjs.map +1 -1
  47. package/build/i18n/id.json +1 -0
  48. package/build/i18n/id.json.js +1 -0
  49. package/build/i18n/id.json.js.map +1 -1
  50. package/build/i18n/id.json.mjs +1 -0
  51. package/build/i18n/id.json.mjs.map +1 -1
  52. package/build/i18n/it.json +1 -0
  53. package/build/i18n/it.json.js +1 -0
  54. package/build/i18n/it.json.js.map +1 -1
  55. package/build/i18n/it.json.mjs +1 -0
  56. package/build/i18n/it.json.mjs.map +1 -1
  57. package/build/i18n/ja.json +1 -0
  58. package/build/i18n/ja.json.js +1 -0
  59. package/build/i18n/ja.json.js.map +1 -1
  60. package/build/i18n/ja.json.mjs +1 -0
  61. package/build/i18n/ja.json.mjs.map +1 -1
  62. package/build/i18n/nl.json +1 -0
  63. package/build/i18n/nl.json.js +1 -0
  64. package/build/i18n/nl.json.js.map +1 -1
  65. package/build/i18n/nl.json.mjs +1 -0
  66. package/build/i18n/nl.json.mjs.map +1 -1
  67. package/build/i18n/pl.json +1 -0
  68. package/build/i18n/pl.json.js +1 -0
  69. package/build/i18n/pl.json.js.map +1 -1
  70. package/build/i18n/pl.json.mjs +1 -0
  71. package/build/i18n/pl.json.mjs.map +1 -1
  72. package/build/i18n/ro.json +1 -0
  73. package/build/i18n/ro.json.js +1 -0
  74. package/build/i18n/ro.json.js.map +1 -1
  75. package/build/i18n/ro.json.mjs +1 -0
  76. package/build/i18n/ro.json.mjs.map +1 -1
  77. package/build/i18n/uk.json +1 -0
  78. package/build/i18n/uk.json.js +1 -0
  79. package/build/i18n/uk.json.js.map +1 -1
  80. package/build/i18n/uk.json.mjs +1 -0
  81. package/build/i18n/uk.json.mjs.map +1 -1
  82. package/build/i18n/zh-HK.json +1 -0
  83. package/build/i18n/zh-HK.json.js +1 -0
  84. package/build/i18n/zh-HK.json.js.map +1 -1
  85. package/build/i18n/zh-HK.json.mjs +1 -0
  86. package/build/i18n/zh-HK.json.mjs.map +1 -1
  87. package/build/main.css +20 -7
  88. package/build/styles/Button/Button.css +0 -3
  89. package/build/styles/DateInput/DateInput.css +13 -0
  90. package/build/styles/ListItem/ListItem.css +1 -1
  91. package/build/styles/PhoneNumberInput/PhoneNumberInput.css +3 -0
  92. package/build/styles/PromoCard/PromoCard.css +0 -3
  93. package/build/styles/Typeahead/Typeahead.css +3 -0
  94. package/build/styles/main.css +20 -7
  95. package/build/types/Body/Body.d.ts +2 -2
  96. package/build/types/Chips/Chip/Chip.d.ts +3 -1
  97. package/build/types/Chips/Chip/Chip.d.ts.map +1 -1
  98. package/build/types/IconButton/IconButton.d.ts +1 -1
  99. package/build/types/Inputs/SelectInput/OptionContent/SelectInputOptionContent.d.ts +2 -1
  100. package/build/types/Inputs/SelectInput/OptionContent/SelectInputOptionContent.d.ts.map +1 -1
  101. package/build/types/MoneyInput/MoneyInput.d.ts +1 -1
  102. package/build/types/MoneyInput/MoneyInput.d.ts.map +1 -1
  103. package/build/types/PhoneNumberInput/PhoneNumberInput.d.ts.map +1 -1
  104. package/build/types/Title/Title.d.ts +2 -2
  105. package/build/types/Typeahead/Typeahead.d.ts +2 -0
  106. package/build/types/Typeahead/Typeahead.d.ts.map +1 -1
  107. package/build/types/Typeahead/TypeaheadInput/TypeaheadInput.d.ts +1 -1
  108. package/build/types/Typeahead/TypeaheadInput/TypeaheadInput.d.ts.map +1 -1
  109. package/build/types/Upload/Steps/UploadImageStep/UploadImageStep.d.ts +1 -1
  110. package/build/types/UploadInput/UploadItem/UploadItemLink.d.ts +1 -1
  111. package/package.json +5 -5
  112. package/src/Button/Button.css +0 -3
  113. package/src/Button/Button.less +0 -16
  114. package/src/Button/_stories/Button.test.story.tsx +12 -12
  115. package/src/Calendar/DateHeader/DateHeader.tsx +3 -3
  116. package/src/Carousel/Carousel.tsx +3 -3
  117. package/src/Chips/Chip/Chip.test.tsx +34 -0
  118. package/src/Chips/Chip/Chip.tsx +11 -3
  119. package/src/DateInput/DateInput.css +13 -0
  120. package/src/DateInput/DateInput.less +10 -2
  121. package/src/DateInput/_stories/DateInput.rtl.test.story.tsx +36 -0
  122. package/src/DateInput/_stories/DateInput.test.story.tsx +0 -13
  123. package/src/ExpressiveMoneyInput/_stories/ExpressiveMoneyInput.rtl.test.story.tsx +1 -1
  124. package/src/InputWithDisplayFormat/_stories/InputWithDisplayFormat.test.story.tsx +52 -0
  125. package/src/Inputs/SelectInput/OptionContent/SelectInputOptionContent.tsx +4 -1
  126. package/src/Inputs/SelectInput/SelectInput.test.tsx +74 -0
  127. package/src/ListItem/ListItem.css +1 -1
  128. package/src/ListItem/ListItem.grid.less +4 -4
  129. package/src/ListItem/ListItem.less +1 -1
  130. package/src/ListItem/Navigation/ListItemNavigation.tsx +2 -2
  131. package/src/PhoneNumberInput/PhoneNumberInput.css +3 -0
  132. package/src/PhoneNumberInput/PhoneNumberInput.less +10 -2
  133. package/src/PhoneNumberInput/PhoneNumberInput.tsx +1 -0
  134. package/src/PhoneNumberInput/_stories/PhoneNumberInput.rtl.test.story.tsx +1 -1
  135. package/src/PromoCard/Indicator/PromoCardIndicator.tsx +2 -2
  136. package/src/PromoCard/PromoCard.css +0 -3
  137. package/src/PromoCard/PromoCard.less +0 -5
  138. package/src/Prompt/CriticalBanner/_stories/CriticalBanner.test.story.tsx +1 -1
  139. package/src/Typeahead/Typeahead.css +3 -0
  140. package/src/Typeahead/Typeahead.less +4 -0
  141. package/src/Typeahead/Typeahead.test.tsx +73 -0
  142. package/src/Typeahead/Typeahead.tsx +18 -3
  143. package/src/Typeahead/TypeaheadInput/TypeaheadInput.tsx +6 -1
  144. package/src/Typeahead/_stories/Typeahead.story.tsx +1 -0
  145. package/src/Typeahead/_stories/Typeahead.test.story.tsx +41 -0
  146. package/src/i18n/en.json +1 -0
  147. package/src/i18n/es.json +1 -0
  148. package/src/i18n/hu.json +1 -0
  149. package/src/i18n/id.json +1 -0
  150. package/src/i18n/it.json +1 -0
  151. package/src/i18n/ja.json +1 -0
  152. package/src/i18n/nl.json +1 -0
  153. package/src/i18n/pl.json +1 -0
  154. package/src/i18n/ro.json +1 -0
  155. package/src/i18n/uk.json +1 -0
  156. package/src/i18n/zh-HK.json +1 -0
  157. package/src/main.css +20 -7
@@ -7,6 +7,7 @@ export interface SelectInputOptionContentProps {
7
7
  note?: string;
8
8
  description?: string;
9
9
  icon?: React.ReactNode;
10
+ className?: string;
10
11
  }
11
12
 
12
13
  /**
@@ -19,6 +20,7 @@ export function SelectInputOptionContent({
19
20
  note,
20
21
  description,
21
22
  icon,
23
+ className,
22
24
  }: SelectInputOptionContentProps) {
23
25
  const withinTrigger = useContext(SelectInputOptionContentWithinTriggerContext);
24
26
 
@@ -27,6 +29,7 @@ export function SelectInputOptionContent({
27
29
  className={clsx(
28
30
  'np-select-input-option-content-container',
29
31
  (note || description) && 'np-text-body-large',
32
+ className,
30
33
  )}
31
34
  >
32
35
  {icon ? (
@@ -47,7 +50,7 @@ export function SelectInputOptionContent({
47
50
  withinTrigger && 'np-select-input-option-content-text-within-trigger',
48
51
  )}
49
52
  >
50
- <div className="d-inline">{title}</div>
53
+ <div className="d-inline np-select-input-option-content-title">{title}</div>
51
54
  {note ? (
52
55
  <span className="np-select-input-option-content-text-secondary np-text-body-default">
53
56
  {note}
@@ -721,6 +721,80 @@ describe('SelectInput', () => {
721
721
  expect(descriptionElement).toHaveClass('np-select-input-option-description-in-trigger');
722
722
  });
723
723
 
724
+ it('applies custom className to option content container in the trigger', () => {
725
+ render(
726
+ <SelectInput
727
+ items={[
728
+ { type: 'option', value: 'USD' },
729
+ { type: 'option', value: 'EUR' },
730
+ ]}
731
+ renderValue={(currency) => (
732
+ <SelectInputOptionContent title={currency} className="custom-option-class" />
733
+ )}
734
+ value="USD"
735
+ />,
736
+ );
737
+
738
+ const triggerContent = screen
739
+ .getByText('USD')
740
+ .closest('.np-select-input-option-content-container');
741
+ expect(triggerContent).toHaveClass('custom-option-class');
742
+ });
743
+
744
+ it('applies custom className to option content container in dropdown options', async () => {
745
+ render(
746
+ <SelectInput
747
+ items={[
748
+ { type: 'option', value: 'USD' },
749
+ { type: 'option', value: 'EUR' },
750
+ ]}
751
+ renderValue={(currency) => (
752
+ <SelectInputOptionContent title={currency} className="custom-option-class" />
753
+ )}
754
+ value="USD"
755
+ />,
756
+ );
757
+
758
+ const trigger = screen.getByRole('combobox');
759
+ await userEvent.click(trigger);
760
+
761
+ const listbox = screen.getByRole('listbox');
762
+ const options = within(listbox).getAllByRole('option');
763
+ const optionContent = options[0].querySelector('.np-select-input-option-content-container');
764
+ expect(optionContent).toHaveClass('custom-option-class');
765
+ });
766
+
767
+ it('supports conditional className based on withinTrigger', async () => {
768
+ render(
769
+ <SelectInput
770
+ items={[
771
+ { type: 'option', value: 'USD' },
772
+ { type: 'option', value: 'EUR' },
773
+ ]}
774
+ renderValue={(currency, withinTrigger) => (
775
+ <SelectInputOptionContent
776
+ title={currency}
777
+ className={withinTrigger ? undefined : 'dropdown-only-class'}
778
+ />
779
+ )}
780
+ value="USD"
781
+ />,
782
+ );
783
+
784
+ const triggerContent = screen
785
+ .getByText('USD')
786
+ .closest('.np-select-input-option-content-container');
787
+ expect(triggerContent).not.toHaveClass('dropdown-only-class');
788
+
789
+ const trigger = screen.getByRole('combobox');
790
+ await userEvent.click(trigger);
791
+
792
+ const listbox = screen.getByRole('listbox');
793
+ const options = within(listbox).getAllByRole('option');
794
+ const optionContent = options[0].querySelector('.np-select-input-option-content-container');
795
+ expect(optionContent).toHaveClass('dropdown-only-class');
796
+ });
797
+
724
798
  describe('listbox label', () => {
725
799
  const fieldLabel = 'Fruits';
726
800
  const triggerLabel = 'Select fruit';
@@ -567,7 +567,7 @@
567
567
  .wds-list-item-control-wrapper {
568
568
  grid-area: control;
569
569
  }
570
- .wds-list-item-navigation .tw-icon-chevron-right {
570
+ .wds-list-item-navigation .tw-icon-chevron-end {
571
571
  color: #c9cbce;
572
572
  color: var(--color-interactive-secondary);
573
573
  display: block;
@@ -169,10 +169,10 @@
169
169
  }
170
170
  }
171
171
 
172
- // LESS struggles with ranged container queries so
173
- // we need to escape the interpolated expression.
174
- @container (~"@{wds-list-item-cq-min} < width <= @{wds-list-item-cq-max}") {
175
- .wds-list-item-media-image{
172
+ // LESS struggles with ranged container queries so we need to
173
+ // escape the interpolated expression using `e()` function.
174
+ @container (e("@{wds-list-item-cq-min} < width <= @{wds-list-item-cq-max}")) {
175
+ .wds-list-item-media-image {
176
176
  object-position: bottom left;
177
177
  }
178
178
 
@@ -224,7 +224,7 @@
224
224
  }
225
225
 
226
226
  &-navigation {
227
- .tw-icon-chevron-right {
227
+ .tw-icon-chevron-end {
228
228
  color: var(--color-interactive-secondary);
229
229
  display: block;
230
230
  }
@@ -1,4 +1,4 @@
1
- import { ChevronRight } from '@transferwise/icons';
1
+ import { ChevronEnd } from '@transferwise/icons';
2
2
  import type { ButtonProps } from '../../Button/Button.types';
3
3
  import { useListItemControl } from '../useListItemControl';
4
4
  import { PrimitiveButton } from '../../Primitives';
@@ -32,7 +32,7 @@ export const Navigation = function Navigation({
32
32
  }: ListItemNavigationProps) {
33
33
  const { baseItemProps } = useListItemControl('navigation', { href, hideIndicator, ...props });
34
34
  const { ids, describedByIds } = useContext(ListItemContext);
35
- const icon = hideIndicator ? null : <ChevronRight size={16} data-testid="NavigationIndicator" />;
35
+ const icon = hideIndicator ? null : <ChevronEnd size={16} data-testid="NavigationIndicator" />;
36
36
 
37
37
  return href ? (
38
38
  <>{icon}</>
@@ -27,3 +27,6 @@
27
27
  .tw-telephone__number-input {
28
28
  flex: auto 1 1;
29
29
  }
30
+ [dir="rtl"] .tw-telephone__prefix-option .np-select-input-option-content-title {
31
+ direction: ltr;
32
+ }
@@ -7,7 +7,7 @@
7
7
  margin-left: var(--size-12);
8
8
  }
9
9
 
10
- &__number-input-field{
10
+ &__number-input-field {
11
11
  [dir="rtl"] & {
12
12
  text-align: end;
13
13
  }
@@ -22,7 +22,7 @@
22
22
  width: 100%;
23
23
  }
24
24
 
25
- .np-select-input-content{
25
+ .np-select-input-content {
26
26
  [dir="rtl"] & {
27
27
  text-align: end;
28
28
  direction: ltr;
@@ -40,3 +40,11 @@
40
40
  .tw-telephone__number-input {
41
41
  flex: auto 1 1;
42
42
  }
43
+
44
+ // Prevent bidi algorithm from flipping "+{country-code}" to "{country-code}+"
45
+ // in RTL dropdown options as phone numbers are universally read as LTR.
46
+ .tw-telephone__prefix-option .np-select-input-option-content-title {
47
+ [dir="rtl"] & {
48
+ direction: ltr;
49
+ }
50
+ }
@@ -229,6 +229,7 @@ const PhoneNumberInput = ({
229
229
  ?.map((country) => country.iso3)
230
230
  .join(', ')
231
231
  }
232
+ className={withinTrigger ? undefined : 'tw-telephone__prefix-option'}
232
233
  />
233
234
  )}
234
235
  filterable
@@ -33,7 +33,7 @@ export const Sizes: Story = {
33
33
  </Field>
34
34
  </>
35
35
  ),
36
- ...withRTLDiff(),
36
+ ...withRTLDiff({ renderAs: 'iframe' }),
37
37
  };
38
38
 
39
39
  export const DropdownOpen: Story = {
@@ -1,4 +1,4 @@
1
- import { ArrowRight, Check, Download } from '@transferwise/icons';
1
+ import { ArrowEnd, Check, Download } from '@transferwise/icons';
2
2
  import { clsx } from 'clsx';
3
3
  import { ReactElement, ReactNode } from 'react';
4
4
 
@@ -57,7 +57,7 @@ const PromoCardIndicator: React.FC<PromoCardIndicatorProps> = ({
57
57
  isIconString &&
58
58
  {
59
59
  check: Check,
60
- arrow: ArrowRight,
60
+ arrow: ArrowEnd,
61
61
  download: Download,
62
62
  }[icon];
63
63
 
@@ -53,9 +53,6 @@
53
53
  .np-Card-indicatorIcon .tw-icon {
54
54
  color: var(--Card-indicator-icon-color);
55
55
  }
56
- [dir="rtl"] .np-Card-indicatorIcon .tw-icon {
57
- transform: scaleX(-1);
58
- }
59
56
  .np-Card--checked,
60
57
  .np-Card--link {
61
58
  color: currentColor;
@@ -50,11 +50,6 @@
50
50
 
51
51
  &Icon .tw-icon {
52
52
  color: var(--Card-indicator-icon-color);
53
-
54
- // Flips icon for right to left content
55
- [dir="rtl"] & {
56
- transform: scaleX(-1);
57
- }
58
53
  }
59
54
  }
60
55
 
@@ -511,5 +511,5 @@ export const RTLDiff: Story = {
511
511
  />
512
512
  </>
513
513
  ),
514
- ...withRTLDiff(),
514
+ ...withRTLDiff({ renderAs: 'iframe' }),
515
515
  };
@@ -65,6 +65,9 @@
65
65
  cursor: text;
66
66
  display: flex;
67
67
  }
68
+ .typeahead--multiple .input-group.disabled .typeahead__input-container {
69
+ cursor: not-allowed;
70
+ }
68
71
  .typeahead--multiple .sizer {
69
72
  white-space: pre;
70
73
  position: absolute;
@@ -77,6 +77,10 @@
77
77
  display: flex;
78
78
  }
79
79
 
80
+ .input-group.disabled .typeahead__input-container {
81
+ cursor: not-allowed;
82
+ }
83
+
80
84
  .sizer {
81
85
  white-space: pre;
82
86
  position: absolute;
@@ -256,4 +256,77 @@ describe('Typeahead', () => {
256
256
  expect.arrayContaining([{ label: 'foo' }, { label: 'bar' }]),
257
257
  );
258
258
  });
259
+
260
+ describe('when disabled', () => {
261
+ it('disables the input', () => {
262
+ render(<Typeahead id="test" name="test" options={[]} disabled onChange={() => {}} />);
263
+ expect(screen.getByRole('combobox')).toBeDisabled();
264
+ });
265
+
266
+ it('does not show the clear button when there is a value', () => {
267
+ render(
268
+ <Typeahead
269
+ id="test"
270
+ name="test"
271
+ options={[]}
272
+ initialValue={[{ label: 'Chip' }]}
273
+ disabled
274
+ onChange={() => {}}
275
+ />,
276
+ );
277
+ expect(screen.queryByRole('button', { name: /clear/i })).not.toBeInTheDocument();
278
+ });
279
+
280
+ it('keeps chips but does not let them be removed', () => {
281
+ render(
282
+ <Typeahead
283
+ id="test"
284
+ name="test"
285
+ options={[]}
286
+ multiple
287
+ initialValue={[{ label: 'Chip' }]}
288
+ disabled
289
+ onChange={() => {}}
290
+ />,
291
+ );
292
+ expect(screen.getByText('Chip').closest('.np-chip')).toHaveClass('disabled');
293
+ expect(screen.queryByRole('button')).not.toBeInTheDocument();
294
+ });
295
+
296
+ it('does not open the suggestions when the input is clicked', async () => {
297
+ render(
298
+ <Typeahead
299
+ id="test"
300
+ name="test"
301
+ options={[{ label: 'Option 1' }]}
302
+ minQueryLength={0}
303
+ disabled
304
+ onChange={() => {}}
305
+ />,
306
+ );
307
+
308
+ await userEvent.click(screen.getByRole('combobox'));
309
+
310
+ expect(screen.getByRole('combobox')).toHaveAttribute('aria-expanded', 'false');
311
+ });
312
+
313
+ it('closes the open menu when it becomes disabled', async () => {
314
+ const onChange = jest.fn();
315
+ const props = {
316
+ id: 'test',
317
+ name: 'test',
318
+ options: [],
319
+ allowNew: true,
320
+ minQueryLength: 0,
321
+ onChange,
322
+ };
323
+ const { rerender } = render(<Typeahead {...props} />);
324
+
325
+ await userEvent.type(screen.getByRole('combobox'), 'New thing');
326
+ rerender(<Typeahead {...props} disabled />);
327
+ await userEvent.click(document.body);
328
+
329
+ expect(onChange).not.toHaveBeenCalled();
330
+ });
331
+ });
259
332
  });
@@ -61,6 +61,8 @@ export interface TypeaheadProps<T> extends Partial<WrappedComponentProps> {
61
61
  chipSeparators?: readonly string[];
62
62
  /** @default true */
63
63
  clearable?: boolean;
64
+ /** @default false */
65
+ disabled?: boolean;
64
66
  footer?: ReactNode;
65
67
  /** @default [] */
66
68
  initialValue?: readonly TypeaheadOption<T>[];
@@ -112,6 +114,7 @@ class Typeahead<T> extends Component<TypeaheadPropsWithInputAttributes<T>, Typea
112
114
  autoFocus: false,
113
115
  chipSeparators: [],
114
116
  clearable: true,
117
+ disabled: false,
115
118
  initialValue: [],
116
119
  inputAutoComplete: 'new-password',
117
120
  minQueryLength: DEFAULT_MIN_QUERY_LENGTH,
@@ -160,6 +163,12 @@ class Typeahead<T> extends Component<TypeaheadPropsWithInputAttributes<T>, Typea
160
163
  }
161
164
  }
162
165
 
166
+ componentDidUpdate(previousProps: TypeaheadPropsWithInputAttributes<T>) {
167
+ if (this.props.disabled && !previousProps.disabled) {
168
+ this.hideMenu();
169
+ }
170
+ }
171
+
163
172
  componentWillUnmount() {
164
173
  this.handleSearchDebounced.cancel();
165
174
  }
@@ -398,6 +407,7 @@ class Typeahead<T> extends Component<TypeaheadPropsWithInputAttributes<T>, Typea
398
407
  };
399
408
 
400
409
  renderChip = (option: TypeaheadOption<T>, idx: number): ReactNode => {
410
+ const { disabled } = this.props;
401
411
  const valid = this.props.validateChip?.(option);
402
412
 
403
413
  return (
@@ -408,7 +418,8 @@ class Typeahead<T> extends Component<TypeaheadPropsWithInputAttributes<T>, Typea
408
418
  'has-error': !valid,
409
419
  'np-chip--valid': valid,
410
420
  })}
411
- onRemove={() => this.removeChip(option)}
421
+ disabled={disabled}
422
+ onRemove={disabled ? undefined : () => this.removeChip(option)}
412
423
  />
413
424
  );
414
425
  };
@@ -486,6 +497,7 @@ class Typeahead<T> extends Component<TypeaheadPropsWithInputAttributes<T>, Typea
486
497
  addon,
487
498
  name,
488
499
  clearable,
500
+ disabled,
489
501
  allowNew,
490
502
  footer,
491
503
  showSuggestions,
@@ -501,9 +513,10 @@ class Typeahead<T> extends Component<TypeaheadPropsWithInputAttributes<T>, Typea
501
513
 
502
514
  const { errorState, query, selected, optionsShown, keyboardFocusedOptionIndex } = this.state;
503
515
 
504
- const clearButton = clearable && (query || selected.length > 0);
516
+ const clearButton = !disabled && clearable && (query || selected.length > 0);
505
517
 
506
- const dropdownOpen = optionsShown && showSuggestions && query.length >= minQueryLength;
518
+ const dropdownOpen =
519
+ !disabled && optionsShown && showSuggestions && query.length >= minQueryLength;
507
520
 
508
521
  const menu = this.renderMenu({
509
522
  footer,
@@ -556,6 +569,7 @@ class Typeahead<T> extends Component<TypeaheadPropsWithInputAttributes<T>, Typea
556
569
  <div
557
570
  className={clsx(`input-group input-group-${size}`, {
558
571
  'input-group--has-error': hasError,
572
+ disabled,
559
573
  })}
560
574
  >
561
575
  {addon && <span className="input-group-addon input-group-addon--search">{addon}</span>}
@@ -563,6 +577,7 @@ class Typeahead<T> extends Component<TypeaheadPropsWithInputAttributes<T>, Typea
563
577
  <TypeaheadInput
564
578
  {...{
565
579
  autoFocus,
580
+ disabled,
566
581
  multiple,
567
582
  dropdownOpen,
568
583
  placeholder,
@@ -23,7 +23,10 @@ export type TypeaheadInputProps<T> = {
23
23
  onPaste: React.ClipboardEventHandler<HTMLInputElement>;
24
24
  renderChip: (chip: TypeaheadOption<T>, index: number) => ReactNode;
25
25
  ariaActivedescendant?: string;
26
- } & Pick<TypeaheadProps<T>, 'id' | 'name' | 'autoFocus' | 'multiple' | 'placeholder' | 'maxHeight'>;
26
+ } & Pick<
27
+ TypeaheadProps<T>,
28
+ 'id' | 'name' | 'autoFocus' | 'disabled' | 'multiple' | 'placeholder' | 'maxHeight'
29
+ >;
27
30
 
28
31
  type TypeaheadInputState = {
29
32
  inputWidth: number;
@@ -73,6 +76,7 @@ export default class TypeaheadInput<T> extends Component<
73
76
  const {
74
77
  typeaheadId,
75
78
  autoFocus,
79
+ disabled,
76
80
  multiple,
77
81
  name,
78
82
  dropdownOpen,
@@ -98,6 +102,7 @@ export default class TypeaheadInput<T> extends Component<
98
102
  id={`input-${typeaheadId}`}
99
103
  aria-activedescendant={ariaActivedescendant}
100
104
  autoFocus={autoFocus}
105
+ disabled={disabled}
101
106
  placeholder={hasPlaceholder ? placeholder : ''}
102
107
  aria-autocomplete="list"
103
108
  aria-expanded={dropdownOpen}
@@ -36,6 +36,7 @@ const meta: Meta<typeof Typeahead> = {
36
36
  autoFocus: false,
37
37
  chipSeparators: [',', ' '],
38
38
  clearable: true,
39
+ disabled: false,
39
40
  inputAutoComplete: 'new-password',
40
41
  minQueryLength: 3,
41
42
  multiple: false,
@@ -3,6 +3,7 @@ import { Search as SearchIcon } from '@transferwise/icons';
3
3
  import { useState } from 'react';
4
4
 
5
5
  import Button from '../../Button';
6
+ import Field from '../../Field/Field';
6
7
  import Modal from '../../Modal';
7
8
  import Typeahead from '../Typeahead';
8
9
 
@@ -17,6 +18,46 @@ const meta: Meta<typeof Typeahead> = {
17
18
  };
18
19
  export default meta;
19
20
 
21
+ export const Disabled: Story = {
22
+ render: () => (
23
+ <div className="d-flex flex-column">
24
+ <Field id="typeahead-disabled-empty" label="Empty">
25
+ <Typeahead
26
+ id="typeahead-disabled-empty"
27
+ name="typeahead-disabled-empty"
28
+ disabled
29
+ placeholder="Search currencies"
30
+ addon={<SearchIcon size={24} />}
31
+ options={[]}
32
+ onChange={() => {}}
33
+ />
34
+ </Field>
35
+ <Field id="typeahead-disabled-value" label="With a value">
36
+ <Typeahead
37
+ id="typeahead-disabled-value"
38
+ name="typeahead-disabled-value"
39
+ disabled
40
+ addon={<SearchIcon size={24} />}
41
+ initialValue={[{ label: 'British pound' }]}
42
+ options={[]}
43
+ onChange={() => {}}
44
+ />
45
+ </Field>
46
+ <Field id="typeahead-disabled-chips" label="With chips">
47
+ <Typeahead
48
+ id="typeahead-disabled-chips"
49
+ name="typeahead-disabled-chips"
50
+ disabled
51
+ multiple
52
+ initialValue={[{ label: 'British pound' }, { label: 'Euro' }]}
53
+ options={[]}
54
+ onChange={() => {}}
55
+ />
56
+ </Field>
57
+ </div>
58
+ ),
59
+ };
60
+
20
61
  export const AutoFocusInModal: Story = {
21
62
  parameters: {
22
63
  docs: {
package/src/i18n/en.json CHANGED
@@ -35,6 +35,7 @@
35
35
  "neptune.PhoneNumberInput.SelectInput.placeholder": "Select an option...",
36
36
  "neptune.PhoneNumberInput.countryCodeLabel": "Country code",
37
37
  "neptune.PhoneNumberInput.phoneNumberLabel": "Phone number",
38
+ "neptune.PhoneNumberInput.phoneNumberPrefix": "Prefix",
38
39
  "neptune.Select.searchPlaceholder": "Search...",
39
40
  "neptune.SelectInput.noResultsFound": "No results found",
40
41
  "neptune.StatusIcon.iconLabel.error": "Error:",
package/src/i18n/es.json CHANGED
@@ -35,6 +35,7 @@
35
35
  "neptune.PhoneNumberInput.SelectInput.placeholder": "Selecciona una opción...",
36
36
  "neptune.PhoneNumberInput.countryCodeLabel": "Código de país",
37
37
  "neptune.PhoneNumberInput.phoneNumberLabel": "Número de teléfono",
38
+ "neptune.PhoneNumberInput.phoneNumberPrefix": "Prefijo",
38
39
  "neptune.Select.searchPlaceholder": "Buscar...",
39
40
  "neptune.SelectInput.noResultsFound": "No se han encontrado resultados",
40
41
  "neptune.StatusIcon.iconLabel.error": "Error:",
package/src/i18n/hu.json CHANGED
@@ -35,6 +35,7 @@
35
35
  "neptune.PhoneNumberInput.SelectInput.placeholder": "Válassz ki egy lehetőséget...",
36
36
  "neptune.PhoneNumberInput.countryCodeLabel": "Országazonosító",
37
37
  "neptune.PhoneNumberInput.phoneNumberLabel": "Telefonszám",
38
+ "neptune.PhoneNumberInput.phoneNumberPrefix": "Előtag",
38
39
  "neptune.Select.searchPlaceholder": "Keresés...",
39
40
  "neptune.SelectInput.noResultsFound": "Nincs találat",
40
41
  "neptune.StatusIcon.iconLabel.error": "Hiba:",
package/src/i18n/id.json CHANGED
@@ -35,6 +35,7 @@
35
35
  "neptune.PhoneNumberInput.SelectInput.placeholder": "Pilih opsi...",
36
36
  "neptune.PhoneNumberInput.countryCodeLabel": "Kode negara",
37
37
  "neptune.PhoneNumberInput.phoneNumberLabel": "Nomor telepon",
38
+ "neptune.PhoneNumberInput.phoneNumberPrefix": "Awalan",
38
39
  "neptune.Select.searchPlaceholder": "Cari...",
39
40
  "neptune.SelectInput.noResultsFound": "Hasil tidak ditemukan",
40
41
  "neptune.StatusIcon.iconLabel.error": "Kesalahan:",
package/src/i18n/it.json CHANGED
@@ -35,6 +35,7 @@
35
35
  "neptune.PhoneNumberInput.SelectInput.placeholder": "Seleziona un'opzione...",
36
36
  "neptune.PhoneNumberInput.countryCodeLabel": "Prefisso",
37
37
  "neptune.PhoneNumberInput.phoneNumberLabel": "Numero di telefono",
38
+ "neptune.PhoneNumberInput.phoneNumberPrefix": "Prefisso",
38
39
  "neptune.Select.searchPlaceholder": "Cerca...",
39
40
  "neptune.SelectInput.noResultsFound": "Nessun risultato trovato",
40
41
  "neptune.StatusIcon.iconLabel.error": "Errore:",
package/src/i18n/ja.json CHANGED
@@ -35,6 +35,7 @@
35
35
  "neptune.PhoneNumberInput.SelectInput.placeholder": "選択してください…",
36
36
  "neptune.PhoneNumberInput.countryCodeLabel": "国コード",
37
37
  "neptune.PhoneNumberInput.phoneNumberLabel": "電話番号",
38
+ "neptune.PhoneNumberInput.phoneNumberPrefix": "プレフィックス",
38
39
  "neptune.Select.searchPlaceholder": "検索する... ",
39
40
  "neptune.SelectInput.noResultsFound": "結果が見つかりませんでした",
40
41
  "neptune.StatusIcon.iconLabel.error": "エラー:",
package/src/i18n/nl.json CHANGED
@@ -35,6 +35,7 @@
35
35
  "neptune.PhoneNumberInput.SelectInput.placeholder": "Selecteer een optie...",
36
36
  "neptune.PhoneNumberInput.countryCodeLabel": "Landcode",
37
37
  "neptune.PhoneNumberInput.phoneNumberLabel": "Telefoonnummer",
38
+ "neptune.PhoneNumberInput.phoneNumberPrefix": "Voorvoegsel",
38
39
  "neptune.Select.searchPlaceholder": "Zoeken...",
39
40
  "neptune.SelectInput.noResultsFound": "Geen resultaten gevonden",
40
41
  "neptune.StatusIcon.iconLabel.error": "Fout:",
package/src/i18n/pl.json CHANGED
@@ -35,6 +35,7 @@
35
35
  "neptune.PhoneNumberInput.SelectInput.placeholder": "Wybierz opcję...",
36
36
  "neptune.PhoneNumberInput.countryCodeLabel": "Numer kierunkowy",
37
37
  "neptune.PhoneNumberInput.phoneNumberLabel": "Numer telefonu",
38
+ "neptune.PhoneNumberInput.phoneNumberPrefix": "Prefiks",
38
39
  "neptune.Select.searchPlaceholder": "Wyszukaj...",
39
40
  "neptune.SelectInput.noResultsFound": "Nie znaleziono wyników",
40
41
  "neptune.StatusIcon.iconLabel.error": "Błąd:",
package/src/i18n/ro.json CHANGED
@@ -35,6 +35,7 @@
35
35
  "neptune.PhoneNumberInput.SelectInput.placeholder": "Selectează o opțiune...",
36
36
  "neptune.PhoneNumberInput.countryCodeLabel": "Codul țării",
37
37
  "neptune.PhoneNumberInput.phoneNumberLabel": "Număr de telefon",
38
+ "neptune.PhoneNumberInput.phoneNumberPrefix": "Prefix",
38
39
  "neptune.Select.searchPlaceholder": "Caută...",
39
40
  "neptune.SelectInput.noResultsFound": "Nu s-a găsit niciun rezultat",
40
41
  "neptune.StatusIcon.iconLabel.error": "Eroare:",
package/src/i18n/uk.json CHANGED
@@ -35,6 +35,7 @@
35
35
  "neptune.PhoneNumberInput.SelectInput.placeholder": "Виберіть варіант…",
36
36
  "neptune.PhoneNumberInput.countryCodeLabel": "Код країни",
37
37
  "neptune.PhoneNumberInput.phoneNumberLabel": "Номер телефону",
38
+ "neptune.PhoneNumberInput.phoneNumberPrefix": "Префікс",
38
39
  "neptune.Select.searchPlaceholder": "Пошук…",
39
40
  "neptune.SelectInput.noResultsFound": "Нічого не знайдено",
40
41
  "neptune.StatusIcon.iconLabel.error": "Помилка:",
@@ -35,6 +35,7 @@
35
35
  "neptune.PhoneNumberInput.SelectInput.placeholder": "選擇其中一項…",
36
36
  "neptune.PhoneNumberInput.countryCodeLabel": "國家/地區代碼",
37
37
  "neptune.PhoneNumberInput.phoneNumberLabel": "電話號碼",
38
+ "neptune.PhoneNumberInput.phoneNumberPrefix": "前綴",
38
39
  "neptune.Select.searchPlaceholder": "搜尋…",
39
40
  "neptune.SelectInput.noResultsFound": "找不到任何結果",
40
41
  "neptune.StatusIcon.iconLabel.error": "錯誤:",