diff --git a/CHANGELOG.md b/CHANGELOG.md index 55fc299..bf64854 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Change Log +## 2.1.2 + +* Add htmlFor/id attribute to labels associating them to inputs + ## 2.1.1 * Increase contrast in disabled mini button text diff --git a/src/components/AutocompleteInput.jsx b/src/components/AutocompleteInput.jsx index 135e27b..1aec5c7 100644 --- a/src/components/AutocompleteInput.jsx +++ b/src/components/AutocompleteInput.jsx @@ -480,6 +480,7 @@ export default class AutocompleteInput extends Component { asText, embedded, readOnly, + id, } = this.props; const { @@ -492,6 +493,7 @@ export default class AutocompleteInput extends Component { readOnly={readOnly} value={getDisplayName(value)} embedded={embedded} + id={id} /> ); } diff --git a/src/components/CheckboxInput.jsx b/src/components/CheckboxInput.jsx index eeff357..93be7f7 100644 --- a/src/components/CheckboxInput.jsx +++ b/src/components/CheckboxInput.jsx @@ -116,14 +116,12 @@ export default class CheckboxInput extends Component { ); } - // FIXME: Don't break jsx-a11y/label-has-associated-control. - /* eslint-disable - jsx-a11y/label-has-associated-control, + jsx-a11y/no-static-element-interactions, jsx-a11y/no-noninteractive-element-interactions, jsx-a11y/click-events-have-key-events */ return ( - + ); /* eslint-enable - jsx-a11y/label-has-associated-control, + jsx-a11y/no-static-element-interactions, jsx-a11y/no-noninteractive-element-interactions, jsx-a11y/click-events-have-key-events */ } diff --git a/src/components/CustomCompoundInput.jsx b/src/components/CustomCompoundInput.jsx index a322649..2b72d26 100644 --- a/src/components/CustomCompoundInput.jsx +++ b/src/components/CustomCompoundInput.jsx @@ -6,11 +6,14 @@ import get from 'lodash/get'; import { getPath, pathPropType } from '../helpers/pathHelpers'; import { isInput } from '../helpers/inputHelpers'; import styles from '../../styles/cspace-input/CompoundInput.css'; +import labelToLegend from '../helpers/labelToLegend'; const propTypes = { children: PropTypes.node, className: PropTypes.string, defaultChildSubpath: pathPropType, + id: PropTypes.string, + label: PropTypes.node, name: PropTypes.string, // TODO: Stop using propTypes in isInput. Until then, these unused props need to be declared so // this component is recognized as an input. @@ -29,10 +32,12 @@ const defaultProps = { children: undefined, className: undefined, defaultChildSubpath: undefined, + label: undefined, name: undefined, parentPath: undefined, subpath: undefined, readOnly: undefined, + id: undefined, value: {}, }; @@ -62,6 +67,7 @@ export default class CustomCompoundInput extends Component { decorateInputs(children) { const { readOnly, + id: parentId, } = this.props; return React.Children.map(children, (child) => { @@ -88,12 +94,19 @@ export default class CustomCompoundInput extends Component { subpath = defaultChildSubpath; } - return React.cloneElement(child, { + const overrides = { readOnly, subpath, parentPath: getPath(this.props), value: getChildValue(value, subpath, name), - }); + }; + + // Propagate id to inputs that don't have one + if (!child.props.id && parentId && name) { + overrides.id = `${parentId}-${name}`; + } + + return React.cloneElement(child, overrides); } return React.cloneElement(child, { @@ -106,6 +119,7 @@ export default class CustomCompoundInput extends Component { const { children, className, + label, name, readOnly, } = this.props; @@ -114,11 +128,14 @@ export default class CustomCompoundInput extends Component { [styles.readOnly]: readOnly, }); + const legend = labelToLegend(label); + return (
+ {legend} {this.decorateInputs(children)}
); @@ -127,3 +144,4 @@ export default class CustomCompoundInput extends Component { CustomCompoundInput.propTypes = propTypes; CustomCompoundInput.defaultProps = defaultProps; +CustomCompoundInput.useLegend = true; diff --git a/src/components/InputTable.jsx b/src/components/InputTable.jsx index 81cda21..3aed2c7 100644 --- a/src/components/InputTable.jsx +++ b/src/components/InputTable.jsx @@ -3,10 +3,13 @@ import PropTypes from 'prop-types'; import InputTableHeader from './InputTableHeader'; import InputTableRow from './InputTableRow'; import styles from '../../styles/cspace-input/InputTable.css'; +import labelToLegend from '../helpers/labelToLegend'; const propTypes = { children: PropTypes.node, embedded: PropTypes.bool, + id: PropTypes.string, + label: PropTypes.node, renderLabel: PropTypes.func, renderAriaLabel: PropTypes.func, }; @@ -14,6 +17,8 @@ const propTypes = { const defaultProps = { children: undefined, embedded: undefined, + id: undefined, + label: undefined, renderLabel: undefined, renderAriaLabel: undefined, }; @@ -22,12 +27,17 @@ export default function InputTable(props) { const { children, embedded, + id, + label, renderLabel, renderAriaLabel, } = props; + const legend = labelToLegend(label); + return ( -
+
+ {legend} {children} @@ -35,9 +45,10 @@ export default function InputTable(props) { {children} -
+ ); } InputTable.propTypes = propTypes; InputTable.defaultProps = defaultProps; +InputTable.useLegend = true; diff --git a/src/components/Label.jsx b/src/components/Label.jsx index 9e47cbf..46c5733 100644 --- a/src/components/Label.jsx +++ b/src/components/Label.jsx @@ -6,12 +6,16 @@ const propTypes = { children: PropTypes.node, readOnly: PropTypes.bool, required: PropTypes.bool, + htmlFor: PropTypes.string, + id: PropTypes.string, }; const defaultProps = { children: undefined, readOnly: undefined, required: undefined, + htmlFor: undefined, + id: undefined, }; /** @@ -22,14 +26,14 @@ export default function Label(props) { children, readOnly, required, + htmlFor, + id, } = props; const className = (required && !readOnly) ? styles.required : styles.common; return ( - // FIXME: Set the htmlFor prop to associate the labeled control. - // eslint-disable-next-line jsx-a11y/label-has-associated-control -