@transferwise/components 46.51.0 → 46.52.1
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/i18n/pt.json +2 -0
- package/build/i18n/pt.json.js +2 -0
- package/build/i18n/pt.json.js.map +1 -1
- package/build/i18n/pt.json.mjs +2 -0
- package/build/i18n/pt.json.mjs.map +1 -1
- package/build/i18n/zh-CN.json +2 -0
- package/build/i18n/zh-CN.json.js +2 -0
- package/build/i18n/zh-CN.json.js.map +1 -1
- package/build/i18n/zh-CN.json.mjs +2 -0
- package/build/i18n/zh-CN.json.mjs.map +1 -1
- package/build/inputs/SelectInput.js +107 -36
- package/build/inputs/SelectInput.js.map +1 -1
- package/build/inputs/SelectInput.mjs +109 -38
- package/build/inputs/SelectInput.mjs.map +1 -1
- package/build/main.css +10 -0
- package/build/styles/inputs/SelectInput.css +10 -0
- package/build/styles/main.css +10 -0
- package/build/typeahead/Typeahead.js +63 -59
- package/build/typeahead/Typeahead.js.map +1 -1
- package/build/typeahead/Typeahead.messages.js +12 -0
- package/build/typeahead/Typeahead.messages.js.map +1 -0
- package/build/typeahead/Typeahead.messages.mjs +10 -0
- package/build/typeahead/Typeahead.messages.mjs.map +1 -0
- package/build/typeahead/Typeahead.mjs +63 -59
- package/build/typeahead/Typeahead.mjs.map +1 -1
- package/build/types/inputs/SelectInput.d.ts.map +1 -1
- package/build/types/typeahead/Typeahead.d.ts +2 -1
- package/build/types/typeahead/Typeahead.d.ts.map +1 -1
- package/build/types/typeahead/Typeahead.messages.d.ts +9 -0
- package/build/types/typeahead/Typeahead.messages.d.ts.map +1 -0
- package/package.json +5 -4
- package/src/i18n/pt.json +2 -0
- package/src/i18n/zh-CN.json +2 -0
- package/src/inputs/SelectInput.css +10 -0
- package/src/inputs/SelectInput.less +12 -0
- package/src/inputs/SelectInput.story.tsx +20 -0
- package/src/inputs/SelectInput.tsx +144 -46
- package/src/main.css +10 -0
- package/src/typeahead/Typeahead.messages.ts +9 -0
- package/src/typeahead/Typeahead.rtl.spec.tsx +13 -1
- package/src/typeahead/Typeahead.spec.js +12 -10
- package/src/typeahead/Typeahead.story.tsx +194 -195
- package/src/typeahead/Typeahead.tsx +16 -9
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
createContext,
|
|
7
7
|
forwardRef,
|
|
8
8
|
useContext,
|
|
9
|
+
useDeferredValue,
|
|
9
10
|
useEffect,
|
|
10
11
|
useId,
|
|
11
12
|
useMemo,
|
|
@@ -13,6 +14,7 @@ import {
|
|
|
13
14
|
useState,
|
|
14
15
|
} from 'react';
|
|
15
16
|
import { useIntl } from 'react-intl';
|
|
17
|
+
import { Virtualizer } from 'virtua';
|
|
16
18
|
|
|
17
19
|
import { useEffectEvent } from '../common/hooks/useEffectEvent';
|
|
18
20
|
import { useScreenSize } from '../common/hooks/useScreenSize';
|
|
@@ -29,6 +31,8 @@ import { InputGroup } from './InputGroup';
|
|
|
29
31
|
import { SearchInput } from './SearchInput';
|
|
30
32
|
import messages from './SelectInput.messages';
|
|
31
33
|
|
|
34
|
+
const MAX_ITEMS_WITHOUT_VIRTUALIZATION = 50;
|
|
35
|
+
|
|
32
36
|
function searchableString(value: string) {
|
|
33
37
|
return value.trim().replace(/\s+/gu, ' ').normalize('NFKC').toLowerCase();
|
|
34
38
|
}
|
|
@@ -40,22 +44,13 @@ function inferSearchableStrings(value: unknown) {
|
|
|
40
44
|
|
|
41
45
|
if (typeof value === 'object' && value != null) {
|
|
42
46
|
return Object.values(value)
|
|
43
|
-
.filter((innerValue)
|
|
47
|
+
.filter((innerValue) => typeof innerValue === 'string')
|
|
44
48
|
.map((innerValue) => searchableString(innerValue));
|
|
45
49
|
}
|
|
46
50
|
|
|
47
51
|
return [];
|
|
48
52
|
}
|
|
49
53
|
|
|
50
|
-
const SelectInputTriggerButtonPropsContext = createContext<{
|
|
51
|
-
ref?: React.ForwardedRef<HTMLButtonElement | null>;
|
|
52
|
-
id?: string;
|
|
53
|
-
onClick?: (event: React.MouseEvent) => void;
|
|
54
|
-
onKeyDown?: (event: React.KeyboardEvent) => void;
|
|
55
|
-
[key: string]: unknown;
|
|
56
|
-
}>({});
|
|
57
|
-
const SelectInputOptionContentWithinTriggerContext = createContext(false);
|
|
58
|
-
|
|
59
54
|
export interface SelectInputOptionItem<T = string> {
|
|
60
55
|
type: 'option';
|
|
61
56
|
value: T;
|
|
@@ -89,6 +84,11 @@ function dedupeSelectInputOptionItem<T>(
|
|
|
89
84
|
return { ...item, value: undefined };
|
|
90
85
|
}
|
|
91
86
|
|
|
87
|
+
/**
|
|
88
|
+
* Sets the `value` of duplicate option items to `undefined`, hiding them when
|
|
89
|
+
* rendered. Indexes are kept intact within groups to preserve the active item
|
|
90
|
+
* between filter changes when possible.
|
|
91
|
+
*/
|
|
92
92
|
function dedupeSelectInputItems<T>(
|
|
93
93
|
items: readonly SelectInputItem<T>[],
|
|
94
94
|
): SelectInputItem<T | undefined>[] {
|
|
@@ -112,20 +112,23 @@ function dedupeSelectInputItems<T>(
|
|
|
112
112
|
});
|
|
113
113
|
}
|
|
114
114
|
|
|
115
|
-
function
|
|
115
|
+
function selectInputOptionItemIncludesNeedle<T>(item: SelectInputOptionItem<T>, needle: string) {
|
|
116
116
|
return inferSearchableStrings(item.filterMatchers ?? item.value).some((haystack) =>
|
|
117
117
|
haystack.includes(needle),
|
|
118
118
|
);
|
|
119
119
|
}
|
|
120
120
|
|
|
121
|
-
function filterSelectInputItems<T>(
|
|
121
|
+
function filterSelectInputItems<T>(
|
|
122
|
+
items: readonly SelectInputItem<T>[],
|
|
123
|
+
predicate: (item: SelectInputOptionItem<T>) => boolean,
|
|
124
|
+
) {
|
|
122
125
|
return items.filter((item) => {
|
|
123
126
|
switch (item.type) {
|
|
124
127
|
case 'option': {
|
|
125
|
-
return
|
|
128
|
+
return predicate(item);
|
|
126
129
|
}
|
|
127
130
|
case 'group': {
|
|
128
|
-
return item.options.some((option) =>
|
|
131
|
+
return item.options.some((option) => predicate(option));
|
|
129
132
|
}
|
|
130
133
|
default:
|
|
131
134
|
}
|
|
@@ -271,12 +274,15 @@ export function SelectInput<T = string, M extends boolean = false>({
|
|
|
271
274
|
}, [handleClose, open]);
|
|
272
275
|
|
|
273
276
|
const [filterQuery, _setFilterQuery] = useState('');
|
|
277
|
+
const deferredFilterQuery = useDeferredValue(filterQuery);
|
|
274
278
|
const setFilterQuery = useEffectEvent((query: string) => {
|
|
275
279
|
_setFilterQuery(query);
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
+
if (query !== filterQuery) {
|
|
281
|
+
onFilterChange({
|
|
282
|
+
query,
|
|
283
|
+
queryNormalized: query ? searchableString(query) : null,
|
|
284
|
+
});
|
|
285
|
+
}
|
|
280
286
|
});
|
|
281
287
|
|
|
282
288
|
const triggerRef = useRef<HTMLButtonElement | null>(null);
|
|
@@ -294,9 +300,7 @@ export function SelectInput<T = string, M extends boolean = false>({
|
|
|
294
300
|
multiple={multiple}
|
|
295
301
|
defaultValue={defaultValue}
|
|
296
302
|
value={controlledValue}
|
|
297
|
-
|
|
298
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
299
|
-
by={compareValues as any}
|
|
303
|
+
by={compareValues}
|
|
300
304
|
disabled={disabled}
|
|
301
305
|
onChange={
|
|
302
306
|
((value) => {
|
|
@@ -349,8 +353,8 @@ export function SelectInput<T = string, M extends boolean = false>({
|
|
|
349
353
|
content: !placeholderShown ? (
|
|
350
354
|
<SelectInputOptionContentWithinTriggerContext.Provider value>
|
|
351
355
|
{multiple && Array.isArray(value)
|
|
352
|
-
? value
|
|
353
|
-
.map((option
|
|
356
|
+
? (value as readonly NonNullable<T>[])
|
|
357
|
+
.map((option) => renderValue(option, true))
|
|
354
358
|
.filter((node) => node != null)
|
|
355
359
|
.join(', ')
|
|
356
360
|
: renderValue(value as NonNullable<T>, true)}
|
|
@@ -379,9 +383,7 @@ export function SelectInput<T = string, M extends boolean = false>({
|
|
|
379
383
|
setOpen(false);
|
|
380
384
|
}}
|
|
381
385
|
onCloseEnd={() => {
|
|
382
|
-
|
|
383
|
-
setFilterQuery('');
|
|
384
|
-
}
|
|
386
|
+
setFilterQuery('');
|
|
385
387
|
}}
|
|
386
388
|
>
|
|
387
389
|
<SelectInputOptions
|
|
@@ -392,7 +394,7 @@ export function SelectInput<T = string, M extends boolean = false>({
|
|
|
392
394
|
filterPlaceholder={filterPlaceholder}
|
|
393
395
|
searchInputRef={searchInputRef}
|
|
394
396
|
listboxRef={listboxRef}
|
|
395
|
-
filterQuery={
|
|
397
|
+
filterQuery={deferredFilterQuery}
|
|
396
398
|
onFilterChange={setFilterQuery}
|
|
397
399
|
/>
|
|
398
400
|
</OptionsOverlay>
|
|
@@ -402,6 +404,14 @@ export function SelectInput<T = string, M extends boolean = false>({
|
|
|
402
404
|
);
|
|
403
405
|
}
|
|
404
406
|
|
|
407
|
+
const SelectInputTriggerButtonPropsContext = createContext<{
|
|
408
|
+
ref?: React.ForwardedRef<HTMLButtonElement | null>;
|
|
409
|
+
id?: string;
|
|
410
|
+
onClick?: (event: React.MouseEvent) => void;
|
|
411
|
+
onKeyDown?: (event: React.KeyboardEvent) => void;
|
|
412
|
+
[key: string]: unknown;
|
|
413
|
+
}>({});
|
|
414
|
+
|
|
405
415
|
type SelectInputTriggerButtonElementType = 'button' | React.ComponentType;
|
|
406
416
|
|
|
407
417
|
export type SelectInputTriggerButtonProps<
|
|
@@ -506,7 +516,51 @@ function SelectInputOptions<T = string>({
|
|
|
506
516
|
}
|
|
507
517
|
return undefined;
|
|
508
518
|
}, [filterQuery, filterable]);
|
|
509
|
-
|
|
519
|
+
useEffect(() => {
|
|
520
|
+
if (needle) {
|
|
521
|
+
// Ensure having an active option while filtering.
|
|
522
|
+
// Without `requestAnimationFrame` upon which React depends for scheduling
|
|
523
|
+
// updates, the active status would only show for a split second and then
|
|
524
|
+
// disappear inadvertently.
|
|
525
|
+
requestAnimationFrame(() => {
|
|
526
|
+
if (
|
|
527
|
+
controllerRef.current != null &&
|
|
528
|
+
!controllerRef.current.hasAttribute('aria-activedescendant')
|
|
529
|
+
) {
|
|
530
|
+
// Activate first option via synthetic key press
|
|
531
|
+
controllerRef.current.dispatchEvent(
|
|
532
|
+
new KeyboardEvent('keydown', { key: 'Home', bubbles: true }),
|
|
533
|
+
);
|
|
534
|
+
}
|
|
535
|
+
});
|
|
536
|
+
}
|
|
537
|
+
}, [controllerRef, needle]);
|
|
538
|
+
|
|
539
|
+
const filteredItems: readonly SelectInputItem<NonNullable<T> | undefined>[] =
|
|
540
|
+
needle != null
|
|
541
|
+
? filterSelectInputItems(dedupeSelectInputItems(items), (item) =>
|
|
542
|
+
selectInputOptionItemIncludesNeedle(item, needle),
|
|
543
|
+
)
|
|
544
|
+
: items;
|
|
545
|
+
const resultsEmpty = needle != null && filteredItems.length === 0;
|
|
546
|
+
|
|
547
|
+
const virtualized = filteredItems.length > MAX_ITEMS_WITHOUT_VIRTUALIZATION;
|
|
548
|
+
|
|
549
|
+
// Items shown once shall be kept mounted until the needle changes, otherwise
|
|
550
|
+
// the scroll position may jump around inadvertently. Pattern adopted from:
|
|
551
|
+
// https://inokawa.github.io/virtua/?path=/story/advanced-keep-offscreen-items--append-only
|
|
552
|
+
const [mountedIndexes, setMountedIndexes] = useState<number[]>([]);
|
|
553
|
+
useEffect(() => {
|
|
554
|
+
// Ensure the 'End' key works as intended by keeping the last item mounted
|
|
555
|
+
setMountedIndexes((prevMountedIndexes) => {
|
|
556
|
+
const indexes = new Set(prevMountedIndexes);
|
|
557
|
+
indexes.add(filteredItems.length - 1);
|
|
558
|
+
return [...indexes]; // Sorting is redundant by nature here
|
|
559
|
+
});
|
|
560
|
+
}, [
|
|
561
|
+
needle, // Needed as `filteredItems.length` may be equal between two updates
|
|
562
|
+
filteredItems.length,
|
|
563
|
+
]);
|
|
510
564
|
|
|
511
565
|
const listboxContainerRef = useRef<HTMLDivElement>(null);
|
|
512
566
|
useEffect(() => {
|
|
@@ -522,6 +576,19 @@ function SelectInputOptions<T = string>({
|
|
|
522
576
|
const statusId = useId();
|
|
523
577
|
const listboxId = useId();
|
|
524
578
|
|
|
579
|
+
const getItemNode = (index: number) => {
|
|
580
|
+
const item = filteredItems[index];
|
|
581
|
+
return (
|
|
582
|
+
<SelectInputItemView
|
|
583
|
+
// eslint-disable-next-line react/no-array-index-key
|
|
584
|
+
key={index}
|
|
585
|
+
item={item}
|
|
586
|
+
renderValue={renderValue}
|
|
587
|
+
needle={needle}
|
|
588
|
+
/>
|
|
589
|
+
);
|
|
590
|
+
};
|
|
591
|
+
|
|
525
592
|
return (
|
|
526
593
|
<ListboxBase.Options
|
|
527
594
|
as={SelectInputOptionsContainer}
|
|
@@ -533,12 +600,6 @@ function SelectInputOptions<T = string>({
|
|
|
533
600
|
controllerRef.current.setAttribute('aria-activedescendant', value);
|
|
534
601
|
} else {
|
|
535
602
|
controllerRef.current.removeAttribute('aria-activedescendant');
|
|
536
|
-
if (filterQuery) {
|
|
537
|
-
// Ensure having an active option while filtering
|
|
538
|
-
controllerRef.current.dispatchEvent(
|
|
539
|
-
new KeyboardEvent('keydown', { key: 'Home', bubbles: true }),
|
|
540
|
-
);
|
|
541
|
-
}
|
|
542
603
|
}
|
|
543
604
|
}
|
|
544
605
|
}}
|
|
@@ -549,7 +610,7 @@ function SelectInputOptions<T = string>({
|
|
|
549
610
|
ref={searchInputRef}
|
|
550
611
|
shape="rectangle"
|
|
551
612
|
placeholder={filterPlaceholder}
|
|
552
|
-
|
|
613
|
+
defaultValue={filterQuery}
|
|
553
614
|
aria-controls={listboxId}
|
|
554
615
|
aria-describedby={showStatus ? statusId : undefined}
|
|
555
616
|
onKeyDown={(event) => {
|
|
@@ -560,6 +621,9 @@ function SelectInputOptions<T = string>({
|
|
|
560
621
|
}
|
|
561
622
|
}}
|
|
562
623
|
onChange={(event) => {
|
|
624
|
+
// Free up resources and ensure not to go out of bounds when the
|
|
625
|
+
// resulting item count is less than before
|
|
626
|
+
setMountedIndexes([]);
|
|
563
627
|
onFilterChange(event.currentTarget.value);
|
|
564
628
|
}}
|
|
565
629
|
/>
|
|
@@ -571,7 +635,9 @@ function SelectInputOptions<T = string>({
|
|
|
571
635
|
tabIndex={-1}
|
|
572
636
|
className={clsx(
|
|
573
637
|
'np-select-input-listbox-container',
|
|
574
|
-
|
|
638
|
+
virtualized && 'np-select-input-listbox-container--virtualized',
|
|
639
|
+
needle == null && // Groups aren't shown when filtering
|
|
640
|
+
items.some((item) => item.type === 'group') &&
|
|
575
641
|
'np-select-input-listbox-container--has-group',
|
|
576
642
|
)}
|
|
577
643
|
>
|
|
@@ -590,15 +656,35 @@ function SelectInputOptions<T = string>({
|
|
|
590
656
|
tabIndex={0}
|
|
591
657
|
className="np-select-input-listbox"
|
|
592
658
|
>
|
|
593
|
-
{
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
659
|
+
{!virtualized ? (
|
|
660
|
+
filteredItems.map((_, index) => getItemNode(index))
|
|
661
|
+
) : (
|
|
662
|
+
<Virtualizer
|
|
663
|
+
key={needle}
|
|
664
|
+
count={filteredItems.length}
|
|
665
|
+
keepMounted={mountedIndexes}
|
|
666
|
+
scrollRef={listboxRef} // `VList` doesn't expose this
|
|
667
|
+
onRangeChange={(startIndex, endIndex) => {
|
|
668
|
+
setMountedIndexes((prevMountedIndexes) => {
|
|
669
|
+
const indexes = new Set(prevMountedIndexes);
|
|
670
|
+
for (let index = startIndex; index <= endIndex; index += 1) {
|
|
671
|
+
indexes.add(index);
|
|
672
|
+
}
|
|
673
|
+
return [...indexes].sort((a, b) => a - b);
|
|
674
|
+
});
|
|
675
|
+
}}
|
|
676
|
+
>
|
|
677
|
+
{(index) => (
|
|
678
|
+
// The position of each item can't be inferred by browsers when
|
|
679
|
+
// virtualizing, as some of the items may not be in the DOM
|
|
680
|
+
<SelectInputItemsCountContext.Provider value={filteredItems.length}>
|
|
681
|
+
<SelectInputItemPositionContext.Provider value={index + 1}>
|
|
682
|
+
{getItemNode(index)}
|
|
683
|
+
</SelectInputItemPositionContext.Provider>
|
|
684
|
+
</SelectInputItemsCountContext.Provider>
|
|
685
|
+
)}
|
|
686
|
+
</Virtualizer>
|
|
687
|
+
)}
|
|
602
688
|
</div>
|
|
603
689
|
|
|
604
690
|
{renderFooter != null ? (
|
|
@@ -639,7 +725,10 @@ function SelectInputItemView<T = string>({
|
|
|
639
725
|
}: SelectInputItemViewProps<T>) {
|
|
640
726
|
switch (item.type) {
|
|
641
727
|
case 'option': {
|
|
642
|
-
if (
|
|
728
|
+
if (
|
|
729
|
+
item.value != null &&
|
|
730
|
+
(needle == null || selectInputOptionItemIncludesNeedle(item, needle))
|
|
731
|
+
) {
|
|
643
732
|
return (
|
|
644
733
|
<SelectInputOption value={item.value} disabled={item.disabled}>
|
|
645
734
|
{renderValue(item.value, false)}
|
|
@@ -701,6 +790,9 @@ function SelectInputGroupItemView<T = string>({
|
|
|
701
790
|
);
|
|
702
791
|
}
|
|
703
792
|
|
|
793
|
+
const SelectInputItemsCountContext = createContext<number | undefined>(undefined);
|
|
794
|
+
const SelectInputItemPositionContext = createContext<number | undefined>(undefined);
|
|
795
|
+
|
|
704
796
|
interface SelectInputOptionProps<T = string> {
|
|
705
797
|
value: T;
|
|
706
798
|
disabled?: boolean;
|
|
@@ -708,10 +800,14 @@ interface SelectInputOptionProps<T = string> {
|
|
|
708
800
|
}
|
|
709
801
|
|
|
710
802
|
function SelectInputOption<T = string>({ value, disabled, children }: SelectInputOptionProps<T>) {
|
|
803
|
+
const itemsCount = useContext(SelectInputItemsCountContext);
|
|
804
|
+
const itemPosition = useContext(SelectInputItemPositionContext);
|
|
711
805
|
return (
|
|
712
806
|
<ListboxBase.Option
|
|
713
807
|
as="div"
|
|
714
808
|
value={value}
|
|
809
|
+
aria-setsize={itemsCount}
|
|
810
|
+
aria-posinset={itemPosition}
|
|
715
811
|
disabled={disabled}
|
|
716
812
|
className={({ active, disabled: uiDisabled }) =>
|
|
717
813
|
clsx(
|
|
@@ -737,6 +833,8 @@ function SelectInputOption<T = string>({ value, disabled, children }: SelectInpu
|
|
|
737
833
|
);
|
|
738
834
|
}
|
|
739
835
|
|
|
836
|
+
const SelectInputOptionContentWithinTriggerContext = createContext(false);
|
|
837
|
+
|
|
740
838
|
export interface SelectInputOptionContentProps {
|
|
741
839
|
title: string;
|
|
742
840
|
note?: string;
|
package/src/main.css
CHANGED
|
@@ -2655,6 +2655,10 @@ html:not([dir="rtl"]) .np-flow-navigation--sm .np-flow-navigation__stepper {
|
|
|
2655
2655
|
height: auto;
|
|
2656
2656
|
}
|
|
2657
2657
|
}
|
|
2658
|
+
.np-select-input-listbox-container--virtualized {
|
|
2659
|
+
/* The wrapping element shrinks this as needed */
|
|
2660
|
+
height: 100vh;
|
|
2661
|
+
}
|
|
2658
2662
|
.np-select-input-listbox-container--has-group {
|
|
2659
2663
|
scroll-padding-top: 32px;
|
|
2660
2664
|
scroll-padding-top: var(--size-32);
|
|
@@ -2673,6 +2677,12 @@ html:not([dir="rtl"]) .np-flow-navigation--sm .np-flow-navigation__stepper {
|
|
|
2673
2677
|
outline: var(--ring-outline-color) solid var(--ring-outline-width);
|
|
2674
2678
|
outline-offset: var(--ring-outline-offset);
|
|
2675
2679
|
}
|
|
2680
|
+
.np-select-input-listbox-container--virtualized .np-select-input-listbox {
|
|
2681
|
+
/* Adopted from `VList` in virtua: https://github.com/inokawa/virtua/blob/7f6ed5b37df6b480d4ff350f3960067c5b3519d2/src/react/VList.tsx#L113-L116 */
|
|
2682
|
+
overflow-y: auto;
|
|
2683
|
+
contain: strict;
|
|
2684
|
+
height: 100%;
|
|
2685
|
+
}
|
|
2676
2686
|
.np-select-input-separator-item {
|
|
2677
2687
|
margin: 8px;
|
|
2678
2688
|
margin: var(--size-8);
|
|
@@ -1,14 +1,26 @@
|
|
|
1
1
|
import { Field } from '../field/Field';
|
|
2
2
|
import { mockMatchMedia, render, screen } from '../test-utils';
|
|
3
3
|
import Typeahead from './Typeahead';
|
|
4
|
+
import { createIntl, createIntlCache } from 'react-intl';
|
|
5
|
+
import messages from '../i18n';
|
|
6
|
+
import { DEFAULT_LANG, DEFAULT_LOCALE } from '../common';
|
|
4
7
|
|
|
5
8
|
mockMatchMedia();
|
|
6
9
|
|
|
10
|
+
const cache = createIntlCache();
|
|
11
|
+
const intl = createIntl({ locale: DEFAULT_LOCALE, messages: messages[DEFAULT_LANG] }, cache);
|
|
12
|
+
|
|
7
13
|
describe('Typeahead', () => {
|
|
8
14
|
it('supports `Field` for labeling', () => {
|
|
9
15
|
render(
|
|
10
16
|
<Field id="test" label="Tags">
|
|
11
|
-
<Typeahead
|
|
17
|
+
<Typeahead
|
|
18
|
+
id="test"
|
|
19
|
+
name="test"
|
|
20
|
+
options={[{ label: 'Test' }]}
|
|
21
|
+
intl={intl}
|
|
22
|
+
onChange={() => {}}
|
|
23
|
+
/>
|
|
12
24
|
</Field>,
|
|
13
25
|
);
|
|
14
26
|
expect(screen.getAllByRole('group')[0]).toHaveAccessibleName(/^Tags/);
|
|
@@ -8,15 +8,17 @@ import { fakeEvent, fakeKeyDownEventForKey } from '../common/fakeEvents';
|
|
|
8
8
|
import Typeahead from './Typeahead';
|
|
9
9
|
|
|
10
10
|
const defaultLocale = 'en-GB';
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
11
|
+
jest.mock('react-intl', () => {
|
|
12
|
+
const mockedIntl = {
|
|
13
|
+
locale: defaultLocale,
|
|
14
|
+
formatMessage: (id) => String(id),
|
|
15
|
+
};
|
|
16
|
+
return {
|
|
17
|
+
injectIntl: (Component) => (props) => <Component {...props} intl={mockedIntl} />,
|
|
18
|
+
defineMessages: (translations) => translations,
|
|
19
|
+
useIntl: () => mockedIntl,
|
|
20
|
+
};
|
|
21
|
+
});
|
|
20
22
|
|
|
21
23
|
describe('Typeahead', () => {
|
|
22
24
|
let component;
|
|
@@ -317,7 +319,7 @@ describe('Typeahead', () => {
|
|
|
317
319
|
onChange: (selections) => {
|
|
318
320
|
selectedOption = selections[0];
|
|
319
321
|
},
|
|
320
|
-
options
|
|
322
|
+
options,
|
|
321
323
|
});
|
|
322
324
|
|
|
323
325
|
input().simulate('change', { target: { value: text } });
|