Skip to content

Commit

Permalink
feat(BFormCheckBox): Implements tri-state checkbox (bootstrap-vue-nex…
Browse files Browse the repository at this point in the history
…t#1725)

fix(BFormCheckbox)!: Correct casing of property ariaLabelledby , ariaLabelledBy => ariaLabelledby

test(BFormCheckbox): Add basic unit tests to this component

feat(BFormCheckbox): Implement indeterminate state fixes bootstrap-vue-next#1611

doc(BFormCheckbox): Update docs to reflect changes to indeterminate
  • Loading branch information
dwgray authored Jan 22, 2024
1 parent afd7e5f commit 0908731
Show file tree
Hide file tree
Showing 5 changed files with 817 additions and 32 deletions.
33 changes: 19 additions & 14 deletions apps/docs/src/data/components/formCheckbox.data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@ export default {
{
prop: 'indeterminate',
type: 'Booleanish',
default: undefined,
default: false,
description:
'Set to true to show the checkbox as indeterminate, false to show its normal checked/unchecked.',
},
{
prop: 'name',
Expand Down Expand Up @@ -108,35 +110,38 @@ export default {
],
emits: [
{
event: 'update:modelValue',
description: 'Emitted when the checked value is changed',
args: [
{
arg: 'update:modelValue',
description: '',
type: 'unknown',
arg: 'checked',
description:
'Value of the checkbox. Value will be an array for grouped checkboxes or a single value for standalone checkboxes.',
type: 'CheckboxValue | readonly CheckboxValue[]',
},
],
description: '',
event: 'update:modelValue',
},
{
event: 'input',
description: '',
description: 'Emitted before the checked value is changed',
args: [
{
arg: 'input',
description: '',
type: 'unknown',
arg: 'checked',
description:
'Value of the checkbox before the event. Value will be an array for grouped checkboxes or a single value for standalone checkboxes.',
type: 'CheckboxValue | readonly CheckboxValue[]',
},
],
},
{
event: 'change',
description: '',
description: 'Emitted when the checked value is changed',
args: [
{
arg: 'change',
description: '',
type: 'unknown',
arg: 'checked',
description:
'Value of the checkbox. Value will be an array for grouped checkboxes or a single value for standalone checkboxes.',
type: 'CheckboxValue | readonly CheckboxValue[]',
},
],
},
Expand Down
24 changes: 19 additions & 5 deletions apps/docs/src/docs/components/form-checkbox.md
Original file line number Diff line number Diff line change
Expand Up @@ -719,22 +719,35 @@ Visually, there are actually three states a checkbox can be in: checked, uncheck

The indeterminate state is **visual only**. The checkbox is still either checked or unchecked as a value. That means the visual indeterminate state masks the real value of the checkbox, so that better make sense in your UI!.

`BFormCheckbox` supports setting this visual indeterminate state via the indeterminate prop (defaults to false). Clicking the checkbox will clear its indeterminate state.
`BFormCheckbox` supports setting this visual indeterminate state via a secondary named model called indeterminate (defaults to undefined). Clicking the checkbox will clear the indeterminate state and emit an `update:indeterminate=false` event. To reset the state set the indeterminate model value to true.

<HighlightCard>
<BFormCheckbox v-model="intermChecked" :indeterminate="true">Click me to see what happens</BFormCheckbox>
<BFormCheckbox v-model="intermChecked" v-model:indeterminate="indeterminate">Click me to see what happens</BFormCheckbox>
<BButton class="mt-2" :disabled="indeterminate" @click="indeterminate = true">Reset Indeterminate</BButton>
<div class="mt-2">
Checked: <strong>{{ intermChecked }}</strong>
Checked: <strong>{{ intermChecked }}</strong><br>
Indeterminate: <strong>{{ indeterminate }}</strong>
</div>
<template #html>

```vue
<template>
<BFormCheckbox :indeterminate="true">Click me to see what happens</BFormCheckbox>
<BFormCheckbox v-model="intermChecked" v-model:indeterminate="indeterminate"
>Click me to see what happens</BFormCheckbox
>
<BButton class="mt-2" :disabled="indeterminate" @click="indeterminate = true"
>Reset Indeterminate</BButton
>
<div class="mt-2">
Checked: <strong>{{ intermChecked }}</strong
><br />
Indeterminate: <strong>{{ indeterminate }}</strong>
</div>
</template>
<script setup lang="ts">
const intermChecked = ref(true)
const indeterminate = ref(true)
</script>
```

Expand All @@ -749,12 +762,13 @@ import {ref, computed} from 'vue'
import ComponentReference from '../../components/ComponentReference.vue'
import HighlightCard from '../../components/HighlightCard.vue'
import CrossSiteScriptingWarning from '../../components/CrossSiteScriptingWarning.vue'
import {BFormCheckboxGroup, BFormCheckbox, BCard, BCardBody, BAlert} from 'bootstrap-vue-next'
import {BButton, BFormCheckboxGroup, BFormCheckbox, BCard, BCardBody, BAlert} from 'bootstrap-vue-next'

const button1Checked = ref(false);
const button2Checked = ref(false);
const switchChecked = ref(false);
const intermChecked = ref(true);
const indeterminate = ref(true);

const availableCars = ['BMW', 'Mercedes', 'Toyota'];
const selectedCars = ref([]);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
:name="name || parentData?.name.value"
:form="form || parentData?.form.value"
:aria-label="ariaLabel"
:aria-labelledby="ariaLabelledBy"
:aria-labelledby="ariaLabelledby"
:aria-required="computedRequired || undefined"
:value="value"
:true-value="value"
Expand Down Expand Up @@ -42,7 +42,7 @@ defineOptions({
const props = withDefaults(
defineProps<{
ariaLabel?: string
ariaLabelledBy?: string
ariaLabelledby?: string
autofocus?: Booleanish
button?: Booleanish
buttonGroup?: Booleanish
Expand All @@ -64,15 +64,15 @@ const props = withDefaults(
}>(),
{
ariaLabel: undefined,
ariaLabelledBy: undefined,
ariaLabelledby: undefined,
autofocus: false,
button: false,
buttonGroup: false,
buttonVariant: null,
disabled: false,
form: undefined,
id: undefined,
indeterminate: undefined,
indeterminate: false,
inline: false,
modelValue: undefined,
name: undefined,
Expand All @@ -90,6 +90,7 @@ const emit = defineEmits<{
'change': [value: CheckboxValue | CheckboxValue[]]
'input': [value: CheckboxValue | CheckboxValue[]]
'update:modelValue': [value: CheckboxValue | CheckboxValue[]]
'update:indeterminate': [value: boolean]
}>()
const slots = defineSlots<{
Expand All @@ -98,6 +99,7 @@ const slots = defineSlots<{
}>()
const modelValue = useVModel(props, 'modelValue', emit, {passive: true})
const indeterminate = useVModel(props, 'indeterminate', emit)
const computedId = useId(() => props.id, 'form-check')
Expand Down Expand Up @@ -126,6 +128,10 @@ const localValue = computed({
get: () => parentData?.modelValue.value ?? modelValue.value,
set: (newVal) => {
if (newVal === undefined) return
// Indeterminate is implicitly cleared when the checked state is changed to any value
// by the user. We reflect that here by setting our indetermiate model to false
// which will emit the indeterminate event to the parent
indeterminate.value = false
if (parentData !== null && Array.isArray(newVal)) {
// The type cast isn't perfect. Array.isArray detects CheckboxValue.unknown[],
// but since it's parentData, it should always be CheckboxValue[]
Expand Down
Loading

0 comments on commit 0908731

Please sign in to comment.