Skip to content

feat(form-checkbox): support custom switch styling #2293

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 19 commits into from
Dec 15, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 72 additions & 1 deletion src/components/form-checkbox/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,77 @@ export default {
```


## Switch style checkboxes
Bootstrap V4.2 (unreleased) adds in support for _Switch Style_ checkboxes. Bootstrap-Vue has added
this pre-release feature by conditionally including the required custom SCSS styles.

Switch styling is supported on `<b-form-checkbox>` and `<b-form-checkbox-group>` components.

**Note:** If the checkbox is in [button mode](#button-style-checkboxes), switch mode will have no effect.

### Individual checkbox switch style
A single checkbox can be rendered with a switch appearance by setting the prop `switch` to `true`

```html
<template>
<div>
<b-form-checkbox switch v-model="checked" name="check-button">
Switch Checkbox <b>(Checked: {{ checked }})</b>
</b-form-checkbox>
</div>
</template>

<script>
export default {
data () {
return {
checked: false
}
}
}
</script>

<!-- form-checkbox-switch.vue -->
```

### Grouped switch style checkboxes
Render groups of checkboxes with the look of a switches by setting the prop `switches` on `<b-form-checkbox-group>`.

```html
<template>
<div>
<b-form-group label="Inline switch style checkboxes">
<b-form-checkbox-group switches v-model="selected" name="switches1" :options="options">
</b-form-checkbox-group>
</b-form-group>

<b-form-group label="Stacked (vertical) switch style checkboxes">
<b-form-checkbox-group switches v-model="selected" stacked :options="options">
</b-form-checkbox-group>
</b-form-group>
</div>
</template>

<script>
export default {
data () {
return {
selected: [], // Must be an array reference!
options: [
{text: 'Red', value: 'red'},
{text: 'Green', value: 'green'},
{text: 'Yellow (disabled)', value: 'yellow', disabled: true},
{text: 'Blue', value: 'blue'}
]
}
}
}
</script>

<!-- form-checkboxs-switch-group.vue -->
```


## Non custom check inputs (plain)
You can have `<b-form-checkbox-group>` or `<b-form-checkbox>` render a browser native
chechbox input by setting the `plain` prop.
Expand Down Expand Up @@ -335,7 +406,7 @@ prop (defaults to `false`). Clicking the checkbox will clear its indeterminate s
The `indeterminate` prop can be synced to the checkbox's state by v-binding the
`indeterminate` prop with the `.sync` modifier.

**Note:** indeterminate is not supported in button mode, nor is it supported in
**Note:** indeterminate styling is not supported in button or switch mode, nor is it supported in
`<b-form-checkbox-group>` (multiple checkboxes).

**Single Indeterminate checkbox:**
Expand Down
61 changes: 61 additions & 0 deletions src/components/form-checkbox/_form-checkbox.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// This SCSS file can be removed once Bootstrap V4.2 is released and Boostrap-Vue is upgraded to use it.

@if variable-exists(custom-switch-width) == false {
// Only include this SCSS if not already defined (i.e. not using V4.2 SCSS)

// From Boostrap v4.2 SCSS _custom-forms.scss
// Define default values
$custom-control-indicator-border-width: $input-border-width !default;
$custom-control-indicator-border-color: $gray-500 !default;
$custom-switch-width: $custom-control-indicator-size * 1.75 !default;
$custom-switch-indicator-border-radius: $custom-control-indicator-size / 2 !default;
$custom-switch-indicator-size: calc(#{$custom-control-indicator-size} - #{$custom-control-indicator-border-width * 4}) !default;

// Override for V4.1.3 SCSS
$bv-temp-custom-control-gutter: .5rem !default;

// switches
//
// Tweak a few things for switches
.custom-switch {
// V4.2 uses a different gutter size
// padding-left: $custom-switch-width + $custom-control-gutter;
padding-left: $custom-switch-width + $bv-temp-custom-control-gutter;

.custom-control-label {
&::before {
// V4.2 uses a different gutter size
// left: -($custom-switch-width + $custom-control-gutter);
left: -($custom-switch-width + $bv-temp-custom-control-gutter);
width: $custom-switch-width;
pointer-events: all;
border-radius: $custom-switch-indicator-border-radius;
}

&::after {
top: calc(#{(($font-size-base * $line-height-base - $custom-control-indicator-size) / 2)} + #{$custom-control-indicator-border-width * 2});
// V4.2 uses a different gutter size
// left: calc(#{-($custom-switch-width + $custom-control-gutter)} + #{$custom-control-indicator-border-width * 2});
left: calc(#{-($custom-switch-width + $bv-temp-custom-control-gutter)} + #{$custom-control-indicator-border-width * 2});
width: $custom-switch-indicator-size;
height: $custom-switch-indicator-size;
background-color: $custom-control-indicator-border-color;
border-radius: $custom-switch-indicator-border-radius;
@include transition(transform .15s ease-in-out, $custom-forms-transition);
}
}

.custom-control-input:checked ~ .custom-control-label {
&::after {
background-color: $custom-control-indicator-bg;
transform: translateX($custom-switch-width - $custom-control-indicator-size);
}
}

.custom-control-input:disabled {
&:checked ~ .custom-control-label::before {
background-color: $custom-control-indicator-checked-disabled-bg;
}
}
}
}
5 changes: 5 additions & 0 deletions src/components/form-checkbox/form-checkbox-group.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@ export default {
}
},
props: {
switches: {
// Custom switch styling
type: Boolean,
default: false
},
checked: {
type: [String, Number, Object, Array, Boolean],
default: null
Expand Down
5 changes: 5 additions & 0 deletions src/components/form-checkbox/form-checkbox.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,11 @@ export default {
type: Boolean,
default: false
},
switch: {
// Custom switch styling
type: Boolean,
default: false
},
checked: {
// v-model
type: [String, Number, Object, Array, Boolean],
Expand Down
83 changes: 83 additions & 0 deletions src/components/form-checkbox/form-checkbox.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -539,6 +539,89 @@ describe('form-checkbox', async () => {
expect(input.classes()).not.toContain('is-valid')
})

/* switch styling - stand alone */

it('switch has structure <div><input/><label></label></div>', async () => {
const wrapper = mount(Input, {
propsData: {
'switch': true,
checked: '',
value: 'a'
},
slots: {
default: 'foobar'
}
})
expect(wrapper).toBeDefined()
expect(wrapper.is('div')).toBe(true)
const children = wrapper.element.children
expect(children.length).toEqual(2)
expect(children[0].tagName).toEqual('INPUT')
expect(children[1].tagName).toEqual('LABEL')
})

it('switch has wrapper classes custom-control and custom-switch', async () => {
const wrapper = mount(Input, {
propsData: {
'switch': true,
checked: '',
value: 'a'
},
slots: {
default: 'foobar'
}
})
expect(wrapper.classes().length).toEqual(2)
expect(wrapper.classes()).toContain('custom-control')
expect(wrapper.classes()).toContain('custom-switch')
})

it('switch has input type checkbox', async () => {
const wrapper = mount(Input, {
propsData: {
'switch': true,
checked: '',
value: 'a'
},
slots: {
default: 'foobar'
}
})
const input = wrapper.find('input')
expect(input.attributes('type')).toBeDefined()
expect(input.attributes('type')).toEqual('checkbox')
})

it('switch has input class custom-control-input', async () => {
const wrapper = mount(Input, {
propsData: {
'switch': true,
checked: false
},
slots: {
default: 'foobar'
}
})
const input = wrapper.find('input')
expect(input.classes().length).toEqual(1)
expect(input.classes()).toContain('custom-control-input')
})

it('switch has label class custom-control-label', async () => {
const wrapper = mount(Input, {
propsData: {
'switch': true,
checked: false
},
slots: {
default: 'foobar'
}
})
const input = wrapper.find('label')
expect(input.classes().length).toEqual(1)
expect(input.classes()).toContain('custom-control-label')
})

/* button styling - stand-alone mode */

it('stand-alone button has structure <div><label><input/></label></div>', async () => {
Expand Down
1 change: 1 addition & 0 deletions src/components/form-checkbox/index.scss
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
@import "form-checkbox";
@import "form-checkbox-group";
7 changes: 6 additions & 1 deletion src/mixins/form-radio-check.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,10 @@ export default {
is_Custom () {
return this.is_BtnMode ? false : !this.bvGroup.plain
},
is_Switch () {
// Custom switch styling (checkboxes only)
return (this.is_BtnMode || this.is_Radio || this.is_Plain) ? false : (this.is_Group ? this.bvGroup.switches : this.switch)
},
is_Inline () {
return this.bvGroup.inline
},
Expand Down Expand Up @@ -213,7 +217,8 @@ export default {
'form-check-inline': this.is_Plain && this.is_Inline,
'custom-control': this.is_Custom,
'custom-control-inline': this.is_Custom && this.is_Inline,
'custom-checkbox': this.is_Custom && this.is_Check,
'custom-checkbox': this.is_Custom && this.is_Check && !this.is_Switch,
'custom-switch': this.is_Switch,
'custom-radio': this.is_Custom && this.is_Radio,
// Temprary until BS V4 supports sizing
[`form-control-${this.get_Size}`]: Boolean(this.get_Size && !this.is_BtnMode)
Expand Down