@transferwise/components 0.0.0-experimental-2e23c2a → 0.0.0-experimental-16e2eec

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 (124) hide show
  1. package/build/index.esm.js +185 -343
  2. package/build/index.esm.js.map +1 -1
  3. package/build/index.js +184 -342
  4. package/build/index.js.map +1 -1
  5. package/build/main.css +1 -3
  6. package/build/styles/inputs/SelectInput.css +1 -3
  7. package/build/styles/main.css +1 -3
  8. package/build/types/common/locale/index.d.ts +26 -43
  9. package/build/types/common/locale/index.d.ts.map +1 -1
  10. package/build/types/inputs/SelectInput.d.ts +5 -6
  11. package/build/types/inputs/SelectInput.d.ts.map +1 -1
  12. package/build/types/phoneNumberInput/PhoneNumberInput.d.ts +22 -27
  13. package/build/types/phoneNumberInput/PhoneNumberInput.d.ts.map +1 -1
  14. package/build/types/phoneNumberInput/data/countries.d.ts +5 -10
  15. package/build/types/phoneNumberInput/data/countries.d.ts.map +1 -1
  16. package/build/types/phoneNumberInput/index.d.ts +1 -1
  17. package/build/types/phoneNumberInput/index.d.ts.map +1 -1
  18. package/build/types/phoneNumberInput/utils/cleanNumber/cleanNumber.d.ts +1 -1
  19. package/build/types/phoneNumberInput/utils/cleanNumber/cleanNumber.d.ts.map +1 -1
  20. package/build/types/phoneNumberInput/utils/cleanNumber/index.d.ts +1 -1
  21. package/build/types/phoneNumberInput/utils/cleanNumber/index.d.ts.map +1 -1
  22. package/build/types/phoneNumberInput/utils/excludeCountries/excludeCountries.d.ts +8 -1
  23. package/build/types/phoneNumberInput/utils/excludeCountries/excludeCountries.d.ts.map +1 -1
  24. package/build/types/phoneNumberInput/utils/excludeCountries/index.d.ts +1 -1
  25. package/build/types/phoneNumberInput/utils/excludeCountries/index.d.ts.map +1 -1
  26. package/build/types/phoneNumberInput/utils/explodeNumberModel/index.d.ts +8 -4
  27. package/build/types/phoneNumberInput/utils/explodeNumberModel/index.d.ts.map +1 -1
  28. package/build/types/phoneNumberInput/utils/findCountryByCode/index.d.ts +1 -1
  29. package/build/types/phoneNumberInput/utils/findCountryByCode/index.d.ts.map +1 -1
  30. package/build/types/phoneNumberInput/utils/findCountryByPrefix/index.d.ts +1 -1
  31. package/build/types/phoneNumberInput/utils/findCountryByPrefix/index.d.ts.map +1 -1
  32. package/build/types/phoneNumberInput/utils/groupCountriesByPrefix/groupCountriesByPrefix.d.ts +2 -1
  33. package/build/types/phoneNumberInput/utils/groupCountriesByPrefix/groupCountriesByPrefix.d.ts.map +1 -1
  34. package/build/types/phoneNumberInput/utils/groupCountriesByPrefix/index.d.ts +1 -1
  35. package/build/types/phoneNumberInput/utils/groupCountriesByPrefix/index.d.ts.map +1 -1
  36. package/build/types/phoneNumberInput/utils/index.d.ts +11 -13
  37. package/build/types/phoneNumberInput/utils/index.d.ts.map +1 -1
  38. package/build/types/phoneNumberInput/utils/isStringNumeric/index.d.ts +1 -1
  39. package/build/types/phoneNumberInput/utils/isStringNumeric/index.d.ts.map +1 -1
  40. package/build/types/phoneNumberInput/utils/isStringNumeric/isStringNumeric.d.ts +1 -1
  41. package/build/types/phoneNumberInput/utils/isStringNumeric/isStringNumeric.d.ts.map +1 -1
  42. package/build/types/phoneNumberInput/utils/isValidPhoneNumber/index.d.ts +1 -1
  43. package/build/types/phoneNumberInput/utils/isValidPhoneNumber/index.d.ts.map +1 -1
  44. package/build/types/phoneNumberInput/utils/isValidPhoneNumber/isValidPhoneNumber.d.ts +6 -1
  45. package/build/types/phoneNumberInput/utils/isValidPhoneNumber/isValidPhoneNumber.d.ts.map +1 -1
  46. package/build/types/phoneNumberInput/utils/longestMatchingPrefix/index.d.ts +2 -1
  47. package/build/types/phoneNumberInput/utils/longestMatchingPrefix/index.d.ts.map +1 -1
  48. package/build/types/phoneNumberInput/utils/setDefaultPrefix/index.d.ts +7 -1
  49. package/build/types/phoneNumberInput/utils/setDefaultPrefix/index.d.ts.map +1 -1
  50. package/build/types/phoneNumberInput/utils/sortArrayByProperty/index.d.ts +1 -1
  51. package/build/types/phoneNumberInput/utils/sortArrayByProperty/index.d.ts.map +1 -1
  52. package/build/types/phoneNumberInput/utils/sortArrayByProperty/sortArrayByProperty.d.ts +1 -1
  53. package/build/types/phoneNumberInput/utils/sortArrayByProperty/sortArrayByProperty.d.ts.map +1 -1
  54. package/build/types/utilities/wrapInFragment.d.ts +3 -0
  55. package/build/types/utilities/wrapInFragment.d.ts.map +1 -0
  56. package/package.json +1 -1
  57. package/src/common/locale/{index.spec.js → index.spec.ts} +4 -4
  58. package/src/common/locale/index.ts +96 -0
  59. package/src/inputs/SelectInput.css +1 -3
  60. package/src/inputs/SelectInput.less +1 -4
  61. package/src/inputs/SelectInput.spec.tsx +0 -26
  62. package/src/inputs/SelectInput.story.tsx +1 -73
  63. package/src/inputs/SelectInput.tsx +85 -104
  64. package/src/main.css +1 -3
  65. package/src/phoneNumberInput/PhoneNumberInput.spec.js +18 -22
  66. package/src/phoneNumberInput/PhoneNumberInput.tsx +193 -0
  67. package/src/phoneNumberInput/data/{countries.js → countries.ts} +9 -1
  68. package/src/phoneNumberInput/utils/cleanNumber/cleanNumber.ts +3 -0
  69. package/src/phoneNumberInput/utils/excludeCountries/{excludeCountries.spec.js → excludeCountries.spec.ts} +1 -1
  70. package/src/phoneNumberInput/utils/excludeCountries/{excludeCountries.js → excludeCountries.ts} +6 -5
  71. package/src/phoneNumberInput/utils/explodeNumberModel/{explodeNumberModel.spec.js → explodeNumberModel.spec.ts} +1 -1
  72. package/src/phoneNumberInput/utils/explodeNumberModel/index.ts +24 -0
  73. package/src/phoneNumberInput/utils/findCountryByCode/{findCountryByCode.spec.js → findCountryByCode.spec.ts} +0 -1
  74. package/src/phoneNumberInput/utils/findCountryByCode/index.ts +12 -0
  75. package/src/phoneNumberInput/utils/findCountryByPrefix/index.ts +12 -0
  76. package/src/phoneNumberInput/utils/groupCountriesByPrefix/groupCountriesByPrefix.spec.ts +102 -0
  77. package/src/phoneNumberInput/utils/groupCountriesByPrefix/groupCountriesByPrefix.ts +12 -0
  78. package/src/phoneNumberInput/utils/{index.js → index.ts} +0 -2
  79. package/src/phoneNumberInput/utils/isStringNumeric/{isStringNumeric.spec.js → isStringNumeric.spec.ts} +0 -1
  80. package/src/phoneNumberInput/utils/isStringNumeric/isStringNumeric.ts +1 -0
  81. package/src/phoneNumberInput/utils/isValidPhoneNumber/{isValidPhoneNumber.spec.js → isValidPhoneNumber.spec.ts} +1 -1
  82. package/src/phoneNumberInput/utils/isValidPhoneNumber/isValidPhoneNumber.ts +7 -0
  83. package/src/phoneNumberInput/utils/longestMatchingPrefix/index.ts +4 -0
  84. package/src/phoneNumberInput/utils/setDefaultPrefix/index.ts +20 -0
  85. package/src/phoneNumberInput/utils/sortArrayByProperty/sortArrayByProperty.ts +6 -0
  86. package/src/utilities/wrapInFragment.tsx +3 -0
  87. package/build/types/phoneNumberInput/utils/filterOptionsForQuery/index.d.ts +0 -2
  88. package/build/types/phoneNumberInput/utils/filterOptionsForQuery/index.d.ts.map +0 -1
  89. package/build/types/phoneNumberInput/utils/isOptionAndFitsQuery/index.d.ts +0 -2
  90. package/build/types/phoneNumberInput/utils/isOptionAndFitsQuery/index.d.ts.map +0 -1
  91. package/build/types/phoneNumberInput/utils/isOptionAndFitsQuery/isOptionAndFitsQuery.d.ts +0 -3
  92. package/build/types/phoneNumberInput/utils/isOptionAndFitsQuery/isOptionAndFitsQuery.d.ts.map +0 -1
  93. package/src/common/locale/index.js +0 -139
  94. package/src/phoneNumberInput/PhoneNumberInput.js +0 -210
  95. package/src/phoneNumberInput/data/countries.spec.js +0 -12
  96. package/src/phoneNumberInput/utils/cleanNumber/cleanNumber.js +0 -4
  97. package/src/phoneNumberInput/utils/explodeNumberModel/index.js +0 -27
  98. package/src/phoneNumberInput/utils/filterOptionsForQuery/filterOptionsForQuery.spec.js +0 -36
  99. package/src/phoneNumberInput/utils/filterOptionsForQuery/index.js +0 -11
  100. package/src/phoneNumberInput/utils/findCountryByCode/index.js +0 -10
  101. package/src/phoneNumberInput/utils/findCountryByPrefix/index.js +0 -11
  102. package/src/phoneNumberInput/utils/groupCountriesByPrefix/groupCountriesByPrefix.js +0 -26
  103. package/src/phoneNumberInput/utils/groupCountriesByPrefix/groupCountriesByPrefix.spec.js +0 -67
  104. package/src/phoneNumberInput/utils/isOptionAndFitsQuery/index.js +0 -1
  105. package/src/phoneNumberInput/utils/isOptionAndFitsQuery/isOptionAndFitsQuery.js +0 -25
  106. package/src/phoneNumberInput/utils/isOptionAndFitsQuery/isOptionAndFitsQuery.spec.js +0 -66
  107. package/src/phoneNumberInput/utils/isStringNumeric/isStringNumeric.js +0 -1
  108. package/src/phoneNumberInput/utils/isValidPhoneNumber/isValidPhoneNumber.js +0 -10
  109. package/src/phoneNumberInput/utils/longestMatchingPrefix/index.js +0 -2
  110. package/src/phoneNumberInput/utils/setDefaultPrefix/index.js +0 -25
  111. package/src/phoneNumberInput/utils/sortArrayByProperty/sortArrayByProperty.js +0 -3
  112. /package/src/phoneNumberInput/{PhoneNumberInput.story.js → PhoneNumberInput.story.tsx} +0 -0
  113. /package/src/phoneNumberInput/{index.js → index.ts} +0 -0
  114. /package/src/phoneNumberInput/utils/cleanNumber/{cleanNumber.spec.js → cleanNumber.spec.ts} +0 -0
  115. /package/src/phoneNumberInput/utils/cleanNumber/{index.js → index.ts} +0 -0
  116. /package/src/phoneNumberInput/utils/excludeCountries/{index.js → index.ts} +0 -0
  117. /package/src/phoneNumberInput/utils/findCountryByPrefix/{findCountryByPrefix.spec.js → findCountryByPrefix.spec.ts} +0 -0
  118. /package/src/phoneNumberInput/utils/groupCountriesByPrefix/{index.js → index.ts} +0 -0
  119. /package/src/phoneNumberInput/utils/isStringNumeric/{index.js → index.ts} +0 -0
  120. /package/src/phoneNumberInput/utils/isValidPhoneNumber/{index.js → index.ts} +0 -0
  121. /package/src/phoneNumberInput/utils/longestMatchingPrefix/{longestMatchingPrefix.spec.js → longestMatchingPrefix.spec.ts} +0 -0
  122. /package/src/phoneNumberInput/utils/setDefaultPrefix/{setDefaultPrefix.spec.js → setDefaultPrefix.spec.ts} +0 -0
  123. /package/src/phoneNumberInput/utils/sortArrayByProperty/{index.js → index.ts} +0 -0
  124. /package/src/phoneNumberInput/utils/sortArrayByProperty/{sortArrayByProperty.spec.js → sortArrayByProperty.spec.ts} +0 -0
