Skip to content

Commit

Permalink
feat(ui5-checkbox): add indeterminate state (#3309)
Browse files Browse the repository at this point in the history
Added new property attribute which determines whether the component is displayed as partially checked.

Fixes: #3217
  • Loading branch information
nnaydenow committed May 28, 2021
1 parent 4ac6d23 commit 42a1326
Show file tree
Hide file tree
Showing 6 changed files with 135 additions and 7 deletions.
4 changes: 2 additions & 2 deletions packages/main/src/CheckBox.hbs
@@ -1,7 +1,7 @@
<div
class="ui5-checkbox-root {{classes.main}}"
role="checkbox"
aria-checked="{{checked}}"
aria-checked="{{ariaChecked}}"
aria-readonly="{{ariaReadonly}}"
aria-disabled="{{ariaDisabled}}"
aria-label="{{ariaLabelText}}"
Expand All @@ -14,7 +14,7 @@
dir="{{effectiveDir}}"
>
<div id="{{_id}}-CbBg" class="ui5-checkbox-inner">
{{#if checked}}
{{#if isCompletelyChecked}}
<ui5-icon name="accept" class="ui5-checkbox-icon"></ui5-icon>
{{/if}}

Expand Down
37 changes: 36 additions & 1 deletion packages/main/src/CheckBox.js
Expand Up @@ -56,6 +56,27 @@ const metadata = {
type: Boolean,
},

/**
* Defines whether the component is displayed as partially checked.
* <br><br>
* <b>Note:</b> The indeterminate state can be set only programatically and can’t be achieved by user
* interaction and the resulting visual state depends on the values of the <code>indeterminate</code>
* and <code>checked</code> properties:
* <ul>
* <li> If the component is checked and indeterminate, it will be displayed as partially checked
* <li> If the component is checked and it is not indeterminate, it will be displayed as checked
* <li> If the component is not checked, it will be displayed as not checked regardless value of the indeterminate attribute
* </ul>
*
* @type {boolean}
* @defaultvalue false
* @public
* @since 1.0.0-rc.15
*/
indeterminate: {
type: Boolean,
},

/**
* Defines if the component is checked.
* <br><br>
Expand Down Expand Up @@ -307,7 +328,13 @@ class CheckBox extends UI5Element {

toggle() {
if (this.canToggle()) {
this.checked = !this.checked;
if (this.indeterminate) {
this.indeterminate = false;
this.checked = true;
} else {
this.checked = !this.checked;
}

this.fireEvent("change");
// Angular two way data binding
this.fireEvent("value-changed");
Expand Down Expand Up @@ -349,6 +376,10 @@ class CheckBox extends UI5Element {
return getEffectiveAriaLabelText(this);
}

get ariaChecked() {
return this.indeterminate && this.checked ? "mixed" : this.checked;
}

get ariaLabelledBy() {
if (!this.ariaLabelText) {
return this.text ? `${this._id}-label` : undefined;
Expand All @@ -374,6 +405,10 @@ class CheckBox extends UI5Element {
return this.disabled ? undefined : tabindex || "0";
}

get isCompletelyChecked() {
return this.checked && !this.indeterminate;
}

static get dependencies() {
return [
Label,
Expand Down
13 changes: 9 additions & 4 deletions packages/main/src/themes/CheckBox.css
Expand Up @@ -74,7 +74,8 @@
color: var(--sapField_SuccessColor);
}

:host([value-state="Warning"]) .ui5-checkbox-icon {
:host([value-state="Warning"]) .ui5-checkbox-icon,
:host([value-state="Warning"][indeterminate]) .ui5-checkbox-inner::after {
color: var(--_ui5_checkbox_checkmark_warning_color);
}

Expand Down Expand Up @@ -142,9 +143,11 @@ https://github.com/philipwalton/flexbugs/issues/231
pointer-events: none;
}

.ui5-checkbox-icon {
color: currentColor;
cursor: default;
:host([indeterminate][checked]) .ui5-checkbox-inner::after {
content: "";
background-color: currentColor;
width: var(--_ui5_checkbox_partially_icon_size);
height: var(--_ui5_checkbox_partially_icon_size);
}

.ui5-checkbox-inner input {
Expand All @@ -170,6 +173,8 @@ https://github.com/philipwalton/flexbugs/issues/231
.ui5-checkbox-icon {
width: var(--_ui5_checkbox_icon_size);
height: var(--_ui5_checkbox_icon_size);
color: currentColor;
cursor: default;
}

/* RTL */
Expand Down
2 changes: 2 additions & 0 deletions packages/main/src/themes/base/sizes-parameters.css
Expand Up @@ -9,6 +9,7 @@

--_ui5_checkbox_root_side_padding: .6875rem;
--_ui5_checkbox_icon_size: 1rem;
--_ui5_checkbox_partially_icon_size: .75rem;
--_ui5_custom_list_item_height: 3rem;
--_ui5_custom_list_item_rb_min_width: 2.75rem;
--_ui5_day_picker_item_width: 2.25rem;
Expand Down Expand Up @@ -116,6 +117,7 @@
--_ui5_checkbox_focus_position: var(--_ui5_checkbox_compact_focus_position);
--_ui5_checkbox_inner_width_height: var(--_ui5_checkbox_compact_inner_size);
--_ui5_checkbox_icon_size: .75rem;
--_ui5_checkbox_partially_icon_size: .5rem;

/* ColorPalette */
--_ui5_color-palette-item-height: 1.25rem;
Expand Down
6 changes: 6 additions & 0 deletions packages/main/test/pages/CheckBox.html
Expand Up @@ -35,6 +35,12 @@
<br>
<ui5-title>ACC test - aria-label</ui5-title>
<ui5-checkbox id="accCb" aria-label="Hello world"></ui5-checkbox>
<br />
<ui5-checkbox value-state="Warning" text="Long long long text" indeterminate checked></ui5-checkbox>
<ui5-checkbox value-state="Error" text="Long long long text" indeterminate checked></ui5-checkbox>
<ui5-checkbox value-state="None" text="Long long long text" indeterminate checked></ui5-checkbox>
<ui5-checkbox value-state="Success" text="Long long long text" indeterminate checked></ui5-checkbox>
<ui5-checkbox value-state="Information" text="Long long long text" indeterminate checked></ui5-checkbox>

<script>
var hcb = false;
Expand Down
80 changes: 80 additions & 0 deletions packages/main/test/samples/CheckBox.sample.html
Expand Up @@ -26,24 +26,44 @@ <h3>Basic CheckBox</h3>
<section>
<h3>CheckBox states</h3>
<div class="snippet">
<ui5-checkbox text="Success" value-state="Success"></ui5-checkbox>
<ui5-checkbox text="Error" value-state="Error"></ui5-checkbox>
<ui5-checkbox text="Warning" value-state="Warning"></ui5-checkbox>
<ui5-checkbox text="Information" value-state="Information"></ui5-checkbox>
<ui5-checkbox text="Disabled" disabled checked></ui5-checkbox>
<ui5-checkbox text="Readonly" readonly checked></ui5-checkbox>
<ui5-checkbox text="Success disabled" disabled value-state="Success" checked></ui5-checkbox>
<ui5-checkbox text="Error disabled" disabled value-state="Error" checked></ui5-checkbox>
<ui5-checkbox text="Warning disabled " disabled value-state="Warning" checked></ui5-checkbox>
<ui5-checkbox text="Information disabled " disabled value-state="Information" checked></ui5-checkbox>
<ui5-checkbox text="Success readonly" readonly value-state="Success" checked></ui5-checkbox>
<ui5-checkbox text="Error readonly" readonly value-state="Error" checked></ui5-checkbox>
<ui5-checkbox text="Warning readonly" readonly value-state="Warning" checked></ui5-checkbox>
<ui5-checkbox text="Information readonly" readonly value-state="Information" checked></ui5-checkbox>
<ui5-checkbox text="Success indeterminate" value-state="Success" indeterminate checked></ui5-checkbox>
<ui5-checkbox text="Error indeterminate" value-state="Error" indeterminate checked></ui5-checkbox>
<ui5-checkbox text="Warning indeterminate" value-state="Warning" indeterminate checked></ui5-checkbox>
<ui5-checkbox text="Information indeterminate" value-state="Information" indeterminate checked></ui5-checkbox>
</div>
<pre class="prettyprint lang-html"><xmp>
<ui5-checkbox text="Success" value-state="Success"></ui5-checkbox>
<ui5-checkbox text="Error" value-state="Error"></ui5-checkbox>
<ui5-checkbox text="Warning" value-state="Warning"></ui5-checkbox>
<ui5-checkbox text="Information" value-state="Information"></ui5-checkbox>
<ui5-checkbox text="Disabled" disabled checked></ui5-checkbox>
<ui5-checkbox text="Readonly" readonly checked></ui5-checkbox>
<ui5-checkbox text="Success disabled" disabled value-state="Success" checked></ui5-checkbox>
<ui5-checkbox text="Error disabled" disabled value-state="Error" checked></ui5-checkbox>
<ui5-checkbox text="Warning disabled " disabled value-state="Warning" checked></ui5-checkbox>
<ui5-checkbox text="Information disabled " disabled value-state="Information" checked></ui5-checkbox>
<ui5-checkbox text="Success readonly" readonly value-state="Success" checked></ui5-checkbox>
<ui5-checkbox text="Error readonly" readonly value-state="Error" checked></ui5-checkbox>
<ui5-checkbox text="Warning readonly" readonly value-state="Warning" checked></ui5-checkbox>
<ui5-checkbox text="Information readonly" readonly value-state="Information" checked></ui5-checkbox>
<ui5-checkbox text="Success indeterminate" value-state="Success" indeterminate checked></ui5-checkbox>
<ui5-checkbox text="Error indeterminate" value-state="Error" indeterminate checked></ui5-checkbox>
<ui5-checkbox text="Warning indeterminate" value-state="Warning" indeterminate checked></ui5-checkbox>
<ui5-checkbox text="Information indeterminate" value-state="Information" indeterminate checked></ui5-checkbox>
</xmp></pre>
</section>

Expand All @@ -59,4 +79,64 @@ <h3>CheckBox with Text Wrapping</h3>
</xmp></pre>
</section>

<section>
<h3>CheckBox with indeterminate</h3>
<div class="snippet">
<ui5-checkbox id="result-cb" text="Select / deselect all" indeterminate checked></ui5-checkbox>
<hr />
<div style="display: flex; flex-direction: column; align-items: flex-start;">
<ui5-checkbox id="cb1" text="English" checked></ui5-checkbox>
<ui5-checkbox id="cb2" text="German"></ui5-checkbox>
<ui5-checkbox id="cb3" text="French"></ui5-checkbox>
</div>
<script>
var resultCb = document.querySelector("#result-cb");
var langCbs = Array.from(document.querySelectorAll("[id^=cb]"));

langCbs.forEach(cbEl => {
cbEl.addEventListener("ui5-change", event => {
const hasChecked = langCbs.some(cb => cb.checked);
const hasUnchecked = langCbs.some(cb => !cb.checked);

resultCb.indeterminate = hasChecked && hasUnchecked;
resultCb.checked = hasChecked;
});
});

resultCb.addEventListener("ui5-change", event => {
langCbs.forEach(cb => cb.checked = event.target.checked);
});
</script>
</div>
<pre class="prettyprint lang-html"><xmp>
<ui5-checkbox id="result-cb" text="Select / deselect all" indeterminate checked></ui5-checkbox>
<hr />
<div style="display: flex; flex-direction: column; align-items: flex-start;">
<ui5-checkbox id="cb1" text="English" checked></ui5-checkbox>
<ui5-checkbox id="cb2" text="German"></ui5-checkbox>
<ui5-checkbox id="cb3" text="French"></ui5-checkbox>
</div>
<script>
var resultCb = document.querySelector("#result-cb");
var langCbs = Array.from(document.querySelectorAll("[id^=cb]"));

langCbs.forEach(function (cbEl) {
cbEl.addEventListener("ui5-change", function (event) {
const hasChecked = langCbs.some(function (cb) { return cb.checked });
const hasUnchecked = langCbs.some(function (cb) { return !cb.checked });

resultCb.indeterminate = hasChecked && hasUnchecked;
resultCb.checked = hasChecked;
});
});

resultCb.addEventListener("ui5-change", function (event) {
langCbs.forEach(function (cb) {
cb.checked = event.target.checked;
});
});
</script>
</xmp></pre>
</section>

<!-- JSDoc marker -->

0 comments on commit 42a1326

Please sign in to comment.