@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.
- package/build/dateInput/DateInput.js +37 -12
- package/build/dateInput/DateInput.js.map +1 -1
- package/build/dateInput/DateInput.mjs +39 -14
- package/build/dateInput/DateInput.mjs.map +1 -1
- package/build/dateLookup/DateLookup.js +4 -2
- package/build/dateLookup/DateLookup.js.map +1 -1
- package/build/dateLookup/DateLookup.mjs +4 -2
- package/build/dateLookup/DateLookup.mjs.map +1 -1
- package/build/dateLookup/dateTrigger/DateTrigger.js +2 -0
- package/build/dateLookup/dateTrigger/DateTrigger.js.map +1 -1
- package/build/dateLookup/dateTrigger/DateTrigger.mjs +2 -0
- package/build/dateLookup/dateTrigger/DateTrigger.mjs.map +1 -1
- package/build/field/Field.js +7 -2
- package/build/field/Field.js.map +1 -1
- package/build/field/Field.mjs +13 -8
- package/build/field/Field.mjs.map +1 -1
- package/build/inputs/InputGroup.js +1 -1
- package/build/inputs/InputGroup.js.map +1 -1
- package/build/inputs/InputGroup.mjs +2 -2
- package/build/inputs/InputGroup.mjs.map +1 -1
- package/build/inputs/SelectInput.js +11 -44
- package/build/inputs/SelectInput.js.map +1 -1
- package/build/inputs/SelectInput.mjs +11 -44
- package/build/inputs/SelectInput.mjs.map +1 -1
- package/build/inputs/contexts.js +8 -4
- package/build/inputs/contexts.js.map +1 -1
- package/build/inputs/contexts.mjs +7 -4
- package/build/inputs/contexts.mjs.map +1 -1
- package/build/label/Label.js +14 -8
- package/build/label/Label.js.map +1 -1
- package/build/label/Label.mjs +14 -8
- package/build/label/Label.mjs.map +1 -1
- package/build/moneyInput/MoneyInput.js +6 -5
- package/build/moneyInput/MoneyInput.js.map +1 -1
- package/build/moneyInput/MoneyInput.mjs +6 -5
- package/build/moneyInput/MoneyInput.mjs.map +1 -1
- package/build/phoneNumberInput/PhoneNumberInput.js +24 -2
- package/build/phoneNumberInput/PhoneNumberInput.js.map +1 -1
- package/build/phoneNumberInput/PhoneNumberInput.mjs +26 -4
- package/build/phoneNumberInput/PhoneNumberInput.mjs.map +1 -1
- package/build/types/dateInput/DateInput.d.ts +2 -2
- package/build/types/dateInput/DateInput.d.ts.map +1 -1
- package/build/types/dateLookup/DateLookup.d.ts.map +1 -1
- package/build/types/dateLookup/dateTrigger/DateTrigger.d.ts +1 -0
- package/build/types/dateLookup/dateTrigger/DateTrigger.d.ts.map +1 -1
- package/build/types/field/Field.d.ts.map +1 -1
- package/build/types/inputs/InputGroup.d.ts.map +1 -1
- package/build/types/inputs/SelectInput.d.ts +3 -2
- package/build/types/inputs/SelectInput.d.ts.map +1 -1
- package/build/types/inputs/contexts.d.ts +6 -1
- package/build/types/inputs/contexts.d.ts.map +1 -1
- package/build/types/label/Label.d.ts +5 -15
- package/build/types/label/Label.d.ts.map +1 -1
- package/build/types/moneyInput/MoneyInput.d.ts.map +1 -1
- package/build/types/phoneNumberInput/PhoneNumberInput.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/dateInput/DateInput.tsx +45 -15
- package/src/dateLookup/DateLookup.tsx +5 -3
- package/src/dateLookup/dateTrigger/DateTrigger.tsx +3 -0
- package/src/field/Field.tsx +6 -5
- package/src/inputs/InputGroup.tsx +3 -4
- package/src/inputs/SelectInput.story.tsx +0 -94
- package/src/inputs/SelectInput.tsx +14 -69
- package/src/inputs/contexts.tsx +12 -3
- package/src/label/Label.tsx +26 -20
- package/src/moneyInput/MoneyInput.spec.tsx +2 -1
- package/src/moneyInput/MoneyInput.tsx +7 -6
- 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
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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>
|
package/src/inputs/contexts.tsx
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
import { createContext, useContext } from 'react';
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
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(
|
|
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
|
}
|
package/src/label/Label.tsx
CHANGED
|
@@ -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 =
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
className
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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('
|
|
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
|
-
<
|
|
317
|
-
|
|
318
|
-
{
|
|
319
|
-
aria-labelledby={
|
|
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
|
-
</
|
|
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
|
-
<
|
|
167
|
-
|
|
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
|
-
</
|
|
267
|
+
</fieldset>
|
|
247
268
|
);
|
|
248
269
|
};
|
|
249
270
|
|