@@ -0,0 +1,96 @@
1
+ import { Direction } from '..';
2
+
3
+ export const DEFAULT_LANG = 'en';
4
+ export const DEFAULT_LOCALE = 'en-GB';
5
+
6
+ /**
7
+ * Languages written right-to-left.
8
+ */
9
+ export const RTL_LANGUAGES = ['ar', 'he'];
10
+
11
+ /**
12
+ * @deprecated The source of truth for supported languages lives in Crab.
13
+ */
14
+ export const SUPPORTED_LANGUAGES = [
15
+ DEFAULT_LANG,
16
+ 'de',
17
+ 'es',
18
+ 'fr',
19
+ 'hu',
20
+ 'id',
21
+ 'it',
22
+ 'ja',
23
+ 'pl',
24
+ 'pt',
25
+ 'ro',
26
+ 'ru',
27
+ 'th',
28
+ 'tr',
29
+ 'uk',
30
+ 'zh',
31
+ ];
32
+
33
+ /**
34
+ * Verifies and adjusts locale, replacing `_` with `-`.
35
+ *
36
+ * @param locale `es`, `es_ES`, `en-GB`, `en`, `ja`, `ja-JP`, etc.
37
+ * @returns `null` if locale is unrecognized by `Intl.Locale`.
38
+ */
39
+ export function adjustLocale(locale: string) {
40
+ const localeTrimmed = locale?.trim();
41
+ if (localeTrimmed) {
42
+ try {
43
+ return new Intl.Locale(localeTrimmed.replace('_', '-')).baseName;
44
+ } catch (error) {
45
+ // eslint-disable-next-line no-console
46
+ console.error(error);
47
+ }
48
+ }
49
+ return null;
50
+ }
51
+
52
+ /**
53
+ * Provides corresponding lang (iso2) for provided locale.
54
+ *
55
+ * @deprecated The use of this function almost always breaks language variants
56
+ * e.g. Simplified and Traditional Chinese.
57
+ * There should be no use case for this function.
58
+ * To select the correct translations from a translations object, pass the
59
+ * locale directly into Crab's getLocalisedMessages.
60
+ * @param locale `es`, `es-ES`, `en-GB`, `en`, `ja`, `ja-JP`, etc.
61
+ * @returns Two-letter ISO 639-1 language code, falling back to `null` if locale is invalid or language is unsupported.
62
+ */
63
+ export function getLangFromLocale(locale: string) {
64
+ const adjustedLocale = adjustLocale(locale);
65
+ if (adjustedLocale != null) {
66
+ const { language } = new Intl.Locale(adjustedLocale);
67
+ if (SUPPORTED_LANGUAGES.includes(language)) {
68
+ return language;
69
+ }
70
+ }
71
+ return null;
72
+ }
73
+
74
+ /**
75
+ * Provides corresponding country code (iso2) for locales code with explicit region value.
76
+ *
77
+ * @param locale `es-ES`, `en-GB`, `ja-JP`, etc.
78
+ * @returns `null` if the locale is invalid or the region can‘t be identified.
79
+ */
80
+ export function getCountryFromLocale(locale: string) {
81
+ const adjustedLocale = adjustLocale(locale);
82
+ return adjustedLocale != null ? new Intl.Locale(adjustedLocale).region ?? null : null;
83
+ }
84
+
85
+ /**
86
+ * Provides the layout direction for a given locale.
87
+ *
88
+ * @param locale `es`, `es-ES`, `en-GB`, `en`, `ja`, `ja-JP`, etc.
89
+ * @returns The layout direction based on the locale, falling back to `Direction.LTR` if the locale is invalid or unsupported.
90
+ */
91
+ export function getDirectionFromLocale(locale: string) {
92
+ const adjustedLocale = adjustLocale(locale);
93
+ return adjustedLocale != null && RTL_LANGUAGES.includes(new Intl.Locale(adjustedLocale).language)
94
+ ? Direction.RTL
95
+ : Direction.LTR;
96
+ }
@@ -158,12 +158,10 @@
158
158
  color: #5d7079;
