@transferwise/components 0.0.0-experimental-ff10c0d → 0.0.0-experimental-3ebadff

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 (68) hide show
  1. package/build/dateInput/DateInput.js +37 -12
  2. package/build/dateInput/DateInput.js.map +1 -1
  3. package/build/dateInput/DateInput.mjs +39 -14
  4. package/build/dateInput/DateInput.mjs.map +1 -1
  5. package/build/dateLookup/DateLookup.js +4 -2
  6. package/build/dateLookup/DateLookup.js.map +1 -1
  7. package/build/dateLookup/DateLookup.mjs +4 -2
  8. package/build/dateLookup/DateLookup.mjs.map +1 -1
  9. package/build/dateLookup/dateTrigger/DateTrigger.js +2 -0
  10. package/build/dateLookup/dateTrigger/DateTrigger.js.map +1 -1
  11. package/build/dateLookup/dateTrigger/DateTrigger.mjs +2 -0
  12. package/build/dateLookup/dateTrigger/DateTrigger.mjs.map +1 -1
  13. package/build/field/Field.js +7 -2
  14. package/build/field/Field.js.map +1 -1
  15. package/build/field/Field.mjs +13 -8
  16. package/build/field/Field.mjs.map +1 -1
  17. package/build/inputs/InputGroup.js +1 -1
  18. package/build/inputs/InputGroup.js.map +1 -1
  19. package/build/inputs/InputGroup.mjs +2 -2
  20. package/build/inputs/InputGroup.mjs.map +1 -1
  21. package/build/inputs/SelectInput.js +11 -44
  22. package/build/inputs/SelectInput.js.map +1 -1
  23. package/build/inputs/SelectInput.mjs +11 -44
  24. package/build/inputs/SelectInput.mjs.map +1 -1
  25. package/build/inputs/contexts.js +8 -4
  26. package/build/inputs/contexts.js.map +1 -1
  27. package/build/inputs/contexts.mjs +7 -4
  28. package/build/inputs/contexts.mjs.map +1 -1
  29. package/build/label/Label.js +14 -8
  30. package/build/label/Label.js.map +1 -1
  31. package/build/label/Label.mjs +14 -8
  32. package/build/label/Label.mjs.map +1 -1
  33. package/build/moneyInput/MoneyInput.js +6 -5
  34. package/build/moneyInput/MoneyInput.js.map +1 -1
  35. package/build/moneyInput/MoneyInput.mjs +6 -5
  36. package/build/moneyInput/MoneyInput.mjs.map +1 -1
  37. package/build/phoneNumberInput/PhoneNumberInput.js +24 -2
  38. package/build/phoneNumberInput/PhoneNumberInput.js.map +1 -1
  39. package/build/phoneNumberInput/PhoneNumberInput.mjs +26 -4
  40. package/build/phoneNumberInput/PhoneNumberInput.mjs.map +1 -1
  41. package/build/types/dateInput/DateInput.d.ts +2 -2
  42. package/build/types/dateInput/DateInput.d.ts.map +1 -1
  43. package/build/types/dateLookup/DateLookup.d.ts.map +1 -1
  44. package/build/types/dateLookup/dateTrigger/DateTrigger.d.ts +1 -0
  45. package/build/types/dateLookup/dateTrigger/DateTrigger.d.ts.map +1 -1
  46. package/build/types/field/Field.d.ts.map +1 -1
  47. package/build/types/inputs/InputGroup.d.ts.map +1 -1
  48. package/build/types/inputs/SelectInput.d.ts +3 -2
  49. package/build/types/inputs/SelectInput.d.ts.map +1 -1
  50. package/build/types/inputs/contexts.d.ts +6 -1
  51. package/build/types/inputs/contexts.d.ts.map +1 -1
  52. package/build/types/label/Label.d.ts +5 -15
  53. package/build/types/label/Label.d.ts.map +1 -1
  54. package/build/types/moneyInput/MoneyInput.d.ts.map +1 -1
  55. package/build/types/phoneNumberInput/PhoneNumberInput.d.ts.map +1 -1
  56. package/package.json +3 -3
  57. package/src/dateInput/DateInput.tsx +45 -15
  58. package/src/dateLookup/DateLookup.tsx +5 -3
  59. package/src/dateLookup/dateTrigger/DateTrigger.tsx +3 -0
  60. package/src/field/Field.tsx +6 -5
  61. package/src/inputs/InputGroup.tsx +3 -4
  62. package/src/inputs/SelectInput.story.tsx +0 -94
  63. package/src/inputs/SelectInput.tsx +14 -69
  64. package/src/inputs/contexts.tsx +12 -3
  65. package/src/label/Label.tsx +26 -20
  66. package/src/moneyInput/MoneyInput.spec.tsx +2 -1
  67. package/src/moneyInput/MoneyInput.tsx +7 -6
  68. package/src/phoneNumberInput/PhoneNumberInput.tsx +31 -10
