Skip to content

Commit c7c150f

Browse files
authored
feat(form-radio): Support button style radios (#728)
* feat(form-radio): Support button style radios Adds new Boolean prop `buttons` (defaults to `false`) that wil enable the radios to be rendered with the look of buttons http://v4-alpha.getbootstrap.com/components/buttons/#checkbox-and-radio-buttons Currently the buttons can only be rendered with the same variant specified by the prop `button-variant`. Different variants for each radio button ar currently not supported (not without altering the `form-options.js` mixin. `stacked` button radios are supported,, but `state` is not supported for button radios * docs((form-radio): Add buttons style documentation
1 parent 4648e94 commit c7c150f

File tree

2 files changed

+153
-37
lines changed

2 files changed

+153
-37
lines changed

docs/components/form-radio/README.md

Lines changed: 80 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -32,35 +32,96 @@ semantic and accessible markup, so it is a solid replacement for the default rad
3232
export default {
3333
data: {
3434
selected: 'first',
35-
options: [{
36-
text: 'Toggle this custom radio',
37-
value: 'first'
38-
}, {
39-
text: 'Or toggle this other custom radio',
40-
value: 'second'
41-
}, {
42-
text: 'This one is Disabled',
43-
value: 'third',
44-
disabled: true
45-
}]
35+
options: [
36+
{ text: 'Toggle this custom radio', value: 'first' },
37+
{ text: 'Or toggle this other custom radio', value: 'second' },
38+
{ text: 'This one is Disabled', value: 'third', disabled: true }
39+
]
4640
}
4741
}
4842
</script>
4943

50-
<!-- form-radio.vue -->
44+
<!-- form-radio-1.vue -->
5145
```
5246

5347
### Options
5448

55-
Please see options in [`<b-form-select>`](./form-select) docs for details on passing options
56-
to `<b-form-radio>`
49+
Please see options in [`<b-form-select>`](./form-select) docs for details on passing
50+
options (value array) to `<b-form-radio>`
5751

52+
### Size
53+
Control the size of the radio text by setting the prop `size` to either `sm` for small or
54+
`lg` for large.
5855

5956
### Inline or stacked
6057
By default `<b-form-radio>` generates inline radio inputs. Set the prop `stacked` to make
6158
the radios appear one over the other.
6259

6360

61+
### Button style radios
62+
Render radios with the look of buttons by setting the prop `buttons`. Set the button variant by
63+
setting the `button-variant` prop to one of the standard Bootstrap button variants (see
64+
[`<b-button>`](./button) for supported variants). The default `button-variant` is `secondary`.
65+
66+
The `buttons` prop has precedence over `plain`, and `button-variant` has no effect if
67+
`buttons` is not set.
68+
69+
Button style radios will have the class `.active` automatically applied to their label
70+
when they are in the checked state.
71+
72+
```html
73+
<template>
74+
<div>
75+
<h5>Button style radios</h5>
76+
<b-form-radio id="btnradios1"
77+
buttons
78+
v-model="selected"
79+
:options="options"
80+
></b-form-radio>
81+
<br>
82+
83+
<h5>Button style radios with <code>primary</code> variant and size <code>lg</code></h5>
84+
<b-form-radio id="btnradios2"
85+
buttons
86+
button-variant="primary"
87+
size="lg"
88+
v-model="selected"
89+
:options="options"
90+
></b-form-radio>
91+
<br>
92+
93+
<h5>Stacked button style radios</h5>
94+
<b-form-radio id="btnradios3"
95+
buttons
96+
stacked
97+
v-model="selected"
98+
:options="options"
99+
></b-form-radio>
100+
<hr>
101+
102+
<div>
103+
Selected: <strong>{{ selected }}</strong>
104+
</div>
105+
</div>
106+
</template>
107+
108+
<script>
109+
export default {
110+
data: {
111+
selected: 'radio1',
112+
options: [
113+
{ text: 'Radio 1', value: 'radio1' },
114+
{ text: 'Radio 3', value: 'radio2' },
115+
{ text: 'Radio 3 (disabled)', value: 'radio3', disabled: true },
116+
{ text: 'Radio 4', value: 'radio4' }
117+
]
118+
}
119+
}
120+
</script>
121+
122+
<!-- form-radio-2.vue -->
123+
```
124+
64125
### Contextual States
65126
Bootstrap includes validation styles for danger, warning, and success states on most form controls.
66127

@@ -76,6 +137,8 @@ To apply one of the contextual states on `b-form-radio`, set the `state` prop
76137
to `danger`, `warning`, or `success`. You may also wrap `<b-form-radio>` in a
77138
`<b-form-fieldset>` and set the contextual `state` prop on `<b-form-fieldset>` instead.
78139

140+
**Note:** contextual state is not supported for radios rendered in `buttons` mode.
141+
79142
#### Conveying contextual validation state to assistive technologies and colorblind users:
80143
Using these contextual states to denote the state of a form control only provides
81144
a visual, color-based indication, which will not be conveyed to users of assistive
@@ -98,3 +161,6 @@ Supported `invalid` values are:
98161

99162
### Non custom radio inputs
100163
You can have `b-form-radio` render a browser native radio input by setting the `plain` prop.
164+
165+
**Note:** `plain` will have no effect if `buttons` is set.
166+

lib/components/form-radio.vue

Lines changed: 73 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,20 @@
11
<template>
22
<div :id="id || null"
3-
:class="inputClass"
3+
:class="buttons ? btnGroupClasses : radioGroupClasses"
44
role="radiogroup"
5+
:data-toggle="buttons ? 'buttons' : null"
56
:aria-required="required ? 'true' : null"
6-
:aria-invalid="ariaInvalid"
7+
:aria-invalid="invalid ? 'true' : null"
8+
@focusin.native="handleFocus"
9+
@focusout.native="handleFocus"
710
>
8-
<label :class="[checkboxClass, custom?'custom-radio':null]"
9-
v-for="(option, idx) in formOptions"
11+
<label v-for="(option, idx) in formOptions"
12+
:class="buttons ? btnLabelClasses(option, idx) : labelClasses"
13+
:key="idx"
14+
:aria-pressed="buttons ? (option.value === this.localValue ? 'true' : 'false') : null"
1015
>
1116
<input :id="id ? (id + '__BV_radio_' + idx) : null"
12-
:class="custom?'custom-control-input':null"
17+
:class="(custom && !buttons) ? 'custom-control-input' : null"
1318
ref="inputs"
1419
type="radio"
1520
autocomplete="off"
@@ -20,8 +25,8 @@
2025
:disabled="option.disabled || disabled"
2126
@change="$emit('change', returnObject ? option : option.value)"
2227
>
23-
<span v-if="custom" class="custom-control-indicator" aria-hidden="true"></span>
24-
<span :class="custom?'custom-control-description':null" v-html="option.text"></span>
28+
<span v-if="custom && !buttons" class="custom-control-indicator" aria-hidden="true"></span>
29+
<span :class="(custom && !buttons) ? 'custom-control-description' : null" v-html="option.text"></span>
2530
</label>
2631
</div>
2732
</template>
@@ -36,21 +41,6 @@
3641
localValue: this.value
3742
};
3843
},
39-
computed: {
40-
inputClass() {
41-
return [
42-
this.size ? `form-control-${this.size}` : null,
43-
this.state ? `has-${this.state}` : '',
44-
this.stacked ? 'custom-controls-stacked' : ''
45-
];
46-
},
47-
ariaInvalid() {
48-
if (this.invalid === true || this.invalid === 'true') {
49-
return 'true';
50-
}
51-
return null;
52-
}
53-
},
5444
props: {
5545
value: {},
5646
options: {
@@ -74,11 +64,71 @@
7464
type: Boolean,
7565
default: false
7666
},
67+
buttons: {
68+
// Render as button style
69+
type: Boolean,
70+
default: false
71+
},
72+
buttonVariant: {
73+
// Only applicable when rendered with button style
74+
type: String,
75+
default: 'secondary'
76+
},
7777
returnObject: {
7878
type: Boolean,
7979
default: false
8080
}
81+
},
82+
computed: {
83+
radioGroupClasses() {
84+
return [
85+
this.size ? `form-control-${this.size}` : null,
86+
this.state ? `has-${this.state}` : '',
87+
this.stacked ? 'custom-controls-stacked' : ''
88+
];
89+
},
90+
btnGroupClasses() {
91+
return [
92+
this.size ? `button-group-${this.size}` : null,
93+
this.stacked ? 'btn-group-vertical' : ''
94+
];
95+
},
96+
labelClasses() {
97+
return [
98+
this.checkboxClass,
99+
this.custom ? 'custom-radio' : null
100+
];
101+
},
102+
inline() {
103+
return !this.stacked;
104+
}
105+
},
106+
methods: {
107+
btnLabelClasses(option, idx) {
108+
return [
109+
'btn',
110+
`btn-${this.buttonVariant}`,
111+
(option.disabled || this.disabled) ? 'disabled' : '',
112+
option.value === this.localValue ? 'active' : null,
113+
// Fix staking issue (remove space between buttons)
114+
(this.stacked && idx === this.formOptions.length - 1) ? '' : 'mb-0'
115+
];
116+
},
117+
handleFocus(evt) {
118+
// When in buttons mode, we need to add 'focus' class to label when radio focused
119+
const el = evt.target;
120+
if (this.buttons && el && el.tagName === 'INPUT' && el.parentElement) {
121+
const label = el.parentElement;
122+
if (!label || label.tagName !== 'LABEL') {
123+
return;
124+
}
125+
if (evt.type ==='focusin') {
126+
label.classList.add('focus');
127+
} else if (evt.type ==='focusout') {
128+
label.classList.remove('focus');
129+
}
130+
}
131+
}
81132
}
82133
};
83-
84134
</script>

0 commit comments

Comments
 (0)