@transferwise/components 0.0.0-experimental-2b74aa5 → 0.0.0-experimental-81a961c
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/inputs/SelectInput.js +4 -0
- package/build/inputs/SelectInput.js.map +1 -1
- package/build/inputs/SelectInput.mjs +4 -0
- package/build/inputs/SelectInput.mjs.map +1 -1
- package/build/moneyInput/MoneyInput.js +1 -1
- package/build/moneyInput/MoneyInput.js.map +1 -1
- package/build/moneyInput/MoneyInput.mjs +1 -1
- package/build/moneyInput/MoneyInput.mjs.map +1 -1
- package/build/typeahead/Typeahead.js +13 -35
- package/build/typeahead/Typeahead.js.map +1 -1
- package/build/typeahead/Typeahead.mjs +14 -35
- package/build/typeahead/Typeahead.mjs.map +1 -1
- package/build/typeahead/typeaheadInput/TypeaheadInput.js +1 -3
- package/build/typeahead/typeaheadInput/TypeaheadInput.js.map +1 -1
- package/build/typeahead/typeaheadInput/TypeaheadInput.mjs +1 -3
- package/build/typeahead/typeaheadInput/TypeaheadInput.mjs.map +1 -1
- package/build/typeahead/typeaheadOption/TypeaheadOption.js +5 -7
- package/build/typeahead/typeaheadOption/TypeaheadOption.js.map +1 -1
- package/build/typeahead/typeaheadOption/TypeaheadOption.mjs +5 -7
- package/build/typeahead/typeaheadOption/TypeaheadOption.mjs.map +1 -1
- package/build/types/inputs/SelectInput.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 +0 -1
- package/build/types/typeahead/typeaheadInput/TypeaheadInput.d.ts.map +1 -1
- package/build/types/typeahead/typeaheadOption/TypeaheadOption.d.ts +1 -1
- package/build/types/typeahead/typeaheadOption/TypeaheadOption.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/inputs/SelectInput.tsx +5 -2
- package/src/moneyInput/MoneyInput.docs.mdx +40 -0
- package/src/moneyInput/MoneyInput.rtl.spec.tsx +38 -0
- package/src/moneyInput/MoneyInput.spec.js +0 -11
- package/src/moneyInput/MoneyInput.story.tsx +10 -7
- package/src/moneyInput/MoneyInput.tsx +1 -1
- package/src/typeahead/Typeahead.tsx +18 -44
- package/src/typeahead/typeaheadInput/TypeaheadInput.tsx +0 -3
- package/src/typeahead/typeaheadOption/TypeaheadOption.tsx +5 -7
|
@@ -258,7 +258,6 @@ export function SelectInput<T = string, M extends boolean = false>({
|
|
|
258
258
|
}: SelectInputProps<T, M>) {
|
|
259
259
|
const inputAttributes = useInputAttributes();
|
|
260
260
|
const id = idProp ?? inputAttributes.id;
|
|
261
|
-
|
|
262
261
|
const [open, setOpen] = useState(false);
|
|
263
262
|
|
|
264
263
|
const initialized = useRef(false);
|
|
@@ -387,6 +386,7 @@ export function SelectInput<T = string, M extends boolean = false>({
|
|
|
387
386
|
}}
|
|
388
387
|
>
|
|
389
388
|
<SelectInputOptions
|
|
389
|
+
id={`${id}Search`}
|
|
390
390
|
items={items}
|
|
391
391
|
renderValue={renderValue}
|
|
392
392
|
renderFooter={renderFooter}
|
|
@@ -488,7 +488,7 @@ const SelectInputOptionsContainer = forwardRef(function SelectInputOptionsContai
|
|
|
488
488
|
interface SelectInputOptionsProps<T = string>
|
|
489
489
|
extends Pick<
|
|
490
490
|
SelectInputProps<T>,
|
|
491
|
-
'items' | 'renderValue' | 'renderFooter' | 'filterable' | 'filterPlaceholder'
|
|
491
|
+
'items' | 'renderValue' | 'renderFooter' | 'filterable' | 'filterPlaceholder' | 'id'
|
|
492
492
|
> {
|
|
493
493
|
searchInputRef: React.MutableRefObject<HTMLInputElement | null>;
|
|
494
494
|
listboxRef: React.MutableRefObject<HTMLDivElement | null>;
|
|
@@ -497,6 +497,7 @@ interface SelectInputOptionsProps<T = string>
|
|
|
497
497
|
}
|
|
498
498
|
|
|
499
499
|
function SelectInputOptions<T = string>({
|
|
500
|
+
id,
|
|
500
501
|
items,
|
|
501
502
|
renderValue = String,
|
|
502
503
|
renderFooter,
|
|
@@ -609,9 +610,11 @@ function SelectInputOptions<T = string>({
|
|
|
609
610
|
<div className="np-select-input-query-container">
|
|
610
611
|
<SearchInput
|
|
611
612
|
ref={searchInputRef}
|
|
613
|
+
id={id}
|
|
612
614
|
role="combobox"
|
|
613
615
|
shape="rectangle"
|
|
614
616
|
placeholder={filterPlaceholder}
|
|
617
|
+
aria-label={filterPlaceholder}
|
|
615
618
|
defaultValue={filterQuery}
|
|
616
619
|
aria-autocomplete="list"
|
|
617
620
|
aria-expanded
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { Meta } from '@storybook/blocks';
|
|
2
|
+
|
|
3
|
+
<Meta title="Forms/MoneyInput/Dev notes" />
|
|
4
|
+
|
|
5
|
+
# Dev notes
|
|
6
|
+
|
|
7
|
+
## Known accessibility issues
|
|
8
|
+
|
|
9
|
+
There are few issues reported by Axe that are not fixable or safe to ignore at this point in time.
|
|
10
|
+
|
|
11
|
+
### Form elements should have a visible label / Form elements must have labels
|
|
12
|
+
|
|
13
|
+
While this requirement is normally considered as very serious, it's missing the fact that the input is wrapped by a labelled element with `role="group"`. We’ve tested it in NVDA on Windows and VoiceOver on MacOS, and discovered that if we were to satisfy Axe’s requirements, the screen reader would read out the label twice, something similar to `{Label Text} group, {Label Text} editable` (depending on the SR and the browser) which would result in an unnecessary noise. While the rule is valid, in this particular case the group label seems sufficient.
|
|
14
|
+
|
|
15
|
+
#### Further resources
|
|
16
|
+
|
|
17
|
+
- [Deque reference 1](https://dequeuniversity.com/rules/axe/4.9/label?application=axeAPI)
|
|
18
|
+
- [Deque reference 2](https://dequeuniversity.com/rules/axe/4.9/label-title-only?application=axeAPI)
|
|
19
|
+
- ['Group Labels Do Not Guarantee… Uniquity?' by Adrian Roselli](https://adrianroselli.com/2019/06/group-labels-do-not-guarantee-uniquity.html)
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
### Buttons must have discernible text
|
|
24
|
+
|
|
25
|
+
This affects HTML buttons with `role="combobox"` and deeply nested label text, not being discoverable by Axe. It is a known false-positive.
|
|
26
|
+
|
|
27
|
+
#### Further resources
|
|
28
|
+
|
|
29
|
+
- [Deque reference](https://dequeuniversity.com/rules/axe/4.9/button-name?application=axeAPI)
|
|
30
|
+
- [axe-core github issue](https://github.com/dequelabs/axe-core/issues/4472)
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
### ARIA hidden element must not be focusable or contain focusable elements
|
|
35
|
+
|
|
36
|
+
This is a genuine problem – according to the accessibility guidelines, no element with `aria-hidden="true"` should have any focusable children. This issue is caused by the `SelectInput`'s third party dependency (`Headless UI`) and, by extension, is visible in few other places. Work is planned to address it.
|
|
37
|
+
|
|
38
|
+
#### Further resources
|
|
39
|
+
|
|
40
|
+
- [Deque reference](https://dequeuniversity.com/rules/axe/4.9/aria-hidden-focus?application=axeAPI)
|
|
@@ -51,6 +51,7 @@ describe('MoneyInput', () => {
|
|
|
51
51
|
const props = {
|
|
52
52
|
currencies,
|
|
53
53
|
selectedCurrency: currencies[1],
|
|
54
|
+
searchPlaceholder: 'Type a currency / country',
|
|
54
55
|
amount: 1000,
|
|
55
56
|
onAmountChange: jest.fn(),
|
|
56
57
|
onCurrencyChange: jest.fn(),
|
|
@@ -90,4 +91,41 @@ describe('MoneyInput', () => {
|
|
|
90
91
|
);
|
|
91
92
|
expect(screen.getAllByRole('group')[0]).toHaveAccessibleName(/^Recipient gets/);
|
|
92
93
|
});
|
|
94
|
+
|
|
95
|
+
describe('ids', () => {
|
|
96
|
+
it('should guarantee id and connect the input with the selected currency via withId HoC', () => {
|
|
97
|
+
render(<MoneyInput {...props} />);
|
|
98
|
+
const input = screen.getByRole('textbox');
|
|
99
|
+
const button = screen.getByRole('combobox');
|
|
100
|
+
expect(input.getAttribute('id')).toBeTruthy();
|
|
101
|
+
expect(input).toHaveAttribute('aria-describedby', button.getAttribute('id'));
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('should have unique id for the select filter with predefined id', async () => {
|
|
105
|
+
const fieldId = 'myFieldId';
|
|
106
|
+
render(
|
|
107
|
+
<Field label="Multiple currencies" id={fieldId}>
|
|
108
|
+
<MoneyInput {...props} />
|
|
109
|
+
</Field>,
|
|
110
|
+
);
|
|
111
|
+
await userEvent.click(screen.getByRole('combobox'));
|
|
112
|
+
expect(screen.getByLabelText(props.searchPlaceholder)).toHaveAttribute(
|
|
113
|
+
'id',
|
|
114
|
+
`${fieldId}SelectedCurrencySearch`,
|
|
115
|
+
);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it('should have unique id for the select filter without predefined id', async () => {
|
|
119
|
+
render(
|
|
120
|
+
<Field label="Multiple currencies">
|
|
121
|
+
<MoneyInput {...props} />
|
|
122
|
+
</Field>,
|
|
123
|
+
);
|
|
124
|
+
await userEvent.click(screen.getByRole('combobox'));
|
|
125
|
+
expect(screen.getByLabelText(props.searchPlaceholder)).toHaveAttribute(
|
|
126
|
+
'id',
|
|
127
|
+
expect.stringMatching(/^:.*?:SelectedCurrencySearch$/),
|
|
128
|
+
);
|
|
129
|
+
});
|
|
130
|
+
});
|
|
93
131
|
});
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { shallow } from 'enzyme';
|
|
2
|
-
import { render, screen } from '@testing-library/react';
|
|
3
2
|
|
|
4
3
|
import { MoneyInput, Title, Input, SelectInput } from '..';
|
|
5
4
|
import { mockMatchMedia, mockResizeObserver } from '../test-utils';
|
|
@@ -818,14 +817,4 @@ describe('Money Input', () => {
|
|
|
818
817
|
});
|
|
819
818
|
});
|
|
820
819
|
});
|
|
821
|
-
|
|
822
|
-
describe('withId', () => {
|
|
823
|
-
it('should guarantee id and connect the input with the selected currency', () => {
|
|
824
|
-
render(<MoneyInput {...props} />);
|
|
825
|
-
const input = screen.getByRole('textbox');
|
|
826
|
-
const button = screen.getByRole('combobox');
|
|
827
|
-
expect(input.getAttribute('id')).toBeTruthy();
|
|
828
|
-
expect(input).toHaveAttribute('aria-describedby', button.getAttribute('id'));
|
|
829
|
-
});
|
|
830
|
-
});
|
|
831
820
|
});
|
|
@@ -9,13 +9,13 @@ import { Field } from '../field/Field';
|
|
|
9
9
|
export default {
|
|
10
10
|
component: MoneyInput,
|
|
11
11
|
title: 'Forms/MoneyInput',
|
|
12
|
-
render: function Render(args) {
|
|
12
|
+
render: function Render({ id, ...args }) {
|
|
13
13
|
const [selectedCurrency, setSelectedCurrency] = useState(args.selectedCurrency);
|
|
14
14
|
|
|
15
15
|
const handleOnCurrencyChange = (value: CurrencyOptionItem) => setSelectedCurrency(value);
|
|
16
16
|
|
|
17
17
|
return (
|
|
18
|
-
<Field label="Editable money input label" required>
|
|
18
|
+
<Field label="Editable money input label" required id={id}>
|
|
19
19
|
<MoneyInput
|
|
20
20
|
{...args}
|
|
21
21
|
selectedCurrency={selectedCurrency}
|
|
@@ -129,6 +129,7 @@ export const MultipleCurrencies: Story = {
|
|
|
129
129
|
},
|
|
130
130
|
selectedCurrency: exampleCurrency.usd,
|
|
131
131
|
searchPlaceholder: 'Type a currency / country',
|
|
132
|
+
id: 'moneyInput',
|
|
132
133
|
},
|
|
133
134
|
};
|
|
134
135
|
|
|
@@ -155,20 +156,22 @@ export const OpenedInput: Story = {
|
|
|
155
156
|
};
|
|
156
157
|
|
|
157
158
|
export const SmallInput: Story = {
|
|
158
|
-
render: (args) => {
|
|
159
|
+
render: ({ id, ...args }) => {
|
|
159
160
|
return (
|
|
160
161
|
<>
|
|
161
|
-
<Field
|
|
162
|
+
<Field label="Money inputs" required>
|
|
162
163
|
<MoneyInput {...args} {...SingleCurrency.args} />
|
|
163
164
|
</Field>
|
|
164
165
|
<br />
|
|
165
|
-
<
|
|
166
|
+
<Field label="Multiple currencies">
|
|
167
|
+
<MoneyInput {...args} {...MultipleCurrencies.args} />
|
|
168
|
+
</Field>
|
|
166
169
|
<hr />
|
|
167
|
-
<Field
|
|
170
|
+
<Field label="Error state" sentiment="negative" required>
|
|
168
171
|
<MoneyInput {...args} {...SingleCurrency.args} />
|
|
169
172
|
</Field>
|
|
170
173
|
<br />
|
|
171
|
-
<Field sentiment="negative">
|
|
174
|
+
<Field label="Multiple currencies: error state" sentiment="negative">
|
|
172
175
|
<MoneyInput {...args} {...MultipleCurrencies.args} />
|
|
173
176
|
</Field>
|
|
174
177
|
</>
|
|
@@ -314,7 +314,7 @@ class MoneyInput extends Component<MoneyInputPropsWithInputAttributes, MoneyInpu
|
|
|
314
314
|
|
|
315
315
|
const isFixedCurrency = (!this.state.searchQuery && hasSingleCurrency()) || !onCurrencyChange;
|
|
316
316
|
const disabled = !this.props.onAmountChange;
|
|
317
|
-
const selectedCurrencyElementId = `${amountInputId}SelectedCurrency`;
|
|
317
|
+
const selectedCurrencyElementId = `${inputAttributes?.id ?? amountInputId}SelectedCurrency`;
|
|
318
318
|
|
|
319
319
|
return (
|
|
320
320
|
<div
|
|
@@ -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
|
|
6
|
+
import { Component, ReactNode } from 'react';
|
|
7
7
|
import { injectIntl, WrappedComponentProps } from 'react-intl';
|
|
8
8
|
|
|
9
9
|
import Chip from '../chips/Chip';
|
|
@@ -101,7 +101,6 @@ 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)[];
|
|
105
104
|
|
|
106
105
|
constructor(props: TypeaheadPropsWithInputAttributes<T>) {
|
|
107
106
|
super(props);
|
|
@@ -116,7 +115,6 @@ class Typeahead<T> extends Component<TypeaheadPropsWithInputAttributes<T>, Typea
|
|
|
116
115
|
optionsShown: false,
|
|
117
116
|
isFocused: false,
|
|
118
117
|
};
|
|
119
|
-
this.optionRefs = [] as (React.RefObject<HTMLLIElement> | null)[];
|
|
120
118
|
}
|
|
121
119
|
|
|
122
120
|
handleSearchDebounced: DebouncedFunc<Typeahead<T>['handleSearch']>;
|
|
@@ -199,9 +197,6 @@ class Typeahead<T> extends Component<TypeaheadPropsWithInputAttributes<T>, Typea
|
|
|
199
197
|
const { keyboardFocusedOptionIndex, query, selected } = this.state;
|
|
200
198
|
const chipsMode = !showSuggestions && allowNew && multiple;
|
|
201
199
|
|
|
202
|
-
const { key } = event;
|
|
203
|
-
console.log(`Key pressed: ${key}`);
|
|
204
|
-
|
|
205
200
|
if (chipsMode && ['Enter', 'Tab', ...chipSeparators].includes(event.key) && query.trim()) {
|
|
206
201
|
event.preventDefault();
|
|
207
202
|
this.selectItem({ label: query });
|
|
@@ -209,14 +204,10 @@ class Typeahead<T> extends Component<TypeaheadPropsWithInputAttributes<T>, Typea
|
|
|
209
204
|
switch (event.key) {
|
|
210
205
|
case 'ArrowDown':
|
|
211
206
|
event.preventDefault();
|
|
212
|
-
console.log('Down');
|
|
213
|
-
console.log(keyboardFocusedOptionIndex);
|
|
214
207
|
this.moveFocusedOption(1);
|
|
215
208
|
break;
|
|
216
209
|
case 'ArrowUp':
|
|
217
210
|
event.preventDefault();
|
|
218
|
-
console.log('Up');
|
|
219
|
-
console.log(keyboardFocusedOptionIndex);
|
|
220
211
|
this.moveFocusedOption(-1);
|
|
221
212
|
break;
|
|
222
213
|
case 'Enter':
|
|
@@ -237,29 +228,17 @@ class Typeahead<T> extends Component<TypeaheadPropsWithInputAttributes<T>, Typea
|
|
|
237
228
|
}
|
|
238
229
|
}
|
|
239
230
|
};
|
|
231
|
+
|
|
240
232
|
moveFocusedOption(offset: number) {
|
|
241
233
|
this.setState((previousState) => {
|
|
242
|
-
|
|
234
|
+
const { keyboardFocusedOptionIndex } = previousState;
|
|
243
235
|
const { options } = this.props;
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
keyboardFocusedOptionIndex = 0;
|
|
248
|
-
} else {
|
|
249
|
-
keyboardFocusedOptionIndex = clamp(
|
|
250
|
-
keyboardFocusedOptionIndex + offset,
|
|
251
|
-
0,
|
|
252
|
-
options.length - 1,
|
|
253
|
-
);
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
const optionRef = this.optionRefs[keyboardFocusedOptionIndex];
|
|
257
|
-
if (optionRef?.current) {
|
|
258
|
-
optionRef.current.focus(); // Set focus on the option element
|
|
236
|
+
let index = 0;
|
|
237
|
+
if (keyboardFocusedOptionIndex !== null) {
|
|
238
|
+
index = clamp(keyboardFocusedOptionIndex + offset, 0, options.length - 1);
|
|
259
239
|
}
|
|
260
|
-
|
|
261
240
|
return {
|
|
262
|
-
keyboardFocusedOptionIndex,
|
|
241
|
+
keyboardFocusedOptionIndex: index,
|
|
263
242
|
};
|
|
264
243
|
});
|
|
265
244
|
}
|
|
@@ -423,22 +402,17 @@ class Typeahead<T> extends Component<TypeaheadPropsWithInputAttributes<T>, Typea
|
|
|
423
402
|
id={`menu-${id}`}
|
|
424
403
|
>
|
|
425
404
|
<ul className="dropdown-menu" role="menu" aria-labelledby="dropdownMenu1">
|
|
426
|
-
{optionsToRender.map((option, idx) =>
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
this.onOptionSelected(event, option);
|
|
438
|
-
}}
|
|
439
|
-
/>
|
|
440
|
-
);
|
|
441
|
-
})}
|
|
405
|
+
{optionsToRender.map((option, idx) => (
|
|
406
|
+
<TypeaheadOption
|
|
407
|
+
key={`${option.label}${idx.toString()}`}
|
|
408
|
+
query={query}
|
|
409
|
+
option={option}
|
|
410
|
+
selected={keyboardFocusedOptionIndex === idx}
|
|
411
|
+
onClick={(event) => {
|
|
412
|
+
this.onOptionSelected(event, option);
|
|
413
|
+
}}
|
|
414
|
+
/>
|
|
415
|
+
))}
|
|
442
416
|
{footer}
|
|
443
417
|
</ul>
|
|
444
418
|
</div>
|
|
@@ -24,7 +24,6 @@ export type TypeaheadInputProps<T> = {
|
|
|
24
24
|
|
|
25
25
|
type TypeaheadInputState = {
|
|
26
26
|
inputWidth: number;
|
|
27
|
-
keyboardFocusedOptionIndex: number | null;
|
|
28
27
|
};
|
|
29
28
|
|
|
30
29
|
export default class TypeaheadInput<T> extends Component<
|
|
@@ -38,7 +37,6 @@ export default class TypeaheadInput<T> extends Component<
|
|
|
38
37
|
super(props);
|
|
39
38
|
this.state = {
|
|
40
39
|
inputWidth: DEFAULT_INPUT_MIN_WIDTH,
|
|
41
|
-
keyboardFocusedOptionIndex: null,
|
|
42
40
|
};
|
|
43
41
|
}
|
|
44
42
|
|
|
@@ -88,7 +86,6 @@ export default class TypeaheadInput<T> extends Component<
|
|
|
88
86
|
className={clsx(multiple && 'typeahead__input')}
|
|
89
87
|
name={name}
|
|
90
88
|
id={`input-${typeaheadId}`}
|
|
91
|
-
aria-activedescendant={`menu-${typeaheadId} ${this.state.keyboardFocusedOptionIndex !== null ? `option-${this.state.keyboardFocusedOptionIndex}` : ''}`}
|
|
92
89
|
autoFocus={autoFocus}
|
|
93
90
|
placeholder={hasPlaceholder ? placeholder : ''}
|
|
94
91
|
aria-autocomplete="list"
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
+
/* eslint-disable jsx-a11y/anchor-is-valid */
|
|
1
2
|
import { clsx } from 'clsx';
|
|
2
|
-
import { forwardRef } from 'react';
|
|
3
3
|
|
|
4
4
|
import { TypeaheadOption } from '../Typeahead';
|
|
5
5
|
import highlight from '../util/highlight';
|
|
@@ -11,21 +11,19 @@ export type TypeaheadOptionProps<T> = {
|
|
|
11
11
|
query?: string;
|
|
12
12
|
};
|
|
13
13
|
|
|
14
|
-
const Option =
|
|
14
|
+
const Option = <T,>(props: TypeaheadOptionProps<T>) => {
|
|
15
15
|
const { option, selected = false, onClick = () => {}, query = '' } = props;
|
|
16
16
|
const { label, note, secondary } = option;
|
|
17
17
|
|
|
18
18
|
return (
|
|
19
19
|
<li
|
|
20
|
-
ref={ref}
|
|
21
20
|
role="option"
|
|
22
|
-
aria-selected=
|
|
23
|
-
id={`option-${props.option.label}`}
|
|
21
|
+
aria-selected="false"
|
|
24
22
|
className={clsx('typeahead__option tw-dropdown-item clickable', {
|
|
25
23
|
'tw-dropdown-item--focused': selected,
|
|
26
24
|
})}
|
|
27
25
|
>
|
|
28
|
-
<a className="dropdown-item" href="#" tabIndex={
|
|
26
|
+
<a className="dropdown-item" href="#" tabIndex={-1} onClick={onClick}>
|
|
29
27
|
<span>{highlight(label, query)}</span>
|
|
30
28
|
{note && <span className="np-text-body-default m-l-1">{note}</span>}
|
|
31
29
|
{secondary && (
|
|
@@ -34,6 +32,6 @@ const Option = forwardRef<HTMLLIElement, TypeaheadOptionProps<any>>((props, ref)
|
|
|
34
32
|
</a>
|
|
35
33
|
</li>
|
|
36
34
|
);
|
|
37
|
-
}
|
|
35
|
+
};
|
|
38
36
|
|
|
39
37
|
export default Option;
|