Skip to content

Commit 4ce110a

Browse files
committed
Country selection - select by keyboard #2540
Country selection - select by keyboard #2540
1 parent 244517c commit 4ce110a

File tree

4 files changed

+168
-40
lines changed

4 files changed

+168
-40
lines changed

components/Dropdown/Dropdown.jsx

Lines changed: 146 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -4,50 +4,163 @@ import React, { PropTypes } from 'react'
44
import classNames from 'classnames'
55
import enhanceDropdown from './enhanceDropdown'
66

7-
function Dropdown(props) {
8-
const { className, pointerShadow, noPointer, pointerLeft, isOpen, handleClick, theme, noAutoclose } = props
9-
const ddClasses = classNames('dropdown-wrap', {
10-
[`${className}`] : true,
11-
[`${ theme }`] : true
12-
})
13-
const ndClasses = classNames('Dropdown', {
14-
'pointer-shadow' : pointerShadow,
15-
'pointer-hide' : noPointer,
16-
'pointer-left' : pointerLeft,
17-
'no-autoclose' : noAutoclose,
18-
hide : !isOpen
19-
})
20-
21-
return (
22-
<div className={ddClasses} onClick={noAutoclose ? () => { } : handleClick}>
23-
{
24-
props.children.map((child, index) => {
25-
if (child.props.className.indexOf('dropdown-menu-header') > -1)
26-
return noAutoclose ? React.cloneElement(child, {
27-
onClick: handleClick,
28-
key: child.props.key || index
29-
}) : child
30-
})
31-
}
32-
33-
<div className = {ndClasses}>
7+
class Dropdown extends React.Component {
8+
constructor(props) {
9+
super(props)
10+
}
11+
12+
render() {
13+
const props = this.props
14+
const { children, className, pointerShadow, noPointer, pointerLeft, isOpen, handleClick, theme, noAutoclose, handleKeyboardNavigation } = props
15+
const ddClasses = classNames('dropdown-wrap', {
16+
[`${className}`] : true,
17+
[`${ theme }`] : true
18+
})
19+
const ndClasses = classNames('Dropdown', {
20+
'pointer-shadow' : pointerShadow,
21+
'pointer-hide' : noPointer,
22+
'pointer-left' : pointerLeft,
23+
'no-autoclose' : noAutoclose,
24+
hide : !isOpen
25+
})
26+
27+
let childSelectionIndex = -1
28+
const focusOnNextChild = () => {
29+
const listChild = this.listRef.getElementsByTagName('li')
30+
if (listChild.length === 0) {
31+
return
32+
}
33+
childSelectionIndex += 1
34+
if (childSelectionIndex > listChild.length) {
35+
childSelectionIndex = 0
36+
}
37+
listChild[childSelectionIndex].focus()
38+
}
39+
const focusOnPreviousChild = () => {
40+
const listChild = this.listRef.getElementsByTagName('li')
41+
if (listChild.length === 0) {
42+
return
43+
}
44+
childSelectionIndex -= 1
45+
if (childSelectionIndex < 0) {
46+
childSelectionIndex = listChild.length - 1
47+
}
48+
listChild[childSelectionIndex].focus()
49+
}
50+
const focusOnCharacter = (value) => {
51+
const listChild = this.listRef.getElementsByTagName('li')
52+
if (listChild.length === 0) {
53+
return
54+
}
55+
const length = listChild.length
56+
for (let i = 0; i < length; i++) {
57+
const textContent = listChild[i].textContent
58+
if (textContent && textContent.length > 0) {
59+
const firstChar = textContent.charAt(0)
60+
if (firstChar === value || firstChar === value.toLowerCase()) {
61+
listChild[i].focus()
62+
return true
63+
}
64+
}
65+
}
66+
return false
67+
}
68+
const onKeydown = (e) => {
69+
if (!handleKeyboardNavigation) {
70+
return
71+
}
72+
const keyCode = e.keyCode
73+
if (keyCode === 32 || keyCode === 38 || keyCode === 40) { // space or Up/Down
74+
// open dropdown menu
75+
if (!noAutoclose && !isOpen) {
76+
e.preventDefault()
77+
handleClick(event)
78+
} else {
79+
if (keyCode === 40) {
80+
focusOnNextChild()
81+
} else if (keyCode === 38) {
82+
focusOnPreviousChild()
83+
}
84+
e.preventDefault()
85+
}
86+
} else if (isOpen) {
87+
const value = String.fromCharCode(e.keyCode)
88+
if (focusOnCharacter(value)) {
89+
e.preventDefault()
90+
}
91+
}
92+
}
93+
const onChildKeydown = (e) => {
94+
if (!handleKeyboardNavigation) {
95+
return
96+
}
97+
const keyCode = e.keyCode
98+
if (keyCode === 38 || keyCode === 40 || keyCode === 13) { // Up/Down or enter
99+
if (keyCode === 40) {
100+
focusOnNextChild()
101+
} else if (keyCode === 38) {
102+
focusOnPreviousChild()
103+
} else if (keyCode === 13) {
104+
const listChild = this.listRef.getElementsByTagName('li')
105+
if (listChild.length === 0) {
106+
return
107+
}
108+
listChild[childSelectionIndex].click()
109+
}
110+
e.preventDefault()
111+
} else {
112+
const value = String.fromCharCode(e.keyCode)
113+
if (focusOnCharacter(value)) {
114+
e.preventDefault()
115+
}
116+
}
117+
}
118+
119+
const setListRef = (c) => this.listRef = c
120+
121+
const childrenWithProps = React.Children.map(children, child =>
122+
React.cloneElement(child, {onKeyDown: onChildKeydown})
123+
)
124+
return (
125+
<div className={ddClasses} onClick={noAutoclose ? () => { } : handleClick}>
126+
{handleKeyboardNavigation && (<a onKeyDown={onKeydown} className="handle-keyboard" href="javascript:;"></a>)}
34127
{
35-
props.children.map((child) => {
36-
if (child.props.className.indexOf('dropdown-menu-list') > -1)
37-
return child
128+
childrenWithProps.map((child, index) => {
129+
if (child.props.className.indexOf('dropdown-menu-header') > -1)
130+
return noAutoclose ? React.cloneElement(child, {
131+
onClick: handleClick,
132+
key: child.props.key || index
133+
}) : child
38134
})
39135
}
136+
<div ref={setListRef} className = {ndClasses}>
137+
{
138+
childrenWithProps.map((child) => {
139+
if (child.props.className.indexOf('dropdown-menu-list') > -1)
140+
return child
141+
})
142+
}
143+
</div>
40144
</div>
41-
</div>
42-
)
145+
)
146+
147+
}
43148
}
44149

45150
Dropdown.propTypes = {
46151
children: PropTypes.array.isRequired,
47152
/*
48153
If true, prevents dropdown closing when clicked inside dropdown
49154
*/
50-
noAutoclose: PropTypes.bool
155+
noAutoclose: PropTypes.bool,
156+
/*
157+
If true, prevents handle keyboard event
158+
*/
159+
handleKeyboardNavigation: PropTypes.bool
160+
}
161+
162+
Dropdown.defaultProps = {
163+
handleKeyboardNavigation: false
51164
}
52165

53166
export default enhanceDropdown(Dropdown)

components/Dropdown/Dropdown.scss

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,10 @@
5656
@include ellipsis;
5757
}
5858

59+
li:focus,
5960
li:hover {
6061
background-color: $tc-gray-neutral-dark;
62+
outline: none;
6163
}
6264
}
6365
}
@@ -75,6 +77,17 @@
7577
border-bottom: 2px solid $tc-gray-20;
7678
border-right: 2px solid $tc-gray-20;
7779
}
80+
81+
.dropdown-wrap {
82+
.handle-keyboard {
83+
position: absolute;
84+
width: 100%;
85+
max-height: 40px;
86+
top: 0;
87+
left: 0;
88+
height: 100%;
89+
}
90+
}
7891

