Skip to content

Commit

Permalink
Tie field to label without explicit id, and to supporting elements (#130
Browse files Browse the repository at this point in the history
)

* Tie field to label without explicit id, and to supporting elements via aria attributes

* Don’t need a map

* Tweak id generation and add test

* Add comment about make_id

* Add changelog entries

* Tweak changelog entries
  • Loading branch information
zinckiwi authored Nov 10, 2017
1 parent 98010b5 commit d9bad70
Show file tree
Hide file tree
Showing 5 changed files with 61 additions and 6 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
- Changed the hover states of `EuiButtonEmpty` to look more like links. [#135](https://github.com/elastic/eui/pull/135)
- Added `transparentBackground` prop to `EuiCodeBlock`. Made light theme the default.
`EuiCode` now just wraps `EuiCodeBlock` so you can do inline highlighting. [#138](https://github.com/elastic/eui/pull/138)
- `EuiFormRow` generates its own unique `id` prop if none is provided. [(#130)](https://github.com/elastic/eui/pull/130)
- `EuiFormRow` associates help text and errors with the field element via ARIA attributes. [(#130)](https://github.com/elastic/eui/pull/130)

# [`0.0.1`](https://github.com/elastic/eui/tree/v0.0.1) Initial Release

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ exports[`EuiFormRow is rendered 1`] = `
class="euiFormRow testClass1 testClass2"
data-test-subj="test subject string"
>
<input />
<input
id="generated-id"
/>
</div>
`;
26 changes: 22 additions & 4 deletions src/components/form/form_row/form_row.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,15 @@ import { EuiFormHelpText } from '../form_help_text';
import { EuiFormErrorText } from '../form_error_text';
import { EuiFormLabel } from '../form_label';

import makeId from './make_id';

export class EuiFormRow extends Component {
constructor(props) {
super(props);

this.state = {
isFocused: false,
id: props.id || makeId()
};

this.onFocus = this.onFocus.bind(this);
Expand All @@ -40,13 +43,14 @@ export class EuiFormRow extends Component {
isInvalid,
error,
label,
id,
hasEmptyLabelSpace,
fullWidth,
className,
...rest
} = this.props;

const { id } = this.state;

const classes = classNames(
'euiFormRow',
{
Expand All @@ -60,7 +64,7 @@ export class EuiFormRow extends Component {

if (helpText) {
optionalHelpText = (
<EuiFormHelpText className="euiFormRow__text">
<EuiFormHelpText id={`${id}-help`} className="euiFormRow__text">
{helpText}
</EuiFormHelpText>
);
Expand All @@ -70,8 +74,8 @@ export class EuiFormRow extends Component {

if (error) {
const errorTexts = Array.isArray(error) ? error : [error];
optionalErrors = errorTexts.map(error => (
<EuiFormErrorText key={error} className="euiFormRow__text">
optionalErrors = errorTexts.map((error, i) => (
<EuiFormErrorText key={error} id={`${id}-error-${i}`} className="euiFormRow__text">
{error}
</EuiFormErrorText>
));
Expand All @@ -91,10 +95,24 @@ export class EuiFormRow extends Component {
);
}

const describingIds = [];
if (optionalHelpText) {
describingIds.push(optionalHelpText.props.id);
}
if (optionalErrors) {
optionalErrors.forEach(error => describingIds.push(error.props.id));
}

const optionalProps = {};
if (describingIds.length > 0) {
optionalProps[`aria-describedby`] = describingIds.join(` `);
}

const field = cloneElement(children, {
id,
onFocus: this.onFocus,
onBlur: this.onBlur,
...optionalProps
});

return (
Expand Down
30 changes: 29 additions & 1 deletion src/components/form/form_row/form_row.test.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import React from 'react';
import { render } from 'enzyme';
import { shallow, render } from 'enzyme';
import { requiredProps } from '../../../test/required_props';

import { EuiFormRow } from './form_row';

jest.mock(`./make_id`, () => () => `generated-id`);

describe('EuiFormRow', () => {
test('is rendered', () => {
const component = render(
Expand All @@ -15,4 +17,30 @@ describe('EuiFormRow', () => {
expect(component)
.toMatchSnapshot();
});

test('ties together parts for accessibility', () => {
const props = {
label: `Label`,
helpText: `Help text`,
error: [
`Error one`,
`Error two`
]
};

const tree = shallow(
<EuiFormRow {...requiredProps} {...props}>
<input />
</EuiFormRow>
);

expect(tree.find(`EuiFormLabel`).prop(`htmlFor`)).toEqual(`generated-id`);
expect(tree.find(`EuiFormHelpText`).prop(`id`)).toEqual(`generated-id-help`);
expect(tree.find(`EuiFormErrorText`).at(0).prop(`id`)).toEqual(`generated-id-error-0`);
expect(tree.find(`EuiFormErrorText`).at(1).prop(`id`)).toEqual(`generated-id-error-1`);

expect(tree.find(`input`).prop(`id`)).toEqual(`generated-id`);
expect(tree.find(`input`).prop(`aria-describedby`))
.toEqual(`generated-id-help generated-id-error-0 generated-id-error-1`);
});
});
5 changes: 5 additions & 0 deletions src/components/form/form_row/make_id.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// Generate statistically almost-certainly-unique `id`s for associating form
// inputs with their labels and other descriptive text elements.
export default function makeId() {
return Math.random().toString(36).slice(-8);
}

0 comments on commit d9bad70

Please sign in to comment.