@transferwise/components 0.0.0-experimental-9254674 → 0.0.0-experimental-fbd2ec1
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/de.json +0 -1
- package/build/i18n/de.json.js +0 -1
- package/build/i18n/de.json.js.map +1 -1
- package/build/i18n/de.json.mjs +0 -1
- package/build/i18n/de.json.mjs.map +1 -1
- package/build/i18n/es.json +0 -1
- package/build/i18n/es.json.js +0 -1
- package/build/i18n/es.json.js.map +1 -1
- package/build/i18n/es.json.mjs +0 -1
- package/build/i18n/es.json.mjs.map +1 -1
- package/build/i18n/fr.json +0 -1
- package/build/i18n/fr.json.js +0 -1
- package/build/i18n/fr.json.js.map +1 -1
- package/build/i18n/fr.json.mjs +0 -1
- package/build/i18n/fr.json.mjs.map +1 -1
- package/build/i18n/hu.json +0 -1
- package/build/i18n/hu.json.js +0 -1
- package/build/i18n/hu.json.js.map +1 -1
- package/build/i18n/hu.json.mjs +0 -1
- package/build/i18n/hu.json.mjs.map +1 -1
- package/build/i18n/id.json +0 -1
- package/build/i18n/id.json.js +0 -1
- package/build/i18n/id.json.js.map +1 -1
- package/build/i18n/id.json.mjs +0 -1
- package/build/i18n/id.json.mjs.map +1 -1
- package/build/i18n/it.json +0 -1
- package/build/i18n/it.json.js +0 -1
- package/build/i18n/it.json.js.map +1 -1
- package/build/i18n/it.json.mjs +0 -1
- package/build/i18n/it.json.mjs.map +1 -1
- package/build/i18n/ja.json +0 -1
- package/build/i18n/ja.json.js +0 -1
- package/build/i18n/ja.json.js.map +1 -1
- package/build/i18n/ja.json.mjs +0 -1
- package/build/i18n/ja.json.mjs.map +1 -1
- package/build/i18n/pl.json +0 -1
- package/build/i18n/pl.json.js +0 -1
- package/build/i18n/pl.json.js.map +1 -1
- package/build/i18n/pl.json.mjs +0 -1
- package/build/i18n/pl.json.mjs.map +1 -1
- package/build/i18n/pt.json +0 -1
- package/build/i18n/pt.json.js +0 -1
- package/build/i18n/pt.json.js.map +1 -1
- package/build/i18n/pt.json.mjs +0 -1
- package/build/i18n/pt.json.mjs.map +1 -1
- package/build/i18n/ro.json +0 -1
- package/build/i18n/ro.json.js +0 -1
- package/build/i18n/ro.json.js.map +1 -1
- package/build/i18n/ro.json.mjs +0 -1
- package/build/i18n/ro.json.mjs.map +1 -1
- package/build/i18n/ru.json +0 -1
- package/build/i18n/ru.json.js +0 -1
- package/build/i18n/ru.json.js.map +1 -1
- package/build/i18n/ru.json.mjs +0 -1
- package/build/i18n/ru.json.mjs.map +1 -1
- package/build/i18n/th.json +0 -1
- package/build/i18n/th.json.js +0 -1
- package/build/i18n/th.json.js.map +1 -1
- package/build/i18n/th.json.mjs +0 -1
- package/build/i18n/th.json.mjs.map +1 -1
- package/build/i18n/tr.json +0 -1
- package/build/i18n/tr.json.js +0 -1
- package/build/i18n/tr.json.js.map +1 -1
- package/build/i18n/tr.json.mjs +0 -1
- package/build/i18n/tr.json.mjs.map +1 -1
- package/build/i18n/zh-CN.json +0 -1
- package/build/i18n/zh-CN.json.js +0 -1
- package/build/i18n/zh-CN.json.js.map +1 -1
- package/build/i18n/zh-CN.json.mjs +0 -1
- package/build/i18n/zh-CN.json.mjs.map +1 -1
- package/build/i18n/zh-HK.json +0 -1
- package/build/i18n/zh-HK.json.js +0 -1
- package/build/i18n/zh-HK.json.js.map +1 -1
- package/build/i18n/zh-HK.json.mjs +0 -1
- package/build/i18n/zh-HK.json.mjs.map +1 -1
- package/build/main.css +0 -24
- package/build/stepper/Stepper.js +12 -1
- package/build/stepper/Stepper.js.map +1 -1
- package/build/stepper/Stepper.mjs +12 -1
- package/build/stepper/Stepper.mjs.map +1 -1
- package/build/styles/main.css +0 -24
- package/build/styles/stepper/Stepper.css +0 -24
- package/build/typeahead/Typeahead.js +22 -8
- package/build/typeahead/Typeahead.js.map +1 -1
- package/build/typeahead/Typeahead.mjs +22 -9
- package/build/typeahead/Typeahead.mjs.map +1 -1
- package/build/typeahead/typeaheadInput/TypeaheadInput.js +5 -2
- package/build/typeahead/typeaheadInput/TypeaheadInput.js.map +1 -1
- package/build/typeahead/typeaheadInput/TypeaheadInput.mjs +5 -2
- package/build/typeahead/typeaheadInput/TypeaheadInput.mjs.map +1 -1
- package/build/typeahead/typeaheadOption/TypeaheadOption.js +9 -6
- package/build/typeahead/typeaheadOption/TypeaheadOption.js.map +1 -1
- package/build/typeahead/typeaheadOption/TypeaheadOption.mjs +9 -6
- package/build/typeahead/typeaheadOption/TypeaheadOption.mjs.map +1 -1
- package/build/types/stepper/Stepper.d.ts.map +1 -1
- package/build/types/typeahead/Typeahead.d.ts +1 -1
- package/build/types/typeahead/Typeahead.d.ts.map +1 -1
- package/build/types/typeahead/typeaheadInput/TypeaheadInput.d.ts +2 -0
- package/build/types/typeahead/typeaheadInput/TypeaheadInput.d.ts.map +1 -1
- package/build/types/typeahead/typeaheadOption/TypeaheadOption.d.ts +2 -1
- package/build/types/typeahead/typeaheadOption/TypeaheadOption.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/flowNavigation/__snapshots__/FlowNavigation.spec.js.snap +1 -2
- package/src/i18n/de.json +0 -1
- package/src/i18n/es.json +0 -1
- package/src/i18n/fr.json +0 -1
- package/src/i18n/hu.json +0 -1
- package/src/i18n/id.json +0 -1
- package/src/i18n/it.json +0 -1
- package/src/i18n/ja.json +0 -1
- package/src/i18n/pl.json +0 -1
- package/src/i18n/pt.json +0 -1
- package/src/i18n/ro.json +0 -1
- package/src/i18n/ru.json +0 -1
- package/src/i18n/th.json +0 -1
- package/src/i18n/tr.json +0 -1
- package/src/i18n/zh-CN.json +0 -1
- package/src/i18n/zh-HK.json +0 -1
- package/src/main.css +0 -24
- package/src/stepper/Stepper.css +0 -24
- package/src/stepper/Stepper.less +0 -17
- package/src/stepper/Stepper.spec.js +11 -5
- package/src/stepper/Stepper.tsx +13 -2
- package/src/typeahead/Typeahead.spec.js +8 -0
- package/src/typeahead/Typeahead.tsx +29 -13
- package/src/typeahead/typeaheadInput/TypeaheadInput.tsx +9 -0
- package/src/typeahead/typeaheadOption/TypeaheadOption.spec.js +8 -0
- package/src/typeahead/typeaheadOption/TypeaheadOption.tsx +9 -6
package/src/i18n/it.json
CHANGED
|
@@ -21,7 +21,6 @@
|
|
|
21
21
|
"neptune.Label.optional": "(Facoltativo)",
|
|
22
22
|
"neptune.Link.opensInNewTab": "(si apre in una nuova scheda)",
|
|
23
23
|
"neptune.MoneyInput.Select.placeholder": "Seleziona un'opzione...",
|
|
24
|
-
"neptune.MoneyInput.Select.selectCurrencyLabel": "Scegli la valuta",
|
|
25
24
|
"neptune.PhoneNumberInput.SelectInput.placeholder": "Seleziona un'opzione...",
|
|
26
25
|
"neptune.Select.searchPlaceholder": "Cerca...",
|
|
27
26
|
"neptune.SelectInput.noResultsFound": "Nessun risultato trovato",
|
package/src/i18n/ja.json
CHANGED
|
@@ -21,7 +21,6 @@
|
|
|
21
21
|
"neptune.Label.optional": "(任意)",
|
|
22
22
|
"neptune.Link.opensInNewTab": "(新しいタブで開きます)",
|
|
23
23
|
"neptune.MoneyInput.Select.placeholder": "選択してください...",
|
|
24
|
-
"neptune.MoneyInput.Select.selectCurrencyLabel": "通貨を選択してください",
|
|
25
24
|
"neptune.PhoneNumberInput.SelectInput.placeholder": "選択してください…",
|
|
26
25
|
"neptune.Select.searchPlaceholder": "検索する... ",
|
|
27
26
|
"neptune.SelectInput.noResultsFound": "結果が見つかりませんでした",
|
package/src/i18n/pl.json
CHANGED
|
@@ -21,7 +21,6 @@
|
|
|
21
21
|
"neptune.Label.optional": "(opcjonalne)",
|
|
22
22
|
"neptune.Link.opensInNewTab": "(otworzy się w nowej zakładce)",
|
|
23
23
|
"neptune.MoneyInput.Select.placeholder": "Wybierz opcję...",
|
|
24
|
-
"neptune.MoneyInput.Select.selectCurrencyLabel": "Wybierz walutę",
|
|
25
24
|
"neptune.PhoneNumberInput.SelectInput.placeholder": "Wybierz opcję...",
|
|
26
25
|
"neptune.Select.searchPlaceholder": "Wyszukaj...",
|
|
27
26
|
"neptune.SelectInput.noResultsFound": "Nie znaleziono wyników",
|
package/src/i18n/pt.json
CHANGED
|
@@ -21,7 +21,6 @@
|
|
|
21
21
|
"neptune.Label.optional": "(Opcional)",
|
|
22
22
|
"neptune.Link.opensInNewTab": "(abre em uma nova aba)",
|
|
23
23
|
"neptune.MoneyInput.Select.placeholder": "Escolha uma opção...",
|
|
24
|
-
"neptune.MoneyInput.Select.selectCurrencyLabel": "Selecione a moeda",
|
|
25
24
|
"neptune.PhoneNumberInput.SelectInput.placeholder": "Escolha uma opção...",
|
|
26
25
|
"neptune.Select.searchPlaceholder": "Buscar...",
|
|
27
26
|
"neptune.SelectInput.noResultsFound": "Nenhum resultado encontrado",
|
package/src/i18n/ro.json
CHANGED
|
@@ -21,7 +21,6 @@
|
|
|
21
21
|
"neptune.Label.optional": "(Opțional)",
|
|
22
22
|
"neptune.Link.opensInNewTab": "(se deschide într-o filă nouă)",
|
|
23
23
|
"neptune.MoneyInput.Select.placeholder": "Selectează o opţiune...",
|
|
24
|
-
"neptune.MoneyInput.Select.selectCurrencyLabel": "Selectează moneda",
|
|
25
24
|
"neptune.PhoneNumberInput.SelectInput.placeholder": "Selectează o opțiune...",
|
|
26
25
|
"neptune.Select.searchPlaceholder": "Caută...",
|
|
27
26
|
"neptune.SelectInput.noResultsFound": "Nu s-a găsit niciun rezultat",
|
package/src/i18n/ru.json
CHANGED
|
@@ -21,7 +21,6 @@
|
|
|
21
21
|
"neptune.Label.optional": "(необязательно)",
|
|
22
22
|
"neptune.Link.opensInNewTab": "(откроется в новой вкладке)",
|
|
23
23
|
"neptune.MoneyInput.Select.placeholder": "Выберите вариант...",
|
|
24
|
-
"neptune.MoneyInput.Select.selectCurrencyLabel": "Выберите валюту",
|
|
25
24
|
"neptune.PhoneNumberInput.SelectInput.placeholder": "Выберите вариант...",
|
|
26
25
|
"neptune.Select.searchPlaceholder": "Поиск...",
|
|
27
26
|
"neptune.SelectInput.noResultsFound": "Ничего не найдено",
|
package/src/i18n/th.json
CHANGED
|
@@ -21,7 +21,6 @@
|
|
|
21
21
|
"neptune.Label.optional": "(ไม่บังคับ)",
|
|
22
22
|
"neptune.Link.opensInNewTab": "(เปิดในแท็บใหม่)",
|
|
23
23
|
"neptune.MoneyInput.Select.placeholder": "เลือกตัวเลือก...",
|
|
24
|
-
"neptune.MoneyInput.Select.selectCurrencyLabel": "เลือกสกุลเงิน",
|
|
25
24
|
"neptune.PhoneNumberInput.SelectInput.placeholder": "เลือกตัวเลือก...",
|
|
26
25
|
"neptune.Select.searchPlaceholder": "ค้นหา...",
|
|
27
26
|
"neptune.SelectInput.noResultsFound": "ไม่พบผลลัพธ์",
|
package/src/i18n/tr.json
CHANGED
|
@@ -21,7 +21,6 @@
|
|
|
21
21
|
"neptune.Label.optional": "(İsteğe bağlı)",
|
|
22
22
|
"neptune.Link.opensInNewTab": "(yeni sekmede açılır)",
|
|
23
23
|
"neptune.MoneyInput.Select.placeholder": "Bir seçenek seçin...",
|
|
24
|
-
"neptune.MoneyInput.Select.selectCurrencyLabel": "Para birimi seçin",
|
|
25
24
|
"neptune.PhoneNumberInput.SelectInput.placeholder": "Bir seçenek seçin...",
|
|
26
25
|
"neptune.Select.searchPlaceholder": "Ara...",
|
|
27
26
|
"neptune.SelectInput.noResultsFound": "Sonuç bulunamadı",
|
package/src/i18n/zh-CN.json
CHANGED
|
@@ -21,7 +21,6 @@
|
|
|
21
21
|
"neptune.Label.optional": "(可选)",
|
|
22
22
|
"neptune.Link.opensInNewTab": "(在新标签页中打开)",
|
|
23
23
|
"neptune.MoneyInput.Select.placeholder": "请选择...",
|
|
24
|
-
"neptune.MoneyInput.Select.selectCurrencyLabel": "选择货币",
|
|
25
24
|
"neptune.PhoneNumberInput.SelectInput.placeholder": "选择其中一项...",
|
|
26
25
|
"neptune.Select.searchPlaceholder": "搜索",
|
|
27
26
|
"neptune.SelectInput.noResultsFound": "找不到结果",
|
package/src/i18n/zh-HK.json
CHANGED
|
@@ -21,7 +21,6 @@
|
|
|
21
21
|
"neptune.Label.optional": "(可選)",
|
|
22
22
|
"neptune.Link.opensInNewTab": "(在新分頁中開啟)",
|
|
23
23
|
"neptune.MoneyInput.Select.placeholder": "選擇一個選項…",
|
|
24
|
-
"neptune.MoneyInput.Select.selectCurrencyLabel": "選擇貨幣",
|
|
25
24
|
"neptune.PhoneNumberInput.SelectInput.placeholder": "選擇其中一項…",
|
|
26
25
|
"neptune.Select.searchPlaceholder": "搜尋…",
|
|
27
26
|
"neptune.SelectInput.noResultsFound": "找不到任何結果",
|
package/src/main.css
CHANGED
|
@@ -4368,35 +4368,11 @@ html:not([dir="rtl"]) .np-navigation-option {
|
|
|
4368
4368
|
padding: 0 ;
|
|
4369
4369
|
}
|
|
4370
4370
|
.progress-bar {
|
|
4371
|
-
float: left;
|
|
4372
4371
|
-webkit-backface-visibility: hidden;
|
|
4373
|
-
height: 100%;
|
|
4374
4372
|
background-color: var(--color-interactive-primary);
|
|
4375
|
-
border-top-left-radius: 1px;
|
|
4376
|
-
border-bottom-left-radius: 1px;
|
|
4377
4373
|
transition: width 0.6s ease-in-out;
|
|
4378
4374
|
will-change: width;
|
|
4379
4375
|
}
|
|
4380
|
-
[dir="rtl"] .progress-bar {
|
|
4381
|
-
float: right;
|
|
4382
|
-
}
|
|
4383
|
-
.progress-bar::after {
|
|
4384
|
-
float: right;
|
|
4385
|
-
width: 8px;
|
|
4386
|
-
height: 8px;
|
|
4387
|
-
margin-top: -3px;
|
|
4388
|
-
margin-right: -4px;
|
|
4389
|
-
content: "";
|
|
4390
|
-
border-radius: 4px;
|
|
4391
|
-
}
|
|
4392
|
-
[dir="rtl"] .progress-bar::after {
|
|
4393
|
-
float: left;
|
|
4394
|
-
}
|
|
4395
|
-
[dir="rtl"] .progress-bar::after {
|
|
4396
|
-
margin-left: -4px;
|
|
4397
|
-
margin-right: 0;
|
|
4398
|
-
margin-right: initial;
|
|
4399
|
-
}
|
|
4400
4376
|
.btn-unstyled {
|
|
4401
4377
|
background: none;
|
|
4402
4378
|
border: none;
|
package/src/stepper/Stepper.css
CHANGED
|
@@ -70,35 +70,11 @@
|
|
|
70
70
|
padding: 0 ;
|
|
71
71
|
}
|
|
72
72
|
.progress-bar {
|
|
73
|
-
float: left;
|
|
74
73
|
-webkit-backface-visibility: hidden;
|
|
75
|
-
height: 100%;
|
|
76
74
|
background-color: var(--color-interactive-primary);
|
|
77
|
-
border-top-left-radius: 1px;
|
|
78
|
-
border-bottom-left-radius: 1px;
|
|
79
75
|
transition: width 0.6s ease-in-out;
|
|
80
76
|
will-change: width;
|
|
81
77
|
}
|
|
82
|
-
[dir="rtl"] .progress-bar {
|
|
83
|
-
float: right;
|
|
84
|
-
}
|
|
85
|
-
.progress-bar::after {
|
|
86
|
-
float: right;
|
|
87
|
-
width: 8px;
|
|
88
|
-
height: 8px;
|
|
89
|
-
margin-top: -3px;
|
|
90
|
-
margin-right: -4px;
|
|
91
|
-
content: "";
|
|
92
|
-
border-radius: 4px;
|
|
93
|
-
}
|
|
94
|
-
[dir="rtl"] .progress-bar::after {
|
|
95
|
-
float: left;
|
|
96
|
-
}
|
|
97
|
-
[dir="rtl"] .progress-bar::after {
|
|
98
|
-
margin-left: -4px;
|
|
99
|
-
margin-right: 0;
|
|
100
|
-
margin-right: initial;
|
|
101
|
-
}
|
|
102
78
|
.btn-unstyled {
|
|
103
79
|
background: none;
|
|
104
80
|
border: none;
|
package/src/stepper/Stepper.less
CHANGED
|
@@ -78,27 +78,10 @@
|
|
|
78
78
|
}
|
|
79
79
|
|
|
80
80
|
.progress-bar {
|
|
81
|
-
.float(left);
|
|
82
|
-
|
|
83
81
|
-webkit-backface-visibility: hidden;
|
|
84
|
-
height: 100%;
|
|
85
82
|
background-color: var(--color-interactive-primary);
|
|
86
|
-
border-top-left-radius: 1px;
|
|
87
|
-
border-bottom-left-radius: 1px;
|
|
88
83
|
transition: width 0.6s ease-in-out;
|
|
89
84
|
will-change: width;
|
|
90
|
-
|
|
91
|
-
&::after {
|
|
92
|
-
.float(right);
|
|
93
|
-
|
|
94
|
-
width: 8px;
|
|
95
|
-
height: 8px;
|
|
96
|
-
margin-top: -3px;
|
|
97
|
-
.margin(right, -4px);
|
|
98
|
-
|
|
99
|
-
content: "";
|
|
100
|
-
border-radius: 4px;
|
|
101
|
-
}
|
|
102
85
|
}
|
|
103
86
|
|
|
104
87
|
.btn-unstyled {
|
|
@@ -40,15 +40,21 @@ describe('Stepper', () => {
|
|
|
40
40
|
});
|
|
41
41
|
|
|
42
42
|
it('sets the widths of the progress bar to match where you are in the flow', () => {
|
|
43
|
-
expect(totalWidth()).toBe('
|
|
43
|
+
expect(totalWidth()).toBe('0px');
|
|
44
44
|
activeStep(2);
|
|
45
|
-
expect(totalWidth()).toBe(
|
|
45
|
+
expect(totalWidth()).toBe(
|
|
46
|
+
'calc(100% + var(--progress-bar-start-shift) + var(--progress-bar-border-width))',
|
|
47
|
+
);
|
|
46
48
|
steps(5);
|
|
47
|
-
expect(totalWidth()).toBe(
|
|
49
|
+
expect(totalWidth()).toBe(
|
|
50
|
+
'calc(50% + var(--progress-bar-start-shift) + var(--progress-bar-border-width))',
|
|
51
|
+
);
|
|
48
52
|
activeStep(10000);
|
|
49
|
-
expect(totalWidth()).toBe(
|
|
53
|
+
expect(totalWidth()).toBe(
|
|
54
|
+
'calc(100% + var(--progress-bar-start-shift) + var(--progress-bar-border-width))',
|
|
55
|
+
);
|
|
50
56
|
activeStep(-10);
|
|
51
|
-
expect(totalWidth()).toBe('
|
|
57
|
+
expect(totalWidth()).toBe('0px');
|
|
52
58
|
});
|
|
53
59
|
});
|
|
54
60
|
|
package/src/stepper/Stepper.tsx
CHANGED
|
@@ -38,6 +38,18 @@ const Stepper = ({ steps, activeStep = 0, className }: StepperProps) => {
|
|
|
38
38
|
const stepPercentage = 1 / (steps.length - 1);
|
|
39
39
|
const percentageCompleted = activeStepIndex / (steps.length - 1);
|
|
40
40
|
|
|
41
|
+
const getProgressWidth = (): string => {
|
|
42
|
+
if (percentageCompleted === 0) {
|
|
43
|
+
return '0px';
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Progress bar starts with left/right (depends on rtl) shift `--progress-bar-start-shift` for hiding Progress bar's left and right borders
|
|
47
|
+
* which are used for progress vertical delimiter.
|
|
48
|
+
* When progress is completed, we need to add `--progress-bar-border-width` to the width to allow the right border be outside of the progress area.
|
|
49
|
+
*/
|
|
50
|
+
return `calc(${percentageCompleted * 100}% + var(--progress-bar-start-shift) + var(--progress-bar-border-width))`;
|
|
51
|
+
};
|
|
52
|
+
|
|
41
53
|
const renderStep = (step: Step, index: number) => {
|
|
42
54
|
const active = index === activeStepIndex;
|
|
43
55
|
const clickable = step.onClick && !active;
|
|
@@ -85,9 +97,8 @@ const Stepper = ({ steps, activeStep = 0, className }: StepperProps) => {
|
|
|
85
97
|
return (
|
|
86
98
|
<div className={clsx('tw-stepper', className)}>
|
|
87
99
|
<div className="progress">
|
|
88
|
-
<div className="progress-bar" style={{ width:
|
|
100
|
+
<div className="progress-bar" style={{ width: getProgressWidth() }} />
|
|
89
101
|
</div>
|
|
90
|
-
|
|
91
102
|
<ol className="tw-stepper-steps p-t-1 m-b-0">{steps.map(renderStep)}</ol>
|
|
92
103
|
</div>
|
|
93
104
|
);
|
|
@@ -258,6 +258,14 @@ describe('Typeahead', () => {
|
|
|
258
258
|
expect(event.preventDefault).toHaveBeenCalledTimes(1);
|
|
259
259
|
});
|
|
260
260
|
|
|
261
|
+
it('sets aria-activedescendant correctly based on focus', () => {
|
|
262
|
+
const inputElement = input().getDOMNode();
|
|
263
|
+
|
|
264
|
+
doTimes(3, () => input().simulate('keyDown', fakeKeyDownEventForKey('ArrowDown')));
|
|
265
|
+
|
|
266
|
+
expect(inputElement).toHaveAttribute('aria-activedescendant', 'menu-test option-Test2');
|
|
267
|
+
});
|
|
268
|
+
|
|
261
269
|
it('displays alert when alert is provided and chips are valid or alert type is error', () => {
|
|
262
270
|
const event = {
|
|
263
271
|
key: 'Enter',
|
|
@@ -3,7 +3,7 @@ import { clsx } from 'clsx';
|
|
|
3
3
|
import { DebouncedFunc } from 'lodash';
|
|
4
4
|
import clamp from 'lodash.clamp';
|
|
5
5
|
import debounce from 'lodash.debounce';
|
|
6
|
-
import { Component, ReactNode } from 'react';
|
|
6
|
+
import React, { Component, ReactNode } from 'react';
|
|
7
7
|
import { injectIntl, WrappedComponentProps } from 'react-intl';
|
|
8
8
|
|
|
9
9
|
import Chip from '../chips/Chip';
|
|
@@ -101,6 +101,7 @@ class Typeahead<T> extends Component<TypeaheadPropsWithInputAttributes<T>, Typea
|
|
|
101
101
|
size: Size.MEDIUM,
|
|
102
102
|
validateChip: () => true,
|
|
103
103
|
} satisfies Partial<TypeaheadProps<unknown>>;
|
|
104
|
+
optionRefs: (React.RefObject<HTMLLIElement> | null)[];
|
|
104
105
|
|
|
105
106
|
constructor(props: TypeaheadPropsWithInputAttributes<T>) {
|
|
106
107
|
super(props);
|
|
@@ -115,6 +116,7 @@ class Typeahead<T> extends Component<TypeaheadPropsWithInputAttributes<T>, Typea
|
|
|
115
116
|
optionsShown: false,
|
|
116
117
|
isFocused: false,
|
|
117
118
|
};
|
|
119
|
+
this.optionRefs = [] as (React.RefObject<HTMLLIElement> | null)[];
|
|
118
120
|
}
|
|
119
121
|
|
|
120
122
|
handleSearchDebounced: DebouncedFunc<Typeahead<T>['handleSearch']>;
|
|
@@ -228,7 +230,6 @@ class Typeahead<T> extends Component<TypeaheadPropsWithInputAttributes<T>, Typea
|
|
|
228
230
|
}
|
|
229
231
|
}
|
|
230
232
|
};
|
|
231
|
-
|
|
232
233
|
moveFocusedOption(offset: number) {
|
|
233
234
|
this.setState((previousState) => {
|
|
234
235
|
const { keyboardFocusedOptionIndex } = previousState;
|
|
@@ -237,6 +238,10 @@ class Typeahead<T> extends Component<TypeaheadPropsWithInputAttributes<T>, Typea
|
|
|
237
238
|
if (keyboardFocusedOptionIndex !== null) {
|
|
238
239
|
index = clamp(keyboardFocusedOptionIndex + offset, 0, options.length - 1);
|
|
239
240
|
}
|
|
241
|
+
const optionRef = this.optionRefs[index];
|
|
242
|
+
if (optionRef?.current) {
|
|
243
|
+
optionRef.current.focus(); // Set focus on the option element
|
|
244
|
+
}
|
|
240
245
|
return {
|
|
241
246
|
keyboardFocusedOptionIndex: index,
|
|
242
247
|
};
|
|
@@ -402,17 +407,23 @@ class Typeahead<T> extends Component<TypeaheadPropsWithInputAttributes<T>, Typea
|
|
|
402
407
|
id={`menu-${id}`}
|
|
403
408
|
>
|
|
404
409
|
<ul className="dropdown-menu" role="menu" aria-labelledby="dropdownMenu1">
|
|
405
|
-
{optionsToRender.map((option, idx) =>
|
|
406
|
-
<
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
410
|
+
{optionsToRender.map((option, idx) => {
|
|
411
|
+
const ref = React.createRef<HTMLLIElement>();
|
|
412
|
+
this.optionRefs[idx] = ref;
|
|
413
|
+
return (
|
|
414
|
+
<TypeaheadOption
|
|
415
|
+
key={`${option.label}${idx.toString()}`}
|
|
416
|
+
ref={ref}
|
|
417
|
+
query={query}
|
|
418
|
+
option={option}
|
|
419
|
+
selected={keyboardFocusedOptionIndex === idx}
|
|
420
|
+
id={String(option.label.replace(/\s+/g, '-'))}
|
|
421
|
+
onClick={(event) => {
|
|
422
|
+
this.onOptionSelected(event, option);
|
|
423
|
+
}}
|
|
424
|
+
/>
|
|
425
|
+
);
|
|
426
|
+
})}
|
|
416
427
|
{footer}
|
|
417
428
|
</ul>
|
|
418
429
|
</div>
|
|
@@ -507,6 +518,11 @@ class Typeahead<T> extends Component<TypeaheadPropsWithInputAttributes<T>, Typea
|
|
|
507
518
|
typeaheadId={id}
|
|
508
519
|
renderChip={this.renderChip}
|
|
509
520
|
autoComplete={inputAutoComplete}
|
|
521
|
+
ariaActivedescendant={
|
|
522
|
+
keyboardFocusedOptionIndex !== null && options[keyboardFocusedOptionIndex]
|
|
523
|
+
? `option-${options[keyboardFocusedOptionIndex].label.replace(/\s+/g, '-')}`
|
|
524
|
+
: undefined
|
|
525
|
+
}
|
|
510
526
|
onChange={this.handleOnChange}
|
|
511
527
|
onKeyDown={this.handleOnKeyDown}
|
|
512
528
|
onFocus={this.handleOnFocus}
|
|
@@ -20,10 +20,12 @@ export type TypeaheadInputProps<T> = {
|
|
|
20
20
|
onFocus: () => void;
|
|
21
21
|
onPaste: React.ClipboardEventHandler<HTMLInputElement>;
|
|
22
22
|
renderChip: (chip: TypeaheadOption<T>, index: number) => ReactNode;
|
|
23
|
+
ariaActivedescendant?: string;
|
|
23
24
|
} & Pick<TypeaheadProps<T>, 'id' | 'name' | 'autoFocus' | 'multiple' | 'placeholder' | 'maxHeight'>;
|
|
24
25
|
|
|
25
26
|
type TypeaheadInputState = {
|
|
26
27
|
inputWidth: number;
|
|
28
|
+
keyboardFocusedOptionIndex: number | null;
|
|
27
29
|
};
|
|
28
30
|
|
|
29
31
|
export default class TypeaheadInput<T> extends Component<
|
|
@@ -37,6 +39,7 @@ export default class TypeaheadInput<T> extends Component<
|
|
|
37
39
|
super(props);
|
|
38
40
|
this.state = {
|
|
39
41
|
inputWidth: DEFAULT_INPUT_MIN_WIDTH,
|
|
42
|
+
keyboardFocusedOptionIndex: null,
|
|
40
43
|
};
|
|
41
44
|
}
|
|
42
45
|
|
|
@@ -76,6 +79,7 @@ export default class TypeaheadInput<T> extends Component<
|
|
|
76
79
|
onFocus,
|
|
77
80
|
onPaste,
|
|
78
81
|
autoComplete,
|
|
82
|
+
ariaActivedescendant, // Declare the ariaActivedescendant variable
|
|
79
83
|
} = this.props;
|
|
80
84
|
const { inputWidth } = this.state;
|
|
81
85
|
|
|
@@ -86,6 +90,11 @@ export default class TypeaheadInput<T> extends Component<
|
|
|
86
90
|
className={clsx(multiple && 'typeahead__input')}
|
|
87
91
|
name={name}
|
|
88
92
|
id={`input-${typeaheadId}`}
|
|
93
|
+
aria-activedescendant={
|
|
94
|
+
ariaActivedescendant
|
|
95
|
+
? `menu-${typeaheadId} ${ariaActivedescendant}`
|
|
96
|
+
: `menu-${typeaheadId}`
|
|
97
|
+
}
|
|
89
98
|
autoFocus={autoFocus}
|
|
90
99
|
placeholder={hasPlaceholder ? placeholder : ''}
|
|
91
100
|
aria-autocomplete="list"
|
|
@@ -51,6 +51,14 @@ describe('Typeahead Option', () => {
|
|
|
51
51
|
expect(component.is('.tw-dropdown-item--focused')).toBe(true);
|
|
52
52
|
});
|
|
53
53
|
|
|
54
|
+
it('expect aria-selected to be true', () => {
|
|
55
|
+
component.setProps({
|
|
56
|
+
selected: true,
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
expect(component.prop('aria-selected')).toBe('true');
|
|
60
|
+
});
|
|
61
|
+
|
|
54
62
|
it('calls onClick when clicked', () => {
|
|
55
63
|
const onClick = jest.fn();
|
|
56
64
|
component.setProps({
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
/* eslint-disable jsx-a11y/anchor-is-valid */
|
|
2
1
|
import { clsx } from 'clsx';
|
|
2
|
+
import { forwardRef } from 'react';
|
|
3
3
|
|
|
4
4
|
import { TypeaheadOption } from '../Typeahead';
|
|
5
5
|
import highlight from '../util/highlight';
|
|
@@ -9,21 +9,24 @@ export type TypeaheadOptionProps<T> = {
|
|
|
9
9
|
selected?: boolean;
|
|
10
10
|
onClick?: React.MouseEventHandler;
|
|
11
11
|
query?: string;
|
|
12
|
+
id?: string;
|
|
12
13
|
};
|
|
13
14
|
|
|
14
|
-
const Option = <
|
|
15
|
-
const { option, selected = false, onClick = () => {}, query = '' } = props;
|
|
15
|
+
const Option = forwardRef<HTMLLIElement, TypeaheadOptionProps<any>>((props, ref) => {
|
|
16
|
+
const { option, selected = false, onClick = () => {}, query = '', id } = props;
|
|
16
17
|
const { label, note, secondary } = option;
|
|
17
18
|
|
|
18
19
|
return (
|
|
19
20
|
<li
|
|
21
|
+
ref={ref}
|
|
20
22
|
role="option"
|
|
21
|
-
aria-selected=
|
|
23
|
+
aria-selected={selected ? 'true' : 'false'}
|
|
24
|
+
id={`option-${id}`}
|
|
22
25
|
className={clsx('typeahead__option tw-dropdown-item clickable', {
|
|
23
26
|
'tw-dropdown-item--focused': selected,
|
|
24
27
|
})}
|
|
25
28
|
>
|
|
26
|
-
<a className="dropdown-item" href="#" tabIndex={
|
|
29
|
+
<a className="dropdown-item" href="#" tabIndex={0} onClick={onClick}>
|
|
27
30
|
<span>{highlight(label, query)}</span>
|
|
28
31
|
{note && <span className="np-text-body-default m-l-1">{note}</span>}
|
|
29
32
|
{secondary && (
|
|
@@ -32,6 +35,6 @@ const Option = <T,>(props: TypeaheadOptionProps<T>) => {
|
|
|
32
35
|
</a>
|
|
33
36
|
</li>
|
|
34
37
|
);
|
|
35
|
-
};
|
|
38
|
+
});
|
|
36
39
|
|
|
37
40
|
export default Option;
|