Эх сурвалжийг харах

Add button style to checkbox (#3697)

* Add button style to checkbox

* Implement min/max for checkbox button group

* Fixing a display bug when chekbox is ':checked'

* Update docs to integrate example button+min/max option

* Register correctly checkbox-button
Last fixes after cherry pick and bad rebase...
Mathieu DARTIGUES 8 жил өмнө
parent
commit
0bf50bfc2e

+ 1 - 0
components.json

@@ -15,6 +15,7 @@
   "radio-group": "./packages/radio-group/index.js",
   "radio-button": "./packages/radio-button/index.js",
   "checkbox": "./packages/checkbox/index.js",
+  "checkbox-button": "./packages/checkbox-button/index.js",
   "checkbox-group": "./packages/checkbox-group/index.js",
   "switch": "./packages/switch/index.js",
   "select": "./packages/select/index.js",

+ 44 - 2
examples/docs/en-US/checkbox.md

@@ -13,7 +13,10 @@
         cities: cityOptions,
         checkedCities: ['Shanghai', 'Beijing'],
         checkedCities1: ['Shanghai', 'Beijing'],
-        isIndeterminate: true
+        isIndeterminate: true,
+        checkboxGroup1: ['Shanghai'],
+        checkboxGroup2: ['Beijing'],
+        checkboxGroup3: ['Guangzhou']
       };
     },
     methods: {
@@ -154,7 +157,6 @@ The `indeterminate` property can help you to achieve a 'check all' effect.
 ```
 :::
 
-
 ### Minimum / Maximum items checked
 
 The `min` and `max` properties can help you to limit the number of checked items.
@@ -184,6 +186,43 @@ The `min` and `max` properties can help you to limit the number of checked items
 ```
 :::
 
+### Button style
+
+Checkbox with button styles.
+
+:::demo You just need to change `<el-checkbox>` element into `<el-checkbox-button>` element. We also provide `size` attribute for these buttons: `large` and `small`.
+```html
+<template>
+  <div style="margin: 15px 0;"></div>
+  <el-checkbox-group v-model="checkboxGroup1">
+    <el-checkbox-button v-for="city in cities" :label="city">{{city}}</el-checkbox-button>
+  </el-checkbox-group>
+  <div style="margin: 15px 0;"></div>
+  <el-checkbox-group v-model="checkboxGroup2" size="small">
+    <el-checkbox-button v-for="city in cities" :label="city" :disabled="city === 'Shenzhen'">{{city}}</el-checkbox-button>
+  </el-checkbox-group>
+  <div style="margin: 15px 0;"></div>
+  <el-checkbox-group v-model="checkboxGroup3" size="large" fill="#324057" text-color="#a4aebd" :min="1" :max="3">
+    <el-checkbox-button v-for="city in cities" :label="city">{{city}}</el-checkbox-button>
+  </el-checkbox-group>
+</template>
+<script>
+  const cityOptions = ['Shanghai', 'Beijing', 'Guangzhou', 'Shenzhen'];
+
+  export default {
+    data () {
+      return {
+        checkboxGroup1: ['Shanghai'],
+        checkboxGroup2: ['Beijing'],
+        checkboxGroup3: ['Guangzhou'],
+        cities: cityOptions
+      };
+    }
+  }
+</script>
+```
+:::
+
 ### Checkbox Attributes
 | Attribute      | Description         | Type    | Options                         | Default|
 |---------- |-------- |---------- |-------------  |-------- |
@@ -198,6 +237,9 @@ The `min` and `max` properties can help you to limit the number of checked items
 ### Checkbox-group Attributes
 | Attribute      | Description         | Type    | Options                         | Default|
 |---------- |-------- |---------- |-------------  |-------- |
+|size | the size of checkbox buttons | string | large/small | —
+|fill  | border and background color when button is active | string   | — | #20a0ff   |
+|text-color | font color when button is active | string   | — | #ffffff   |
 | min     | minimum number of checkbox checked   | number    |       —        |     —    |
 | max     | maximum number of checkbox checked   | number    |       —        |     —    |
 

+ 48 - 3
examples/docs/zh-CN/checkbox.md

@@ -13,7 +13,10 @@
         cities: cityOptions,
         checkedCities: ['上海', '北京'],
         checkedCities1: ['上海', '北京'],
-        isIndeterminate: true
+        isIndeterminate: true,
+        checkboxGroup1: ['上海'],
+        checkboxGroup2: ['北京'],
+        checkboxGroup3: ['广州']
       };
     },
     methods: {
@@ -191,7 +194,46 @@
   };
 </script>
 ```
+
 :::
+
+### Button style (to be translated)
+
+Checkbox with button styles.
+
+:::demo 只需要把`el-checkbox`元素换成`el-checkbox-button`元素即可,此外,Element 还提供了`size`属性给按钮组,支持`large`和`small`两种(如果不设定为默认)
+```html
+<template>
+  <div style="margin: 15px 0;"></div>
+  <el-checkbox-group v-model="checkboxGroup1">
+    <el-checkbox-button v-for="city in cities" :label="city">{{city}}</el-checkbox-button>
+  </el-checkbox-group>
+  <div style="margin: 15px 0;"></div>
+  <el-checkbox-group v-model="checkboxGroup2" size="small">
+    <el-checkbox-button v-for="city in cities" :label="city" :disabled="city === '深圳'">{{city}}</el-checkbox-button>
+  </el-checkbox-group>
+  <div style="margin: 15px 0;"></div>
+  <el-checkbox-group v-model="checkboxGroup3" size="large" fill="#324057" text-color="#a4aebd" :min="1" :max="3">
+    <el-checkbox-button v-for="city in cities" :label="city">{{city}}</el-checkbox-button>
+  </el-checkbox-group>
+</template>
+<script>
+  const cityOptions = ['上海', '北京', '广州', '深圳'];
+  export default {
+    data () {
+      return {
+        checkboxGroup1: ['上海'],
+        checkboxGroup2: ['北京'],
+        checkboxGroup3: ['广州'],
+        cities: cityOptions
+      };
+    }
+  }
+</script>
+```
+
+:::
+
 ### Checkbox Attributes
 | 参数      | 说明    | 类型      | 可选值       | 默认值   |
 |---------- |-------- |---------- |-------------  |-------- |
@@ -203,9 +245,12 @@
 | checked  | 当前是否勾选    | boolean   |  — | false   |
 | indeterminate  | 设置 indeterminate 状态,只负责样式控制    | boolean   |  — | false   |
 
-### Checkbox-group Attributes
-| 参数       | 说明        | 类型    | 可选值                         | 默认值 |
+### Checkbox-group Attributes (to be translated)
+| 参数      | 说明    | 类型      | 可选值       | 默认值   |
 |---------- |-------- |---------- |-------------  |-------- |
+| size     | Checkbox 按钮组尺寸   | string  | large, small  |    —     |
+| fill  | 按钮激活时的填充色和边框色    | string   | — | #20a0ff   |
+| text-color  | 按钮激活时的文本颜色    | string   | — | #ffffff   |
 | min     | 可被勾选的 checkbox 的最大数量   | number    |       —        |     —    |
 | max     | 可被勾选的 checkbox 的最小数量   | number    |       —        |     —    |
 

+ 8 - 0
packages/checkbox-button/index.js

@@ -0,0 +1,8 @@
+import ElCheckboxButton from '../checkbox/src/checkbox-button.vue';
+
+/* istanbul ignore next */
+ElCheckboxButton.install = function(Vue) {
+  Vue.component(ElCheckboxButton.name, ElCheckboxButton);
+};
+
+export default ElCheckboxButton;

+ 161 - 0
packages/checkbox/src/checkbox-button.vue

@@ -0,0 +1,161 @@
+<template>
+  <label
+    class="el-checkbox-button"
+      :class="[
+        size ? 'el-checkbox-button--' + size : '',
+        { 'is-disabled': disabled },
+        { 'is-checked': isChecked },
+        { 'is-focus': focus },
+      ]"
+    >
+    <input
+      v-if="trueLabel || falseLabel"
+      class="el-checkbox-button__original"
+      type="checkbox"
+      :name="name"
+      :disabled="disabled"
+      :true-value="trueLabel"
+      :false-value="falseLabel"
+      v-model="model"
+      @change="handleChange"
+      @focus="focus = true"
+      @blur="focus = false">
+    <input
+      v-else
+      class="el-checkbox-button__original"
+      type="checkbox"
+      :name="name"
+      :disabled="disabled"
+      :value="label"
+      v-model="model"
+      @change="handleChange"
+      @focus="focus = true"
+      @blur="focus = false">
+
+    <span class="el-checkbox-button__inner" 
+      v-if="$slots.default || label"
+      :style="isChecked ? activeStyle : null">
+      <slot>{{label}}</slot>
+    </span>
+
+  </label>
+</template>
+<script>
+  import Emitter from 'element-ui/src/mixins/emitter';
+
+  export default {
+    name: 'ElCheckboxButton',
+
+    mixins: [Emitter],
+
+    data() {
+      return {
+        selfModel: false,
+        focus: false
+      };
+    },
+
+    props: {
+      value: {},
+      label: {},
+      disabled: Boolean,
+      checked: Boolean,
+      name: String,
+      trueLabel: [String, Number],
+      falseLabel: [String, Number]
+    },
+    computed: {
+      model: {
+        get() {
+          return this._checkboxGroup
+            ? this.store : this.value !== undefined
+            ? this.value : this.selfModel;
+        },
+
+        set(val) {
+          if (this._checkboxGroup) {
+            let isLimitExceeded = false;
+            (this._checkboxGroup.min !== undefined &&
+              val.length < this._checkboxGroup.min &&
+              (isLimitExceeded = true));
+
+            (this._checkboxGroup.max !== undefined &&
+              val.length > this._checkboxGroup.max &&
+              (isLimitExceeded = true));
+
+            isLimitExceeded === false &&
+            this.dispatch('ElCheckboxGroup', 'input', [val]);
+          } else if (this.value !== undefined) {
+            this.$emit('input', val);
+          } else {
+            this.selfModel = val;
+          }
+        }
+      },
+
+      isChecked() {
+        if ({}.toString.call(this.model) === '[object Boolean]') {
+          return this.model;
+        } else if (Array.isArray(this.model)) {
+          return this.model.indexOf(this.label) > -1;
+        } else if (this.model !== null && this.model !== undefined) {
+          return this.model === this.trueLabel;
+        }
+      },
+
+      _checkboxGroup() {
+        let parent = this.$parent;
+        while (parent) {
+          if (parent.$options.componentName !== 'ElCheckboxGroup') {
+            parent = parent.$parent;
+          } else {
+            return parent;
+          }
+        }
+        return false;
+      },
+
+      store() {
+        return this._checkboxGroup ? this._checkboxGroup.value : this.value;
+      },
+
+      activeStyle() {
+        return {
+          backgroundColor: this._checkboxGroup.fill || '',
+          borderColor: this._checkboxGroup.fill || '',
+          color: this._checkboxGroup.textColor || '',
+          'box-shadow': '-1px 0 0 0 ' + this._checkboxGroup.fill
+
+        };
+      },
+
+      size() {
+        return this._checkboxGroup.size;
+      }
+    },
+    methods: {
+      addToStore() {
+        if (
+          Array.isArray(this.model) &&
+          this.model.indexOf(this.label) === -1
+        ) {
+          this.model.push(this.label);
+        } else {
+          this.model = this.trueLabel || true;
+        }
+      },
+      handleChange(ev) {
+        this.$emit('change', ev);
+        if (this._checkboxGroup) {
+          this.$nextTick(_ => {
+            this.dispatch('ElCheckboxGroup', 'change', [this._checkboxGroup.value]);
+          });
+        }
+      }
+    },
+
+    created() {
+      this.checked && this.addToStore();
+    }
+  };
+</script>

+ 4 - 1
packages/checkbox/src/checkbox-group.vue

@@ -11,7 +11,10 @@
     props: {
       value: {},
       min: Number,
-      max: Number
+      max: Number,
+      size: String,
+      fill: String,
+      textColor: String
     },
 
     watch: {

+ 104 - 0
packages/theme-default/src/checkbox.css

@@ -147,4 +147,108 @@
       margin-left: 15px;
     }
   }
+
+  @b checkbox-button {
+    position: relative;
+    display: inline-block;
+
+    @e inner {
+      display: inline-block;
+      line-height: 1;
+      white-space: nowrap;
+      vertical-align: middle;
+      cursor: pointer;
+      background: var(--button-default-fill);
+      border: var(--border-base);
+      border-left: 0;
+      color: var(--button-default-color);
+      -webkit-appearance: none;
+      text-align: center;
+      box-sizing: border-box;
+      outline: none;
+      margin: 0;
+      position: relative;
+      cursor: pointer;
+      transition: var(--all-transition);
+      @utils-user-select none;
+
+      @mixin button-size var(--button-padding-vertical), var(--button-padding-horizontal), var(--button-font-size), 0;
+
+      &:hover {
+        color: var(--color-primary);
+      }
+
+      & [class*="el-icon-"] {
+        line-height: 0.9;
+
+        & + span {
+          margin-left: 5px;
+        }
+      }
+    }
+
+    @e original {
+      opacity: 0;
+      outline: none;
+      position: absolute;
+      margin: 0;
+      visibility: hidden;
+      left: -999px;
+    }
+
+    &.is-checked {
+      & .el-checkbox-button__inner {
+        color: var(--checkbox-button-checked-color);
+        background-color: var(--checkbox-button-checked-fill);
+        border-color: var(--checkbox-button-checked-border-color);
+        box-shadow: -1px 0 0 0 var(--checkbox-button-checked-border-color);
+      }
+    }
+
+    &.is-disabled {
+      & .el-checkbox-button__inner {
+        color: var(--button-disabled-color);
+        cursor: not-allowed;
+        background-image: none;
+        background-color: var(--button-disabled-fill);
+        border-color: var(--button-disabled-border);
+        box-shadow: none;
+      }
+    }
+    
+    @.is-focus {
+      & .el-checkbox-button__inner {
+        border-color: var(--checkbox-button-checked-border-color);
+      }
+    }
+
+
+    &:first-child {
+      .el-checkbox-button__inner {
+        border-left: var(--border-base);
+        border-radius: var(--border-radius-base) 0 0 var(--border-radius-base);
+        box-shadow: none !important;
+      }
+    }
+    &:last-child {
+      .el-checkbox-button__inner {
+        border-radius: 0 var(--border-radius-base) var(--border-radius-base) 0;
+      }
+    }
+    @m large {
+      & .el-checkbox-button__inner {
+        @mixin button-size var(--button-large-padding-vertical), var(--button-large-padding-horizontal), var(--button-large-font-size), 0;
+      }
+    }
+    @m small {
+      & .el-checkbox-button__inner {
+        @mixin button-size var(--button-small-padding-vertical), var(--button-small-padding-horizontal), var(--button-small-font-size), 0;
+      }
+    }
+    @m mini {
+      & .el-checkbox-button__inner {
+        @mixin button-size var(--button-mini-padding-vertical), var(--button-mini-padding-horizontal), var(--button-mini-font-size), 0;
+      }
+    }
+  }
 }

+ 7 - 0
packages/theme-default/src/common/var.css

@@ -112,6 +112,13 @@
 
   --checkbox-input-border-color-hover: var(--color-primary);
 
+  --checkbox-button-font-size: var(--font-size-base);
+  --checkbox-button-checked-fill: var(--color-primary);
+  --checkbox-button-checked-color: var(--color-white);
+  --checkbox-button-checked-border-color: var(--color-primary);
+
+
+
   /* Radio
   -------------------------- */
   --radio-font-size: 14px;

+ 3 - 0
src/index.js

@@ -16,6 +16,7 @@ import Radio from '../packages/radio/index.js';
 import RadioGroup from '../packages/radio-group/index.js';
 import RadioButton from '../packages/radio-button/index.js';
 import Checkbox from '../packages/checkbox/index.js';
+import CheckboxButton from '../packages/checkbox-button/index.js';
 import CheckboxGroup from '../packages/checkbox-group/index.js';
 import Switch from '../packages/switch/index.js';
 import Select from '../packages/select/index.js';
@@ -82,6 +83,7 @@ const components = [
   RadioGroup,
   RadioButton,
   Checkbox,
+  CheckboxButton,
   CheckboxGroup,
   Switch,
   Select,
@@ -176,6 +178,7 @@ module.exports = {
   RadioGroup,
   RadioButton,
   Checkbox,
+  CheckboxButton,
   CheckboxGroup,
   Switch,
   Select,

+ 211 - 0
test/unit/specs/checkbox.spec.js

@@ -170,4 +170,215 @@ describe('Checkbox', () => {
     expect(vm.checked).to.be.true;
     expect(vm.checklist.indexOf('a') !== -1).to.be.true;
   });
+
+  describe('checkbox-button', () => {
+    let vm;
+    afterEach(() => {
+      destroyVM(vm);
+    });
+
+    it('create', done => {
+      vm = createVue({
+        template: `
+          <el-checkbox-button v-model="checked">
+          </el-checkbox-button>
+        `,
+        data() {
+          return {
+            checked: false
+          };
+        }
+      }, true);
+      let checkboxElm = vm.$el;
+      expect(checkboxElm.classList.contains('el-checkbox-button')).to.be.true;
+      checkboxElm.click();
+      vm.$nextTick(_ => {
+        expect(checkboxElm.classList.contains('is-checked')).to.be.ok;
+        done();
+      });
+    });
+    it('disabled', () => {
+      vm = createVue({
+        template: `
+          <el-checkbox-button
+            v-model="checked"
+            disabled
+          >
+          </el-checkbox-button>
+        `,
+        data() {
+          return {
+            checked: false
+          };
+        }
+      }, true);
+      let checkboxElm = vm.$el;
+      expect(checkboxElm.classList.contains('is-disabled')).to.be.ok;
+    });
+
+    it('checkbox group', done => {
+      vm = createVue({
+        template: `
+          <el-checkbox-group v-model="checkList">
+            <el-checkbox-button label="a" ref="a"></el-checkbox-button>
+            <el-checkbox-button label="b" ref="b"></el-checkbox-button>
+            <el-checkbox-button label="c" ref="c"></el-checkbox-button>
+            <el-checkbox-button label="d" ref="d"></el-checkbox-button>
+          </el-checkbox-group>
+        `,
+        data() {
+          return {
+            checkList: []
+          };
+        }
+      }, true);
+      expect(vm.checkList.length === 0).to.be.true;
+      vm.$refs.a.$el.click();
+      vm.$nextTick(_ => {
+        expect(vm.checkList.indexOf('a') !== -1).to.be.true;
+        vm.$refs.b.$el.click();
+        vm.$nextTick(_ => {
+          expect(vm.checkList.indexOf('a') !== -1).to.be.true;
+          expect(vm.checkList.indexOf('b') !== -1).to.be.true;
+          done();
+        });
+      });
+    });
+
+    it('checkbox group props', () => {
+      vm = createVue({
+        template: `
+          <el-checkbox-group v-model="checkList" size="large" fill="#FF0000" text-color="#000">
+            <el-checkbox-button label="a" ref="a"></el-checkbox-button>
+            <el-checkbox-button label="b" ref="b"></el-checkbox-button>
+            <el-checkbox-button label="c" ref="c"></el-checkbox-button>
+            <el-checkbox-button label="d" ref="d"></el-checkbox-button>
+          </el-checkbox-group>
+        `,
+        data() {
+          return {
+            checkList: ['a', 'd']
+          };
+        }
+      }, true);
+      expect(vm.checkList.length === 2).to.be.true;
+      expect(vm.$refs.a.$el.classList.contains('is-checked')).to.be.true;
+      expect(vm.$refs.a.$el.classList.contains('el-checkbox-button--large')).to.be.true;
+      expect(vm.$refs.a.$el.querySelector('.el-checkbox-button__inner').style.backgroundColor).to.be.eql('rgb(255, 0, 0)');
+      expect(vm.$refs.a.$el.querySelector('.el-checkbox-button__inner').style.boxShadow).to.be.eql('rgb(255, 0, 0) -1px 0px 0px 0px');
+      expect(vm.$refs.a.$el.querySelector('.el-checkbox-button__inner').style.borderColor).to.be.eql('rgb(255, 0, 0)');
+      expect(vm.$refs.a.$el.querySelector('.el-checkbox-button__inner').style.color).to.be.eql('rgb(0, 0, 0)');
+      expect(vm.$refs.b.$el.classList.contains('is-checked')).to.be.false;
+      expect(vm.$refs.c.$el.classList.contains('is-checked')).to.be.false;
+      expect(vm.$refs.d.$el.classList.contains('is-checked')).to.be.true;
+    });
+
+    it('checkbox group minimum and maximum', done => {
+      vm = createVue({
+        template: `
+          <el-checkbox-group 
+            v-model="checkList" 
+            :min="1" 
+            :max="2"
+          >
+            <el-checkbox-button label="a" ref="a"></el-checkbox-button>
+            <el-checkbox-button label="b" ref="b"></el-checkbox-button>
+            <el-checkbox-button label="c" ref="c"></el-checkbox-button>
+            <el-checkbox-button label="d" ref="d"></el-checkbox-button>
+          </el-checkbox-group>
+        `,
+        data() {
+          return {
+            checkList: ['a'],
+            lastEvent: null
+          };
+        }
+      }, true);
+      expect(vm.checkList.length === 1).to.be.true;
+      vm.$refs.a.$el.click();
+      vm.$nextTick(() => {
+        expect(vm.checkList.indexOf('a') !== -1).to.be.true;
+        vm.$refs.b.$el.click();
+        vm.$nextTick(() => {
+          expect(vm.checkList.indexOf('a') !== -1).to.be.true;
+          expect(vm.checkList.indexOf('b') !== -1).to.be.true;
+          vm.$refs.c.$el.click();
+          vm.$nextTick(() => {
+            expect(vm.checkList.indexOf('c') !== -1).to.be.false;
+            expect(vm.checkList.indexOf('d') !== -1).to.be.false;
+            done();
+          });
+        });
+      });
+    });
+
+    it('nested group', done => {
+      vm = createVue({
+        template: `
+          <el-checkbox-group v-model="checkList">
+            <el-row>
+              <el-checkbox-button label="a" ref="a"></el-checkbox-button>
+              <el-checkbox-button label="b" ref="b"></el-checkbox-button>
+              <el-checkbox-button label="c" ref="c"></el-checkbox-button>
+              <el-checkbox-button label="d" ref="d"></el-checkbox-button>
+            </el-row>
+          </el-checkbox-group>
+        `,
+        data() {
+          return {
+            checkList: []
+          };
+        }
+      }, true);
+      expect(vm.checkList.length === 0).to.be.true;
+      vm.$refs.a.$el.click();
+      vm.$nextTick(_ => {
+        expect(vm.checkList.indexOf('a') !== -1).to.be.true;
+        done();
+      });
+    });
+
+    it('true false label', done => {
+      vm = createVue({
+        template: `
+          <el-checkbox-button 
+            true-label="a" 
+            :false-label="3" 
+            v-model="checked"
+          ></el-checkbox-button>
+        `,
+        data() {
+          return {
+            checked: 'a'
+          };
+        }
+      }, true);
+      vm.$el.click();
+      vm.$nextTick(_ => {
+        expect(vm.checked === 3).to.be.true;
+        done();
+      });
+    });
+    it('checked', () => {
+      vm = createVue({
+        template: `
+          <div>
+            <el-checkbox-button v-model="checked" checked></el-checkbox-button>
+            <el-checkbox-group v-model="checklist">
+              <el-checkbox-button checked label="a"></el-checkbox-button>
+            </el-checkbox-group>
+          </div>
+        `,
+        data() {
+          return {
+            checked: false,
+            checklist: []
+          };
+        }
+      }, true);
+      expect(vm.checked).to.be.true;
+      expect(vm.checklist.indexOf('a') !== -1).to.be.true;
+    });
+
+  });
 });