diff --git a/components/Dropdown/Dropdown.jsx b/components/Dropdown/Dropdown.jsx
index 46ccefeab..381dedfe3 100644
--- a/components/Dropdown/Dropdown.jsx
+++ b/components/Dropdown/Dropdown.jsx
@@ -4,42 +4,167 @@ import React, { PropTypes } from 'react'
import classNames from 'classnames'
import enhanceDropdown from './enhanceDropdown'
-function Dropdown(props) {
- const { className, pointerShadow, noPointer, pointerLeft, isOpen, handleClick, theme, noAutoclose } = props
- const ddClasses = classNames('dropdown-wrap', {
- [`${className}`] : true,
- [`${ theme }`] : true
- })
- const ndClasses = classNames('Dropdown', {
- 'pointer-shadow' : pointerShadow,
- 'pointer-hide' : noPointer,
- 'pointer-left' : pointerLeft,
- 'no-autoclose' : noAutoclose,
- hide : !isOpen
- })
-
- return (
-
{ } : handleClick}>
- {
- props.children.map((child, index) => {
- if (child.props.className.indexOf('dropdown-menu-header') > -1)
- return noAutoclose ? React.cloneElement(child, {
- onClick: handleClick,
- key: child.props.key || index
- }) : child
- })
- }
-
-
+class Dropdown extends React.Component {
+ constructor(props) {
+ super(props)
+ }
+
+ render() {
+ const props = this.props
+ const { children, className, pointerShadow, noPointer, pointerLeft, isOpen, handleClick, theme, noAutoclose, handleKeyboardNavigation } = props
+ const ddClasses = classNames('dropdown-wrap', {
+ [`${className}`] : true,
+ [`${ theme }`] : true
+ })
+ const ndClasses = classNames('Dropdown', {
+ 'pointer-shadow' : pointerShadow,
+ 'pointer-hide' : noPointer,
+ 'pointer-left' : pointerLeft,
+ 'no-autoclose' : noAutoclose,
+ hide : !isOpen
+ })
+
+ let childSelectionIndex = -1
+ const focusOnNextChild = () => {
+ const listChild = this.listRef.getElementsByTagName('li')
+ if (listChild.length === 0) {
+ return
+ }
+ childSelectionIndex += 1
+ if (childSelectionIndex >= listChild.length) {
+ childSelectionIndex -= 1
+ } else {
+ listChild[childSelectionIndex].focus()
+ }
+ }
+ const focusOnPreviousChild = () => {
+ const listChild = this.listRef.getElementsByTagName('li')
+ if (listChild.length === 0) {
+ return
+ }
+ childSelectionIndex -= 1
+ if (childSelectionIndex < 0) {
+ childSelectionIndex = 0
+ } else {
+ listChild[childSelectionIndex].focus()
+ }
+ }
+ let searchKey = ''
+ let timer
+ const focusOnCharacter = (value) => {
+ searchKey += value
+ if (timer) {
+ clearTimeout(timer)
+ }
+ timer = setTimeout(() => { searchKey = '' }, 500)
+ const listChild = this.listRef.getElementsByTagName('li')
+ if (listChild.length === 0) {
+ return
+ }
+ const length = listChild.length
+ for (let i = 0; i < length; i++) {
+ let textContent = listChild[i].textContent
+ if (textContent && textContent.length > 0) {
+ textContent = textContent.toLowerCase()
+ const search = searchKey.toLowerCase()
+ if (textContent.startsWith(search)) {
+ childSelectionIndex = i
+ listChild[i].focus()
+ return true
+ }
+ }
+ }
+ return false
+ }
+ const onFocus = () => {
+ this.containerRef.classList.add('focused')
+ }
+ const onBlur = () => {
+ this.containerRef.classList.remove('focused')
+ }
+ const onKeydown = (e) => {
+ if (!handleKeyboardNavigation) {
+ return
+ }
+ const keyCode = e.keyCode
+ if (keyCode === 32 || keyCode === 38 || keyCode === 40) { // space or Up/Down
+ // open dropdown menu
+ if (!noAutoclose && !isOpen) {
+ e.preventDefault()
+ handleClick(event)
+ } else {
+ if (keyCode === 40) {
+ focusOnNextChild()
+ } else if (keyCode === 38) {
+ focusOnPreviousChild()
+ }
+ e.preventDefault()
+ }
+ } else if (isOpen) {
+ const value = String.fromCharCode(e.keyCode)
+ if (focusOnCharacter(value)) {
+ e.preventDefault()
+ }
+ }
+ }
+ const onChildKeydown = (e) => {
+ if (!handleKeyboardNavigation) {
+ return
+ }
+ const keyCode = e.keyCode
+ if (keyCode === 38 || keyCode === 40 || keyCode === 13) { // Up/Down or enter
+ if (keyCode === 40) {
+ focusOnNextChild()
+ } else if (keyCode === 38) {
+ focusOnPreviousChild()
+ } else if (keyCode === 13) { // enter
+ const listChild = this.listRef.getElementsByTagName('li')
+ if (listChild.length === 0) {
+ return
+ }
+ listChild[childSelectionIndex].click()
+ this.handleKeyboardRef.focus()
+ }
+ e.preventDefault()
+ } else {
+ const value = String.fromCharCode(e.keyCode)
+ if (focusOnCharacter(value)) {
+ e.preventDefault()
+ }
+ }
+ }
+
+ const setListRef = (c) => this.listRef = c
+ const setContainerRef = (c) => this.containerRef = c
+ const setHandleKeyboardRef = (c) => this.handleKeyboardRef = c
+
+ const childrenWithProps = React.Children.map(children, child =>
+ React.cloneElement(child, {onKeyDown: onChildKeydown})
+ )
+ return (
+
{ } : handleClick}>
+ {handleKeyboardNavigation && (
)}
{
- props.children.map((child) => {
- if (child.props.className.indexOf('dropdown-menu-list') > -1)
- return child
+ childrenWithProps.map((child, index) => {
+ if (child.props.className.indexOf('dropdown-menu-header') > -1)
+ return noAutoclose ? React.cloneElement(child, {
+ onClick: handleClick,
+ key: child.props.key || index
+ }) : child
})
}
+
+ {
+ childrenWithProps.map((child) => {
+ if (child.props.className.indexOf('dropdown-menu-list') > -1)
+ return child
+ })
+ }
+
-
- )
+ )
+
+ }
}
Dropdown.propTypes = {
@@ -47,7 +172,15 @@ Dropdown.propTypes = {
/*
If true, prevents dropdown closing when clicked inside dropdown
*/
- noAutoclose: PropTypes.bool
+ noAutoclose: PropTypes.bool,
+ /*
+ If true, prevents handle keyboard event
+ */
+ handleKeyboardNavigation: PropTypes.bool
+}
+
+Dropdown.defaultProps = {
+ handleKeyboardNavigation: false
}
export default enhanceDropdown(Dropdown)
diff --git a/components/Dropdown/Dropdown.scss b/components/Dropdown/Dropdown.scss
index 07cd37aff..86a5bc9c1 100644
--- a/components/Dropdown/Dropdown.scss
+++ b/components/Dropdown/Dropdown.scss
@@ -56,8 +56,10 @@
@include ellipsis;
}
+ li:focus,
li:hover {
background-color: $tc-gray-neutral-dark;
+ outline: none;
}
}
}
@@ -75,6 +77,25 @@
border-bottom: 2px solid $tc-gray-20;
border-right: 2px solid $tc-gray-20;
}
+
+ .dropdown-wrap {
+ &.focused {
+ box-shadow: 0 0 2px 0 rgba(6, 129, 255, 0.7);
+ border: 1px solid $tc-dark-blue-100!important;
+ }
+ .handle-keyboard {
+ position: absolute;
+ width: 100%;
+ max-height: 40px;
+ top: 0;
+ left: 0;
+ height: 100%;
+
+ &:focus {
+ outline: none;
+ }
+ }
+ }
.Dropdown.hide {
display: none;
@@ -155,8 +176,10 @@
padding: 0 20px;
@include ellipsis;
}
+ li:focus,
li:hover {
background-color: $tc-gray-neutral-dark;
+ outline: none;
}
}
}
diff --git a/components/Dropdown/DropdownExamples.jsx b/components/Dropdown/DropdownExamples.jsx
index e6324037f..33af951d6 100644
--- a/components/Dropdown/DropdownExamples.jsx
+++ b/components/Dropdown/DropdownExamples.jsx
@@ -19,7 +19,7 @@ const DropdownExamples = {
{
items.map((link, i) => {
- return - {link}
+ return - {link}
})
}
@@ -32,7 +32,7 @@ const DropdownExamples = {
{
items.map((link, i) => {
- return - {link}
+ return - {link}
})
}
@@ -45,7 +45,7 @@ const DropdownExamples = {
{
items.map((link, i) => {
- return - {link}
+ return - {link}
})
}
@@ -58,7 +58,7 @@ const DropdownExamples = {
{
items.map((link, i) => {
- return - {link}
+ return - {link}
})
}
@@ -71,7 +71,7 @@ const DropdownExamples = {
{
items.map((link, i) => {
- return - {link}
+ return - {link}
})
}
@@ -84,7 +84,7 @@ const DropdownExamples = {
{
items.map((link, i) => {
- return - {link}
+ return - {link}
})
}
diff --git a/components/Formsy/PhoneInput.jsx b/components/Formsy/PhoneInput.jsx
index 93a8da336..ce167f70b 100644
--- a/components/Formsy/PhoneInput.jsx
+++ b/components/Formsy/PhoneInput.jsx
@@ -98,14 +98,14 @@ class PhoneInput extends Component {
min={minValue}
max={maxValue}
/>
-
+
{this.state.currentCountry ? this.state.currentCountry.alpha3 : ''}
{
this.props.listCountry.map((country, i) => {
/* eslint-disable react/jsx-no-bind */
- return - this.choseCountry(country)} key={i}>{country.name}
+ return - this.choseCountry(country)} key={i}>{country.name}
})
}
diff --git a/components/Formsy/PhoneInput.scss b/components/Formsy/PhoneInput.scss
index d3d0b24ff..5cba08053 100644
--- a/components/Formsy/PhoneInput.scss
+++ b/components/Formsy/PhoneInput.scss
@@ -69,7 +69,7 @@
}
.Dropdown {
- width: 200px;
+ width: auto;
margin-left: -150px;
margin-top: 30px;
color: black;