mirror of
https://github.com/wassname/talk.git
synced 2026-07-11 08:19:34 +08:00
Better accessbility
This commit is contained in:
@@ -3,15 +3,27 @@
|
||||
width: 150px;
|
||||
height: 36px;
|
||||
background: #2c2c2c;
|
||||
padding: 10px 15px;
|
||||
box-sizing: border-box;
|
||||
color: white;
|
||||
border-radius: 2px;
|
||||
box-shadow: 0 2px 2px 0 rgba(0,0,0,.14), 0 3px 1px -2px rgba(0,0,0,.2), 0 1px 5px 0 rgba(0,0,0,.12);
|
||||
|
||||
line-height: 1.4;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.dropdown {
|
||||
.toggle {
|
||||
padding: 10px 15px;
|
||||
cursor: pointer;
|
||||
outline: none;
|
||||
|
||||
&:focus {
|
||||
background: #666;
|
||||
}
|
||||
}
|
||||
|
||||
.toggleOpen {
|
||||
background: #666;
|
||||
}
|
||||
|
||||
.label {
|
||||
@@ -47,4 +59,4 @@
|
||||
font-size: 1.2rem;
|
||||
vertical-align: middle;
|
||||
top: 13px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,17 +7,25 @@ import ClickOutside from 'coral-framework/components/ClickOutside';
|
||||
|
||||
class Dropdown extends React.Component {
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
toggleRef = null;
|
||||
firstOptionRef = null;
|
||||
lastOptionRef = null;
|
||||
|
||||
this.state = {
|
||||
isOpen: false
|
||||
};
|
||||
state = {
|
||||
isOpen: false
|
||||
};
|
||||
|
||||
componentDidUpdate(_, prevState) {
|
||||
if (!this.state.isOpen && prevState.isOpen) {
|
||||
|
||||
// Refocus on the toggle element when menu closes.
|
||||
this.toggleRef.focus();
|
||||
}
|
||||
}
|
||||
|
||||
handleOptionKeyDown = (value, e) => {
|
||||
const code = e.which;
|
||||
|
||||
|
||||
// 13 = Return, 32 = Space
|
||||
if ((code === 13) || (code === 32)) {
|
||||
e.preventDefault();
|
||||
@@ -28,7 +36,7 @@ class Dropdown extends React.Component {
|
||||
handleOptionClick = (value) => {
|
||||
this.setValue(value);
|
||||
}
|
||||
|
||||
|
||||
setValue = (value) => {
|
||||
if (this.props.onChange) {
|
||||
this.props.onChange(value);
|
||||
@@ -51,7 +59,7 @@ class Dropdown extends React.Component {
|
||||
|
||||
handleKeyDown = (e) => {
|
||||
const code = e.which;
|
||||
|
||||
|
||||
// 13 = Return, 32 = Space
|
||||
if ((code === 13) || (code === 32)) {
|
||||
e.preventDefault();
|
||||
@@ -65,10 +73,33 @@ class Dropdown extends React.Component {
|
||||
});
|
||||
}
|
||||
|
||||
handleToggleRef = (ref) => this.toggleRef = ref;
|
||||
|
||||
handleOptionsRef = (ref, index) => {
|
||||
if (index === 0) {
|
||||
this.firstOptionRef = ref;
|
||||
}
|
||||
if (index === this.props.children.length - 1) {
|
||||
this.lastOptionRef = ref;
|
||||
}
|
||||
|
||||
// Focus on current value when menu opens.
|
||||
if (ref) {
|
||||
if (ref.props.value === this.props.value || index === 0 && !this.props.value) {
|
||||
ref.focus();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Trap keyboard focus inside the dropdown until a value has been chosen.
|
||||
trapFocusBegin = () => this.lastOptionRef.focus();
|
||||
trapFocusEnd = () => this.firstOptionRef.focus();
|
||||
|
||||
renderLabel() {
|
||||
const options = React.Children.toArray(this.props.children);
|
||||
const option = options.find((option) => option.props.value === this.props.value);
|
||||
|
||||
|
||||
if (option) {
|
||||
return option.props.label ? option.props.label : option.props.value;
|
||||
} else if (this.props.value) {
|
||||
@@ -79,22 +110,41 @@ class Dropdown extends React.Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
const {className = ''} = this.props;
|
||||
const {className, toggleClassName} = this.props;
|
||||
return (
|
||||
<ClickOutside onClickOutside={this.hideMenu}>
|
||||
<div className={cn(styles.dropdown, className)} onClick={this.handleClick} onKeyDown={this.handleKeyDown} role="button" aria-label="Dropdown" aria-haspopup="true" tabIndex="0">
|
||||
{this.props.icon && <Icon name={this.props.icon} className={styles.icon} />}
|
||||
<span className={styles.label}>{this.renderLabel()}</span>
|
||||
{this.state.isOpen ? <Icon name="keyboard_arrow_up" className={styles.arrow} /> : <Icon name="keyboard_arrow_down" className={styles.arrow} />}
|
||||
<ul className={cn(styles.list, {[styles.listActive] : this.state.isOpen})} role="menubar" aria-hidden="true">
|
||||
{React.Children.toArray(this.props.children)
|
||||
.map((child) =>
|
||||
React.cloneElement(child, {
|
||||
key: child.props.value,
|
||||
onClick: () => this.handleOptionClick(child.props.value),
|
||||
onKeyDown: (e) => this.handleOptionKeyDown(child.props.value, e)
|
||||
}))}
|
||||
</ul>
|
||||
<div className={cn(styles.dropdown, className)}>
|
||||
<div
|
||||
className={cn(styles.toggle, toggleClassName, {[styles.toggleOpen]: this.state.isOpen})}
|
||||
onClick={this.handleClick}
|
||||
onKeyDown={this.handleKeyDown}
|
||||
role="button"
|
||||
aria-pressed={this.state.isOpen}
|
||||
aria-haspopup="true"
|
||||
tabIndex="0"
|
||||
ref={this.handleToggleRef}
|
||||
>
|
||||
{this.props.icon && <Icon name={this.props.icon} className={styles.icon} aria-hidden="true" />}
|
||||
<span className={styles.label}>{this.renderLabel()}</span>
|
||||
{this.state.isOpen ? <Icon name="keyboard_arrow_up" className={styles.arrow} aria-hidden="true"/> : <Icon name="keyboard_arrow_down" className={styles.arrow} aria-hidden="true"/>}
|
||||
</div>
|
||||
{this.state.isOpen &&
|
||||
<div>
|
||||
<div tabIndex="0" onFocus={this.trapFocusBegin} />
|
||||
<ul className={cn(styles.list, {[styles.listActive] : this.state.isOpen})}>
|
||||
{React.Children.toArray(this.props.children)
|
||||
.map((child, i) =>
|
||||
React.cloneElement(child, {
|
||||
key: child.props.value,
|
||||
ref: (ref) => this.handleOptionsRef(ref, i),
|
||||
index: i,
|
||||
onClick: () => this.handleOptionClick(child.props.value),
|
||||
onKeyDown: (e) => this.handleOptionKeyDown(child.props.value, e)
|
||||
}))}
|
||||
</ul>
|
||||
<div tabIndex="0" onFocus={this.trapFocusEnd} />
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</ClickOutside>
|
||||
);
|
||||
@@ -103,6 +153,7 @@ class Dropdown extends React.Component {
|
||||
|
||||
Dropdown.propTypes = {
|
||||
className: PropTypes.string,
|
||||
toggleClassName: PropTypes.string,
|
||||
placeholder: PropTypes.string,
|
||||
icon: PropTypes.string,
|
||||
onChange: PropTypes.func.isRequired,
|
||||
|
||||
@@ -4,12 +4,13 @@ import {Icon as IconMDL} from 'react-mdl';
|
||||
import cn from 'classnames';
|
||||
import styles from './Icon.css';
|
||||
|
||||
const Icon = ({className = '', name}) => (
|
||||
<IconMDL className={cn(styles.root, className)} name={name} />
|
||||
const Icon = ({className = '', ...rest}) => (
|
||||
<IconMDL className={cn(styles.root, className)} {...rest} />
|
||||
);
|
||||
|
||||
Icon.propTypes = {
|
||||
name: PropTypes.string.isRequired
|
||||
name: PropTypes.string.isRequired,
|
||||
className: PropTypes.string,
|
||||
};
|
||||
|
||||
export default Icon;
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
.option {
|
||||
padding: 10px;
|
||||
text-transform: capitalize;
|
||||
}
|
||||
outline: none;
|
||||
|
||||
.option:hover {
|
||||
background-color: #eee;
|
||||
cursor: pointer;
|
||||
}
|
||||
&:focus, &:hover {
|
||||
background-color: #d8d8d8;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,11 +3,25 @@ import PropTypes from 'prop-types';
|
||||
import styles from './Option.css';
|
||||
import cn from 'classnames';
|
||||
|
||||
const Option = ({className, label = '', onClick, onKeyDown}) => (
|
||||
<li className={cn(styles.option, className)} onClick={onClick} onKeyDown={onKeyDown} role="menuitem" tabIndex="0">
|
||||
{label}
|
||||
</li>
|
||||
);
|
||||
class Option extends React.Component {
|
||||
|
||||
ref = null;
|
||||
|
||||
handleRef = (ref) => {
|
||||
this.ref = ref;
|
||||
};
|
||||
|
||||
focus = () => {this.ref.focus();}
|
||||
|
||||
render() {
|
||||
const {className, label = '', onClick, onKeyDown} = this.props;
|
||||
return (
|
||||
<li className={cn(styles.option, className)} onClick={onClick} onKeyDown={onKeyDown} role="option" tabIndex="0" ref={this.handleRef}>
|
||||
{label}
|
||||
</li>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Option.propTypes = {
|
||||
className: PropTypes.string,
|
||||
|
||||
Reference in New Issue
Block a user