Skip to content
Permalink
Browse files

feat(checkbox): implement indeterminate state (#16951)

This adds an `indeterminate` prop to the `ion-checkbox` component, which visually renders the checkbox with a dash to indicate an indeterminate state.

closes #16943
  • Loading branch information...
simonhaenisch authored and brandyscarney committed Mar 4, 2019
1 parent 28fd75e commit c641ae10ed95d31958451064576b9a8803ae3228
@@ -140,7 +140,7 @@ export class IonCardTitle {
proxyInputs(IonCardTitle, ['color', 'mode']);

export declare interface IonCheckbox extends StencilComponents<'IonCheckbox'> {}
@Component({ selector: 'ion-checkbox', changeDetection: 0, template: '<ng-content></ng-content>', inputs: ['color', 'mode', 'name', 'checked', 'disabled', 'value'] })
@Component({ selector: 'ion-checkbox', changeDetection: 0, template: '<ng-content></ng-content>', inputs: ['color', 'mode', 'name', 'checked', 'indeterminate', 'disabled', 'value'] })
export class IonCheckbox {
ionChange!: EventEmitter<CustomEvent>;
ionFocus!: EventEmitter<CustomEvent>;
@@ -152,7 +152,7 @@ export class IonCheckbox {
proxyOutputs(this, this.el, ['ionChange', 'ionFocus', 'ionBlur']);
}
}
proxyInputs(IonCheckbox, ['color', 'mode', 'name', 'checked', 'disabled', 'value']);
proxyInputs(IonCheckbox, ['color', 'mode', 'name', 'checked', 'indeterminate', 'disabled', 'value']);

export declare interface IonChip extends StencilComponents<'IonChip'> {}
@Component({ selector: 'ion-chip', changeDetection: 0, template: '<ng-content></ng-content>', inputs: ['color', 'mode', 'outline'] })
@@ -195,6 +195,7 @@ ion-checkbox,shadow
ion-checkbox,prop,checked,boolean,false,false,false
ion-checkbox,prop,color,string | undefined,undefined,false,false
ion-checkbox,prop,disabled,boolean,false,false,false
ion-checkbox,prop,indeterminate,boolean,false,false,false
ion-checkbox,prop,mode,"ios" | "md",undefined,false,false
ion-checkbox,prop,name,string,this.inputId,false,false
ion-checkbox,prop,value,string,'on',false,false
@@ -749,6 +749,10 @@ export namespace Components {
*/
'disabled': boolean;
/**
* If `true`, the checkbox will visually appear as indeterminate.
*/
'indeterminate': boolean;
/**
* The mode determines which platform styles to use.
*/
'mode': Mode;
@@ -775,6 +779,10 @@ export namespace Components {
*/
'disabled'?: boolean;
/**
* If `true`, the checkbox will visually appear as indeterminate.
*/
'indeterminate'?: boolean;
/**
* The mode determines which platform styles to use.
*/
'mode'?: Mode;
@@ -16,8 +16,8 @@

// Size
--size: #{$checkbox-ios-icon-size};
width: var(--size);

width: var(--size);
height: var(--size);
}

@@ -39,6 +39,7 @@
@include margin($checkbox-ios-item-end-margin-top, $checkbox-ios-item-end-margin-end, $checkbox-ios-item-end-margin-bottom, $checkbox-ios-item-end-margin-start);

display: block;

position: static;
}

@@ -13,45 +13,52 @@

// Background
--background: #{$checkbox-md-icon-background-color-off};

// Transition
--transition: #{background $checkbox-md-transition-duration $checkbox-md-transition-easing};

// Size
--size: #{$checkbox-md-icon-size};
width: var(--size);

width: var(--size);
height: var(--size);
}


.checkbox-icon path {
stroke-dasharray: 30;
stroke-dashoffset: 30;
stroke-width: 3;
}

:host(.checkbox-checked) .checkbox-icon path {
// Material Design Checkbox: Checked / Indeterminate
// --------------------------------------------------------

:host(.checkbox-checked) .checkbox-icon path,
:host(.checkbox-indeterminate) .checkbox-icon path {
stroke-dashoffset: 0;

transition: stroke-dashoffset 90ms linear 90ms;
}


// Material Design Checkbox: Disabled
// -----------------------------------------
// --------------------------------------------------------

// TODO .item-md.item-checkbox-disabled ion-label
:host(.checkbox-disabled) {
opacity: $checkbox-md-disabled-opacity;
}


// Material Design Checkbox Within An Item
// -----------------------------------------
// --------------------------------------------------------

:host(.in-item) {
// end position by default
@include margin($checkbox-md-item-end-margin-top, $checkbox-md-item-end-margin-end, $checkbox-md-item-end-margin-bottom, $checkbox-md-item-end-margin-start);

display: block;

position: static;
}

@@ -6,14 +6,18 @@
:host {
/**
* @prop --size: Size of the checkbox icon
*
* @prop --background: Background of the checkbox icon
* @prop --background-checked: Background of the checkbox icon when checked
*
* @prop --border-color: Border color of the checkbox icon
* @prop --border-radius: Border radius of the checkbox icon
* @prop --border-width: Border width of the checkbox icon
* @prop --border-style: Border style of the checkbox icon
* @prop --transition: Transition of the checkbox icon
* @prop --background-checked: Background of the checkbox icon when checked
* @prop --border-color-checked: Border color of the checkbox icon when checked
*
* @prop --transition: Transition of the checkbox icon
*
* @prop --checkmark-color: Color of the checkbox checkmark when checked
*/
--background-checked: #{ion-color(primary, base)};
@@ -67,19 +71,23 @@ button {
opacity: 0;
}

// Checked Checkbox

// Checked / Indeterminate Checkbox
// ---------------------------------------------

:host(.checkbox-checked) .checkbox-icon {
:host(.checkbox-checked) .checkbox-icon,
:host(.checkbox-indeterminate) .checkbox-icon {
border-color: var(--border-color-checked);

background: var(--background-checked);
}

:host(.checkbox-checked) .checkbox-icon path {
:host(.checkbox-checked) .checkbox-icon path,
:host(.checkbox-indeterminate) .checkbox-icon path {
opacity: 1;
}


// Disabled Checkbox
// ---------------------------------------------

@@ -41,6 +41,11 @@ export class Checkbox implements ComponentInterface {
*/
@Prop({ mutable: true }) checked = false;

/**
* If `true`, the checkbox will visually appear as indeterminate.
*/
@Prop({ mutable: true }) indeterminate = false;

/**
* If `true`, the user cannot interact with the checkbox.
*/
@@ -101,6 +106,7 @@ export class Checkbox implements ComponentInterface {
onClick() {
this.setFocus();
this.checked = !this.checked;
this.indeterminate = false;
}

private setFocus() {
@@ -134,6 +140,7 @@ export class Checkbox implements ComponentInterface {
'in-item': hostContext('ion-item', el),
'checkbox-checked': checked,
'checkbox-disabled': disabled,
'checkbox-indeterminate': this.indeterminate,
'interactive': true
}
};
@@ -142,12 +149,19 @@ export class Checkbox implements ComponentInterface {
render() {
renderHiddenInput(true, this.el, this.name, (this.checked ? this.value : ''), this.disabled);

let path = this.indeterminate
? <path d="M6 12L18 12"/>
: <path d="M5.9,12.5l3.8,3.8l8.8-8.8" />;

if (this.mode === 'md') {
path = this.indeterminate
? <path d="M2 12H22"/>
: <path d="M1.73,12.91 8.1,19.28 22.79,4.59"/>;
}

return [
<svg class="checkbox-icon" viewBox="0 0 24 24">
{ this.mode === 'md'
? <path d="M1.73,12.91 8.1,19.28 22.79,4.59"></path>
: <path d="M5.9,12.5l3.8,3.8l8.8-8.8"/>
}
{path}
</svg>,
<button
type="button"
@@ -188,14 +188,15 @@ export default CheckboxExample;

## Properties

| Property | Attribute | Description | Type | Default |
| ---------- | ---------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------- | -------------- |
| `checked` | `checked` | If `true`, the checkbox is selected. | `boolean` | `false` |
| `color` | `color` | The color to use from your application's color palette. Default options are: `"primary"`, `"secondary"`, `"tertiary"`, `"success"`, `"warning"`, `"danger"`, `"light"`, `"medium"`, and `"dark"`. For more information on colors, see [theming](/docs/theming/basics). | `string \| undefined` | `undefined` |
| `disabled` | `disabled` | If `true`, the user cannot interact with the checkbox. | `boolean` | `false` |
| `mode` | `mode` | The mode determines which platform styles to use. | `"ios" \| "md"` | `undefined` |
| `name` | `name` | The name of the control, which is submitted with the form data. | `string` | `this.inputId` |
| `value` | `value` | The value of the toggle does not mean if it's checked or not, use the `checked` property for that. The value of a toggle is analogous to the value of a `<input type="checkbox">`, it's only used when the toggle participates in a native `<form>`. | `string` | `'on'` |
| Property | Attribute | Description | Type | Default |
| --------------- | --------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------- | -------------- |
| `checked` | `checked` | If `true`, the checkbox is selected. | `boolean` | `false` |
| `color` | `color` | The color to use from your application's color palette. Default options are: `"primary"`, `"secondary"`, `"tertiary"`, `"success"`, `"warning"`, `"danger"`, `"light"`, `"medium"`, and `"dark"`. For more information on colors, see [theming](/docs/theming/basics). | `string \| undefined` | `undefined` |
| `disabled` | `disabled` | If `true`, the user cannot interact with the checkbox. | `boolean` | `false` |
| `indeterminate` | `indeterminate` | If `true`, the checkbox will visually appear as indeterminate. | `boolean` | `false` |
| `mode` | `mode` | The mode determines which platform styles to use. | `"ios" \| "md"` | `undefined` |
| `name` | `name` | The name of the control, which is submitted with the form data. | `string` | `this.inputId` |
| `value` | `value` | The value of the toggle does not mean if it's checked or not, use the `checked` property for that. The value of a toggle is analogous to the value of a `<input type="checkbox">`, it's only used when the toggle participates in a native `<form>`. | `string` | `'on'` |


## Events
@@ -0,0 +1,10 @@
import { newE2EPage } from '@stencil/core/testing';

test('checkbox: indeterminate', async () => {
const page = await newE2EPage({
url: '/src/components/checkbox/test/indeterminate?ionic:_testing=true'
});

const compare = await page.compareScreenshot();
expect(compare).toMatchScreenshot();
});

0 comments on commit c641ae1

Please sign in to comment.
You can’t perform that action at this time.