Skip to content

Input icon accessibility #346

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Jun 25, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 28 additions & 6 deletions components/forms/input/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,11 @@ import InputIcon from '../../icon/input-icon';
// This component's `checkProps` which issues warnings to developers about properties when in development mode (similar to React's built in development tools)
import checkProps from './check-props';

// ### isFunction
import isFunction from 'lodash.isfunction';

import Button from '../../button';

// Remove the need for `React.PropTypes`
const { PropTypes } = React;

Expand Down Expand Up @@ -171,6 +176,24 @@ const Input = React.createClass({
};
},

getIconRender (position) {
if (position !== this.props.iconPosition) return '';

return isFunction(this.props.onIconClick)
? (<Button
iconVariant="small"
variant="base"
className="slds-input__icon slds-button--icon"
iconName={this.props.iconName}
iconCategory={this.props.iconCategory}
onClick={this.props.onIconClick}
/>)
: (<InputIcon
name={this.props.iconName}
category={this.props.iconCategory}
/>);
},

// ### Render
render () {
const {
Expand All @@ -188,7 +211,7 @@ const Input = React.createClass({
label,
onChange,
onClick,
onIconClick,
onIconClick, // eslint-disable-line no-unused-vars
placeholder,
readOnly,
required,
Expand Down Expand Up @@ -234,11 +257,8 @@ const Input = React.createClass({
'slds-has-divider--bottom': readOnly && !inlineEditTrigger
})}
>
{hasIcon && <InputIcon
name={iconName}
category={iconCategory}
onClick={onIconClick}
/>}
{hasIcon && this.getIconRender('left')}

{!readOnly && <input
{...props}
aria-controls={ariaControls}
Expand All @@ -253,6 +273,8 @@ const Input = React.createClass({
type={type}
value={value}
/>}
{hasIcon && this.getIconRender('right')}

{readOnly && <span className="slds-form-element__static" onClick={onClick}>
{value}
{inlineEditTrigger}
Expand Down
23 changes: 22 additions & 1 deletion stories/forms/input/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,18 @@ storiesOf(FORMS_INPUT, module)
placeholder="Placeholder Text"
/>
))
.add('Input with Clickable Icon', () => (
.add('Input with Left Clickable Icon', () => (
<Input
id="unique-id-123"
label="Input Label"
iconName="search"
iconCategory="utility"
iconPosition="left"
onIconClick={iconClicked('Search icon clicked')}
placeholder="Placeholder Text"
/>
))
.add('Input with Right Clickable Icon', () => (
<Input
label="Input Label"
iconName="close"
Expand All @@ -30,6 +41,16 @@ storiesOf(FORMS_INPUT, module)
placeholder="Placeholder Text"
/>
))
.add('Input with Non-Clickable Icon', () => (
<Input
id="unique-id-123"
label="Input Label"
iconName="search"
iconCategory="utility"
iconPosition="left"
placeholder="Placeholder Text"
/>
))
.add('Read Only Input', () => (
<Input
label="Input Label"
Expand Down
86 changes: 85 additions & 1 deletion tests/input/input.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import TestUtils from 'react-addons-test-utils';

import { SLDSInput } from '../../components';
const {
// Simulate,
Simulate,
findRenderedDOMComponentWithTag,
scryRenderedDOMComponentsWithTag,
findRenderedDOMComponentWithClass
Expand Down Expand Up @@ -147,4 +147,88 @@ describe('SLDS INPUT **************************************************', () =>
expect(error.textContent).to.equal('Error Message');
});
});

describe('Input with Left Clickable Icon', () => {
let component;
let elementControl;
let leftButton;

const clickCallback = sinon.spy();

beforeEach(() => {
component = getInput({ iconName: 'search', iconCategory: 'utility', iconPosition: 'left', onIconClick: clickCallback });
leftButton = findRenderedDOMComponentWithTag(component, 'button');
elementControl = findRenderedDOMComponentWithClass(component, 'slds-form-element__control');
});

afterEach(() => {
removeInput();
});

it('element control has class "slds-input-has-icon"', () => {
expect(elementControl.className).to.include('slds-input-has-icon');
});

it('icon renders button BEFORE input in DOM', () => {
const render = elementControl.innerHTML;
expect(render.indexOf('<button')).to.be.below(render.indexOf('<input'));
});

it('icon can be clicked', () => {
TestUtils.Simulate.click(leftButton);

expect(clickCallback.calledOnce).to.be.true;
});
});

describe('Input with Right Clickable Icon', () => {
let component;
let elementControl;
let leftButton;

const clickCallback = sinon.spy();

beforeEach(() => {
component = getInput({ iconName: 'search', iconCategory: 'utility', iconPosition: 'right', onIconClick: clickCallback });
leftButton = findRenderedDOMComponentWithTag(component, 'button');
elementControl = findRenderedDOMComponentWithClass(component, 'slds-form-element__control');
});

afterEach(() => {
removeInput();
});

it('element control has class "slds-input-has-icon"', () => {
expect(elementControl.className).to.include('slds-input-has-icon');
});

it('icon renders button AFTER input in DOM', () => {
const render = elementControl.innerHTML;
expect(render.indexOf('<button')).to.be.above(render.indexOf('<input'));
});

it('icon can be clicked', () => {
TestUtils.Simulate.click(leftButton);

expect(clickCallback.calledOnce).to.be.true;
});
});

describe('Input with Non-Clickable Icon', () => {
let component;
let elementControl;

beforeEach(() => {
component = getInput({ iconName: 'search', iconCategory: 'utility', iconPosition: 'right' });
elementControl = findRenderedDOMComponentWithClass(component, 'slds-form-element__control');
});

afterEach(() => {
removeInput();
});

it('button tag does not exist', () => {
expect(elementControl.getElementsByTagName('button')[0]).to.not.be.ok;
});
});
});