From 37f4500e3c47832a83e3f2f3916218cf15129311 Mon Sep 17 00:00:00 2001 From: Gavin <gavinwjw@163.com> Date: Sun, 27 Aug 2023 15:21:44 +0800 Subject: [PATCH] feat(Checkbox): add indeterminate status (#12216) * feat(Checkbox): add indeterminate status * chore: update * chore: update test * chore: update * chore: update * chore: update --- packages/vant/src/checkbox/Checkbox.tsx | 10 +- packages/vant/src/checkbox/Checker.tsx | 13 +- packages/vant/src/checkbox/README.md | 53 ++++++++ packages/vant/src/checkbox/README.zh-CN.md | 55 ++++++++ packages/vant/src/checkbox/demo/index.vue | 40 ++++++ packages/vant/src/checkbox/index.less | 11 ++ .../test/__snapshots__/demo-ssr.spec.ts.snap | 124 ++++++++++++++++++ .../test/__snapshots__/demo.spec.ts.snap | 76 +++++++++++ 8 files changed, 378 insertions(+), 4 deletions(-) diff --git a/packages/vant/src/checkbox/Checkbox.tsx b/packages/vant/src/checkbox/Checkbox.tsx index c6f672e34..d5da36af6 100644 --- a/packages/vant/src/checkbox/Checkbox.tsx +++ b/packages/vant/src/checkbox/Checkbox.tsx @@ -25,6 +25,10 @@ const [name, bem] = createNamespace('checkbox'); export const checkboxProps = extend({}, checkerProps, { shape: String as PropType<CheckerShape>, bindGroup: truthProp, + indeterminate: { + type: Boolean as PropType<boolean | null>, + default: null, + }, }); export type CheckboxProps = ExtractPropTypes<typeof checkboxProps>; @@ -80,11 +84,15 @@ export default defineComponent({ } else { emit('update:modelValue', newValue); } + + if (props.indeterminate !== null) emit('change', newValue); }; watch( () => props.modelValue, - (value) => emit('change', value), + (value) => { + if (props.indeterminate === null) emit('change', value); + }, ); useExpose<CheckboxExpose>({ toggle, props, checked }); diff --git a/packages/vant/src/checkbox/Checker.tsx b/packages/vant/src/checkbox/Checker.tsx index 17bede19d..6c0e6d69f 100644 --- a/packages/vant/src/checkbox/Checker.tsx +++ b/packages/vant/src/checkbox/Checker.tsx @@ -45,6 +45,10 @@ export default defineComponent({ parent: Object as PropType<CheckerParent | null>, checked: Boolean, bindGroup: truthProp, + indeterminate: { + type: Boolean as PropType<boolean | null>, + default: null, + }, }), emits: ['click', 'toggle'], @@ -106,7 +110,7 @@ export default defineComponent({ }; const renderIcon = () => { - const { bem, checked } = props; + const { bem, checked, indeterminate } = props; const iconSize = props.iconSize || getParentProp('iconSize'); return ( @@ -114,7 +118,7 @@ export default defineComponent({ ref={iconRef} class={bem('icon', [ shape.value, - { disabled: disabled.value, checked }, + { disabled: disabled.value, checked, indeterminate }, ])} style={ shape.value !== 'dot' @@ -129,7 +133,10 @@ export default defineComponent({ {slots.icon ? ( slots.icon({ checked, disabled: disabled.value }) ) : shape.value !== 'dot' ? ( - <Icon name="success" style={iconStyle.value} /> + <Icon + name={indeterminate ? 'minus' : 'success'} + style={iconStyle.value} + /> ) : ( <div class={bem('icon--dot__icon')} diff --git a/packages/vant/src/checkbox/README.md b/packages/vant/src/checkbox/README.md index bafda4935..5ba1e0bd4 100644 --- a/packages/vant/src/checkbox/README.md +++ b/packages/vant/src/checkbox/README.md @@ -265,6 +265,58 @@ export default { }; ``` +### indeterminate + +```html +<van-checkbox + v-model="isCheckAll" + :indeterminate="isIndeterminate" + @change="checkAllChange" +> + Check All +</van-checkbox> + +<van-checkbox-group v-model="checkedResult" @change="checkedResultChange"> + <van-checkbox v-for="item in list" :key="item" :name="item"> + Checkbox {{ item }} + </van-checkbox> +</van-checkbox-group> +``` + +```js +import { ref } from 'vue'; + +export default { + setup() { + const list = ['a', 'b', 'c', 'd'] + + const isCheckAll = ref(false); + const checkedResult = ref(['a', 'b', 'd']); + const isIndeterminate = ref(true); + + const checkAllChange = (val: boolean) => { + checkedResult.value = val ? list : [] + isIndeterminate.value = false + } + + const checkedResultChange = (value: string[]) => { + const checkedCount = value.length + isCheckAll.value = checkedCount === list.length + isIndeterminate.value = checkedCount > 0 && checkedCount < list.length + } + + return { + list, + isCheckAll, + checkedResult, + checkAllChange, + isIndeterminate, + checkedResultChange + }; + }, +}; +``` + ## API ### Checkbox Props @@ -280,6 +332,7 @@ export default { | icon-size | Icon size | _number \| string_ | `20px` | | checked-color | Checked color | _string_ | `#1989fa` | | bind-group | Whether to bind with CheckboxGroup | _boolean_ | `true` | +| indeterminate | Whether indeterminate status | _boolean_ | `false` | ### CheckboxGroup Props diff --git a/packages/vant/src/checkbox/README.zh-CN.md b/packages/vant/src/checkbox/README.zh-CN.md index f33b53c5d..947f03385 100644 --- a/packages/vant/src/checkbox/README.zh-CN.md +++ b/packages/vant/src/checkbox/README.zh-CN.md @@ -282,6 +282,60 @@ export default { }; ``` +### 不确定状态 + +通过 `indeterminate` 设置复选框是否为不确定状态。 + +```html +<van-checkbox + v-model="isCheckAll" + :indeterminate="isIndeterminate" + @change="checkAllChange" +> + 全选 +</van-checkbox> + +<van-checkbox-group v-model="checkedResult" @change="checkedResultChange"> + <van-checkbox v-for="item in list" :key="item" :name="item"> + 复选框 {{ item }} + </van-checkbox> +</van-checkbox-group> +``` + +```js +import { ref } from 'vue'; + +export default { + setup() { + const list = ['a', 'b', 'c', 'd'] + + const isCheckAll = ref(false); + const checkedResult = ref(['a', 'b', 'd']); + const isIndeterminate = ref(true); + + const checkAllChange = (val: boolean) => { + checkedResult.value = val ? list : [] + isIndeterminate.value = false + } + + const checkedResultChange = (value: string[]) => { + const checkedCount = value.length + isCheckAll.value = checkedCount === list.length + isIndeterminate.value = checkedCount > 0 && checkedCount < list.length + } + + return { + list, + isCheckAll, + checkedResult, + checkAllChange, + isIndeterminate, + checkedResultChange + }; + }, +}; +``` + ## API ### Checkbox Props @@ -297,6 +351,7 @@ export default { | icon-size | 图标大小,默认单位为 `px` | _number \| string_ | `20px` | | checked-color | 选中状态颜色 | _string_ | `#1989fa` | | bind-group | 是否与复选框组绑定 | _boolean_ | `true` | +| indeterminate | 是否为不确定状态 | _boolean_ | `false` | ### CheckboxGroup Props diff --git a/packages/vant/src/checkbox/demo/index.vue b/packages/vant/src/checkbox/demo/index.vue index d6a08f537..745f57d5e 100644 --- a/packages/vant/src/checkbox/demo/index.vue +++ b/packages/vant/src/checkbox/demo/index.vue @@ -26,6 +26,7 @@ const t = useTranslate({ inverse: '反选', horizontal: '水平排列', disableLabel: '禁用文本点击', + indeterminate: '不确定状态', }, 'en-US': { checkbox: 'Checkbox', @@ -42,6 +43,7 @@ const t = useTranslate({ inverse: 'Inverse', horizontal: 'Horizontal', disableLabel: 'Disable label click', + indeterminate: 'indeterminate', }, }); @@ -49,6 +51,8 @@ const state = reactive({ checkbox1: true, checkbox2: true, checkbox3: true, + isCheckAll: false, + isIndeterminate: true, checkboxLabel: true, checkboxIcon: true, leftLabel: false, @@ -57,10 +61,13 @@ const state = reactive({ checkboxShape: ['a', 'b'], result2: [], result3: [], + result4: ['a', 'b', 'd'], checkAllResult: [], horizontalResult: [], }); +const list = ['a', 'b', 'c', 'd']; + const activeIcon = cdnURL('user-active.png'); const inactiveIcon = cdnURL('user-inactive.png'); @@ -78,6 +85,17 @@ const checkAll = () => { const toggleAll = () => { group.value?.toggleAll(); }; + +const checkAllChange = (val: boolean) => { + state.result4 = val ? list : []; + state.isIndeterminate = false; +}; + +const checkedResultChange = (value: string[]) => { + const checkedCount = value.length; + state.isCheckAll = checkedCount === list.length; + state.isIndeterminate = checkedCount > 0 && checkedCount < list.length; +}; </script> <template> @@ -190,6 +208,22 @@ const toggleAll = () => { </van-cell-group> </van-checkbox-group> </demo-block> + + <demo-block :title="t('indeterminate')"> + <van-checkbox + v-model="state.isCheckAll" + :indeterminate="state.isIndeterminate" + @change="checkAllChange" + > + {{ t('checkAll') }} + </van-checkbox> + <div class="divider" /> + <van-checkbox-group v-model="state.result4" @change="checkedResultChange"> + <van-checkbox v-for="item in list" :key="item" :name="item"> + {{ t('checkbox') }} {{ item }} + </van-checkbox> + </van-checkbox-group> + </demo-block> </template> <style lang="less"> @@ -220,4 +254,10 @@ const toggleAll = () => { margin-top: -8px; } } + +.divider { + margin: 20px; + height: 1px; + background: #ccc; +} </style> diff --git a/packages/vant/src/checkbox/index.less b/packages/vant/src/checkbox/index.less index f64a42e98..9ef71cd7d 100644 --- a/packages/vant/src/checkbox/index.less +++ b/packages/vant/src/checkbox/index.less @@ -56,6 +56,17 @@ } } + &--indeterminate { + .van-icon { + display: flex; + align-items: center; + justify-content: center; + color: var(--van-white); + border-color: var(--van-checkbox-checked-icon-color); + background-color: var(--van-checkbox-checked-icon-color); + } + } + &--checked { .van-icon { color: var(--van-white); diff --git a/packages/vant/src/checkbox/test/__snapshots__/demo-ssr.spec.ts.snap b/packages/vant/src/checkbox/test/__snapshots__/demo-ssr.spec.ts.snap index fc07e09d8..dfe53d717 100644 --- a/packages/vant/src/checkbox/test/__snapshots__/demo-ssr.spec.ts.snap +++ b/packages/vant/src/checkbox/test/__snapshots__/demo-ssr.spec.ts.snap @@ -611,4 +611,128 @@ exports[`should render demo and match snapshot 1`] = ` </div> </div> </div> +<div> + <!--[--> + <div + role="checkbox" + class="van-checkbox" + tabindex="0" + aria-checked="false" + > + <!--[--> + <div + class="van-checkbox__icon van-checkbox__icon--round van-checkbox__icon--indeterminate" + style + > + <i + class="van-badge__wrapper van-icon van-icon-minus" + style + > + <!--[--> + </i> + </div> + <span class="van-checkbox__label"> + <!--[--> + Check All + </span> + </div> + <div class="divider"> + </div> + <div class="van-checkbox-group"> + <!--[--> + <!--[--> + <div + role="checkbox" + class="van-checkbox" + tabindex="0" + aria-checked="true" + > + <!--[--> + <div + class="van-checkbox__icon van-checkbox__icon--round van-checkbox__icon--checked" + style + > + <i + class="van-badge__wrapper van-icon van-icon-success" + style + > + <!--[--> + </i> + </div> + <span class="van-checkbox__label"> + <!--[--> + Checkbox a + </span> + </div> + <div + role="checkbox" + class="van-checkbox" + tabindex="0" + aria-checked="true" + > + <!--[--> + <div + class="van-checkbox__icon van-checkbox__icon--round van-checkbox__icon--checked" + style + > + <i + class="van-badge__wrapper van-icon van-icon-success" + style + > + <!--[--> + </i> + </div> + <span class="van-checkbox__label"> + <!--[--> + Checkbox b + </span> + </div> + <div + role="checkbox" + class="van-checkbox" + tabindex="0" + aria-checked="false" + > + <!--[--> + <div + class="van-checkbox__icon van-checkbox__icon--round" + style + > + <i + class="van-badge__wrapper van-icon van-icon-success" + style + > + <!--[--> + </i> + </div> + <span class="van-checkbox__label"> + <!--[--> + Checkbox c + </span> + </div> + <div + role="checkbox" + class="van-checkbox" + tabindex="0" + aria-checked="true" + > + <!--[--> + <div + class="van-checkbox__icon van-checkbox__icon--round van-checkbox__icon--checked" + style + > + <i + class="van-badge__wrapper van-icon van-icon-success" + style + > + <!--[--> + </i> + </div> + <span class="van-checkbox__label"> + <!--[--> + Checkbox d + </span> + </div> + </div> +</div> `; diff --git a/packages/vant/src/checkbox/test/__snapshots__/demo.spec.ts.snap b/packages/vant/src/checkbox/test/__snapshots__/demo.spec.ts.snap index c82b16ea5..2a202ba6a 100644 --- a/packages/vant/src/checkbox/test/__snapshots__/demo.spec.ts.snap +++ b/packages/vant/src/checkbox/test/__snapshots__/demo.spec.ts.snap @@ -390,4 +390,80 @@ exports[`should render demo and match snapshot 1`] = ` </div> </div> </div> +<div> + <div + role="checkbox" + class="van-checkbox" + tabindex="0" + aria-checked="false" + > + <div class="van-checkbox__icon van-checkbox__icon--round van-checkbox__icon--indeterminate"> + <i class="van-badge__wrapper van-icon van-icon-minus"> + </i> + </div> + <span class="van-checkbox__label"> + Check All + </span> + </div> + <div class="divider"> + </div> + <div class="van-checkbox-group"> + <div + role="checkbox" + class="van-checkbox" + tabindex="0" + aria-checked="true" + > + <div class="van-checkbox__icon van-checkbox__icon--round van-checkbox__icon--checked"> + <i class="van-badge__wrapper van-icon van-icon-success"> + </i> + </div> + <span class="van-checkbox__label"> + Checkbox a + </span> + </div> + <div + role="checkbox" + class="van-checkbox" + tabindex="0" + aria-checked="true" + > + <div class="van-checkbox__icon van-checkbox__icon--round van-checkbox__icon--checked"> + <i class="van-badge__wrapper van-icon van-icon-success"> + </i> + </div> + <span class="van-checkbox__label"> + Checkbox b + </span> + </div> + <div + role="checkbox" + class="van-checkbox" + tabindex="0" + aria-checked="false" + > + <div class="van-checkbox__icon van-checkbox__icon--round"> + <i class="van-badge__wrapper van-icon van-icon-success"> + </i> + </div> + <span class="van-checkbox__label"> + Checkbox c + </span> + </div> + <div + role="checkbox" + class="van-checkbox" + tabindex="0" + aria-checked="true" + > + <div class="van-checkbox__icon van-checkbox__icon--round van-checkbox__icon--checked"> + <i class="van-badge__wrapper van-icon van-icon-success"> + </i> + </div> + <span class="van-checkbox__label"> + Checkbox d + </span> + </div> + </div> +</div> `;