@@ -516,97 +516,3 @@ export const WithinModal: Story<Currency> = {
516
516
  },
517
517
  ],
518
518
  };
519
-
520
- interface Country {
521
- code: string;
522
- name: string;
523
- }
524
-
525
- const countries: Country[] = [
526
- { code: 'US', name: 'United States' },
527
- { code: 'GB', name: 'United Kingdom' },
528
- { code: 'CA', name: 'Canada' },
529
- { code: 'AU', name: 'Australia' },
530
- { code: 'DE', name: 'Germany' },
531
- { code: 'FR', name: 'France' },
532
- { code: 'JP', name: 'Japan' },
533
- { code: 'BR', name: 'Brazil' },
534
- { code: 'IN', name: 'India' },
535
- { code: 'CN', name: 'China' },
536
- { code: 'IT', name: 'Italy' },
537
- { code: 'ES', name: 'Spain' },
538
- { code: 'NL', name: 'Netherlands' },
539
- { code: 'CH', name: 'Switzerland' },
540
- { code: 'SE', name: 'Sweden' },
541
- ];
542
-
543
- function countryOption(country: Country) {
544
- return {
545
- type: 'option',
546
- value: country.code,
547
- filterMatchers: [country.code, country.name],
548
- } satisfies SelectInputItem;
549
- }
550
-
551
- export const WithAutocomplete: Story<string> = {
552
- args: {
553
- name: 'country',
554
- autocomplete: 'country-name',
555
- placeholder: 'Select your country',
556
- items: countries.map(countryOption),
557
- renderValue: (countryCode, withinTrigger) => {
558
- const country = countries.find((c) => c.code === countryCode);
559
- return (
560
- <SelectInputOptionContent
561
- title={withinTrigger ? countryCode : country?.name || countryCode}
562
- note={withinTrigger ? undefined : countryCode}
563
- icon={<Flag code={countryCode} intrinsicSize={24} />}
564
- />
565
- );
566
- },
567
- filterable: true,
568
- filterPlaceholder: 'Type a country name',
569
- size: 'lg',
570
- },
571
- render: function Render({ onChange, onClear, ...args }) {
572
- const [selectedCountry, setSelectedCountry] = useState<string | undefined>(undefined);
573
-
574
- return (
575
- <div>
576
- <form
577
- method="post"
578
- onSubmit={(e) => {
579
- e.preventDefault();
580
- console.log(
581
- `Form submitted with country: ${selectedCountry}. This saves data for browser autocomplete!`,
582
- );
583
- }}
584
- >
585
- <div>
586
- <label htmlFor="country-select" className="block text-sm font-medium mb-2">
587
- Country Selection with Autocomplete:
588
- </label>
589
- <SelectInput
590
- {...args}
591
- id="country-select"
592
- value={selectedCountry}
593
- onChange={(country) => {
594
- setSelectedCountry(country);
595
- onChange?.(country);
596
- console.log('Country selected via SelectInput:', country);
597
- }}
598
- onClear={() => {
599
- setSelectedCountry(undefined);
600
- onClear?.();
601
- }}
602
- />
603
- </div>
604
-
605
- <Button type="submit" v2 className="m-t-2" data-testid="submit-btn">
606
- Submit Form
607
- </Button>
608
- </form>
609
- </div>
610
- );
611
- },
612
- };
@@ -155,7 +155,6 @@ export interface SelectInputProps<T = string, M extends boolean = false> {
155
155
  multiple?: M;
156
156
  placeholder?: string;
157
157
  items: readonly SelectInputItem<NonNullable<T>>[];
158
- autocomplete?: string;
159
158
  defaultValue?: M extends true ? readonly T[] : T;
160
159
  value?: M extends true ? readonly T[] : T;
161
160
  compareValues?:
@@ -182,6 +181,8 @@ export interface SelectInputProps<T = string, M extends boolean = false> {
182
181
  UNSAFE_triggerButtonProps?: WithInputAttributesProps['inputAttributes'] & {
183
182
  'aria-label'?: string;
184
183
  };
184
+ /** Ref to the select trigger button element. */
185
+ triggerRef?: React.MutableRefObject<HTMLButtonElement | null>;
185
186
  onFilterChange?: (args: { query: string; queryNormalized: string | null }) => void;
186
187
  onChange?: (value: M extends true ? T[] : T) => void;
187
188
  onOpen?: () => void;
@@ -256,7 +257,6 @@ export function SelectInput<T = string, M extends boolean = false>({
256
257
  name,
257
258
  multiple,
258
259
  placeholder,
259
- autocomplete,
260
260
  items,
261
261
  defaultValue,
262
262
  value: controlledValue,
@@ -270,6 +270,7 @@ export function SelectInput<T = string, M extends boolean = false>({
270
270
  size = 'md',
271
271
  className,
272
272
  UNSAFE_triggerButtonProps,
273
+ triggerRef: externalTriggerRef,
273
274
  onFilterChange = noop,
274
275
  onChange,
275
276
  onOpen,
@@ -308,7 +309,7 @@ export function SelectInput<T = string, M extends boolean = false>({
308
309
  }
309
310
  });
310
311
 
311
- const triggerRef = useRef<HTMLButtonElement | null>(null);
312
+ const internalTriggerRef = useRef<HTMLButtonElement | null>(null);
312
313
 
313
314
  const screenSm = useScreenSize(Breakpoint.SMALL);
314
315
  const OptionsOverlay = screenSm ? Popover : BottomSheet;
@@ -376,7 +377,12 @@ export function SelectInput<T = string, M extends boolean = false>({
376
377
  value={{
377
378
  ref: (node) => {
378
379
  ref(node);
379
- triggerRef.current = node;
380
+ if (externalTriggerRef) {
381
+ // eslint-disable-next-line no-param-reassign
382
+ externalTriggerRef.current = node;
383
+ } else {
384
+ internalTriggerRef.current = node;
385
+ }
380
386
  },
381
387
  ...inputAttributes,
382
388
  ...UNSAFE_triggerButtonProps,
@@ -419,7 +425,9 @@ export function SelectInput<T = string, M extends boolean = false>({
419
425
  onClear != null
420
426
  ? () => {
421
427
  onClear();
422
- triggerRef.current?.focus({ preventScroll: true });
428
+ (externalTriggerRef?.current ?? internalTriggerRef.current)?.focus({
429
+ preventScroll: true,
430
+ });
423
431
  }
424
432
  : undefined,
425
433
  disabled: uiDisabled,
@@ -449,15 +457,7 @@ export function SelectInput<T = string, M extends boolean = false>({
449
457
  searchInputRef={searchInputRef}
450
458
  listboxRef={listboxRef}
451
459
  filterQuery={deferredFilterQuery}
452
- autocomplete={autocomplete}
453
- name={name}
454
460
  onFilterChange={setFilterQuery}
455
- onAutocompleteSelect={(matchedValue) => {
456
- onChange?.(matchedValue as M extends true ? T[] : T);
457
- if (!multiple) {
458
- setOpen(false);
459
- }
460
- }}
461
461
  {...getListBoxLabelProps()}
462
462
  />
463
463
  </OptionsOverlay>
@@ -565,9 +565,6 @@ interface SelectInputOptionsProps<T = string>
565
565
  onFilterChange: (query: string) => void;
566
566
  listBoxLabel?: string;
567
567
  listBoxLabelledBy?: string;
568
- autocomplete?: string;
569
- name?: string;
570
- onAutocompleteSelect?: (value: T | T[] | null) => void;
571
568
  }
572
569
 
573
570
  function SelectInputOptions<T = string>({
@@ -584,9 +581,6 @@ function SelectInputOptions<T = string>({
584
581
  onFilterChange,
585
582
  listBoxLabel,
586
583
  listBoxLabelledBy,
587
- autocomplete,
588
- name,
589
- onAutocompleteSelect,
590
584
  }: SelectInputOptionsProps<T>) {
591
585
  const intl = useIntl();
592
586
  const virtualiserHandlerRef = useRef<VirtualizerHandle>(null);
@@ -670,35 +664,6 @@ function SelectInputOptions<T = string>({
670
664
  );
671
665
  };
672
666
 
673
- const findMatchingItem = (autocompleteValue: string): T | T[] | null => {
674
- const flatOptions = items
675
- .flatMap((item) =>
676
- item.type === 'group' ? item.options : item.type === 'option' ? [item] : [],
677
- )
678
- .filter(
679
- (item): item is SelectInputOptionItem<NonNullable<T>> =>
680
- item.type === 'option' && item.value != null,
681
- );
682
-
683
- const exactMatch = flatOptions.find(
684
- (option) =>
685
- String(option.value) === autocompleteValue ||
686
- option.filterMatchers?.some((matcher) => matcher === autocompleteValue),
687
- );
688
-
689
- if (exactMatch) {
690
- return exactMatch.value;
691
- }
692
-
693
- const fuzzyMatch = flatOptions.find((option) =>
694
- option.filterMatchers?.some((matcher) =>
695
- matcher.toLowerCase().includes(autocompleteValue.toLowerCase()),
696
- ),
697
- );
698
-
699
- return fuzzyMatch ? fuzzyMatch.value : null;
700
- };
701
-
702
667
  return (
703
668
  <ListboxBase.Options
704
669
  as={SelectInputOptionsContainer}
@@ -719,8 +684,6 @@ function SelectInputOptions<T = string>({
719
684
  <SearchInput
720
685
  ref={searchInputRef}
721
686
  id={id}
722
- name={name}
723
- autoComplete={autocomplete}
724
687
  role="combobox"
725
688
  shape="rectangle"
726
689
  placeholder={filterPlaceholder}
@@ -740,26 +703,8 @@ function SelectInputOptions<T = string>({
740
703
  onChange={(event) => {
741
704
  // Free up resources and ensure not to go out of bounds when the
742
705
  // resulting item count is less than before
743
- const inputValue = event.currentTarget.value;
744
-
745
- // Free up resources and ensure not to go out of bounds
746
706
  setMountedIndexes([]);
747
- onFilterChange(inputValue);
748
- }}
749
- onInput={(event) => {
750
- const inputValue = event.currentTarget.value;
751
- const inputElement = event.currentTarget;
752
-
753
- if (autocomplete && onAutocompleteSelect && inputValue) {
754
- setTimeout(() => {
755
- if (inputElement.value === inputValue && inputValue.length > 2) {
756
- const matchedValue = findMatchingItem(inputValue);
757
- if (matchedValue !== null) {
758
- onAutocompleteSelect(matchedValue);
759
- }
760
- }
761
- }, 50);
762
- }
707
+ onFilterChange(event.currentTarget.value);
763
708
  }}
764
709
  />
765
710
  </div>
@@ -1,7 +1,12 @@
1
1
  import { createContext, useContext } from 'react';
2
2
 
3
- const FieldLabelIdContext = createContext<string | undefined>(undefined);
4
- export const FieldLabelIdContextProvider = FieldLabelIdContext.Provider;
3
+ type FieldLabelContextType = {
4
+ id?: string;
5
+ ref?: React.RefObject<HTMLLabelElement>;
6
+ };
7
+
8
+ const FieldLabelContext = createContext<FieldLabelContextType | undefined>(undefined);
9
+ export const FieldLabelContextProvider = FieldLabelContext.Provider;
5
10
 
6
11
  const InputIdContext = createContext<string | undefined>(undefined);
7
12
  export const InputIdContextProvider = InputIdContext.Provider;
@@ -18,7 +23,7 @@ interface UseInputAttributesArgs {
18
23
  }
19
24
 
20
25
  export function useInputAttributes({ nonLabelable }: UseInputAttributesArgs = {}) {
21
- const labelId = useContext(FieldLabelIdContext);
26
+ const labelId = useContext(FieldLabelContext)?.id;
22
27
  return {
23
28
  id: useContext(InputIdContext),
24
29
  'aria-labelledby': nonLabelable ? labelId : undefined,
@@ -27,6 +32,10 @@ export function useInputAttributes({ nonLabelable }: UseInputAttributesArgs = {}
27
32
  } satisfies React.HTMLAttributes<HTMLElement>;
28
33
  }
29
34
 
35
+ export function useFieldLabelRef() {
36
+ return useContext(FieldLabelContext)?.ref;
37
+ }
38
+
30
39
  export interface WithInputAttributesProps {
31
40
  inputAttributes: ReturnType<typeof useInputAttributes>;
32
41
  }
@@ -3,7 +3,7 @@ import messages from './Label.messages';
3
3
  import { useIntl } from 'react-intl';
4
4
  import Body from '../body';
5
5
  import { CommonProps } from '../common';
6
- import { PropsWithChildren } from 'react';
6
+ import { forwardRef, PropsWithChildren } from 'react';
7
7
 
8
8
  export type LabelProps = {
9
9
  id?: string;
@@ -21,25 +21,29 @@ export type LabelProps = {
21
21
  * <Field label={..} description={..} required={..}>..</Field>
22
22
  * ```
23
23
  */
24
- const Label = ({ className, children, htmlFor, id }: LabelProps) => {
25
- return (
26
- <label
27
- id={id}
28
- htmlFor={htmlFor}
29
- className={clsx(
30
- 'np-label d-flex flex-column np-text-body-default-bold text-primary m-b-0',
31
- className,
32
- )}
33
- >
34
- {children}
35
- </label>
36
- );
37
- };
24
+ const Label = forwardRef<HTMLLabelElement, LabelProps>(
25
+ ({ className, children, htmlFor, id }: LabelProps, ref) => {
26
+ return (
27
+ <label
28
+ ref={ref}
29
+ id={id}
30
+ htmlFor={htmlFor}
31
+ className={clsx(
32
+ 'np-label d-flex flex-column np-text-body-default-bold text-primary m-b-0',
33
+ className,
34
+ )}
35
+ >
36
+ {children}
37
+ </label>
38
+ );
39
+ },
40
+ );
41
+
42
+ Label.displayName = 'Label';
38
43
 
39
44
  export type LabelOptionalProps = PropsWithChildren<CommonProps>;
40
45
 
41
- // eslint-disable-next-line functional/immutable-data
42
- Label.Optional = function Optional({ children, className }: LabelOptionalProps) {
46
+ const Optional = function Optional({ children, className }: LabelOptionalProps) {
43
47
  const { formatMessage } = useIntl();
44
48
  return (
45
49
  <div>
@@ -53,8 +57,7 @@ Label.Optional = function Optional({ children, className }: LabelOptionalProps)
53
57
 
54
58
  export type LabelDescriptionProps = PropsWithChildren<CommonProps> & { id?: string };
55
59
 
56
- // eslint-disable-next-line functional/immutable-data
57
- Label.Description = function Description({ id, children, className }: LabelDescriptionProps) {
60
+ const Description = function Description({ id, children, className }: LabelDescriptionProps) {
58
61
  return children ? (
59
62
  <Body id={id} className={clsx('text-secondary', className)}>
60
63
  {children}
@@ -62,4 +65,7 @@ Label.Description = function Description({ id, children, className }: LabelDescr
62
65
  ) : null;
63
66
  };
64
67
 
65
- export { Label };
68
+ // eslint-disable-next-line functional/immutable-data
69
+ const LabelNamespace = Object.assign(Label, { Optional, Description });
70
+
71
+ export { LabelNamespace as Label };
@@ -406,7 +406,7 @@ describe('Money Input', () => {
406
406
  </>,
407
407
  );
408
408
 
409
- expect(screen.getAllByLabelText('Prioritized label')[0]).toHaveClass('input-group');
409
+ expect(screen.getAllByLabelText('Prioritized label')[0]).toHaveClass('np-form-control');
410
410
  });
411
411
 
412
412
  it('supports `Field` for labeling', () => {
@@ -415,6 +415,7 @@ describe('Money Input', () => {
415
415
  <MoneyInput {...initialProps} />
416
416
  </Field>,
417
417
  );
418
+
418
419
  expect(screen.getAllByRole('group')[0]).toHaveAccessibleName(/^Recipient gets/);
419
420
  });
420
421
 
@@ -313,10 +313,10 @@ class MoneyInput extends Component<MoneyInputPropsWithInputAttributes, MoneyInpu
313
313
  const selectedCurrencyElementId = `${inputAttributes?.id ?? amountInputId}SelectedCurrency`;
314
314
 
315
315
  return (
316
- <div
317
- role="group"
318
- {...inputAttributes}
319
- aria-labelledby={ariaLabelledBy}
316
+ <fieldset
317
+ aria-invalid={inputAttributes?.['aria-invalid']}
318
+ aria-describedby={inputAttributes?.['aria-describedby']}
319
+ aria-labelledby={inputAttributes?.id ?? amountInputId}
320
320
  className={clsx(
321
321
  this.style('tw-money-input'),
322
322
  this.style('input-group'),
@@ -324,7 +324,8 @@ class MoneyInput extends Component<MoneyInputPropsWithInputAttributes, MoneyInpu
324
324
  )}
325
325
  >
326
326
  <Input
327
- id={amountInputId}
327
+ id={inputAttributes?.id ?? amountInputId}
328
+ aria-labelledby={ariaLabelledBy}
328
329
  value={this.state.formattedAmount}
329
330
  inputMode="decimal"
330
331
  disabled={disabled}
@@ -430,7 +431,7 @@ class MoneyInput extends Component<MoneyInputPropsWithInputAttributes, MoneyInpu
430
431
  />
431
432
  </div>
432
433
  )}
433
- </div>
434
+ </fieldset>
434
435
  );
435
436
  }
436
437
  }
@@ -1,8 +1,8 @@
1
- import { useState, useEffect, useMemo } from 'react';
1
+ import { useState, useEffect, useMemo, useRef } from 'react';
2
2
  import { useIntl } from 'react-intl';
3
3
 
4
4
  import { Size, SizeLarge, SizeMedium, SizeSmall } from '../common';
5
- import { useInputAttributes } from '../inputs/contexts';
5
+ import { useFieldLabelRef, useInputAttributes } from '../inputs/contexts';
6
6
  import { SelectInput, SelectInputOptionContent, SelectInputProps } from '../inputs/SelectInput';
7
7
  import messages from './PhoneNumberInput.messages';
8
8
  import countries from './data/countries';
@@ -58,7 +58,10 @@ const PhoneNumberInput = ({
58
58
  selectProps = defaultSelectProps,
59
59
  disabledCountries = defaultDisabledCountries,
60
60
  }: PhoneNumberInputProps) => {
61
+ const countryCodeSelectRef = useRef<HTMLButtonElement>(null);
62
+ const phoneNumberInputRef = useRef<HTMLInputElement>(null);
61
63
  const inputAttributes = useInputAttributes({ nonLabelable: true });
64
+ const fieldLabelRef = useFieldLabelRef();
62
65
  const ariaLabelledBy = ariaLabelledByProp ?? inputAttributes['aria-labelledby'];
63
66
 
64
67
  const { locale, formatMessage } = useIntl();
@@ -71,6 +74,7 @@ const PhoneNumberInput = ({
71
74
  return `${backup}-${random}`;
72
75
  };
73
76
 
77
+ // Link the first non-disabled input to the the Field label, if present
74
78
  const ids = {
75
79
  countryCode: {
76
80
  label: createId(id, 'country-code-label'),
@@ -162,18 +166,34 @@ const PhoneNumberInput = ({
162
166
  setBroadcastedValue(internalValue);
163
167
  }, [onChange, broadcastedValue, internalValue]);
164
168
 
169
+ useEffect(() => {
170
+ const labelRef = fieldLabelRef?.current;
171
+
172
+ if (labelRef) {
173
+ const handleLabelClick = () => {
174
+ if (!selectProps.disabled) {
175
+ countryCodeSelectRef.current?.click();
176
+ } else {
177
+ phoneNumberInputRef.current?.focus();
178
+ }
179
+ };
180
+
181
+ labelRef.addEventListener('click', handleLabelClick);
182
+
183
+ return () => {
184
+ labelRef?.removeEventListener('click', handleLabelClick);
185
+ };
186
+ }
187
+ }, [fieldLabelRef, selectProps.disabled]);
188
+
165
189
  return (
166
- <div
167
- role="group"
168
- {...inputAttributes}
169
- aria-labelledby={ariaLabelledBy}
170
- className="tw-telephone"
171
- >
172
- <label className="sr-only" id={ids.countryCode.label}>
190
+ <fieldset {...inputAttributes} aria-labelledby={ariaLabelledBy} className="tw-telephone">
191
+ <label className="sr-only" id={ids.countryCode.label} htmlFor={ids.countryCode.select}>
173
192
  {formatMessage(messages.countryCodeLabel)}
174
193
  </label>
175
194
  <div className="tw-telephone__country-select">
176
195
  <SelectInput
196
+ triggerRef={countryCodeSelectRef}
177
197
  placeholder={formatMessage(messages.selectInputPlaceholder)}
178
198
  items={[...countriesByPrefix].map(([prefix, countries]) => ({
179
199
  type: 'option',
@@ -227,6 +247,7 @@ const PhoneNumberInput = ({
227
247
  <div className="tw-telephone__number-input">
228
248
  <div className={`input-group input-group-${size}`}>
229
249
  <Input
250
+ ref={phoneNumberInputRef}
230
251
  id={ids.phoneNumber.input}
231
252
  autoComplete="tel-national"
232
253
  name="phoneNumber"
@@ -243,7 +264,7 @@ const PhoneNumberInput = ({
243
264
  />
244
265
  </div>
245
266
  </div>
246
- </div>
267
+ </fieldset>
247
268
  );
248
269
  };
249
270