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 (
+
+ );
+ }
+});
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