159
159
  color: var(--color-content-secondary);
160
160
  }
161
- .np-select-input-content {
161
+ .np-select-input-placeholder {
162
162
  overflow: hidden;
163
163
  text-overflow: ellipsis;
164
164
  white-space: nowrap;
165
- }
166
- .np-select-input-placeholder {
167
165
  color: #768e9c;
168
166
  color: var(--color-content-tertiary);
169
167
  }
@@ -3,13 +3,10 @@
3
3
  @import "./_Popover.less";
4
4
  @import (reference) "../../node_modules/@transferwise/neptune-css/src/less/ring.less";
5
5
 
6
- .np-select-input-content {
6
+ .np-select-input-placeholder {
7
7
  overflow: hidden;
8
8
  text-overflow: ellipsis;
9
9
  white-space: nowrap;
10
- }
11
-
12
- .np-select-input-placeholder {
13
10
  color: var(--color-content-tertiary);
14
11
  }
15
12
 
@@ -182,30 +182,4 @@ describe('SelectInput', () => {
182
182
  const listbox = screen.getByRole('listbox');
183
183
  expect(within(listbox).getAllByRole('option')).toHaveLength(2);
184
184
  });
185
-
186
- it('selects multiple options', async () => {
187
- render(
188
- <SelectInput
189
- multiple
190
- items={[
191
- { type: 'option', value: 'USD' },
192
- { type: 'option', value: 'EUR' },
193
- ]}
194
- />,
195
- );
196
-
197
- const trigger = screen.getAllByRole('button')[0];
198
- // eslint-disable-next-line @typescript-eslint/require-await
199
- await act(async () => {
200
- userEvent.click(trigger);
201
- });
202
-
203
- const listbox = screen.getByRole('listbox');
204
- const options = within(listbox).getAllByRole('option');
205
- options.forEach((option) => {
206
- userEvent.click(option);
207
- });
208
-
209
- expect(trigger).toHaveTextContent('USD, EUR');
210
- });
211
185
  });
