Skip to content

Commit 74e7506

Browse files
authored
Merge pull request #337 from iowillhoit/input-label
Displays label properly on <SLDSInput> when both `label` and `assistiveText` are defined
2 parents eea01f7 + 58e27b6 commit 74e7506

File tree

6 files changed

+277
-6
lines changed

6 files changed

+277
-6
lines changed

components/forms/input/check-props.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
1111
/* eslint-disable import/no-mutable-exports */
1212

1313
import oneOfRequiredProperty from '../../../utilities/warning/one-of-required-property';
14+
import onlyOneOfProperties from '../../../utilities/warning/only-one-of-properties';
1415

1516
let checkProps = function () {};
1617

@@ -21,6 +22,10 @@ if (process.env.NODE_ENV !== 'production') {
2122
assistiveText: props.assistiveText,
2223
label: props.label
2324
});
25+
onlyOneOfProperties(COMPONENT, {
26+
assistiveText: props.assistiveText,
27+
label: props.label
28+
});
2429
/* eslint-enable max-len */
2530
};
2631
}

components/forms/input/index.jsx

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,7 @@ const Input = React.createClass({
191191
const hasIcon = iconCategory && iconName;
192192

193193
// One of these is required to pass accessibility tests
194-
const labelText = assistiveText || label;
194+
const labelText = label || assistiveText;
195195

196196
return (
197197
<div
@@ -201,14 +201,19 @@ const Input = React.createClass({
201201
},
202202
className)}
203203
>
204-
{labelText && (!readOnly
205-
? <label className={classNames('slds-form-element__label', { 'slds-assistive-text': assistiveText })} htmlFor={id}>
204+
{labelText && (readOnly
205+
? <span
206+
className={classNames('slds-form-element__label', { 'slds-assistive-text': assistiveText && !label })}
207+
>
208+
{labelText}
209+
</span>
210+
: <label
211+
className={classNames('slds-form-element__label', { 'slds-assistive-text': assistiveText && !label })} htmlFor={id}
212+
>
206213
{required && <abbr className="slds-required" title="required">*</abbr>}
207214
{labelText}
208215
</label>
209-
: <span className={classNames('slds-form-element__label', { 'slds-assistive-text': assistiveText })}>
210-
{labelText}
211-
</span>)}
216+
)}
212217
<div
213218
className={classNames('slds-form-element__control', hasIcon && [
214219
'slds-input-has-icon',

stories/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,4 @@ export default from './card';
66
export default from './context-bar';
77
export default from './data-table';
88
export default from './icon';
9+
export default from './input';

stories/input/index.jsx

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import React from 'react';
2+
import { storiesOf, action } from '@kadira/storybook';
3+
4+
import { FORMS_INPUT } from '../../utilities/constants';
5+
import Input from '../../components/forms/input';
6+
7+
const iconClicked = action;
8+
9+
storiesOf(FORMS_INPUT, module)
10+
.addDecorator(getStory => <div className="slds-p-around--medium">{getStory()}</div>)
11+
.add('Standard Input', () => (
12+
<Input
13+
id="unique-id-1"
14+
label="Input Label"
15+
placeholder="Placeholder Text"
16+
/>
17+
))
18+
.add('Assistive Text, No Label', () => (
19+
<Input
20+
id="unique-id-1"
21+
assistiveText="Assistive Text"
22+
placeholder="Placeholder Text"
23+
/>
24+
))
25+
.add('Input with Clickable Icon', () => (
26+
<Input
27+
id="unique-id-2"
28+
label="Input Label"
29+
iconName="close"
30+
iconCategory="utility"
31+
iconPosition="right"
32+
onIconClick={iconClicked('Clear icon clicked')}
33+
placeholder="Placeholder Text"
34+
/>
35+
))
36+
.add('Read Only Input', () => (
37+
<Input
38+
id="unique-id-3"
39+
label="Input Label"
40+
readOnly
41+
value="Read Only Value"
42+
/>
43+
))
44+
.add('Required Input in Error State', () => (
45+
<Input
46+
id="unique-id-4"
47+
label="Input Label"
48+
required
49+
errorText="Error Message"
50+
placeholder="Placeholder Text"
51+
/>
52+
));
53+

tests/input/input.test.jsx

Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
import React from 'react';
2+
import ReactDOM from 'react-dom';
3+
import { expect } from 'chai';
4+
import assign from 'lodash.assign';
5+
import TestUtils from 'react-addons-test-utils';
6+
7+
import { SLDSInput } from '../../components';
8+
const {
9+
// Simulate,
10+
findRenderedDOMComponentWithTag,
11+
scryRenderedDOMComponentsWithTag,
12+
findRenderedDOMComponentWithClass
13+
} = TestUtils;
14+
15+
describe('SLDS INPUT **************************************************', () => {
16+
const defaultProps = {
17+
id: 'unique-id-2',
18+
placeholder: 'Placeholder Text'
19+
};
20+
21+
let body;
22+
23+
const renderInput = instance => {
24+
body = document.createElement('div');
25+
document.body.appendChild(body);
26+
return ReactDOM.render(instance, body);
27+
};
28+
29+
function removeInput () {
30+
ReactDOM.unmountComponentAtNode(body);
31+
document.body.removeChild(body);
32+
}
33+
34+
const createInput = (props) => React.createElement(SLDSInput, assign({}, defaultProps, props));
35+
const getInput = (props) => renderInput(createInput(props));
36+
37+
describe('Standard Input with Label', () => {
38+
let component;
39+
let wrapper;
40+
let input;
41+
let label;
42+
43+
beforeEach(() => {
44+
component = getInput({ label: 'Input Label' });
45+
wrapper = findRenderedDOMComponentWithClass(component, 'slds-form-element');
46+
input = findRenderedDOMComponentWithTag(component, 'input');
47+
label = findRenderedDOMComponentWithClass(component, 'slds-form-element__label');
48+
});
49+
50+
afterEach(() => {
51+
removeInput();
52+
});
53+
54+
it('is wrapped in div with class "slds-form-element"', () => {
55+
expect(wrapper.className).to.include('slds-form-element');
56+
});
57+
58+
it('renders label', () => {
59+
expect(label.textContent).to.equal('Input Label');
60+
});
61+
62+
it('renders input element with class "slds-input"', () => {
63+
expect(input.className).to.include('slds-input');
64+
});
65+
66+
it('has an id', () => {
67+
expect(input.getAttribute('id')).to.be.ok;
68+
});
69+
70+
it('renders placeholder text', () => {
71+
expect(input.getAttribute('placeholder')).to.equal('Placeholder Text');
72+
});
73+
});
74+
75+
describe('Input without Label', () => {
76+
let component;
77+
let label;
78+
79+
beforeEach(() => {
80+
component = getInput({ assistiveText: 'Assistive Label' });
81+
label = findRenderedDOMComponentWithClass(component, 'slds-form-element__label');
82+
});
83+
84+
afterEach(() => {
85+
removeInput();
86+
});
87+
88+
it('renders label (assitive)', () => {
89+
expect(label.textContent).to.equal('Assistive Label');
90+
});
91+
92+
it('label has class "slds-assistive-text"', () => {
93+
expect(label.className).to.include('slds-assistive-text');
94+
});
95+
});
96+
97+
describe('Read Only Input', () => {
98+
let component;
99+
let label;
100+
let input;
101+
102+
beforeEach(() => {
103+
component = getInput({ label: 'Input Label', readOnly: true });
104+
label = scryRenderedDOMComponentsWithTag(component, 'span')[0];
105+
input = scryRenderedDOMComponentsWithTag(component, 'span')[1];
106+
});
107+
108+
afterEach(() => {
109+
removeInput();
110+
});
111+
112+
it('label is a span and has class "slds-form-element__label"', () => {
113+
expect(label.className).to.include('slds-form-element__label');
114+
});
115+
116+
it('input is a span and has class "slds-form-element__static"', () => {
117+
expect(input.className).to.include('slds-form-element__static');
118+
});
119+
});
120+
121+
describe('Required Input in Error State', () => {
122+
let component;
123+
let wrapper;
124+
let error;
125+
126+
beforeEach(() => {
127+
component = getInput({ label: 'Input Label', required: true, errorText: 'Error Message' });
128+
wrapper = findRenderedDOMComponentWithClass(component, 'slds-form-element');
129+
error = findRenderedDOMComponentWithClass(component, 'slds-form-element__help');
130+
});
131+
132+
afterEach(() => {
133+
removeInput();
134+
});
135+
136+
it('input wrapper has class "is-required"', () => {
137+
expect(wrapper.className).to.include('is-required');
138+
});
139+
140+
it('input wrapper has class "slds-has-error"', () => {
141+
expect(wrapper.className).to.include('slds-has-error');
142+
});
143+
144+
it('renders error message', () => {
145+
expect(error.textContent).to.equal('Error Message');
146+
});
147+
});
148+
149+
150+
// describe('Input with Clickable Icon', () => {
151+
// let component;
152+
// let input;
153+
// let label;
154+
// let icon;
155+
156+
// beforeEach(() => {
157+
// component = getInput({ label: 'Input Label' });
158+
// input = findRenderedDOMComponentWithTag(component, 'input');
159+
// inputWrapper = findRenderedDOMComponentWithClass(component, 'slds-form-element__control');
160+
// label = findRenderedDOMComponentWithClass(component, 'slds-form-element__label');
161+
// icon = findRenderedDOMComponentWithTag(component, 'svg');
162+
// });
163+
164+
// afterEach(() => {
165+
// removeInput();
166+
// });
167+
168+
// // input wrapper has class "slds-input-has-icon"
169+
// // icon has class "slds-input__icon"
170+
// // icon can be clicked (Simulate)
171+
// });
172+
});
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/*
2+
Copyright (c) 2015, salesforce.com, inc. All rights reserved.
3+
4+
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
5+
Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
6+
Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
7+
Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
8+
9+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
10+
*/
11+
/* eslint-disable indent, import/no-mutable-exports */
12+
13+
// This function will deliver an warning message to the browser console if extraneous properties are defined (falsey).
14+
import warning from 'warning';
15+
16+
let onlyOneOf = function () {};
17+
18+
if (process.env.NODE_ENV !== 'production') {
19+
const hasWarned = {};
20+
21+
onlyOneOf = function (control, selectedProps, comment) {
22+
const additionalComment = comment ? ` ${comment}` : '';
23+
let keys = Object.keys(selectedProps);
24+
keys = keys.filter((key) => selectedProps[key]);
25+
26+
if (!hasWarned[control]) {
27+
/* eslint-disable max-len */
28+
warning((keys.length === 1), `[Design System React] Only one of the following props is needed by ${control}: [${keys.join()}].${additionalComment}`);
29+
/* eslint-enable max-len */
30+
hasWarned[control] = !!selectedProps;
31+
}
32+
};
33+
}
34+
35+
export default onlyOneOf;

0 commit comments

Comments
 (0)