diff --git a/components/autocomplete/Autocomplete.js b/components/autocomplete/Autocomplete.js index 099a03378..8143d11ea 100644 --- a/components/autocomplete/Autocomplete.js +++ b/components/autocomplete/Autocomplete.js @@ -146,6 +146,10 @@ const factory = (Chip, Input) => { ); if (event.which === 13) { + if (!this.state.query.trim().length && !this.state.active) { + events.pauseEvent(event) + return + } this.selectOrCreateActiveItem(event); } }; diff --git a/components/button/Button.js b/components/button/Button.js index 90634ea87..192ba848a 100644 --- a/components/button/Button.js +++ b/components/button/Button.js @@ -33,6 +33,7 @@ const factory = (ripple, FontIcon) => { button: PropTypes.string, flat: PropTypes.string, floating: PropTypes.string, + label: PropTypes.string, icon: PropTypes.string, inverse: PropTypes.string, mini: PropTypes.string, @@ -122,7 +123,7 @@ const factory = (ripple, FontIcon) => { return React.createElement(element, props, icon ? : null, - label, + {label}, children, ); } diff --git a/components/button/IconButton.js b/components/button/IconButton.js index c50496c3d..9f8eb2ab2 100644 --- a/components/button/IconButton.js +++ b/components/button/IconButton.js @@ -88,7 +88,7 @@ const factory = (ripple, FontIcon) => { } = this.props; const element = href ? 'a' : 'button'; const level = this.getLevel(); - const classes = classnames([theme.toggle], { + const classes = classnames(theme.button, [theme.toggle], { [theme[level]]: neutral, [theme.inverse]: inverse, }, className); diff --git a/components/button/base.d.ts b/components/button/base.d.ts index bd54431f9..ae05ed4b8 100644 --- a/components/button/base.d.ts +++ b/components/button/base.d.ts @@ -18,6 +18,10 @@ export interface ButtonTheme { * Used when the button is floating for the root element. */ floating?: string; + /** + * For the label inside a button. + */ + label?: string; /** * For the icon inside a button. */ diff --git a/components/checkbox/Checkbox.js b/components/checkbox/Checkbox.js index b209fb1a0..ebd6e9be0 100644 --- a/components/checkbox/Checkbox.js +++ b/components/checkbox/Checkbox.js @@ -10,6 +10,7 @@ import checkFactory from './Check'; const factory = (Check) => { class Checkbox extends Component { + static propTypes = { checked: PropTypes.bool, children: PropTypes.node, @@ -38,6 +39,11 @@ const factory = (Check) => { disabled: false, }; + constructor(props) { + super(props); + this._id = uuidv4(); + } + handleToggle = (event) => { if (event.pageX !== 0 && event.pageY !== 0) this.blur(); if (!this.props.disabled && this.props.onChange) { @@ -63,24 +69,27 @@ const factory = (Check) => { const className = classnames(theme.field, { [theme.disabled]: this.props.disabled, }, this.props.className); - const id = `field_${uuidv4()}`; + const inputId = `input_${this._id}`; + const labelId = `label_${this._id}`; return ( : null} {hint ? : null} - {error ? {error} : null} + {maxLength ? {length}/{maxLength} : null} + {error + ? + {error} + + : null} + {description + ? + {description} + + : null} {children} ); diff --git a/components/input/theme.css b/components/input/theme.css index a39cb8741..a4f3b0f4a 100644 --- a/components/input/theme.css +++ b/components/input/theme.css @@ -144,11 +144,20 @@ } .error, +.description, .counter { - color: var(--input-text-error-color); font-size: var(--input-label-font-size); line-height: var(--input-underline-height); - margin-bottom: calc(-1 * var(--input-underline-height)); +} + +.error { + color: var(--input-text-error-color); + display: block; +} + +.description { + color: var(--input-text-label-color); + display: block; } .counter { @@ -157,6 +166,10 @@ right: 0; } +.withCounter { + padding-right: 3.125rem; +} + .disabled > .inputElement { border-bottom-style: dotted; color: var(--input-text-disabled-text-color); diff --git a/components/list/ListDivider.js b/components/list/ListDivider.js index 8ad53022b..5c4cf5b9f 100644 --- a/components/list/ListDivider.js +++ b/components/list/ListDivider.js @@ -3,15 +3,19 @@ import PropTypes from 'prop-types'; import { themr } from 'react-css-themr'; import { LIST } from '../identifiers'; -const ListDivider = ({ inset, theme }) => ( -
-); +const ListDivider = ({ inset, li, theme }) => { + const hr = ( + + ); + return li ? : hr; +}; ListDivider.propTypes = { inset: PropTypes.bool, + li: PropTypes.bool, theme: PropTypes.shape({ divider: PropTypes.string, inset: PropTypes.string, @@ -20,6 +24,7 @@ ListDivider.propTypes = { ListDivider.defaultProps = { inset: false, + li: true, }; export default themr(LIST)(ListDivider); diff --git a/components/list/ListItem.js b/components/list/ListItem.js index 4562dbf5a..b8716d66a 100644 --- a/components/list/ListItem.js +++ b/components/list/ListItem.js @@ -113,19 +113,24 @@ const factory = (ripple, ListItemLayout, ListItemContent) => { } = this.props; const children = this.groupChildren(); const content = ; + const tabIndexProp = onClick && !to ? { + tabIndex, + 'aria-label': ariaLabel, + } : {}; return (
  • - {to ? {content} : content} + {to ? ( + {content} + ) : content} {children.ignored} {altText ? {altText} : null}
  • diff --git a/components/menu/MenuItem.d.ts b/components/menu/MenuItem.d.ts index 457718443..151e38837 100644 --- a/components/menu/MenuItem.d.ts +++ b/components/menu/MenuItem.d.ts @@ -29,6 +29,10 @@ export interface MenuItemTheme { } export interface MenuItemProps extends ReactToolbox.Props { + /** + * Aria-label for the
  • DOM element + */ + ariaLabel?: string; /** * The text to include in the menu item. Required. */ diff --git a/components/menu/MenuItem.js b/components/menu/MenuItem.js index 412f78c3f..5d0b562e1 100644 --- a/components/menu/MenuItem.js +++ b/components/menu/MenuItem.js @@ -9,6 +9,7 @@ import rippleFactory from '../ripple/Ripple'; const factory = (ripple) => { class MenuItem extends Component { static propTypes = { + ariaLabel: PropTypes.string, caption: PropTypes.string, children: PropTypes.node, className: PropTypes.string, @@ -52,6 +53,7 @@ const factory = (ripple) => { render() { const { + ariaLabel, caption, children, disabled, @@ -77,6 +79,7 @@ const factory = (ripple) => { role="menuitem" tabIndex={disabled ? '-1' : tabIndex} aria-disabled={disabled} + aria-label={ariaLabel} > {icon ? : null} {caption} diff --git a/components/progress_bar/ProgressBar.d.ts b/components/progress_bar/ProgressBar.d.ts index 90db36011..daf116fe9 100644 --- a/components/progress_bar/ProgressBar.d.ts +++ b/components/progress_bar/ProgressBar.d.ts @@ -47,6 +47,16 @@ export interface ProgressBarProps extends ReactToolbox.Props { * @default false */ disabled?: boolean; + /** + * Whether or not the progress bar should be hidden from the screen reader. + * + * Can be used if the caller wishes to provide more context for progress updates than just + * the percent. Also useful to work around an issue with IE11 + JAWS reading the aria-valuenow + * attribute instead of the aria-valuetext attribute on updates. + * + * @default false + */ + hiddenFromScreenReader?: boolean /** * Maximum value permitted. * @default 100 diff --git a/components/progress_bar/ProgressBar.js b/components/progress_bar/ProgressBar.js index 58eb44dfc..fc0520638 100644 --- a/components/progress_bar/ProgressBar.js +++ b/components/progress_bar/ProgressBar.js @@ -10,6 +10,7 @@ class ProgressBar extends Component { buffer: PropTypes.number, className: PropTypes.string, disabled: PropTypes.bool, + hiddenFromScreenReader: PropTypes.bool, max: PropTypes.number, min: PropTypes.number, mode: PropTypes.oneOf(['determinate', 'indeterminate']), @@ -36,6 +37,7 @@ class ProgressBar extends Component { mode: 'indeterminate', multicolor: false, type: 'linear', + hiddenFromScreenReader: false, value: 0, }; @@ -80,7 +82,18 @@ class ProgressBar extends Component { } render() { - const { className, disabled, max, min, mode, multicolor, type, theme, value } = this.props; + const { + className, + disabled, + hiddenFromScreenReader, + max, + min, + mode, + multicolor, + type, + theme, + value, + } = this.props; const _className = classnames(theme[type], { [theme.indeterminate]: mode === 'indeterminate', [theme.multicolor]: multicolor, @@ -95,6 +108,7 @@ class ProgressBar extends Component { aria-valuenow={value} aria-valuemin={min} aria-valuemax={max} + aria-hidden={hiddenFromScreenReader} className={_className} > {type === 'circular' ? this.renderCircular() : this.renderLinear()} diff --git a/components/radio/RadioButton.js b/components/radio/RadioButton.js index a2290236e..7e765df97 100644 --- a/components/radio/RadioButton.js +++ b/components/radio/RadioButton.js @@ -9,6 +9,7 @@ import radioFactory from './Radio'; const factory = (Radio) => { class RadioButton extends Component { + static propTypes = { checked: PropTypes.bool, children: PropTypes.node, @@ -39,6 +40,11 @@ const factory = (Radio) => { disabled: false, }; + constructor(props) { + super(props); + this._id = uuidv4(); + } + handleClick = (event) => { const { checked, disabled, onChange } = this.props; if (event.pageX !== 0 && event.pageY !== 0) this.blur(); @@ -72,24 +78,27 @@ const factory = (Radio) => { ...others } = this.props; const _className = classnames(theme[this.props.disabled ? 'disabled' : 'field'], className); - const id = `field_${uuidv4()}`; + const inputId = `input_${this._id}`; + const labelId = `label_${this._id}`; return (