mirror of
https://github.com/opensupports/opensupports.git
synced 2025-07-31 01:35:15 +02:00
Merged in dropdown-accesibility (pull request #48)
Dropdown accesibility
This commit is contained in:
commit
be9919baef
@ -66,7 +66,7 @@
|
|||||||
"react-document-title": "^1.0.2",
|
"react-document-title": "^1.0.2",
|
||||||
"react-dom": "^15.0.1",
|
"react-dom": "^15.0.1",
|
||||||
"react-google-recaptcha": "^0.5.2",
|
"react-google-recaptcha": "^0.5.2",
|
||||||
"react-motion": "^0.3.0",
|
"react-motion": "^0.4.4",
|
||||||
"react-redux": "^4.4.5",
|
"react-redux": "^4.4.5",
|
||||||
"react-router": "^2.4.0",
|
"react-router": "^2.4.0",
|
||||||
"react-router-redux": "^4.0.5",
|
"react-router-redux": "^4.0.5",
|
||||||
|
260
client/src/core-components/__tests__/drop-down-test.js
Normal file
260
client/src/core-components/__tests__/drop-down-test.js
Normal file
@ -0,0 +1,260 @@
|
|||||||
|
// LIBS
|
||||||
|
const {Motion} = require('react-motion');
|
||||||
|
const _ = require('lodash');
|
||||||
|
|
||||||
|
// MOCKS
|
||||||
|
const Menu = ReactMock();
|
||||||
|
const Icon = ReactMock();
|
||||||
|
|
||||||
|
// COMPONENT
|
||||||
|
const DropDown = requireUnit('core-components/drop-down', {
|
||||||
|
'core-components/menu': Menu,
|
||||||
|
'core-components/icon': Icon
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('DropDown component', function () {
|
||||||
|
let dropdown, menu, currentItem, menuMotion;
|
||||||
|
|
||||||
|
function renderDropDown(props) {
|
||||||
|
let defaultProps = {
|
||||||
|
items: [
|
||||||
|
{content: 'First Item', icon: 'ICON_1'},
|
||||||
|
{content: 'Second Item', icon: 'ICON_2'},
|
||||||
|
{content: 'Third Item', icon: 'ICON_3'},
|
||||||
|
{content: 'Fourth Item', icon: 'ICON_4'}
|
||||||
|
],
|
||||||
|
onChange: stub()
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
dropdown = TestUtils.renderIntoDocument(
|
||||||
|
<DropDown {..._.extend(defaultProps, props)} />
|
||||||
|
);
|
||||||
|
menu = TestUtils.scryRenderedComponentsWithType(dropdown, Menu)[0];
|
||||||
|
menuMotion = TestUtils.scryRenderedComponentsWithType(dropdown, Motion)[0];
|
||||||
|
currentItem = TestUtils.scryRenderedDOMComponentsWithClass(dropdown, 'drop-down__current-item')[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
beforeEach(function() {
|
||||||
|
renderDropDown();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render a current item and a Menu of items', function () {
|
||||||
|
expect(ReactDOM.findDOMNode(dropdown).className).to.contain('drop-down');
|
||||||
|
expect(ReactDOM.findDOMNode(dropdown).className).to.contain('drop-down_closed');
|
||||||
|
|
||||||
|
expect(currentItem.textContent).to.equal('First Item');
|
||||||
|
expect(currentItem.getAttribute('aria-expanded')).to.equal('false');
|
||||||
|
expect(currentItem.getAttribute('aria-autocomplete')).to.equal('list');
|
||||||
|
expect(currentItem.getAttribute('role')).to.equal('combobox');
|
||||||
|
expect(currentItem.getAttribute('tabindex')).to.equal('0');
|
||||||
|
|
||||||
|
expect(menuMotion.props.style.opacity.val).to.equal(0);
|
||||||
|
expect(menu.props.role).to.equal('listbox');
|
||||||
|
expect(menu.props.itemsRole).to.equal('option');
|
||||||
|
expect(menu.props.items).to.equal(dropdown.props.items);
|
||||||
|
expect(menu.props.selectedIndex).to.equal(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should open/close list when click on current item', function () {
|
||||||
|
TestUtils.Simulate.click(currentItem);
|
||||||
|
|
||||||
|
expect(menuMotion.props.style.opacity.val).to.equal(1);
|
||||||
|
expect(ReactDOM.findDOMNode(dropdown).className).to.not.contain('drop-down_closed');
|
||||||
|
expect(currentItem.getAttribute('aria-expanded')).to.equal('true');
|
||||||
|
|
||||||
|
TestUtils.Simulate.click(currentItem);
|
||||||
|
|
||||||
|
expect(menuMotion.props.style.opacity.val).to.equal(0);
|
||||||
|
expect(ReactDOM.findDOMNode(dropdown).className).to.contain('drop-down_closed');
|
||||||
|
expect(currentItem.getAttribute('aria-expanded')).to.equal('false');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should open/close list when pressing Enter on current item', function () {
|
||||||
|
TestUtils.Simulate.keyDown(currentItem, {key: 'Enter', keyCode: 13, which: 13});
|
||||||
|
|
||||||
|
expect(menuMotion.props.style.opacity.val).to.equal(1);
|
||||||
|
expect(ReactDOM.findDOMNode(dropdown).className).to.not.contain('drop-down_closed');
|
||||||
|
expect(currentItem.getAttribute('aria-expanded')).to.equal('true');
|
||||||
|
|
||||||
|
TestUtils.Simulate.keyDown(currentItem, {key: 'Enter', keyCode: 13, which: 13});
|
||||||
|
|
||||||
|
expect(menuMotion.props.style.opacity.val).to.equal(0);
|
||||||
|
expect(ReactDOM.findDOMNode(dropdown).className).to.contain('drop-down_closed');
|
||||||
|
expect(currentItem.getAttribute('aria-expanded')).to.equal('false');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should open list but no close when pressing Space on current item', function () {
|
||||||
|
TestUtils.Simulate.keyDown(currentItem, {key: 'Space', keyCode: 32, which: 32});
|
||||||
|
|
||||||
|
expect(menuMotion.props.style.opacity.val).to.equal(1);
|
||||||
|
expect(ReactDOM.findDOMNode(dropdown).className).to.not.contain('drop-down_closed');
|
||||||
|
expect(currentItem.getAttribute('aria-expanded')).to.equal('true');
|
||||||
|
|
||||||
|
TestUtils.Simulate.keyDown(currentItem, {key: 'Space', keyCode: 32, which: 32});
|
||||||
|
|
||||||
|
expect(menuMotion.props.style.opacity.val).to.equal(1);
|
||||||
|
expect(ReactDOM.findDOMNode(dropdown).className).to.not.contain('drop-down_closed');
|
||||||
|
expect(currentItem.getAttribute('aria-expanded')).to.equal('true');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should close the list with escape on current item', function () {
|
||||||
|
TestUtils.Simulate.keyDown(currentItem, {key: 'Space', keyCode: 32, which: 32});
|
||||||
|
|
||||||
|
expect(menuMotion.props.style.opacity.val).to.equal(1);
|
||||||
|
expect(ReactDOM.findDOMNode(dropdown).className).to.not.contain('drop-down_closed');
|
||||||
|
expect(currentItem.getAttribute('aria-expanded')).to.equal('true');
|
||||||
|
|
||||||
|
TestUtils.Simulate.keyDown(currentItem, {key: 'Esc', keyCode: 27, which: 27});
|
||||||
|
|
||||||
|
expect(menuMotion.props.style.opacity.val).to.equal(0);
|
||||||
|
expect(ReactDOM.findDOMNode(dropdown).className).to.contain('drop-down_closed');
|
||||||
|
expect(currentItem.getAttribute('aria-expanded')).to.equal('false');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should close the list when current item loses focus', function () {
|
||||||
|
TestUtils.Simulate.keyDown(currentItem, {key: 'Enter', keyCode: 13, which: 13});
|
||||||
|
|
||||||
|
expect(menuMotion.props.style.opacity.val).to.equal(1);
|
||||||
|
expect(ReactDOM.findDOMNode(dropdown).className).to.not.contain('drop-down_closed');
|
||||||
|
expect(currentItem.getAttribute('aria-expanded')).to.equal('true');
|
||||||
|
|
||||||
|
TestUtils.Simulate.blur(currentItem);
|
||||||
|
|
||||||
|
expect(menuMotion.props.style.opacity.val).to.equal(0);
|
||||||
|
expect(ReactDOM.findDOMNode(dropdown).className).to.contain('drop-down_closed');
|
||||||
|
expect(currentItem.getAttribute('aria-expanded')).to.equal('false');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should change selection, close and call onChange when a menu item is clicked', function () {
|
||||||
|
dropdown.props.onChange.reset();
|
||||||
|
TestUtils.Simulate.keyDown(currentItem, {key: 'Space', keyCode: 32, which: 32});
|
||||||
|
menu.props.onItemClick(2);
|
||||||
|
|
||||||
|
// Should be closed
|
||||||
|
expect(menuMotion.props.style.opacity.val).to.equal(0);
|
||||||
|
expect(ReactDOM.findDOMNode(dropdown).className).to.contain('drop-down_closed');
|
||||||
|
expect(currentItem.getAttribute('aria-expanded')).to.equal('false');
|
||||||
|
|
||||||
|
expect(currentItem.textContent).to.equal('Third Item');
|
||||||
|
expect(menu.props.selectedIndex).to.equal(2);
|
||||||
|
expect(dropdown.props.onChange).to.have.been.calledWith({index: 2});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should only change menu section when using arrow keys', function () {
|
||||||
|
dropdown.props.onChange.reset();
|
||||||
|
|
||||||
|
TestUtils.Simulate.keyDown(currentItem, {key: 'Enter', keyCode: 13, which: 13});
|
||||||
|
expect(menu.props.selectedIndex).to.equal(0);
|
||||||
|
TestUtils.Simulate.keyDown(currentItem, {key: 'Down', keyCode: 40, which: 40});
|
||||||
|
expect(menu.props.selectedIndex).to.equal(1);
|
||||||
|
TestUtils.Simulate.keyDown(currentItem, {key: 'Down', keyCode: 40, which: 40});
|
||||||
|
expect(menu.props.selectedIndex).to.equal(2);
|
||||||
|
TestUtils.Simulate.keyDown(currentItem, {key: 'Down', keyCode: 40, which: 40});
|
||||||
|
expect(menu.props.selectedIndex).to.equal(3);
|
||||||
|
TestUtils.Simulate.keyDown(currentItem, {key: 'Down', keyCode: 40, which: 40});
|
||||||
|
expect(menu.props.selectedIndex).to.equal(0);
|
||||||
|
TestUtils.Simulate.keyDown(currentItem, {key: 'Down', keyCode: 40, which: 40});
|
||||||
|
expect(menu.props.selectedIndex).to.equal(1);
|
||||||
|
TestUtils.Simulate.keyDown(currentItem, {key: 'Up', keyCode: 38, which: 38});
|
||||||
|
expect(menu.props.selectedIndex).to.equal(0);
|
||||||
|
TestUtils.Simulate.keyDown(currentItem, {key: 'Up', keyCode: 38, which: 38});
|
||||||
|
expect(menu.props.selectedIndex).to.equal(3);
|
||||||
|
TestUtils.Simulate.keyDown(currentItem, {key: 'Up', keyCode: 38, which: 38});
|
||||||
|
expect(menu.props.selectedIndex).to.equal(2);
|
||||||
|
|
||||||
|
expect(dropdown.props.onChange).to.have.not.been.called;
|
||||||
|
expect(currentItem.textContent).to.equal('First Item');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not change menu selection if it is closed', function () {
|
||||||
|
dropdown.props.onChange.reset();
|
||||||
|
|
||||||
|
TestUtils.Simulate.keyDown(currentItem, {key: 'Down', keyCode: 40, which: 40});
|
||||||
|
expect(menu.props.selectedIndex).to.equal(0);
|
||||||
|
TestUtils.Simulate.keyDown(currentItem, {key: 'Down', keyCode: 40, which: 40});
|
||||||
|
expect(menu.props.selectedIndex).to.equal(0);
|
||||||
|
TestUtils.Simulate.keyDown(currentItem, {key: 'Down', keyCode: 40, which: 40});
|
||||||
|
expect(menu.props.selectedIndex).to.equal(0);
|
||||||
|
|
||||||
|
expect(dropdown.props.onChange).to.have.not.been.called;
|
||||||
|
expect(currentItem.textContent).to.equal('First Item');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should change selection to the menu\'s one, if Enter key is pressed', function () {
|
||||||
|
dropdown.props.onChange.reset();
|
||||||
|
|
||||||
|
TestUtils.Simulate.keyDown(currentItem, {key: 'Enter', keyCode: 13, which: 13});
|
||||||
|
TestUtils.Simulate.keyDown(currentItem, {key: 'Down', keyCode: 40, which: 40});
|
||||||
|
TestUtils.Simulate.keyDown(currentItem, {key: 'Down', keyCode: 40, which: 40});
|
||||||
|
expect(menu.props.selectedIndex).to.equal(2);
|
||||||
|
|
||||||
|
TestUtils.Simulate.keyDown(currentItem, {key: 'Enter', keyCode: 13, which: 13});
|
||||||
|
|
||||||
|
expect(currentItem.textContent).to.equal('Third Item');
|
||||||
|
expect(menu.props.selectedIndex).to.equal(2);
|
||||||
|
expect(dropdown.props.onChange).to.have.been.calledWith({index: 2});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not change selection with esc, blur or space', function () {
|
||||||
|
TestUtils.Simulate.keyDown(currentItem, {key: 'Enter', keyCode: 13, which: 13});
|
||||||
|
TestUtils.Simulate.keyDown(currentItem, {key: 'Down', keyCode: 40, which: 40});
|
||||||
|
TestUtils.Simulate.keyDown(currentItem, {key: 'Down', keyCode: 40, which: 40});
|
||||||
|
TestUtils.Simulate.keyDown(currentItem, {key: 'Enter', keyCode: 13, which: 13});
|
||||||
|
expect(menu.props.selectedIndex).to.equal(2);
|
||||||
|
|
||||||
|
dropdown.props.onChange.reset();
|
||||||
|
TestUtils.Simulate.keyDown(currentItem, {key: 'Enter', keyCode: 13, which: 13});
|
||||||
|
TestUtils.Simulate.keyDown(currentItem, {key: 'Down', keyCode: 40, which: 40});
|
||||||
|
TestUtils.Simulate.keyDown(currentItem, {key: 'Esc', keyCode: 27, which: 27});
|
||||||
|
expect(currentItem.textContent).to.equal('Third Item');
|
||||||
|
expect(dropdown.props.onChange).to.have.not.been.called;
|
||||||
|
|
||||||
|
dropdown.props.onChange.reset();
|
||||||
|
TestUtils.Simulate.keyDown(currentItem, {key: 'Enter', keyCode: 13, which: 13});
|
||||||
|
TestUtils.Simulate.keyDown(currentItem, {key: 'Down', keyCode: 40, which: 40});
|
||||||
|
TestUtils.Simulate.keyDown(currentItem, {key: 'Space', keyCode: 32, which: 32});
|
||||||
|
expect(currentItem.textContent).to.equal('Third Item');
|
||||||
|
expect(dropdown.props.onChange).to.have.not.been.called;
|
||||||
|
|
||||||
|
dropdown.props.onChange.reset();
|
||||||
|
TestUtils.Simulate.keyDown(currentItem, {key: 'Down', keyCode: 40, which: 40});
|
||||||
|
TestUtils.Simulate.blur(currentItem);
|
||||||
|
expect(currentItem.textContent).to.equal('Third Item');
|
||||||
|
expect(dropdown.props.onChange).to.have.not.been.called;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should start selecting defaultSelectedIndex', function () {
|
||||||
|
dropdown.props.onChange.reset();
|
||||||
|
renderDropDown({defaultSelectedIndex: 2});
|
||||||
|
|
||||||
|
expect(currentItem.textContent).to.equal('Third Item');
|
||||||
|
expect(menu.props.selectedIndex).to.equal(2);
|
||||||
|
expect(dropdown.props.onChange).to.have.not.been.called;
|
||||||
|
|
||||||
|
TestUtils.Simulate.keyDown(currentItem, {key: 'Enter', keyCode: 13, which: 13});
|
||||||
|
TestUtils.Simulate.keyDown(currentItem, {key: 'Down', keyCode: 40, which: 40});
|
||||||
|
TestUtils.Simulate.keyDown(currentItem, {key: 'Enter', keyCode: 13, which: 13});
|
||||||
|
|
||||||
|
expect(currentItem.textContent).to.equal('Fourth Item');
|
||||||
|
expect(menu.props.selectedIndex).to.equal(3);
|
||||||
|
expect(dropdown.props.onChange).to.have.been.calledWith({index: 3});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should only show selectedIndex prop on the current selection', function () {
|
||||||
|
dropdown.props.onChange.reset();
|
||||||
|
renderDropDown({selectedIndex: 2});
|
||||||
|
|
||||||
|
expect(currentItem.textContent).to.equal('Third Item');
|
||||||
|
expect(menu.props.selectedIndex).to.equal(2);
|
||||||
|
expect(dropdown.props.onChange).to.have.not.been.called;
|
||||||
|
|
||||||
|
TestUtils.Simulate.keyDown(currentItem, {key: 'Enter', keyCode: 13, which: 13});
|
||||||
|
TestUtils.Simulate.keyDown(currentItem, {key: 'Down', keyCode: 40, which: 40});
|
||||||
|
TestUtils.Simulate.keyDown(currentItem, {key: 'Enter', keyCode: 13, which: 13});
|
||||||
|
|
||||||
|
expect(currentItem.textContent).to.equal('Third Item');
|
||||||
|
expect(menu.props.selectedIndex).to.equal(3);
|
||||||
|
expect(dropdown.props.onChange).to.have.been.calledWith({index: 3});
|
||||||
|
});
|
||||||
|
});
|
@ -37,7 +37,8 @@ describe('Menu component', function () {
|
|||||||
{content: 'Second Item', icon: 'ICON_2'},
|
{content: 'Second Item', icon: 'ICON_2'},
|
||||||
{content: 'Third Item', icon: 'ICON_3'},
|
{content: 'Third Item', icon: 'ICON_3'},
|
||||||
{content: 'Fourth Item', icon: 'ICON_4'}
|
{content: 'Fourth Item', icon: 'ICON_4'}
|
||||||
]
|
],
|
||||||
|
itemsRole: 'some_role'
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(items.length).to.equal(4);
|
expect(items.length).to.equal(4);
|
||||||
@ -47,6 +48,7 @@ describe('Menu component', function () {
|
|||||||
expect(items[3].textContent).to.equal('Fourth Item');
|
expect(items[3].textContent).to.equal('Fourth Item');
|
||||||
|
|
||||||
items.forEach((item, index) => {
|
items.forEach((item, index) => {
|
||||||
|
expect(item.getAttribute('role')).to.equal('some_role');
|
||||||
expect(item.className).to.contain('menu__list-item');
|
expect(item.className).to.contain('menu__list-item');
|
||||||
expect(item.childNodes[0]).to.equal(ReactDOM.findDOMNode(icons[index]));
|
expect(item.childNodes[0]).to.equal(ReactDOM.findDOMNode(icons[index]));
|
||||||
});
|
});
|
||||||
|
@ -5,12 +5,21 @@
|
|||||||
&_primary,
|
&_primary,
|
||||||
&_secondary,
|
&_secondary,
|
||||||
&_tertiary {
|
&_tertiary {
|
||||||
background-color: $primary-red;
|
|
||||||
border: solid transparent;
|
border: solid transparent;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
color: white;
|
color: white;
|
||||||
height: 47px;
|
height: 47px;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
|
transition: background-color 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
&_primary {
|
||||||
|
background-color: $primary-red;
|
||||||
|
|
||||||
|
&:focus, &:hover {
|
||||||
|
background-color: lighten($primary-red, 5%);
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&_secondary {
|
&_secondary {
|
||||||
|
@ -21,6 +21,10 @@
|
|||||||
&_checked {
|
&_checked {
|
||||||
.checkbox--icon {
|
.checkbox--icon {
|
||||||
color: $primary-red;
|
color: $primary-red;
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
color: lighten($primary-red, 5%);;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,6 +1,8 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import _ from 'lodash';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import {Motion, spring} from 'react-motion';
|
import {Motion, spring} from 'react-motion';
|
||||||
|
import keyCode from 'keycode';
|
||||||
|
|
||||||
import Menu from 'core-components/menu';
|
import Menu from 'core-components/menu';
|
||||||
import Icon from 'core-components/icon';
|
import Icon from 'core-components/icon';
|
||||||
@ -11,18 +13,21 @@ class DropDown extends React.Component {
|
|||||||
defaultSelectedIndex: React.PropTypes.number,
|
defaultSelectedIndex: React.PropTypes.number,
|
||||||
selectedIndex: React.PropTypes.number,
|
selectedIndex: React.PropTypes.number,
|
||||||
items: Menu.propTypes.items,
|
items: Menu.propTypes.items,
|
||||||
|
onChange: React.PropTypes.func,
|
||||||
size: React.PropTypes.oneOf(['small', 'medium', 'large'])
|
size: React.PropTypes.oneOf(['small', 'medium', 'large'])
|
||||||
};
|
};
|
||||||
|
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
defaultSelectedIndex: 2
|
defaultSelectedIndex: 0
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
selectedIndex: 0,
|
menuId: _.uniqueId('drop-down-menu_'),
|
||||||
|
selectedIndex: props.selectedIndex || props.defaultSelectedIndex,
|
||||||
|
highlightedIndex: props.selectedIndex || props.defaultSelectedIndex,
|
||||||
opened: false
|
opened: false
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -38,7 +43,7 @@ class DropDown extends React.Component {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
defaultStyle: closedStyle,
|
defaultStyle: {opacity: 0, translateY: 20},
|
||||||
style: (this.state.opened) ? openedStyle : closedStyle
|
style: (this.state.opened) ? openedStyle : closedStyle
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -50,7 +55,7 @@ class DropDown extends React.Component {
|
|||||||
return (
|
return (
|
||||||
<div className={this.getClass()}>
|
<div className={this.getClass()}>
|
||||||
{this.renderCurrentItem(selectedItem)}
|
{this.renderCurrentItem(selectedItem)}
|
||||||
<Motion defaultStyle={animation.defaultStyle} style={animation.style}>
|
<Motion defaultStyle={animation.defaultStyle} style={animation.style} onRest={this.onAnimationFinished.bind(this)}>
|
||||||
{this.renderList.bind(this)}
|
{this.renderList.bind(this)}
|
||||||
</Motion>
|
</Motion>
|
||||||
</div>
|
</div>
|
||||||
@ -59,16 +64,10 @@ class DropDown extends React.Component {
|
|||||||
|
|
||||||
renderList({opacity, translateY}) {
|
renderList({opacity, translateY}) {
|
||||||
let style = { opacity: opacity, transform: `translateY(${translateY}px)`};
|
let style = { opacity: opacity, transform: `translateY(${translateY}px)`};
|
||||||
let menuProps = {
|
|
||||||
items: this.props.items,
|
|
||||||
onItemClick: this.handleItemClick.bind(this),
|
|
||||||
onMouseDown: this.handleListMouseDown.bind(this),
|
|
||||||
selectedIndex: this.getSelectedIndex()
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="drop-down__list-container" style={style}>
|
<div className="drop-down__list-container" style={style}>
|
||||||
<Menu {...menuProps} />
|
<Menu {...this.getMenuProps()} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -81,7 +80,7 @@ class DropDown extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="drop-down__current-item" onBlur={this.handleBlur.bind(this)} onClick={this.handleClick.bind(this)} tabIndex="0">
|
<div {...this.getCurrentItemProps()}>
|
||||||
{iconNode}{item.content}
|
{iconNode}{item.content}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@ -92,13 +91,103 @@ class DropDown extends React.Component {
|
|||||||
'drop-down': true,
|
'drop-down': true,
|
||||||
'drop-down_closed': !this.state.opened,
|
'drop-down_closed': !this.state.opened,
|
||||||
|
|
||||||
['drop-down_' + this.props.size]: true,
|
['drop-down_' + this.props.size]: (this.props.size),
|
||||||
[this.props.className]: (this.props.className)
|
[this.props.className]: (this.props.className)
|
||||||
};
|
};
|
||||||
|
|
||||||
return classNames(classes);
|
return classNames(classes);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getCurrentItemProps() {
|
||||||
|
return {
|
||||||
|
'aria-expanded': this.state.opened,
|
||||||
|
'aria-autocomplete': 'list',
|
||||||
|
'aria-owns': this.state.menuId,
|
||||||
|
'aria-activedescendant': this.state.menuId + '__' + this.state.highlightedIndex,
|
||||||
|
className: 'drop-down__current-item',
|
||||||
|
onClick: this.handleClick.bind(this),
|
||||||
|
onKeyDown: this.onKeyDown.bind(this),
|
||||||
|
onBlur: this.handleBlur.bind(this),
|
||||||
|
role: 'combobox',
|
||||||
|
tabIndex: 0
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
getMenuProps() {
|
||||||
|
return {
|
||||||
|
id: this.state.menuId,
|
||||||
|
itemsRole: 'option',
|
||||||
|
items: this.props.items,
|
||||||
|
onItemClick: this.handleItemClick.bind(this),
|
||||||
|
onMouseDown: this.handleListMouseDown.bind(this),
|
||||||
|
selectedIndex: this.state.highlightedIndex,
|
||||||
|
role: 'listbox'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
onKeyDown(event) {
|
||||||
|
const keyActions = this.getKeyActions(event);
|
||||||
|
const keyAction = keyActions[keyCode(event)];
|
||||||
|
|
||||||
|
if (keyAction) {
|
||||||
|
keyAction();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getKeyActions(event) {
|
||||||
|
const {highlightedIndex, opened} = this.state;
|
||||||
|
const itemsQuantity = this.props.items.length;
|
||||||
|
|
||||||
|
return {
|
||||||
|
'up': () => {
|
||||||
|
if (opened) {
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
highlightedIndex: this.modulo(highlightedIndex - 1, itemsQuantity)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'down': () => {
|
||||||
|
if (opened) {
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
highlightedIndex: this.modulo(highlightedIndex + 1, itemsQuantity)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'enter': () => {
|
||||||
|
if (opened) {
|
||||||
|
this.onIndexSelected(highlightedIndex);
|
||||||
|
} else {
|
||||||
|
this.setState({
|
||||||
|
opened: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'space': () => {
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
opened: true
|
||||||
|
});
|
||||||
|
},
|
||||||
|
'esc': () => {
|
||||||
|
this.setState({
|
||||||
|
opened: false
|
||||||
|
});
|
||||||
|
},
|
||||||
|
'tab': () => {
|
||||||
|
if (this.state.opened) {
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
this.onIndexSelected(highlightedIndex)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
handleBlur() {
|
handleBlur() {
|
||||||
this.setState({
|
this.setState({
|
||||||
opened: false
|
opened: false
|
||||||
@ -112,9 +201,14 @@ class DropDown extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleItemClick(index) {
|
handleItemClick(index) {
|
||||||
|
this.onIndexSelected(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
onIndexSelected(index) {
|
||||||
this.setState({
|
this.setState({
|
||||||
opened: false,
|
opened: false,
|
||||||
selectedIndex: index
|
selectedIndex: index,
|
||||||
|
highlightedIndex: index
|
||||||
});
|
});
|
||||||
|
|
||||||
if (this.props.onChange) {
|
if (this.props.onChange) {
|
||||||
@ -128,9 +222,21 @@ class DropDown extends React.Component {
|
|||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onAnimationFinished() {
|
||||||
|
if (!this.state.opened && this.state.highlightedIndex !== this.getSelectedIndex()) {
|
||||||
|
this.setState({
|
||||||
|
highlightedIndex: this.getSelectedIndex()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
getSelectedIndex() {
|
getSelectedIndex() {
|
||||||
return (this.props.selectedIndex !== undefined) ? this.props.selectedIndex : this.state.selectedIndex;
|
return (this.props.selectedIndex !== undefined) ? this.props.selectedIndex : this.state.selectedIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
modulo(number, mod) {
|
||||||
|
return ((number % mod) + mod) % mod;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default DropDown;
|
export default DropDown;
|
||||||
|
@ -8,6 +8,8 @@ import Icon from 'core-components/icon';
|
|||||||
class Menu extends React.Component {
|
class Menu extends React.Component {
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
|
id: React.PropTypes.string,
|
||||||
|
itemsRole: React.PropTypes.string,
|
||||||
header: React.PropTypes.string,
|
header: React.PropTypes.string,
|
||||||
type: React.PropTypes.oneOf(['primary', 'secondary']),
|
type: React.PropTypes.oneOf(['primary', 'secondary']),
|
||||||
items: React.PropTypes.arrayOf(React.PropTypes.shape({
|
items: React.PropTypes.arrayOf(React.PropTypes.shape({
|
||||||
@ -64,6 +66,7 @@ class Menu extends React.Component {
|
|||||||
|
|
||||||
props.className = 'menu__list';
|
props.className = 'menu__list';
|
||||||
|
|
||||||
|
delete props.itemsRole;
|
||||||
delete props.header;
|
delete props.header;
|
||||||
delete props.items;
|
delete props.items;
|
||||||
delete props.onItemClick;
|
delete props.onItemClick;
|
||||||
@ -87,10 +90,12 @@ class Menu extends React.Component {
|
|||||||
|
|
||||||
getItemProps(index) {
|
getItemProps(index) {
|
||||||
return {
|
return {
|
||||||
|
id: this.props.id + '__' + index,
|
||||||
className: this.getItemClass(index),
|
className: this.getItemClass(index),
|
||||||
onClick: this.onItemClick.bind(this, index),
|
onClick: this.onItemClick.bind(this, index),
|
||||||
tabIndex: (this.props.tabbable) ? '0' : null,
|
tabIndex: (this.props.tabbable) ? '0' : null,
|
||||||
onKeyDown: this.onKeyDown.bind(this, index),
|
onKeyDown: this.onKeyDown.bind(this, index),
|
||||||
|
role: this.props.itemsRole,
|
||||||
key: index
|
key: index
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -13,6 +13,7 @@
|
|||||||
|
|
||||||
&__list-item {
|
&__list-item {
|
||||||
padding: 8px;
|
padding: 8px;
|
||||||
|
transition: background-color 0.3s ease, color 0.3s ease;
|
||||||
|
|
||||||
&_selected,
|
&_selected,
|
||||||
&:hover {
|
&:hover {
|
||||||
|
@ -18,8 +18,8 @@ class Modal extends React.Component {
|
|||||||
getAnimations() {
|
getAnimations() {
|
||||||
return {
|
return {
|
||||||
defaultStyle: {
|
defaultStyle: {
|
||||||
scale: spring(0.7),
|
scale: 0.7,
|
||||||
fade: spring(0.5)
|
fade: 0.5
|
||||||
},
|
},
|
||||||
style: {
|
style: {
|
||||||
scale: spring(1),
|
scale: spring(1),
|
||||||
|
@ -27,9 +27,15 @@ class ConfigReducer extends Reducer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onInitConfigs(state, payload) {
|
onInitConfigs(state, payload) {
|
||||||
sessionStore.storeConfigs(payload.data);
|
const currentLanguage = sessionStore.getItem('language');
|
||||||
|
|
||||||
return _.extend({}, state, payload.data);
|
sessionStore.storeConfigs(_.extend(payload.data, {
|
||||||
|
language: currentLanguage || payload.language
|
||||||
|
}));
|
||||||
|
|
||||||
|
return _.extend({}, state, payload.data, {
|
||||||
|
language: currentLanguage || payload.language
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user