@@ -242,7 +242,7 @@ export const Currencies: StoryObj<{
242
242
  onChange: (value: Currency) => void;
243
243
  }> = {
244
244
  render: function Story({ onChange }) {
245
- const [selectedCurrency, setSelectedCurrency] = useState(popularCurrencies[0]);
245
+ const [selectedCurrency, setSelectedCurrency] = useState<Currency>(popularCurrencies[0]);
246
246
 
247
247
  return (
248
248
  <SelectInput
@@ -301,78 +301,6 @@ export const Currencies: StoryObj<{
301
301
  },
302
302
  };
303
303
 
304
- export const MultipleCurrencies: StoryObj<{
305
- onChange: (value: Currency[]) => void;
306
- }> = {
307
- render: function Story({ onChange }) {
308
- const [selectedCurrencies, setSelectedCurrencies] = useState<readonly Currency[]>([
309
- popularCurrencies[0],
310
- ]);
311
-
312
- return (
313
- <SelectInput
314
- multiple
315
- placeholder="Choose currencies…"
316
- items={[
317
- {
318
- type: 'group',
319
- label: 'Popular currencies',
320
- options: popularCurrencies.map((currency) => currencyOption(currency)),
321
- },
322
- {
323
- type: 'group',
324
- label: 'All currencies',
325
- options: allCurrencies.map((currency) => currencyOption(currency)),
326
- },
327
- ]}
328
- value={selectedCurrencies}
329
- renderValue={(currency, withinTrigger) =>
330
- withinTrigger ? (
331
- currency.code
332
- ) : (
333
- <SelectInputOptionContent
334
- title={currency.code}
335
- note={currency.name}
336
- icon={<Flag code={currency.code} intrinsicSize={24} />}
337
- />
338
- )
339
- }
340
- // eslint-disable-next-line sonarjs/no-identical-functions
341
- renderFooter={({ resultsEmpty, queryNormalized: normalizedQuery }) =>
342
- resultsEmpty && normalizedQuery != null && /^[a-z]{3}$/u.test(normalizedQuery) ? (
343
- <>
344
- It’s not possible use {normalizedQuery.toUpperCase()} yet.{' '}
345
- <a href="#_" className="np-text-link-default">
346
- Email me when it’s available.
347
- </a>
348
- </>
349
- ) : (
350
- <>
351
- Can’t find it?{' '}
352
- <a href="#_" className="np-text-link-default">
353
- Request the currency you need,
354
- </a>{' '}
355
- and we’ll notify you once it’s available.
356
- </>
357
- )
358
- }
359
- filterable
360
- filterPlaceholder="Type a currency / country"
361
- size="lg"
362
- onChange={(currency) => {
363
- setSelectedCurrencies(currency);
364
- onChange(currency);
365
- }}
366
- />
367
- );
368
- },
369
- argTypes: {
370
- onChange: {
371
- action: 'changed',
372
- },
373
- },
374
- };
375
-
376
304
  export const CustomTrigger: StoryObj = {
377
305
  render: function Story() {
378
306
  return (
@@ -12,6 +12,7 @@ import { useScreenSize } from '../common/hooks/useScreenSize';
12
12
  import { PolymorphicWithOverrides } from '../common/polymorphicWithOverrides/PolymorphicWithOverrides';
13
13
  import { Breakpoint } from '../common/propsValues/breakpoint';
14
14
  import dateTriggerMessages from '../dateLookup/dateTrigger/DateTrigger.messages';
15
+ import { wrapInFragment } from '../utilities/wrapInFragment';
15
16
  import { Merge } from '../utils';
16
17
 
17
18
  import { InputGroup } from './InputGroup';
@@ -124,13 +125,13 @@ function filterSelectInputItems<T>(items: readonly SelectInputItem<T>[], needle:
124
125
  });
125
126
  }
126
127
 
127
- export interface SelectInputProps<T = string, M extends boolean = false> {
128
+ export interface SelectInputProps<T = string> {
128
129
  name?: string;
129
- multiple?: M;
130
130
  placeholder?: string;
131
+ // TODO: multiple?: boolean;
131
132
  items: readonly SelectInputItem<NonNullable<T>>[];
132
- defaultValue?: M extends true ? readonly T[] : T;
133
- value?: M extends true ? readonly T[] : T;
133
+ defaultValue?: T;
134
+ value?: T;
134
135
  compareValues?:
135
136
  | (keyof NonNullable<T> & string)
136
137
  | ((a: T | undefined, b: T | undefined) => boolean);
@@ -153,7 +154,7 @@ export interface SelectInputProps<T = string, M extends boolean = false> {
153
154
  size?: 'sm' | 'md' | 'lg';
154
155
  className?: string;
155
156
  onFilterChange?: (args: { query: string; queryNormalized: string | null }) => void;
156
- onChange?: (value: M extends true ? T[] : T) => void;
157
+ onChange?: (value: T) => void;
157
158
  onClear?: () => void;
158
159
  }
159
160
 
@@ -185,14 +186,7 @@ const defaultRenderTrigger = (({ content, placeholderShown, clear, disabled, siz
185
186
  className={className}
186
187
  >
187
188
  <SelectInputTriggerButton as={ButtonInput} size={size}>
188
- <span
189
- className={classNames(
190
- 'np-select-input-content',
191
- placeholderShown && 'np-select-input-placeholder',
192
- )}
193
- >
194
- {content}
195
- </span>
189
+ {placeholderShown ? <span className="np-select-input-placeholder"> {content}</span> : content}
196
190
  </SelectInputTriggerButton>
197
191
  </InputGroup>
198
192
  )) satisfies SelectInputProps['renderTrigger'];
@@ -217,15 +211,14 @@ function SelectInputClearButton({ className, onClick }: SelectInputClearButtonPr
217
211
 
218
212
  const noop = () => {};
219
213
 
220
- export function SelectInput<T = string, M extends boolean = false>({
214
+ export function SelectInput<T = string>({
221
215
  name,
222
- multiple = false as M,
223
216
  placeholder,
224
217
  items,
225
218
  defaultValue,
226
219
  value: controlledValue,
227
220
  compareValues,
228
- renderValue = String,
221
+ renderValue = wrapInFragment,
229
222
  renderFooter,
230
223
  renderTrigger = defaultRenderTrigger,
231
224
  filterable,
@@ -236,7 +229,7 @@ export function SelectInput<T = string, M extends boolean = false>({
236
229
  onFilterChange = noop,
237
230
  onChange,
238
231
  onClear,
239
- }: SelectInputProps<T, M>) {
232
+ }: SelectInputProps<T>) {
240
233
  const [open, setOpen] = useState(false);
241
234
 
242
235
  const [filterQuery, _setFilterQuery] = useState('');
@@ -260,106 +253,94 @@ export function SelectInput<T = string, M extends boolean = false>({
260
253
  return (
261
254
  <ListboxBase
262
255
  name={name}
263
- multiple={multiple}
264
256
  defaultValue={defaultValue}
265
257
  value={controlledValue}
266
258
  // TODO: Remove assertion when upgrading TypeScript to v5
267
259
  // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
268
260
  by={compareValues as any}
269
261
  disabled={disabled}
270
- onChange={
271
- ((value) => {
272
- if (!multiple) {
273
- setOpen(false);
274
- }
275
- onChange?.(value);
276
- }) satisfies SelectInputProps<T, M>['onChange']
277
- }
262
+ onChange={(value) => {
263
+ setOpen(false);
264
+ onChange?.(value);
265
+ }}
278
266
  >
279
- {({ disabled: uiDisabled, value }) => {
280
- const placeholderShown =
281
- multiple && Array.isArray(value) ? value.length === 0 : value == null;
282
- return (
283
- <OptionsOverlay
284
- placement="bottom-start"
285
- open={open}
286
- renderTrigger={({ ref, getInteractionProps }) => (
287
- <SelectInputTriggerButtonPropsContext.Provider
288
- // eslint-disable-next-line react/jsx-no-constructed-context-values
289
- value={{
290
- ref: mergeRefs([ref, triggerRef]),
291
- ...mergeProps(
292
- {
293
- onClick: () => {
267
+ {({ disabled: uiDisabled, value }) => (
268
+ <OptionsOverlay
269
+ placement="bottom-start"
270
+ open={open}
271
+ renderTrigger={({ ref, getInteractionProps }) => (
272
+ <SelectInputTriggerButtonPropsContext.Provider
273
+ // eslint-disable-next-line react/jsx-no-constructed-context-values
274
+ value={{
275
+ ref: mergeRefs([ref, triggerRef]),
276
+ ...mergeProps(
277
+ {
278
+ onClick: () => {
279
+ setOpen((prev) => !prev);
280
+ },
281
+ onKeyDown: (event: React.KeyboardEvent) => {
282
+ if (
283
+ event.key === ' ' ||
284
+ event.key === 'Enter' ||
285
+ event.key === 'ArrowDown' ||
286
+ event.key === 'ArrowUp'
287
+ ) {
294
288
  setOpen((prev) => !prev);
295
- },
296
- onKeyDown: (event: React.KeyboardEvent) => {
297
- if (
298
- event.key === ' ' ||
299
- event.key === 'Enter' ||
300
- event.key === 'ArrowDown' ||
301
- event.key === 'ArrowUp'
302
- ) {
303
- setOpen((prev) => !prev);
304
- }
305
- },
289
+ }
306
290
  },
307
- getInteractionProps(),
308
- ),
309
- }}
310
- >
311
- {renderTrigger({
312
- content: !placeholderShown ? (
291
+ },
292
+ getInteractionProps(),
293
+ ),
294
+ }}
295
+ >
296
+ {renderTrigger({
297
+ content:
298
+ value != null ? (
313
299
  <SelectInputOptionContentWithinTriggerContext.Provider value>
314
- {multiple && Array.isArray(value)
315
- ? value
316
- .map((option: NonNullable<T>) => renderValue(option, true))
317
- .join(', ')
318
- : renderValue(value as NonNullable<T>, true)}
300
+ {renderValue(value, true)}
319
301
  </SelectInputOptionContentWithinTriggerContext.Provider>
320
302
  ) : (
321
303
  placeholder
322
304
  ),
323
- placeholderShown,
324
- clear:
325
- onClear != null
326
- ? () => {
327
- onClear();
328
- triggerRef.current?.focus({ preventScroll: true });
329
- }
330
- : undefined,
331
- disabled: uiDisabled,
332
- size,
333
- className,
334
- })}
335
- </SelectInputTriggerButtonPropsContext.Provider>
336
- )}
337
- initialFocusRef={controllerRef}
338
- size={filterable ? 'lg' : 'md'}
339
- padding="none"
340
- onClose={() => {
341
- setOpen(false);
342
- }}
343
- onCloseEnd={() => {
344
- if (filterQuery !== '') {
345
- setFilterQuery('');
346
- }
347
- }}
348
- >
349
- <SelectInputOptions
350
- items={items}
351
- renderValue={renderValue}
352
- renderFooter={renderFooter}
353
- filterable={filterable}
354
- filterPlaceholder={filterPlaceholder}
355
- searchInputRef={searchInputRef}
356
- listboxRef={listboxRef}
357
- filterQuery={filterQuery}
358
- onFilterChange={setFilterQuery}
359
- />
360
- </OptionsOverlay>
361
- );
362
- }}
305
+ placeholderShown: value == null,
306
+ clear:
307
+ onClear != null
308
+ ? () => {
309
+ onClear();
310
+ triggerRef.current?.focus({ preventScroll: true });
311
+ }
312
+ : undefined,
313
+ disabled: uiDisabled,
314
+ size,
315
+ className,
316
+ })}
317
+ </SelectInputTriggerButtonPropsContext.Provider>
318
+ )}
319
+ initialFocusRef={controllerRef}
320
+ size={filterable ? 'lg' : 'md'}
321
+ padding="none"
322
+ onClose={() => {
323
+ setOpen(false);
324
+ }}
325
+ onCloseEnd={() => {
326
+ if (filterQuery !== '') {
327
+ setFilterQuery('');
328
+ }
329
+ }}
330
+ >
331
+ <SelectInputOptions
332
+ items={items}
333
+ renderValue={renderValue}
334
+ renderFooter={renderFooter}
335
+ filterable={filterable}
336
+ filterPlaceholder={filterPlaceholder}
337
+ searchInputRef={searchInputRef}
338
+ listboxRef={listboxRef}
339
+ filterQuery={filterQuery}
340
+ onFilterChange={setFilterQuery}
341
+ />
342
+ </OptionsOverlay>
343
+ )}
363
344
  </ListboxBase>
364
345
  );
365
346
  }
@@ -449,7 +430,7 @@ interface SelectInputOptionsProps<T = string>
449
430
 
450
431
  function SelectInputOptions<T = string>({
451
432
  items,
452
- renderValue = String,
433
+ renderValue = wrapInFragment,
453
434
  renderFooter,
454
435
  filterable = false,
455
436
  filterPlaceholder,
package/src/main.css CHANGED
@@ -2511,12 +2511,10 @@ html:not([dir="rtl"]) .np-flow-navigation--sm .np-flow-navigation__stepper {
2511
2511
  color: #5d7079;
2512
2512
  color: var(--color-content-secondary);
2513
2513
  }
2514
- .np-select-input-content {
2514
+ .np-select-input-placeholder {
2515
2515
  overflow: hidden;
2516
2516
  text-overflow: ellipsis;
2517
2517
  white-space: nowrap;
2518
- }
2519
- .np-select-input-placeholder {
2520
2518
  color: #768e9c;
2521
2519
  color: var(--color-content-tertiary);
2522
2520
  }
@@ -37,11 +37,7 @@ describe('Given a telephone number component', () => {
37
37
  });
38
38
 
39
39
  it('should set prefix control to default UK value', () => {
40
- expect(select.props().value).toStrictEqual({
41
- value: '+44',
42
- note: 'GBR, GGY, IMN, JEY',
43
- label: '+44',
44
- });
40
+ expect(select.props().value).toBe('+44');
45
41
  });
46
42
 
47
43
  it('should set number control to empty', () => {
@@ -49,11 +45,11 @@ describe('Given a telephone number component', () => {
49
45
  });
50
46
 
51
47
  it('should not disable the select', () => {
52
- expect(select.prop('disabled')).toBe(false);
48
+ expect(select.prop('disabled')).toBeFalsy();
53
49
  });
54
50
 
55
51
  it('should not disable the input', () => {
56
- expect(input.prop('disabled')).toBe(false);
52
+ expect(input.prop('disabled')).toBeFalsy();
57
53
  });
58
54
  });
59
55
 
@@ -65,7 +61,7 @@ describe('Given a telephone number component', () => {
65
61
  });
66
62
 
67
63
  it('should set control values correctly', () => {
68
- expect(select.props().value.value).toStrictEqual('+39');
64
+ expect(select.props().value).toBe('+39');
69
65
  expect(input.prop('value')).toBe('123456789');
70
66
  });
71
67
  });
@@ -92,8 +88,8 @@ describe('Given a telephone number component', () => {
92
88
  input = component.find(NUMBER_SELECTOR);
93
89
  });
94
90
 
95
- it('should render input with null id', () => {
96
- expect(input.prop('id')).toBeNull();
91
+ it('should render input with unspecified id', () => {
92
+ expect(input.prop('id')).toBeUndefined();
97
93
  });
98
94
  });
99
95
 
@@ -118,7 +114,7 @@ describe('Given a telephone number component', () => {
118
114
  it(`${number} code should update the value properly`, () => {
119
115
  simulatePaste(component.find('input'), number);
120
116
 
121
- expect(select().props().value.value).toStrictEqual(countryCode);
117
+ expect(select().props().value).toBe(countryCode);
122
118
  expect(input().prop('value')).toBe(localNumber);
123
119
  expect(props.onChange).toHaveBeenCalledWith(number.replace(/(\s|-)+/g, ''), countryCode);
124
120
  });
@@ -126,28 +122,28 @@ describe('Given a telephone number component', () => {
126
122
 
127
123
  it('should not paste invalid characters', () => {
128
124
  simulatePaste(component.find('input'), '+36asdasdasd');
129
- expect(select().props().value.value).toStrictEqual('+39');
125
+ expect(select().props().value).toBe('+39');
130
126
  expect(input().prop('value')).toBe('123456789');
131
127
  expect(props.onChange).not.toHaveBeenCalled();
132
128
  });
133
129
 
134
130
  it('should not paste countries which are not in the select', () => {
135
131
  simulatePaste(component.find('input'), '+9992342343423');
136
- expect(select().props().value.value).toStrictEqual('+39');
132
+ expect(select().props().value).toBe('+39');
137
133
  expect(input().prop('value')).toBe('123456789');
138
134
  expect(props.onChange).not.toHaveBeenCalled();
139
135
  });
140
136
 
141
137
  it("should not paste numbers which doesn't start with the country code", () => {
142
138
  simulatePaste(component.find('input'), '0+36303932551');
143
- expect(select().props().value.value).toStrictEqual('+39');
139
+ expect(select().props().value).toBe('+39');
144
140
  expect(input().prop('value')).toBe('123456789');
145
141
  expect(props.onChange).not.toHaveBeenCalled();
146
142
  });
147
143
 
148
144
  it("should not paste numbers which doesn't contain a country code", () => {
149
145
  simulatePaste(component.find('input'), '06303932551');
150
- expect(select().props().value.value).toStrictEqual('+39');
146
+ expect(select().props().value).toBe('+39');
151
147
  expect(input().prop('value')).toBe('123456789');
152
148
  expect(props.onChange).not.toHaveBeenCalled();
153
149
  });
@@ -161,7 +157,7 @@ describe('Given a telephone number component', () => {
161
157
  });
162
158
 
163
159
  it('should set the select to the longest matching prefix', () => {
164
- expect(select.props().value.value).toStrictEqual('+1868');
160
+ expect(select.props().value).toBe('+1868');
165
161
  });
166
162
 
167
163
  it('should set the number input to the rest of the number', () => {
@@ -177,7 +173,7 @@ describe('Given a telephone number component', () => {
177
173
  });
178
174
 
179
175
  it('should empty the select', () => {
180
- expect(select.props().value).toBeUndefined();
176
+ expect(select.props().value).toBeNull();
181
177
  });
182
178
 
183
179
  it('should put the whole value in the input without the plus', () => {
@@ -191,7 +187,7 @@ describe('Given a telephone number component', () => {
191
187
  select = component.find(PREFIX_SELECT_SELECTOR);
192
188
  input = component.find(NUMBER_SELECTOR);
193
189
 
194
- expect(select.props().value.value).toStrictEqual('+44');
190
+ expect(select.props().value).toBe('+44');
195
191
  expect(input.prop('value')).toBe('');
196
192
  });
197
193
  });
@@ -254,7 +250,7 @@ describe('Given a telephone number component', () => {
254
250
  });
255
251
 
256
252
  it('should use the prefix of the supplied value', () => {
257
- expect(select.props().value.value).toBe('+1');
253
+ expect(select.props().value).toBe('+1');
258
254
  });
259
255
  });
260
256
 
@@ -268,7 +264,7 @@ describe('Given a telephone number component', () => {
268
264
  });
269
265
 
270
266
  it('should default the prefix to the local country', () => {
271
- expect(select.props().value.value).toBe('+34');
267
+ expect(select.props().value).toBe('+34');
272
268
  });
273
269
  });
274
270
 
@@ -281,7 +277,7 @@ describe('Given a telephone number component', () => {
281
277
  });
282
278
 
283
279
  it('should override locale prefix with country specific prefix', () => {
284
- expect(select.props().value.value).toBe('+1');
280
+ expect(select.props().value).toBe('+1');
285
281
  });
286
282
  });
287
283
  });
@@ -349,7 +345,7 @@ describe('Given a telephone number component', () => {
349
345
 
350
346
  it('renders Select component with expected props', () => {
351
347
  const select = component.find(PREFIX_SELECT_SELECTOR);
352
- expect(select.prop('className')).toStrictEqual('custom-class');
348
+ expect(select.prop('className')).toBe('custom-class');
353
349
  });
354
350
  });
355
351
  });