Skip to content

Commit f490859

Browse files
authored
Merge pull request #346 from iowillhoit/GH315_input-icon-accessibility
Input icon accessibility
2 parents 375c200 + ffb0c8c commit f490859

File tree

3 files changed

+135
-8
lines changed

3 files changed

+135
-8
lines changed

components/forms/input/index.jsx

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,11 @@ import InputIcon from '../../icon/input-icon';
3333
// This component's `checkProps` which issues warnings to developers about properties when in development mode (similar to React's built in development tools)
3434
import checkProps from './check-props';
3535

36+
// ### isFunction
37+
import isFunction from 'lodash.isfunction';
38+
39+
import Button from '../../button';
40+
3641
// Remove the need for `React.PropTypes`
3742
const { PropTypes } = React;
3843

@@ -171,6 +176,24 @@ const Input = React.createClass({
171176
};
172177
},
173178

179+
getIconRender (position) {
180+
if (position !== this.props.iconPosition) return '';
181+
182+
return isFunction(this.props.onIconClick)
183+
? (<Button
184+
iconVariant="small"
185+
variant="base"
186+
className="slds-input__icon slds-button--icon"
187+
iconName={this.props.iconName}
188+
iconCategory={this.props.iconCategory}
189+
onClick={this.props.onIconClick}
190+
/>)
191+
: (<InputIcon
192+
name={this.props.iconName}
193+
category={this.props.iconCategory}
194+
/>);
195+
},
196+
174197
// ### Render
175198
render () {
176199
const {
@@ -188,7 +211,7 @@ const Input = React.createClass({
188211
label,
189212
onChange,
190213
onClick,
191-
onIconClick,
214+
onIconClick, // eslint-disable-line no-unused-vars
192215
placeholder,
193216
readOnly,
194217
required,
@@ -234,11 +257,8 @@ const Input = React.createClass({
234257
'slds-has-divider--bottom': readOnly && !inlineEditTrigger
235258
})}
236259
>
237-
{hasIcon && <InputIcon
238-
name={iconName}
239-
category={iconCategory}
240-
onClick={onIconClick}
241-
/>}
260+
{hasIcon && this.getIconRender('left')}
261+
242262
{!readOnly && <input
243263
{...props}
244264
aria-controls={ariaControls}
@@ -253,6 +273,8 @@ const Input = React.createClass({
253273
type={type}
254274
value={value}
255275
/>}
276+
{hasIcon && this.getIconRender('right')}
277+
256278
{readOnly && <span className="slds-form-element__static" onClick={onClick}>
257279
{value}
258280
{inlineEditTrigger}

stories/forms/input/index.jsx

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,18 @@ storiesOf(FORMS_INPUT, module)
2020
placeholder="Placeholder Text"
2121
/>
2222
))
23-
.add('Input with Clickable Icon', () => (
23+
.add('Input with Left Clickable Icon', () => (
24+
<Input
25+
id="unique-id-123"
26+
label="Input Label"
27+
iconName="search"
28+
iconCategory="utility"
29+
iconPosition="left"
30+
onIconClick={iconClicked('Search icon clicked')}
31+
placeholder="Placeholder Text"
32+
/>
33+
))
34+
.add('Input with Right Clickable Icon', () => (
2435
<Input
2536
label="Input Label"
2637
iconName="close"
@@ -30,6 +41,16 @@ storiesOf(FORMS_INPUT, module)
3041
placeholder="Placeholder Text"
3142
/>
3243
))
44+
.add('Input with Non-Clickable Icon', () => (
45+
<Input
46+
id="unique-id-123"
47+
label="Input Label"
48+
iconName="search"
49+
iconCategory="utility"
50+
iconPosition="left"
51+
placeholder="Placeholder Text"
52+
/>
53+
))
3354
.add('Read Only Input', () => (
3455
<Input
3556
label="Input Label"

tests/input/input.test.jsx

Lines changed: 85 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import TestUtils from 'react-addons-test-utils';
99

1010
import { SLDSInput } from '../../components';
1111
const {
12-
// Simulate,
12+
Simulate,
1313
findRenderedDOMComponentWithTag,
1414
scryRenderedDOMComponentsWithTag,
1515
findRenderedDOMComponentWithClass
@@ -147,4 +147,88 @@ describe('SLDS INPUT **************************************************', () =>
147147
expect(error.textContent).to.equal('Error Message');
148148
});
149149
});
150+
151+
describe('Input with Left Clickable Icon', () => {
152+
let component;
153+
let elementControl;
154+
let leftButton;
155+
156+
const clickCallback = sinon.spy();
157+
158+
beforeEach(() => {
159+
component = getInput({ iconName: 'search', iconCategory: 'utility', iconPosition: 'left', onIconClick: clickCallback });
160+
leftButton = findRenderedDOMComponentWithTag(component, 'button');
161+
elementControl = findRenderedDOMComponentWithClass(component, 'slds-form-element__control');
162+
});
163+
164+
afterEach(() => {
165+
removeInput();
166+
});
167+
168+
it('element control has class "slds-input-has-icon"', () => {
169+
expect(elementControl.className).to.include('slds-input-has-icon');
170+
});
171+
172+
it('icon renders button BEFORE input in DOM', () => {
173+
const render = elementControl.innerHTML;
174+
expect(render.indexOf('<button')).to.be.below(render.indexOf('<input'));
175+
});
176+
177+
it('icon can be clicked', () => {
178+
TestUtils.Simulate.click(leftButton);
179+
180+
expect(clickCallback.calledOnce).to.be.true;
181+
});
182+
});
183+
184+
describe('Input with Right Clickable Icon', () => {
185+
let component;
186+
let elementControl;
187+
let leftButton;
188+
189+
const clickCallback = sinon.spy();
190+
191+
beforeEach(() => {
192+
component = getInput({ iconName: 'search', iconCategory: 'utility', iconPosition: 'right', onIconClick: clickCallback });
193+
leftButton = findRenderedDOMComponentWithTag(component, 'button');
194+
elementControl = findRenderedDOMComponentWithClass(component, 'slds-form-element__control');
195+
});
196+
197+
afterEach(() => {
198+
removeInput();
199+
});
200+
201+
it('element control has class "slds-input-has-icon"', () => {
202+
expect(elementControl.className).to.include('slds-input-has-icon');
203+
});
204+
205+
it('icon renders button AFTER input in DOM', () => {
206+
const render = elementControl.innerHTML;
207+
expect(render.indexOf('<button')).to.be.above(render.indexOf('<input'));
208+
});
209+
210+
it('icon can be clicked', () => {
211+
TestUtils.Simulate.click(leftButton);
212+
213+
expect(clickCallback.calledOnce).to.be.true;
214+
});
215+
});
216+
217+
describe('Input with Non-Clickable Icon', () => {
218+
let component;
219+
let elementControl;
220+
221+
beforeEach(() => {
222+
component = getInput({ iconName: 'search', iconCategory: 'utility', iconPosition: 'right' });
223+
elementControl = findRenderedDOMComponentWithClass(component, 'slds-form-element__control');
224+
});
225+
226+
afterEach(() => {
227+
removeInput();
228+
});
229+
230+
it('button tag does not exist', () => {
231+
expect(elementControl.getElementsByTagName('button')[0]).to.not.be.ok;
232+
});
233+
});
150234
});

0 commit comments

Comments
 (0)