Skip to content

Commit 0409f04

Browse files
committed
Add dropdown return key support
1 parent 69ffdf6 commit 0409f04

File tree

2 files changed

+87
-0
lines changed

2 files changed

+87
-0
lines changed
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import {act} from 'react-dom/test-utils';
2+
import {mount, ReactWrapper} from 'enzyme';
3+
import * as React from 'react';
4+
5+
import {DropDown} from './dropdown';
6+
7+
describe('DropDown', () => {
8+
let wrapper: ReactWrapper | null;
9+
10+
afterEach(() => {
11+
if (wrapper) {
12+
wrapper.unmount();
13+
wrapper = null;
14+
}
15+
});
16+
17+
const renderDropdown = (onSelect: () => void) => mount(
18+
<DropDown anchor={() => <button id='anchor'>anchor</button>} isMenu={true}>
19+
<ul>
20+
<li onClick={onSelect}>first</li>
21+
<li>second</li>
22+
</ul>
23+
</DropDown>,
24+
);
25+
26+
it('selects the first item when pressing Enter inside an open dropdown', async () => {
27+
const onSelect = jest.fn();
28+
wrapper = renderDropdown(onSelect);
29+
30+
await act(async () => {
31+
wrapper!.find('.argo-dropdown__anchor').simulate('click', {stopPropagation: () => {}});
32+
await Promise.resolve();
33+
});
34+
wrapper.update();
35+
36+
const preventDefault = jest.fn();
37+
const event: any = {key: 'Enter', target: wrapper.find('.argo-dropdown__anchor').getDOMNode(), preventDefault};
38+
39+
(wrapper.instance() as any).selectTopResult(event);
40+
41+
expect(onSelect).toHaveBeenCalledTimes(1);
42+
expect(preventDefault).toHaveBeenCalled();
43+
});
44+
45+
it('ignores Enter when the dropdown is closed or when the target is outside', async () => {
46+
const onSelect = jest.fn();
47+
wrapper = renderDropdown(onSelect);
48+
49+
const closedEvent: any = {key: 'Enter', target: wrapper.find('.argo-dropdown__anchor').getDOMNode(), preventDefault: jest.fn()};
50+
51+
(wrapper.instance() as any).selectTopResult(closedEvent);
52+
53+
expect(onSelect).not.toHaveBeenCalled();
54+
55+
await act(async () => {
56+
wrapper!.find('.argo-dropdown__anchor').simulate('click', {stopPropagation: () => {}});
57+
await Promise.resolve();
58+
});
59+
wrapper.update();
60+
61+
const outside = document.createElement('div');
62+
document.body.appendChild(outside);
63+
const outsideEvent: any = {key: 'Enter', target: outside, preventDefault: jest.fn()};
64+
65+
(wrapper.instance() as any).selectTopResult(outsideEvent);
66+
67+
expect(onSelect).not.toHaveBeenCalled();
68+
outside.remove();
69+
});
70+
});

src/components/dropdown/dropdown.tsx

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,8 @@ export class DropDown extends React.Component<DropDownProps, DropDownState> {
7272
if (this.state.opened && this.content && this.el) {
7373
this.setState(this.refreshState());
7474
}
75+
}), fromEvent<KeyboardEvent>(document, 'keydown').pipe(filter((event) => event.key === 'Enter' || event.keyCode === 13)).subscribe((event) => {
76+
this.selectTopResult(event);
7577
})];
7678
}
7779

@@ -87,6 +89,21 @@ export class DropDown extends React.Component<DropDownProps, DropDownState> {
8789
}
8890
}
8991

92+
private selectTopResult(event: KeyboardEvent) {
93+
if (!this.state.opened || !this.content || !this.el) {
94+
return;
95+
}
96+
const target = event.target as Node;
97+
if (target && !this.el.contains(target) && !this.content.contains(target)) {
98+
return;
99+
}
100+
const firstItem = this.content.querySelector('li') as HTMLElement;
101+
if (firstItem) {
102+
event.preventDefault();
103+
firstItem.click();
104+
}
105+
}
106+
90107
private refreshState() {
91108
const anchor = this.el.querySelector('.argo-dropdown__anchor') as HTMLElement;
92109
const {top, left} = anchor.getBoundingClientRect();

0 commit comments

Comments
 (0)