Skip to content

Commit edd8660

Browse files
authored
Merge pull request #751 from interactivellama/GH-626---checkbox-needs-an-indeterminate-state-21
Gut internal intermediate state from checkbox. A-:ok_hand: Though I noticed while reviewing this PR that uncontrolled checkboxen aren't responding to keyboard-driven control as they ought. As it was _already_ like that, I've not held this PR up for it but rather created [a ticket to fix it](#753).
2 parents 9186818 + da4393c commit edd8660

File tree

7 files changed

+232
-41
lines changed

7 files changed

+232
-41
lines changed

RELEASENOTES.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ _Salesforce Lightning Design System :: React Components :: design-system-react_
55

66
These are changes that have backwards-compatible solutions present and that compatibiity will be removed at a breaking change release in the future.
77

8+
- `Checkbox`'s `onChange` now recieves `event, {checked: [boolean], indeterminate: [boolean] }`. Previously, `checked` was the first parameter.
89
- `TabsPane` has be replaced with `TabsPanel`.
910
- `Input`'s props: `iconPosition`, `iconAssistiveText`, `iconCategory`, `iconName`, `onIconClick` are deprecated. An `Icon` component should be used instead.
1011
- `DataTable`'s' `collection`, `onSelect`, `onDeselect` are deprecated.

components/data-table/head.jsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ const DataTableHead = React.createClass({
3333
// ### Prop Types
3434
propTypes: {
3535
allSelected: PropTypes.bool.isRequired,
36+
indeterminateSelected: PropTypes.bool.isRequired,
3637
canSelectRows: PropTypes.bool.isRequired,
3738
columns: PropTypes.arrayOf(
3839
PropTypes.shape({
@@ -56,6 +57,7 @@ const DataTableHead = React.createClass({
5657
<Checkbox
5758
assistiveText="Select All"
5859
checked={this.props.allSelected}
60+
indeterminate={this.props.indeterminateSelected}
5961
id={`${this.props.id}-SelectAll`}
6062
name="SelectAll"
6163
onChange={this.props.onToggleAll}

components/data-table/index.jsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,7 @@ const DataTable = React.createClass({
161161
const numSelected = count(this.props.selection);
162162
const canSelectRows = this.props.selectRows && numRows > 0;
163163
const allSelected = canSelectRows && numRows === numSelected;
164+
const indeterminateSelected = canSelectRows && numRows !== numSelected && numSelected !== 0;
164165
const columns = [];
165166
let RowActions = null;
166167

@@ -220,6 +221,7 @@ const DataTable = React.createClass({
220221
>
221222
<DataTableHead
222223
allSelected={allSelected}
224+
indeterminateSelected={indeterminateSelected}
223225
canSelectRows={canSelectRows}
224226
columns={columns}
225227
id={`${this.props.id}_${DATA_TABLE_HEAD}`}

components/forms/checkbox/index.jsx

Lines changed: 52 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ const Checkbox = React.createClass({
5151
*/
5252
assistiveText: React.PropTypes.string,
5353
/**
54-
* The Checkbox is a controlled component, and will always be in this state.
54+
* The Checkbox is a controlled component, and will always be in this state. If checked is not defined, the state of the uncontrolled native `input` component will be used.
5555
*/
5656
checked: React.PropTypes.bool,
5757
/**
@@ -74,6 +74,10 @@ const Checkbox = React.createClass({
7474
* A unique ID is needed in order to support keyboard navigation and ARIA support.
7575
*/
7676
id: PropTypes.string,
77+
/**
78+
* The Checkbox will be indeterminate if its state can not be figured out or is partially checked. Once a checkbox is indeterminate, a click should cause it to be checked. Since a user cannot put a checkbox into an indeterminate state, it is assumed you are controlling the value of `checked` with the parent, also, and that this is a controlled component.
79+
*/
80+
indeterminate: React.PropTypes.bool,
7781
/**
7882
* An optional label for the Checkbox.
7983
*/
@@ -92,24 +96,24 @@ const Checkbox = React.createClass({
9296
required: PropTypes.bool
9397
},
9498

95-
getDefaultProps () {
96-
return {
97-
id: shortid.generate()
98-
};
99+
componentWillMount () {
100+
this.generatedId = shortid.generate();
99101
},
100102

101103
// ### Render
102104
render () {
103105
const {
104106
assistiveText,
105107
checked,
108+
indeterminate,
106109
className,
107110
disabled,
108111
errorText,
109112
label,
110113
name,
111114
onChange, // eslint-disable-line no-unused-vars
112115
required,
116+
id,
113117

114118
// ### Additional properties
115119
// Using [object destructuring](https://facebook.github.io/react/docs/transferring-props.html#transferring-with-...-in-jsx) to pass on any properties which are not explicitly defined.
@@ -126,46 +130,67 @@ const Checkbox = React.createClass({
126130
onKeyDown={this.handleKeyDown}
127131
>
128132
<div className="slds-form-element__control">
129-
<label className="slds-checkbox">
133+
<span className="slds-checkbox">
130134
{required ? <abbr className="slds-required" title="required">*</abbr> : null}
131135
<input
132136
{...props}
137+
id={id || this.generatedId}
133138
checked={checked}
134139
name={name}
135140
disabled={disabled}
136141
onChange={this.handleChange}
137142
type="checkbox"
143+
ref={
144+
(component) => {
145+
if (component) {
146+
component.indeterminate = indeterminate;
147+
}
148+
this.input = component;
149+
}}
138150
/>
139-
<span className="slds-checkbox--faux"></span>
140-
{label
141-
? <span className="slds-form-element__label">
142-
{label}
143-
</span>
144-
: null}
145-
{assistiveText
146-
? <span className="slds-assistive-text">
147-
{assistiveText}
148-
</span>
149-
: null}
150-
</label>
151+
<label className="slds-checkbox__label" htmlFor={id || this.generatedId}>
152+
<span className="slds-checkbox--faux"></span>
153+
{label
154+
? <span className="slds-form-element__label">
155+
{label}
156+
</span>
157+
: null}
158+
{assistiveText
159+
? <span className="slds-assistive-text">
160+
{assistiveText}
161+
</span>
162+
: null}
163+
</label>
164+
</span>
151165
</div>
152166
{errorText ? <div className="slds-form-element__help">{errorText}</div> : null}
153167
</div>
154168
);
155169
},
156170

157-
handleChange (e) {
158-
if (isFunction(this.props.onChange)) {
159-
this.props.onChange(!this.props.checked, e);
171+
handleChange (event) {
172+
const value = event.target.checked;
173+
const {
174+
checked,
175+
indeterminate,
176+
onChange
177+
} = this.props;
178+
179+
if (isFunction(onChange)) {
180+
// `checked` is present twice to maintain backwards compatibility. Please remove first parameter `value` on the next breaking change.
181+
onChange(value, event, {
182+
checked: indeterminate ? true : !checked,
183+
indeterminate: false
184+
});
160185
}
161186
},
162187

163-
handleKeyDown (e) {
164-
if (e.keyCode) {
165-
if (e.keyCode === KEYS.ENTER ||
166-
e.keyCode === KEYS.SPACE) {
167-
EventUtil.trapImmediate(e);
168-
this.handleChange(e);
188+
handleKeyDown (event) {
189+
if (event.keyCode) {
190+
if (event.keyCode === KEYS.ENTER ||
191+
event.keyCode === KEYS.SPACE) {
192+
EventUtil.trapImmediate(event);
193+
this.handleChange(event);
169194
}
170195
}
171196
}

stories/data-table/index.jsx

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,29 @@ const DemoDataTable = React.createClass({
4343
name: 'Cloud City',
4444
count: 101280,
4545
lastModified: 'Today'
46+
}, {
47+
id: '2FSH2DP0LY',
48+
name: 'IoT',
49+
count: 976,
50+
lastModified: 'Yesterday'
51+
}, {
52+
id: '8NE888QKV1',
53+
name: 'IoT + Anypoint Connectors',
54+
count: 54976,
55+
lastModified: 'Today'
56+
}, {
57+
id: 'M4D37GW83H',
58+
name: 'Salesforce Tower',
59+
count: 101280,
60+
lastModified: 'Today'
4661
}
4762
],
48-
selection: []
63+
selection: [{
64+
id: 'M4D37GW83H',
65+
name: 'Salesforce Tower',
66+
count: 101280,
67+
lastModified: 'Today'
68+
}]
4969
};
5070
},
5171

@@ -83,7 +103,8 @@ const DemoDataTable = React.createClass({
83103

84104
handleChange (selection, ...rest) {
85105
action('change')(selection, ...rest);
86-
106+
console.log("selection", selection);
107+
console.dir(...rest);
87108
this.setState({ selection });
88109
},
89110

stories/forms/checkbox/index.jsx

Lines changed: 123 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,83 @@ import { storiesOf, action } from '@kadira/storybook';
33

44
import { FORMS_CHECKBOX } from '../../../utilities/constants';
55
import Checkbox from '../../../components/forms/checkbox';
6+
import Button from '../../../components/button';
7+
8+
const CheckboxIndeterminate = React.createClass({
9+
displayName: `${FORMS_CHECKBOX}_INDETERMINATE`,
10+
11+
getInitialState () {
12+
return {
13+
indeterminate: true,
14+
checked: true,
15+
currentStateHelper: 'Indeterminate'
16+
};
17+
},
18+
19+
handleChange (checked, event, data) {
20+
const checkedLabel = data.checked ? 'Checked' : 'Unchecked';
21+
this.setState({
22+
checked: data.checked,
23+
currentStateHelper: data.indeterminate ? 'Indeterminate' : checkedLabel,
24+
indeterminate: data.indeterminate
25+
});
26+
27+
action('handleChange')(
28+
checked,
29+
event,
30+
`checked: ${data.checked},
31+
indeterminate: ${data.indeterminate}`
32+
);
33+
},
34+
35+
changeToIndeterminate () {
36+
this.setState({ currentStateHelper: 'Inderterminate', checked: true, indeterminate: true });
37+
action('changeToIndeterminate')(event, 'checked: true, indeterminate: true');
38+
},
39+
40+
changeToCheck () {
41+
this.setState({ currentStateHelper: 'Checked', checked: true, indeterminate: false });
42+
action('changeToCheck')(event, 'checked: true, indeterminate: false');
43+
},
44+
45+
changeToUnChecked () {
46+
this.setState({ currentStateHelper: 'Unchecked', checked: false, indeterminate: false });
47+
action('changeToUnChecked')(event, 'checked: false, indeterminate: false');
48+
},
49+
50+
render () {
51+
return (
52+
<div>
53+
<Button onClick={this.changeToIndeterminate} label="Inderterminate" />
54+
<Button onClick={this.changeToCheck} label="Check" />
55+
<Button onClick={this.changeToUnChecked} label="Uncheck" />
56+
<p>
57+
<strong>Current State:</strong> {this.state.currentStateHelper}
58+
</p>
59+
<Checkbox
60+
assistiveText="Checkbox (indeterminate)"
61+
label="Checkbox Label"
62+
name="checkbox-example-standard-indeterminate"
63+
checked={this.state.checked}
64+
indeterminate={this.state.indeterminate}
65+
onChange={this.handleChange}
66+
/>
67+
<div className="slds-box slds-text-longform slds-m-top--large">
68+
<p>
69+
This example has an <em>indeterminate</em> checkbox.
70+
</p>
71+
<p>
72+
It is set by providing the <code>indeterminate</code> prop as <code><strong>true</strong></code>.
73+
</p>
74+
<p>
75+
Once it is clicked, there is no way to make it go <em>back</em> to the indeterminate state, <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/:indeterminate#Checkbox_radio_button">it must be done programatically, through JavaScript</a>.
76+
</p>
77+
</div>
78+
</div>
79+
);
80+
}
81+
82+
});
683

784
storiesOf(FORMS_CHECKBOX, module)
885
.addDecorator(getStory => <div className="slds-p-around--medium">{getStory()}</div>)
@@ -11,14 +88,58 @@ storiesOf(FORMS_CHECKBOX, module)
1188
assistiveText="Checkbox"
1289
label="Checkbox Label"
1390
name="checkbox-example-standard"
91+
onChange={action('change')}
1492
/>
1593
))
94+
.add('Checkbox (indeterminate)', () => (
95+
<CheckboxIndeterminate />
96+
))
97+
.add('Checkbox (required)', () => (
98+
<Checkbox
99+
assistiveText="Checkbox (required)"
100+
label="Checkbox Label"
101+
name="checkbox-example-standard-required"
102+
onChange={action('change')}
103+
required
104+
/>
105+
))
106+
.add('Checkbox (assistive text)', () => (
107+
<div>
108+
<Checkbox
109+
assistiveText={`This is my checkbox.
110+
There are many like it, but this one is mine.
111+
My checkbox is my best friend.
112+
It is my life.
113+
I must master it as I must master my life.
114+
Without me, my checkbox is useless. Without my checkbox, I am useless.
115+
I must make my checkbox true.
116+
I must make it truer than my radio button who is trying to... `}
117+
label="Checkbox Label"
118+
name="checkbox-example-standard-assistiveText"
119+
onChange={action('change')}
120+
/>
121+
<div className="slds-box slds-text-longform slds-m-top--large">
122+
<p>
123+
This example has assistive text. In Safari on Mac you can
124+
turn assistive text on by using the keyboard combination:
125+
<strong>Command + F5</strong>.
126+
</p>
127+
<p>
128+
Once you have enabled it, use your tab key to focus on the
129+
checkbox input, and the system should read you what is
130+
supplied to the checkbox as the <code>assistiveText</code>
131+
property.
132+
</p>
133+
</div>
134+
</div>
135+
))
16136
.add('Checkbox (checked)', () => (
17137
<Checkbox
18138
assistiveText="Checkbox (checked)"
139+
checked
19140
label="Checkbox Label"
20141
name="checkbox-example-standard-checked"
21-
checked
142+
onChange={action('change')}
22143
/>
23144
))
24-
;
145+
;

0 commit comments

Comments
 (0)