Skip to content

Commit

Permalink
feat(form-radio): Support button style radios (#728)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
tmorehouse authored Jul 25, 2017
1 parent 4648e94 commit c7c150f
Show file tree
Hide file tree
Showing 2 changed files with 153 additions and 37 deletions.
94 changes: 80 additions & 14 deletions docs/components/form-radio/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,35 +32,96 @@ semantic and accessible markup, so it is a solid replacement for the default rad
export default {
data: {
selected: 'first',
options: [{
text: 'Toggle this custom radio',
value: 'first'
}, {
text: 'Or toggle this other custom radio',
value: 'second'
}, {
text: 'This one is Disabled',
value: 'third',
disabled: true
}]
options: [
{ text: 'Toggle this custom radio', value: 'first' },
{ text: 'Or toggle this other custom radio', value: 'second' },
{ text: 'This one is Disabled', value: 'third', disabled: true }
]
}
}
</script>

<!-- form-radio.vue -->
<!-- form-radio-1.vue -->
```

### Options

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

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

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


### Button style radios
Render radios with the look of buttons by setting the prop `buttons`. Set the button variant by
setting the `button-variant` prop to one of the standard Bootstrap button variants (see
[`<b-button>`](./button) for supported variants). The default `button-variant` is `secondary`.

The `buttons` prop has precedence over `plain`, and `button-variant` has no effect if
`buttons` is not set.

Button style radios will have the class `.active` automatically applied to their label
when they are in the checked state.

```html
<template>
<div>
<h5>Button style radios</h5>
<b-form-radio id="btnradios1"
buttons
v-model="selected"
:options="options"
></b-form-radio>
<br>

<h5>Button style radios with <code>primary</code> variant and size <code>lg</code></h5>
<b-form-radio id="btnradios2"
buttons
button-variant="primary"
size="lg"
v-model="selected"
:options="options"
></b-form-radio>
<br>

<h5>Stacked button style radios</h5>
<b-form-radio id="btnradios3"
buttons
stacked
v-model="selected"
:options="options"
></b-form-radio>
<hr>

<div>
Selected: <strong>{{ selected }}</strong>
</div>
</div>
</template>

<script>
export default {
data: {
selected: 'radio1',
options: [
{ text: 'Radio 1', value: 'radio1' },
{ text: 'Radio 3', value: 'radio2' },
{ text: 'Radio 3 (disabled)', value: 'radio3', disabled: true },
{ text: 'Radio 4', value: 'radio4' }
]
}
}
</script>

<!-- form-radio-2.vue -->
```

### Contextual States
Bootstrap includes validation styles for danger, warning, and success states on most form controls.

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

**Note:** contextual state is not supported for radios rendered in `buttons` mode.

#### Conveying contextual validation state to assistive technologies and colorblind users:
Using these contextual states to denote the state of a form control only provides
a visual, color-based indication, which will not be conveyed to users of assistive
Expand All @@ -98,3 +161,6 @@ Supported `invalid` values are:

### Non custom radio inputs
You can have `b-form-radio` render a browser native radio input by setting the `plain` prop.

**Note:** `plain` will have no effect if `buttons` is set.

96 changes: 73 additions & 23 deletions lib/components/form-radio.vue
Original file line number Diff line number Diff line change
@@ -1,15 +1,20 @@
<template>
<div :id="id || null"
:class="inputClass"
:class="buttons ? btnGroupClasses : radioGroupClasses"
role="radiogroup"
:data-toggle="buttons ? 'buttons' : null"
:aria-required="required ? 'true' : null"
:aria-invalid="ariaInvalid"
:aria-invalid="invalid ? 'true' : null"
@focusin.native="handleFocus"
@focusout.native="handleFocus"
>
<label :class="[checkboxClass, custom?'custom-radio':null]"
v-for="(option, idx) in formOptions"
<label v-for="(option, idx) in formOptions"
:class="buttons ? btnLabelClasses(option, idx) : labelClasses"
:key="idx"
:aria-pressed="buttons ? (option.value === this.localValue ? 'true' : 'false') : null"
>
<input :id="id ? (id + '__BV_radio_' + idx) : null"
:class="custom?'custom-control-input':null"
:class="(custom && !buttons) ? 'custom-control-input' : null"
ref="inputs"
type="radio"
autocomplete="off"
Expand All @@ -20,8 +25,8 @@
:disabled="option.disabled || disabled"
@change="$emit('change', returnObject ? option : option.value)"
>
<span v-if="custom" class="custom-control-indicator" aria-hidden="true"></span>
<span :class="custom?'custom-control-description':null" v-html="option.text"></span>
<span v-if="custom && !buttons" class="custom-control-indicator" aria-hidden="true"></span>
<span :class="(custom && !buttons) ? 'custom-control-description' : null" v-html="option.text"></span>
</label>
</div>
</template>
Expand All @@ -36,21 +41,6 @@
localValue: this.value
};
},
computed: {
inputClass() {
return [
this.size ? `form-control-${this.size}` : null,
this.state ? `has-${this.state}` : '',
this.stacked ? 'custom-controls-stacked' : ''
];
},
ariaInvalid() {
if (this.invalid === true || this.invalid === 'true') {
return 'true';
}
return null;
}
},
props: {
value: {},
options: {
Expand All @@ -74,11 +64,71 @@
type: Boolean,
default: false
},
buttons: {
// Render as button style
type: Boolean,
default: false
},
buttonVariant: {
// Only applicable when rendered with button style
type: String,
default: 'secondary'
},
returnObject: {
type: Boolean,
default: false
}
},
computed: {
radioGroupClasses() {
return [
this.size ? `form-control-${this.size}` : null,
this.state ? `has-${this.state}` : '',
this.stacked ? 'custom-controls-stacked' : ''
];
},
btnGroupClasses() {
return [
this.size ? `button-group-${this.size}` : null,
this.stacked ? 'btn-group-vertical' : ''
];
},
labelClasses() {
return [
this.checkboxClass,
this.custom ? 'custom-radio' : null
];
},
inline() {
return !this.stacked;
}
},
methods: {
btnLabelClasses(option, idx) {
return [
'btn',
`btn-${this.buttonVariant}`,
(option.disabled || this.disabled) ? 'disabled' : '',
option.value === this.localValue ? 'active' : null,
// Fix staking issue (remove space between buttons)
(this.stacked && idx === this.formOptions.length - 1) ? '' : 'mb-0'
];
},
handleFocus(evt) {
// When in buttons mode, we need to add 'focus' class to label when radio focused
const el = evt.target;
if (this.buttons && el && el.tagName === 'INPUT' && el.parentElement) {
const label = el.parentElement;
if (!label || label.tagName !== 'LABEL') {
return;
}
if (evt.type ==='focusin') {
label.classList.add('focus');
} else if (evt.type ==='focusout') {
label.classList.remove('focus');
}
}
}
}
};
</script>

0 comments on commit c7c150f

Please sign in to comment.