7992
.Dropdown.hide {
8093
display: none;
@@ -155,8 +168,10 @@
155168
padding: 0 20px;
156169
@include ellipsis;
157170
}
171+
li:focus,
158172
li:hover {
159173
background-color: $tc-gray-neutral-dark;
174+
outline: none;
160175
}
161176
}
162177
}

components/Dropdown/DropdownExamples.jsx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ const DropdownExamples = {
1919
<ul className="dropdown-menu-list">
2020
{
2121
items.map((link, i) => {
22-
return <li key={i}><a href="javascript:;">{link}</a></li>
22+
return <li tabIndex="-1" key={i}><a href="javascript:;">{link}</a></li>
2323
})
2424
}
2525
</ul>
@@ -32,7 +32,7 @@ const DropdownExamples = {
3232
<ul className="dropdown-menu-list">
3333
{
3434
items.map((link, i) => {
35-
return <li key={i}><a href="javascript:;">{link}</a></li>
35+
return <li tabIndex="-1" key={i}><a href="javascript:;">{link}</a></li>
3636
})
3737
}
3838
</ul>
@@ -45,7 +45,7 @@ const DropdownExamples = {
4545
<ul className="dropdown-menu-list">
4646
{
4747
items.map((link, i) => {
48-
return <li key={i}><a href="javascript:;">{link}</a></li>
48+
return <li tabIndex="-1" key={i}><a href="javascript:;">{link}</a></li>
4949
})
5050
}
5151
</ul>
@@ -58,7 +58,7 @@ const DropdownExamples = {
5858
<ul className="dropdown-menu-list">
5959
{
6060
items.map((link, i) => {
61-
return <li key={i}><a href="javascript:;">{link}</a></li>
61+
return <li tabIndex="-1" key={i}><a href="javascript:;">{link}</a></li>
6262
})
6363
}
6464
</ul>
@@ -71,7 +71,7 @@ const DropdownExamples = {
7171
<ul className="dropdown-menu-list">
7272
{
7373
items.map((link, i) => {
74-
return <li key={i}><a href="javascript:;">{link}</a></li>
74+
return <li tabIndex="-1" key={i}><a href="javascript:;">{link}</a></li>
7575
})
7676
}
7777
</ul>
@@ -84,7 +84,7 @@ const DropdownExamples = {
8484
<ul className="dropdown-menu-list">
8585
{
8686
items.map((link, i) => {
87-
return <li key={i}><a href="javascript:;">{link}</a></li>
87+
return <li tabIndex="-1" key={i}><a href="javascript:;">{link}</a></li>
8888
})
8989
}
9090
</ul>

components/Formsy/PhoneInput.jsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ class PhoneInput extends Component {
9898
min={minValue}
9999
max={maxValue}
100100
/>
101-
<Dropdown pointerShadow>
101+
<Dropdown handleKeyboardNavigation pointerShadow>
102102
<div className="dropdown-menu-header flex center middle">{this.state.currentCountry ? this.state.currentCountry.alpha3 : ''}
103103
<IconDown width={20} height={12} fill="#fff" wrapperClass="arrow" /></div>
104104
<ul className="dropdown-menu-list">

0 commit comments

Comments
 (0)