Permalink
Browse files

feat(checkbox): Add indeterminate state prop (#720)

* feat(checkbox): Add indeterminate prop

Add support for `indeterminate` state of checkbox

* docs(form-checkbox): Document indeterminate prop
  • Loading branch information...
tmorehouse committed Jul 21, 2017
1 parent a02270e commit 2271e7ad57206289eb6cd7e29289605a4a2e52fb
Showing with 168 additions and 5 deletions.
  1. +148 −5 docs/components/form-checkbox/README.md
  2. +20 −0 lib/components/form-checkbox.vue
@@ -11,7 +11,6 @@ semantic and accessible markup, so it is a solid replacement for the default che
<b-form-checkbox id="checkbox1" v-model="state" value="accepted" unchecked-value="not_accepted">
I accept terms and use
</b-form-checkbox>
<div>State: <strong>{{state}}</strong></div>
</div>
</template>
@@ -83,20 +82,46 @@ export default {
<!-- form-checkbox-2.vue -->
```
### Values
### Inline and Stacked checkboxes
`<b-form-checbox>` components render as inline elements by default. Add a parent
with class `.custom-controls-stacked` to ensure each form control is on a separate line.
**Example 3: Inline & Stacked checkboxes:**
```html
<div>
<h5>Inline Checkboxes (default)</h5>
<div role="group">
<b-form-checkbox name="flavour" value="orange">Orange</b-form-checkbox>
<b-form-checkbox name="flavour" value="apple">Apple</b-form-checkbox>
<b-form-checkbox name="flavour" value="pineapple">Pineapple</b-form-checkbox>
<b-form-checkbox name="flavour" value="grape">Grape</b-form-checkbox>
</div>
<h5>Stacked Checkboxes</h5>
<div role="group" class="custom-controls-stacked">
<b-form-checkbox name="flavour" value="orange">Orange</b-form-checkbox>
<b-form-checkbox name="flavour" value="apple">Apple</b-form-checkbox>
<b-form-checkbox name="flavour" value="pineapple">Pineapple</b-form-checkbox>
<b-form-checkbox name="flavour" value="grape">Grape</b-form-checkbox>
</div>
</div>
<!-- form-checkbox-3.vue -->
```
### Value(s)
By default, `<b-form-checkbox>` value will be `true` when checked and `false` when unchecked.
You can customize the checked and unchecked values by specifying the `value` and `unchecked-value`
properties.
`v-model` binds to the `checked` property. When you have multiple checkboxes that bind to a
single data state variable, you **must** provide an array reference `[]` to your `v-model`!
Note that when `v-model` is bound to multiple checkboxes, the `unchecked-value` is not used.
Only the value(s) of the chcekboxes will be returned in the `v-model` bound array. You
Note that when `v-model` is bound to multiple checkboxes, the `unchecked-value` is **not used**.
Only the value(s) of the checked chcekboxes will be returned in the `v-model` bound array. You
should provide unique values for each checkbox's `value` prop.
### Multiple checkboxes and accessibility
#### Multiple checkboxes and accessibility
When binding multiple checkboxes together, you should set the `name` prop to the same value for
all checkboxes in the group, as well as wrap the group in a `<div>` (or other block element)
which has the aria attribute `role="group"`. This will inform users of assitive technologies
@@ -116,6 +141,124 @@ would like), or wrapped in another element - such as a `<div>` - which has one
of the standard Bootstrap V4 `.has-*` state class applied.
### Indeterminate (tri-state) support
Normally checkbox inputs can only have two states: `checked` or `unchecked`. They can
have any value, but they either submit that value (checked) or don't (unchecked) with
a form submission (although Bootstrap-Vue allows a value for the `unchecked` state)
_Visually_, there are actually three states a checkbox can be in: `checked`,
`unchecked`, or `indeterminate`.
The `indeterminate` state is **visual only**. The checkbox is still either checked or
unchecked as a state. That means the visual indeterminate state masks the real value
of the checkbox, so that better make sense in your UI!
`<b-form-checkbox>` supports setting this visual indeterminate state via the `indeterminate`
prop (defaults to `false`). Clicking the checkbox will clear its indeterminate state.
The `indeterminate` prop can be synced to the checkboxe's state by v-binding the
`indeterminate` prop with the `.sync` modifier.
**Example 4: Single Indeterminate checkbox:**
```html
<template>
<div>
<b-form-checkbox v-model="checked" :indeterminate.sync="indeterminate">
Click me to see what happens
</b-form-checkbox>
<br>
<div aria-live="polite">
Checked: <strong>{{ checked }}</strong><br>
Indeterminate: <strong>{{ indeterminate }}</strong>
</div>
<b-btn @click="toggleIndeterminate">Toggle Indeterminate State</b-btn>
</div>
</template>
<script>
export default {
data: {
checked: true,
indeterminate: true
},
methods: {
toggleIndeterminate() {
this.indeterminate = !this.indeterminate;
}
}
}
</script>
<!-- form-checkbox-4.vue -->
```
**Example 5: Indeterminate checkbox use-case:**
```html
<template>
<b-card>
<b-form-checkbox v-model="allSelected"
:indeterminate="indeterminate"
aria-describedby="flavours"
@change="toggleAll"
>
{{ allSelected ? 'Un-select' : 'Select' }}
All Flavors
</b-form-checkbox>
<div id="flavors"
role="group"
class="ml-4 custom-controls-stacked"
aria-label="Individual flavours"
>
<b-form-checkbox v-for="flavour in flavours"
v-model="selected"
name="flavs"
:value="flavour"
>{{ flavour }}</b-form-checkbox>
</div>
<p aria-live="polite">Selected: <strong>{{ selected }}</strong></p>
</b-card>
</template>
<script>
export default {
data: {
flavours: ['Orange', 'Grape', 'Apple', 'Lime', 'Very Berry'],
selected: [],
allSelected: false,
indeterminate: false
},
methods: {
toggleAll() {
this.selected = this.allSelected ? this.flavours.slice() : [];
}
},
watch: {
selected(newVal, oldVal) {
// Handle changes in individual flavour checkboxes
if (newVal.length === 0) {
this.indeterminate = false;
this.allSelected = false;
} else if (newVal.length === this.flavours.length) {
this.indeterminate = false;
this.allSelected = true;
} else {
this.indeterminate = true;
this.allSelected = false;
}
}
}
}
</script>
<!-- form-checkbox-5.vue -->
```
#### Indeterminate state and accessibility
Not all screen readers will convey the indeterminate state to screen reader users.
So it is recommended to provide some form of textual feedback to the user (possibly
by via the `.sr-only` class) if the indeterminate state has special contextual
meaning in your application.
### Non custom check inputs
You can have `b-form-checkbox` render a browser native chechbox input by setting the `plain` prop.
@@ -6,6 +6,7 @@
:value="value"
:disabled="disabled"
:required="required"
ref="check"
autocomplete="off"
:aria-required="required ? 'true' : null"
:class="[custom?'custom-control-input':null]"
@@ -40,6 +41,10 @@ export default {
checked: {
default: true
},
indeterminate: {
type: Boolean,
default: false,
},
size: {
type: String,
default: null
@@ -60,6 +65,11 @@ export default {
}
}
},
watch: {
indeterminate(newVal, oldVal) {
this.setIndeterminate(newVal);
}
},
methods: {
handleChange({ target: { checked } }) {
if (isArray(this.checked)) {
@@ -71,7 +81,17 @@ export default {
} else {
this.$emit('change', checked ? this.value : this.uncheckedValue)
}
this.$emit('update:indeterminate', this.$refs.check.indeterminate);
},
setIndeterminate(state) {
this.$refs.check.indeterminate = state;
// Emit update event to prop
this.$emit('update:indeterminate', this.$refs.check.indeterminate);
}
},
mounted() {
// Set initial indeterminate state
this.setIndeterminate(this.indeterminate);
}
};

0 comments on commit 2271e7a

Please sign in to comment.