From b37d11cfb7b60f6857e400aec63d2e1035bf6efa Mon Sep 17 00:00:00 2001 From: "X.Q. Chen" <31237954+brenner8023@users.noreply.github.com> Date: Sun, 4 Jul 2021 18:33:21 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0checkbox?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 描述: - 新增checkbox组件和checkboxGroup组件 自测: - npm run test通过 - demo效果符合预期 --- .../checkbox/__tests__/checkbox-group.spec.ts | 201 +++++++++++++++ devui/checkbox/__tests__/checkbox.spec.ts | 146 +++++++++++ devui/checkbox/checkbox.tsx | 12 - devui/checkbox/demo/checkbox-demo.tsx | 30 ++- devui/checkbox/demo/demo-basic.tsx | 136 +++++++++++ devui/checkbox/demo/demo-checkbox-group.tsx | 155 ++++++++++++ devui/checkbox/src/checkbox-group.scss | 21 ++ devui/checkbox/src/checkbox-group.tsx | 74 ++++++ devui/checkbox/src/checkbox.scss | 228 ++++++++++++++++++ devui/checkbox/src/checkbox.tsx | 160 ++++++++++++ devui/checkbox/src/use-checkbox.ts | 106 ++++++++ 11 files changed, 1248 insertions(+), 21 deletions(-) create mode 100644 devui/checkbox/__tests__/checkbox-group.spec.ts create mode 100644 devui/checkbox/__tests__/checkbox.spec.ts delete mode 100644 devui/checkbox/checkbox.tsx create mode 100644 devui/checkbox/demo/demo-basic.tsx create mode 100644 devui/checkbox/demo/demo-checkbox-group.tsx create mode 100644 devui/checkbox/src/checkbox-group.scss create mode 100644 devui/checkbox/src/checkbox-group.tsx create mode 100644 devui/checkbox/src/checkbox.scss create mode 100644 devui/checkbox/src/checkbox.tsx create mode 100644 devui/checkbox/src/use-checkbox.ts diff --git a/devui/checkbox/__tests__/checkbox-group.spec.ts b/devui/checkbox/__tests__/checkbox-group.spec.ts new file mode 100644 index 00000000..1573bef0 --- /dev/null +++ b/devui/checkbox/__tests__/checkbox-group.spec.ts @@ -0,0 +1,201 @@ +import { mount } from '@vue/test-utils'; +import { reactive, ref, nextTick } from 'vue'; +import DCheckboxGroup from '../src/checkbox-group'; +import DCheckbox from '../src/checkbox'; + +describe('d-checkbox-group', () => { + it('checkbox-group render work', async () => { + const list = reactive(['b']); + const wrapper = mount({ + components: { + DCheckboxGroup, + DCheckbox + }, + template: ` + + + + + `, + setup () { + return { + list + }; + } + }); + const [box1, box2] = wrapper.findAll('.devui-checkbox'); + + expect(wrapper.classes()).toContain('devui-checkbox-group'); + expect(box1.classes()).toContain('unchecked'); + expect(box2.classes()).toContain('active'); + + Object.assign(list, ['a']); + await nextTick(); + expect(box1.classes()).toContain('active'); + expect(box2.classes()).toContain('unchecked'); + }); + + it('checkbox-group disabled work', async () => { + const list = ref(['b']); + const disabled = ref(true); + const onChange = jest.fn(); + const wrapper = mount({ + components: { + DCheckboxGroup, + DCheckbox + }, + template: ` + + 1 + 2 + + `, + setup () { + return { + list, + disabled, + onChange + }; + } + }); + const label1 = wrapper.find('label'); + + await label1.trigger('click'); + expect(list.value).toStrictEqual(['b']); + expect(onChange).toBeCalledTimes(0); + expect(wrapper.findAll('.devui-checkbox').every(el => el.classes().includes('disabled'))).toBe(true); + + disabled.value = false; + await nextTick(); + await label1.trigger('click'); + expect(list.value).toStrictEqual(['b', 'a']); + expect(onChange).toBeCalledTimes(1); + expect(wrapper.findAll('.devui-checkbox').some(el => el.classes().includes('disabled'))).toBe(false); + }); + + it('checkbox-group direction work', async () => { + const direction = ref('column'); + const list = ref(['b']); + const wrapper = mount({ + components: { + DCheckboxGroup, + DCheckbox + }, + template: ` + + 1 + 2 + + `, + setup () { + return { + list, + direction + }; + } + }); + + expect(wrapper.findAll('.devui-checkbox-column-margin').length).toBe(2); + expect(wrapper.find('.devui-checkbox-list-inline').exists()).toBe(false); + + direction.value = 'row'; + await nextTick(); + expect(wrapper.find('.devui-checkbox-list-inline').exists()).toBe(true); + }); + + it('checkbox-group itemWidth work', () => { + const itemWidth = ref(100); + const list = ref(['b']); + const wrapper = mount({ + components: { + DCheckboxGroup, + DCheckbox + }, + template: ` + + 1 + 2 + + `, + setup () { + return { + list, + itemWidth + }; + } + }); + + expect(wrapper.findAll('.devui-checkbox-wrap').length).toBe(2); + }); + + it('checkbox-group options work', () => { + const list = ref(['b']); + const wrapper = mount({ + components: { + DCheckboxGroup + }, + template: ` + + + `, + setup () { + const options = [ + { + value: 'a' + }, { + value: 'b' + } + ]; + return { + list, + options + }; + } + }); + + const boxList = wrapper.findAll('.devui-checkbox'); + + expect(boxList.length).toBe(2); + expect(boxList[0].classes()).toContain('unchecked'); + expect(boxList[1].classes()).toContain('active'); + }); + + it('checkbox-group beforeChange work', async () => { + const list = ref(['b']); + const beforeChange = jest.fn(() => false); + const onChange = jest.fn(); + const wrapper = mount({ + components: { + DCheckboxGroup, + DCheckbox + }, + template: ` + + 1 + 2 + + `, + setup () { + return { + list, + beforeChange, + onChange + }; + } + }); + + const box1 = wrapper.find('label'); + await box1.trigger('click'); + + expect(beforeChange).toHaveBeenCalledTimes(1); + expect(onChange).toBeCalledTimes(0); + expect(list.value).toStrictEqual(['b']); + + beforeChange.mockReturnValue(true); + await box1.trigger('click'); + + expect(beforeChange).toHaveBeenCalledTimes(2); + expect(onChange).toBeCalledTimes(1); + expect(list.value).toStrictEqual(['b', 'a']); + }); +}); diff --git a/devui/checkbox/__tests__/checkbox.spec.ts b/devui/checkbox/__tests__/checkbox.spec.ts new file mode 100644 index 00000000..999edf45 --- /dev/null +++ b/devui/checkbox/__tests__/checkbox.spec.ts @@ -0,0 +1,146 @@ +import { mount } from '@vue/test-utils'; +import { ref, nextTick } from 'vue'; +import DCheckbox from '../src/checkbox'; + +describe('checkbox', () => { + it('checkbox render work', async () => { + const checked = ref(true); + const wrapper = mount({ + components: { DCheckbox }, + template: `1024`, + setup () { + return { + checked + }; + } + }); + const container = wrapper.find('.devui-checkbox'); + + expect(wrapper.text()).toEqual('1024'); + expect(container.exists()).toBeTruthy(); + expect(container.classes()).toContain('active'); + + checked.value = false; + await nextTick(); + + expect(container.classes()).not.toContain('active'); + expect(container.classes()).toContain('unchecked'); + }); + + it('checkbox title work', async () => { + const wrapper = mount(DCheckbox, { + props: { + value: 'a', + label: '1314' + } + }); + + expect(wrapper.text()).toEqual('1314'); + const label = wrapper.find('label'); + expect(label.attributes('title')).toEqual('1314'); + + await wrapper.setProps({ + title: '520', + label: '1314' + }); + expect(label.attributes('title')).toEqual('520'); + + await wrapper.setProps({ + isShowTitle: false + }); + expect(label.attributes('title')).toEqual(''); + }); + + it('checkbox showAnimation work', async () => { + const wrapper = mount(DCheckbox, { + props: { + value: 'a' + } + }); + + expect(wrapper.findAll('.devui-no-animation').length).toBe(0); + + await wrapper.setProps({ + showAnimation: false + }); + expect(wrapper.findAll('.devui-no-animation').length).toBe(2); + }); + + it('checkbox disabled work', async () => { + const onChange = jest.fn(); + const wrapper = mount(DCheckbox, { + props: { + value: 'a', + disabled: true, + onChange + } + }); + const label = wrapper.find('label'); + + await label.trigger('click'); + expect(wrapper.find('.devui-checkbox').classes()).toContain('disabled'); + expect(onChange).toBeCalledTimes(0); + + await wrapper.setProps({ + disabled: false + }); + await label.trigger('click'); + expect(wrapper.find('.devui-checkbox').classes()).not.toContain('disabled'); + expect(onChange).toBeCalledTimes(1); + }); + + it('checkbox halfchecked work', async () => { + const wrapper = mount(DCheckbox, { + props: { + value: '555', + halfchecked: false + } + }); + + const container = wrapper.find('.devui-checkbox'); + expect(container.classes()).not.toContain('halfchecked'); + expect(container.find('.devui-checkbox-default-background').exists()).toBe(true); + + await wrapper.setProps({ + halfchecked: true + }); + expect(container.classes()).toContain('halfchecked'); + expect(container.find('.devui-checkbox-default-background').exists()).toBe(false) + }); + + it('checkbox beforeChange work', async () => { + const beforeChange = jest.fn(() => false); + const onChange = jest.fn(); + const checked = ref(false); + const wrapper = mount({ + components: { DCheckbox }, + template: ` + + 666 + `, + setup () { + return { + beforeChange, + onChange, + checked + }; + } + }); + + const label = wrapper.find('label'); + await label.trigger('click'); + expect(beforeChange).toBeCalledTimes(1); + expect(onChange).toBeCalledTimes(0); + expect(checked.value).toBe(false); + + beforeChange.mockReturnValue(true); + await label.trigger('click'); + expect(beforeChange).toBeCalledTimes(2); + expect(onChange).toBeCalledTimes(1); + expect(checked.value).toBe(true); + }); +}); diff --git a/devui/checkbox/checkbox.tsx b/devui/checkbox/checkbox.tsx deleted file mode 100644 index 1b4bd6cd..00000000 --- a/devui/checkbox/checkbox.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import { defineComponent } from 'vue' - -export default defineComponent({ - name: 'd-checkbox', - props: { - }, - setup(props, ctx) { - return () => { - return
devui-checkbox
- } - } -}) \ No newline at end of file diff --git a/devui/checkbox/demo/checkbox-demo.tsx b/devui/checkbox/demo/checkbox-demo.tsx index 39d7fc2e..21b4207b 100644 --- a/devui/checkbox/demo/checkbox-demo.tsx +++ b/devui/checkbox/demo/checkbox-demo.tsx @@ -1,12 +1,24 @@ -import { defineComponent } from 'vue' +import { defineComponent } from 'vue'; +import { useDemo } from 'hooks/use-demo'; +import DemoBasic from './demo-basic'; +import DemoBasicCode from './demo-basic?raw'; +import DemoCheckboxGroup from './demo-checkbox-group'; +import DemoCheckboxGroupCode from './demo-checkbox-group?raw'; export default defineComponent({ - name: 'd-checkbox-demo', - props: { - }, - setup(props, ctx) { - return () => { - return
devui-checkbox-demo
- } + name: 'DCheckboxDemo', + + render () { + return useDemo([{ + id: 'checkbox-basic', + title: '基本用法', + code: DemoBasicCode, + content: + }, { + id: 'checkbox-group', + title: '复选框组', + code: DemoCheckboxGroupCode, + content: + }]); } -}) \ No newline at end of file +}); diff --git a/devui/checkbox/demo/demo-basic.tsx b/devui/checkbox/demo/demo-basic.tsx new file mode 100644 index 00000000..431661e1 --- /dev/null +++ b/devui/checkbox/demo/demo-basic.tsx @@ -0,0 +1,136 @@ +import { defineComponent, ref, Ref } from 'vue'; +import DCheckbox from '../src/checkbox'; + +export default defineComponent({ + name: 'DemoBasic', + setup () { + const doUpdate = (checkedRef: Ref, newVal: boolean) => { + checkedRef.value = newVal; + }; + const obj: any = {}; + for (let i = 1; i <= 10; i++) { + obj['checked' + i] = ref(false); + obj['updateChecked' + i] = (newVal: boolean) => { + doUpdate(obj['checked' + i], newVal); + }; + } + + obj.checked1.value = true; + obj.checked9.value = true; + const halfchecked7 = ref(true); + obj.updateChecked7 = (newVal: boolean) => { + halfchecked7.value = !newVal; + doUpdate(obj.checked7, newVal); + }; + + return { + halfchecked7, + ...obj + }; + }, + render () { + const { + checked1, + updateChecked1, + checked2, + updateChecked2, + checked3, + updateChecked3, + checked4, + updateChecked4, + halfchecked7, + checked7, + updateChecked7, + checked9, + updateChecked9 + } = this; + + const checkboxProps = { + checked1: { + 'onUpdate:checked': updateChecked1 + }, checked2: { + 'onUpdate:checked': updateChecked2 + }, checked3: { + 'onUpdate:checked': updateChecked3 + }, checked4: { + 'onUpdate:checked': updateChecked4 + }, checked7: { + 'onUpdate:checked': updateChecked7 + }, checked9: { + 'onUpdate:checked': updateChecked9 + } + }; + + return ( +
+ + Checked + + + Not Checked + + + Custom title + + + No Animation + + + disabled + + + disabled + + + Half-checked + + + + + +
+ ); + } +}); diff --git a/devui/checkbox/demo/demo-checkbox-group.tsx b/devui/checkbox/demo/demo-checkbox-group.tsx new file mode 100644 index 00000000..bc7e8427 --- /dev/null +++ b/devui/checkbox/demo/demo-checkbox-group.tsx @@ -0,0 +1,155 @@ +import { defineComponent, reactive } from 'vue'; +import DCheckbox from '../src/checkbox'; +import DCheckboxGroup from '../src/checkbox-group'; + +export default defineComponent({ + name: 'DemoCheckboxGroup', + setup () { + const list1 = reactive(['b']); + const list2 = reactive(['b', 'c']); + const list3 = reactive(['a', 'c']); + const opts2 = [ + { + disabled: true, + value: 'a', + label: '道' + }, { + disabled: true, + value: 'b', + label: '可道' + }, { + disabled: true, + value: 'c', + label: '非常道' + }, { + disabled: true, + value: 'd', + label: '名' + }, { + disabled: true, + value: 'e', + label: '可名' + }, { + disabled: true, + value: 'f', + label: '非常名' + }, { + disabled: true, + value: 'g', + label: '无名' + }, { + disabled: true, + value: 'h', + label: '天地之始' + }, { + disabled: true, + value: 'g', + label: '有名' + }, { + disabled: true, + value: 'i', + label: '万物之母' + }, { + disabled: true, + value: 'j', + label: '故常无' + }, { + disabled: true, + value: 'k', + label: '欲以观其妙' + }, { + disabled: true, + value: 'l', + label: '常有' + }, { + disabled: true, + value: 'm', + label: '欲以观其徼' + }, { + disabled: true, + value: 'n', + label: '此两者' + }, { + disabled: true, + value: 'o', + label: '同出而异名' + }, { + disabled: true, + value: 'p', + label: '同谓之玄' + }, { + disabled: true, + value: 'q', + label: '玄之又玄,众妙之门' + } + ]; + const updateList1 = (v: string[]) => { + Object.assign(list1, v); + }; + const updateList3 = (v: string[]) => { + Object.assign(list3, v); + }; + + return { + list1, + list2, + list3, + opts2, + updateList1, + updateList3 + }; + }, + render () { + const { + list1, + list2, + list3, + opts2, + updateList1, + updateList3 + } = this; + + const groupProps = { + 'onUpdate:value': updateList1 + }; + const groupProps3 = { + 'onUpdate:value': updateList3 + }; + + return ( +
+
第三项根据条件禁止切换
+ v !== 'c' }> + + 金刚经 + + + 佛说世界 + + + 既非世界 + + + 故名世界 + + + +
多行复选框
+ + + +
itemWidth
+ + + + + + + + + + +
+ ); + } +}); diff --git a/devui/checkbox/src/checkbox-group.scss b/devui/checkbox/src/checkbox-group.scss new file mode 100644 index 00000000..796b99dd --- /dev/null +++ b/devui/checkbox/src/checkbox-group.scss @@ -0,0 +1,21 @@ +:host { + display: block; +} + +.devui-checkbox-list-inline { + min-height: 28px; + line-height: 28px; + display: flex; + flex-wrap: wrap; + justify-content: flex-start; + align-items: center; + margin: -8px 0 0 0; + + & div:not(:last-child) { + margin-right: 20px; + } + + & > * { + margin-top: 8px; + } +} diff --git a/devui/checkbox/src/checkbox-group.tsx b/devui/checkbox/src/checkbox-group.tsx new file mode 100644 index 00000000..ab206d60 --- /dev/null +++ b/devui/checkbox/src/checkbox-group.tsx @@ -0,0 +1,74 @@ +import { defineComponent, ExtractPropTypes, provide, toRef } from 'vue'; +import { checkboxGroupProps, checkboxGroupInjectionKey } from './use-checkbox'; +import DCheckbox from './checkbox'; +import './checkbox-group.scss'; + +export default defineComponent({ + name: 'DCheckboxGroup', + props: checkboxGroupProps, + emits: ['change', 'update:value'], + setup (props: ExtractPropTypes, ctx) { + const valList = toRef(props, 'value'); + const defaultOpt = { + checked: false, + isShowTitle: true, + halfchecked: false, + showAnimation: true, + disabled: false + }; + + const toggleGroupVal = (val: string) => { + const index = valList.value.findIndex(item => item === val); + if (index === -1) { + const res = [...valList.value, val]; + ctx.emit('update:value', res); + ctx.emit('change', res); + return; + } + valList.value.splice(index, 1); + ctx.emit('update:value', valList.value); + ctx.emit('change', valList.value); + }; + const isItemChecked = (itemVal: string) => valList.value.includes(itemVal); + + provide(checkboxGroupInjectionKey, { + disabled: toRef(props, 'disabled'), + isShowTitle: toRef(props, 'isShowTitle'), + color: toRef(props, 'color'), + showAnimation: toRef(props, 'showAnimation'), + beforeChange: props.beforeChange, + isItemChecked, + toggleGroupVal, + itemWidth: toRef(props, 'itemWidth'), + direction: toRef(props, 'direction') + }); + + return { + defaultOpt + }; + }, + render () { + const { + direction, + $slots, + defaultOpt, + options + } = this; + let children = $slots.default?.(); + + if (options?.length > 0) { + children = options.map(opt => { + const mergedOpt = Object.assign({}, defaultOpt, opt); + return (); + }); + } + + return ( +
+
+ { children } +
+
+ ); + } +}); diff --git a/devui/checkbox/src/checkbox.scss b/devui/checkbox/src/checkbox.scss new file mode 100644 index 00000000..f976cd21 --- /dev/null +++ b/devui/checkbox/src/checkbox.scss @@ -0,0 +1,228 @@ +@import '../../style/mixins/index'; +@import '../../style/theme/color'; +@import '../../style/theme/corner'; + +.devui-checkbox { + position: relative; + display: flex; + display: -ms-flexbox; + align-items: center; + -ms-flex-align: center; + height: 100%; + margin: 0; + + .devui-checkbox-tick { + position: absolute; + + .devui-tick { + fill: $devui-light-text; + stroke-dashoffset: 50; + opacity: 0; + transform: scale(0); + transform-origin: 50% 50%; + transition: stroke-dashoffset 0.2s ease-in-out, opacity 0.2s ease-in-out, transform 0.2s ease-in-out; + } + } + + &.active:not(.halfchecked) .devui-tick { + opacity: 1; + stroke-dashoffset: 0; + transform: scale(1); + transition: stroke-dashoffset 0.3s cubic-bezier(0.755, 0.05, 0.855, 0.06), opacity 0.2s cubic-bezier(0.755, 0.05, 0.855, 0.06); + } + + &.active, + &.halfchecked { + &:not(.disabled) .devui-checkbox-material:not(.custom-color) { + border-color: $devui-brand; + } + } + + &.active:not(.disabled) { + .devui-checkbox-material { + background-size: 100% 100%; + transition: background-size 0.2s ease-in-out, border-color 0.2s ease-in-out; + } + } + + &.unchecked:not(.disabled) { + .devui-checkbox-material:not(.custom-color) { + background-size: 0% 0%; + transition: background-size 0.2s ease-in-out, border-color 0.2s ease-in-out; + + &:hover { + border-color: $devui-icon-fill-active; + } + } + } + + &.unchecked:not(.disabled) { + .devui-checkbox-material.custom-color { + background-size: 0% 0%; + transition: background-size 0.2s ease-in-out, border-color 0.2s ease-in-out; + } + } + + &.halfchecked { + .devui-checkbox-material:not(.custom-color) { + background-color: $devui-brand; + + & > .devui-checkbox-halfchecked-bg { + opacity: 1; + transform: scale(0.4288); + transition: transform 0.2s cubic-bezier(0.755, 0.05, 0.855, 0.06); + background-color: $devui-light-text; + } + } + } + + &.halfchecked { + .devui-checkbox-material.custom-color { + & > .devui-checkbox-halfchecked-bg { + opacity: 1; + transform: scale(0.4288); + transition: transform 0.2s cubic-bezier(0.755, 0.05, 0.855, 0.06); + background-color: $devui-light-text; + } + } + } + + .devui-checkbox-material { + text-align: initial; + height: 14px; + width: 14px; + position: relative; + user-select: none; + border: 1px solid $devui-line; + border-radius: $devui-border-radius; + background: linear-gradient($devui-brand, $devui-brand) no-repeat center/0%; + margin-right: 8px; + vertical-align: text-bottom; + + &.devui-checkbox-default-background { + background-color: $devui-base-bg; + } + + &.devui-checkbox-no-label { + margin-right: 0; + } + + & > .devui-checkbox-halfchecked-bg { + display: inline-block; + position: absolute; + content: ''; + background-color: $devui-light-text; + top: 0; + left: 0; + height: 100%; + width: 100%; + transform: scale(1); + opacity: 0; + } + + & > svg { + width: 14px; + height: 14px; + } + } + + &:not(.disabled).halfchecked { + .devui-checkbox-material:not(.custom-color) { + &:focus, + &:active, + &:hover { + background-color: $devui-icon-fill-active; + } + + // 激活状态深色 + &:active, + &:focus, + &:hover:active, + &:hover:focus { + background-color: $devui-brand-active-focus; + } + } + } + + &-input { + opacity: 0; + position: absolute; + margin: 0; + z-index: -1; + width: 0; + height: 0; + overflow: hidden; + left: 0; + pointer-events: none; + } + + & label { + position: relative; + font-weight: normal; + height: 16px; + line-height: 16px; + cursor: pointer; + color: $devui-text; + margin: 0; + display: block; + + & > span { + display: inline-block; + box-sizing: content-box; + vertical-align: top; + } + } + + // 禁用状态透明色 + &.disabled { + label { + cursor: not-allowed; + color: $devui-disabled-text; + } + + .devui-checkbox-material { + border-color: $devui-icon-fill-active-disabled; + background-color: $devui-icon-fill-active-disabled; + } + + &.unchecked { + .devui-checkbox-material { + border-color: $devui-disabled-line; + background-color: $devui-disabled-bg; + } + } + + &.halfchecked { + .devui-checkbox-material { + background-color: $devui-disabled-bg; + + .devui-checkbox-halfchecked-bg { + transform: scale(0.4288); + background-color: $devui-disabled-text; + opacity: 1; + } + } + } + + &.active { + svg polygon { + fill: $devui-light-text; + } + } + } +} + +.devui-no-animation { + transition: none !important; +} + +.devui-checkbox-column-margin { + height: 28px; + line-height: 28px; +} + +.devui-checkbox-wrap .devui-checkbox label { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} diff --git a/devui/checkbox/src/checkbox.tsx b/devui/checkbox/src/checkbox.tsx new file mode 100644 index 00000000..6aaab369 --- /dev/null +++ b/devui/checkbox/src/checkbox.tsx @@ -0,0 +1,160 @@ +import { defineComponent, inject, computed } from 'vue'; +import './checkbox.scss'; +import { checkboxGroupInjectionKey, checkboxProps, CheckboxProps } from './use-checkbox'; + +export default defineComponent({ + name: 'DCheckbox', + props: checkboxProps, + emits: ['change', 'update:checked'], + setup (props: CheckboxProps, ctx) { + const checkboxGroupConf = inject(checkboxGroupInjectionKey, null); + const mergedDisabled = computed(() => { + return checkboxGroupConf?.disabled.value || props.disabled; + }); + const mergedChecked = computed(() => { + return checkboxGroupConf ? checkboxGroupConf.isItemChecked(props.value) : props.checked; + }); + const mergedIsShowTitle = computed(() => { + return checkboxGroupConf ? checkboxGroupConf.isShowTitle : props.isShowTitle; + }); + const mergedShowAnimation = computed(() => { + return checkboxGroupConf ? checkboxGroupConf.showAnimation : props.showAnimation; + }); + const mergedColor = computed(() => { + return checkboxGroupConf ? checkboxGroupConf.color : props.color; + }); + const itemWidth = checkboxGroupConf ? checkboxGroupConf.itemWidth.value : undefined; + const direction = checkboxGroupConf ? checkboxGroupConf.direction.value : undefined; + + const canChange = (isChecked: boolean, val: string) => { + if (mergedDisabled.value) { + return Promise.resolve(false); + } + + const beforeChange = props.beforeChange || (checkboxGroupConf ? checkboxGroupConf?.beforeChange : undefined); + if (beforeChange) { + const res = beforeChange(isChecked, val); + if (typeof res === 'boolean') { + return Promise.resolve(res); + } + return res; + } + return Promise.resolve(true); + }; + const toggle = () => { + const isChecked = !props.checked; + checkboxGroupConf?.toggleGroupVal(props.value); + ctx.emit('update:checked', isChecked); + ctx.emit('change', isChecked); + }; + const handleClick = () => { + canChange(!props.checked, props.value).then(res => res && toggle()); + }; + + return { + itemWidth, + direction, + mergedColor, + mergedDisabled, + mergedIsShowTitle, + mergedChecked, + mergedShowAnimation, + handleClick + }; + }, + render () { + const { + itemWidth, + direction, + mergedChecked, + mergedDisabled, + mergedIsShowTitle, + mergedShowAnimation, + halfchecked, + title, + label, + handleClick, + name, + value, + mergedColor, + $slots + } = this; + + const wrapperCls = { + 'devui-checkbox-column-margin': direction === 'column', + 'devui-checkbox-wrap': typeof itemWidth !== 'undefined' + }; + const wrapperStyle = itemWidth? [ + `width: ${itemWidth}px` + ] : []; + const checkboxCls = { + 'devui-checkbox': true, + active: mergedChecked, + halfchecked, + disabled: mergedDisabled, + unchecked: !mergedChecked + }; + const labelTitle = mergedIsShowTitle ? title || label : ''; + const bgImgStyle = (mergedColor && halfchecked) || mergedColor ? `linear-gradient(${mergedColor}, ${mergedColor})` : ''; + const spanStyle = [ + `border-color:${(mergedChecked || halfchecked) && mergedColor ? mergedColor : ''}`, + `background-image:${bgImgStyle}`, + `background-color:${mergedColor && halfchecked ? mergedColor : ''}` + ]; + const spanCls = { + 'devui-checkbox-material': true, + 'custom-color': mergedColor, + 'devui-checkbox-no-label': !label && !$slots.default, + 'devui-no-animation': !mergedShowAnimation, + 'devui-checkbox-default-background': !halfchecked + }; + const polygonCls = { + 'devui-tick': true, + 'devui-no-animation': !mergedShowAnimation + }; + const stopPropagation = ($event: Event) => $event.stopPropagation(); + + const inputProps = { + indeterminate: halfchecked + }; + + return ( +
+
+ +
+
+ ); + } +}); diff --git a/devui/checkbox/src/use-checkbox.ts b/devui/checkbox/src/use-checkbox.ts new file mode 100644 index 00000000..86f34bcd --- /dev/null +++ b/devui/checkbox/src/use-checkbox.ts @@ -0,0 +1,106 @@ +import { PropType, InjectionKey, Ref, ExtractPropTypes } from 'vue'; + +type Direction = 'row' | 'column'; + +const commonProps = { + name: { + type: String, + default: undefined + }, + isShowTitle: { + type: Boolean, + default: true + }, + color: { + type: String, + default: undefined + }, + showAnimation: { + type: Boolean, + default: true + }, + disabled: { + type: Boolean, + default: false + }, + beforeChange: { + type: Function as PropType<(isChecked: boolean, v: string) => boolean | Promise>, + default: undefined + } +} as const; + +export const checkboxProps = { + ...commonProps, + halfchecked: { + type: Boolean, + default: false + }, + checked: { + type: Boolean, + default: false + }, + value: { + type: String, + required: true + }, + label: { + type: String, + default: undefined + }, + title: { + type: String, + default: undefined + }, + 'onUpdate:checked' : { + type: Function as PropType<(v: boolean) => void>, + default: undefined + }, + onChange: { + type: Function as PropType<(v: boolean) => void>, + default: undefined + } +} as const; + +export type CheckboxProps = ExtractPropTypes; + +export const checkboxGroupProps = { + ...commonProps, + value: { + type: Array as PropType, + required: true + }, + direction: { + type: String as PropType, + default: 'column' + }, + itemWidth: { + type: Number, + default: undefined + }, + options: { + type: Array as PropType<({value: string;} & Partial)[]>, + default: () => [] + }, + onChange: { + type: Function as PropType<(v: string[]) => void>, + default: undefined + }, + 'onUpdate:value': { + type: Function as PropType<(v: string[]) => void>, + default: undefined + } +} as const; + +interface checkboxGroupInjection { + disabled: Ref + isShowTitle: Ref + color: Ref + showAnimation: Ref + beforeChange: undefined | ((isChecked: boolean, v: string) => boolean | Promise) + toggleGroupVal: (v: string) => void + isItemChecked: (v: string) => boolean + itemWidth: Ref + direction: Ref +} + +export const checkboxGroupInjectionKey: InjectionKey = Symbol('d-checkbox-group'); -- Gitee