Permalink
Browse files

feat(button): Add pressed prop to place button in active state (#715)

* feat(buton): Add pressed prop to place button in active state

Allows buttons to be toggled active/inactive

* feat(button): Document pressed prop

* Added pressed state test

* [button] Add focus class for toggle buttons

When pressed is true or false, add the focus class when focused. Also sets tab-index to -1 when link is disabled to prevent tab from focusing this element, as links do not respect disabled attribute.

* [button] Add extra examples
  • Loading branch information...
tmorehouse committed Jul 21, 2017
1 parent 2271e7a commit 61a104fab0d835acfcca7fce2f20b240278fcc12
@@ -1,4 +1,4 @@
import { loadFixture, testVM } from '../helpers'
import {loadFixture, testVM, setData, nextTick} from '../helpers';
import { bLink } from '../../lib/components'
import Vue from 'vue/dist/vue.common';
@@ -8,10 +8,11 @@ import Vue from 'vue/dist/vue.common';
* - Sizes: [ 'sm','','lg' ]
* - Props: [ disabled, block ]
* - elements: [ <button/>, <a/> ]
* - pressed state, toggling state
*/
const variants = ['primary', 'secondary', 'success', 'outline-success', 'warning', 'danger', 'link']
const sizes = ['sm', '', 'lg']
const variants = ['primary', 'secondary', 'success', 'outline-success', 'warning', 'danger', 'link'];
const sizes = ['sm', '', 'lg'];
const btnRefs = variants.reduce((memo, variant) => [
...memo,
...sizes.map(size => {
@@ -21,70 +22,131 @@ const btnRefs = variants.reduce((memo, variant) => [
ref: `btn${size ? `_${size}` : ''}_${variant.replace(/-/g, '_')}`
}
})
], [])
], []);
describe('button', async() => {
beforeEach(loadFixture('button'))
testVM()
beforeEach(loadFixture('button'));
testVM();
it('should contain class names', async() => {
const { app: { $refs, $el } } = window
const {app: {$refs, $el}} = window;
btnRefs.forEach(({ ref, variant, size }) => {
// ref will contain an array of children because of v-for
const vm = $refs[ref][0]
const vm = $refs[ref][0];
let classList = ['btn', `btn-${variant}`]
if (size) classList.push(`btn-${size}`)
let classList = ['btn', `btn-${variant}`];
if (size) classList.push(`btn-${size}`);
expect(vm).toHaveAllClasses(classList)
})
});
const vmBlockDisabled = $refs.btn_block_disabled
const vmBlockDisabled = $refs.btn_block_disabled;
expect(vmBlockDisabled).toHaveAllClasses(['btn', 'btn-block', 'disabled'])
})
});
it('should use <b-link> when given href', async() => {
const { app: { $refs, $el } } = window
const btnChildNode = $refs.btn_href.$children[0]
const {app: {$refs, $el}} = window;
const btnChildNode = $refs.btn_href.$children[0];
expect(btnChildNode).toBeInstanceOf(Vue)
expect(btnChildNode).toBeComponent('b-link')
expect(btnChildNode).toBeInstanceOf(Vue);
expect(btnChildNode).toBeComponent('b-link');
expect(btnChildNode.href).toBe('https://github.com/bootstrap-vue/bootstrap-vue')
})
});
it('should emit "click" event when clicked', async() => {
const { app: { $refs, $el } } = window
const vm = $refs.btn_click
const {app: {$refs, $el}} = window;
const vm = $refs.btn_click;
const spy = jest.fn();
vm.$on('click', spy)
vm.$el.click()
vm.$on('click', spy);
vm.$el.click();
expect(spy).toHaveBeenCalled()
})
});
it('should "click" event should emit with native event object', async() => {
const { app: { $refs, $el } } = window
const vm = $refs.btn_click
it('"click" event should emit with native event object', async () => {
const {app: {$refs, $el}} = window;
const vm = $refs.btn_click;
let event = null;
vm.$on('click', e => event = e)
vm.$el.click()
vm.$on('click', e => event = e);
vm.$el.click();
expect(event).toBeInstanceOf(MouseEvent)
})
});
it('should be disabled and not emit click event with `disabled` prop true', async() => {
const { app: { $refs, $el } } = window
const vm = $refs.btn_block_disabled
const {app: {$refs, $el}} = window;
const vm = $refs.btn_block_disabled;
const spy = jest.fn();
vm.$on('click', spy)
vm.$el.click()
vm.$on('click', spy);
vm.$el.click();
expect(vm.disabled).toBe(true)
expect(vm.$el.disabled).toBe(true)
expect(vm.disabled).toBe(true);
expect(vm.$el.disabled).toBe(true);
expect(spy).not.toHaveBeenCalled()
});
it('shoud not have `.active` class and `aria-pressed` when pressed is null', async () => {
const {app: {$refs, $el}} = window;
const vm = $refs.btn_pressed;
await setData(app, 'btnToggle', null);
await nextTick();
expect(vm.pressed).toBeNull();
expect(vm).not.toHaveClass('active');
expect(vm.$el.getAttribute('aria-pressed')).toBeNull();
vm.$el.click();
expect(vm.pressed).toBeNull();
expect(app.btnToggle).toBeNull();
});
it('shoud not have `.active` class and have `aria-pressed="false"` when pressed is false', async () => {
const {app: {$refs, $el}} = window;
const vm = $refs.btn_pressed;
await setData(app, 'btnToggle', false);
await nextTick();
expect(vm.pressed).toBe(false);
expect(vm).not.toHaveClass('active');
expect(vm.$el.getAttribute('aria-pressed')).toBe('false');
});
it('shoud have `.active` class and have `aria-pressed="true"` when pressed is true', async () => {
const {app: {$refs, $el}} = window;
const vm = $refs.btn_pressed;
await setData(app, 'btnToggle', true);
await nextTick();
vm.$el.click();
expect(vm.pressed).toBe(true);
expect(vm).toHaveClass('active');
expect(vm.$el.getAttribute('aria-pressed')).toBe('true');
});
it('shoud emit `update:pressed` event on click and toggle pressed prop when pressed in not null', async () => {
const {app: {$refs, $el}} = window;
const vm = $refs.btn_pressed;
const spy = jest.fn();
await setData(app, 'btnToggle', false);
await nextTick();
vm.$on('update:pressed', spy);
expect(vm.pressed).toBe(false);
expect(vm).not.toHaveClass('active');
expect(vm.$el.getAttribute('aria-pressed')).toBe('false');
vm.$el.click();
await nextTick();
expect(vm).toHaveClass('active');
expect(vm.$el.getAttribute('aria-pressed')).toBe('true');
expect(vm.pressed).toBe(true);
expect(spy).toHaveBeenCalled();
})
})
});
@@ -5,22 +5,32 @@
```html
<div class="row">
<template v-for="variant in ['primary','secondary','success','outline-success','warning','danger','link']">
<div class="col-md-4 pb-2" v-for="size in ['sm','','lg']">
<b-button :size="size" :variant="variant" href="">
{{variant}} {{size}}
<template v-for="var in ['primary','secondary','success','outline-success','warning','danger','link']">
<div class="col-md-4 pb-2" v-for="size in ['sm','','lg']">
<b-button :size="size" :variant="var">
{{variant}} {{size}}
</b-button>
</div>
</div>
</template>
</div>
<!-- button.vue -->
<!-- button-1.vue -->
```
### Element type
The `<b-button>` component generally renders a `<button>` element. However, you can also
render an `<a>` element by providing an `href` prop value. You man also generate
`vue-router` `<router-link>` when providing a value for the `to` prop (`vue-router`
is required).
is required).
```html
<div>
<b-button>I am a Button</b-button>
<b-button href="#">I am a Link</b-button>
</div>
<!-- button-2.vue -->
```
### Button Sizing
Fancy larger or smaller buttons? Specify `lg` or `sm` via the `size` prop.
@@ -51,15 +61,86 @@ default padding and size of a button.
Set the `disabled` prop to disable button default funtionality. `disabled` also
works with buttons, rendered as `<a>` elements and `<router-link>`.
```html
<div>
<b-button disabled variant="success">Disabled</b-button>
<b-button variant="success">Not Disabled</b-button>
</div>
<!-- button-3.vue -->
```
### Button type
When neither `href` nor `to` props are provided, `<b-button>` renders an html `<button>`
element. You can specify the button's type by setting the prop `type` to `button`,
`submit` or `reset`. The default type is `button`.
### Pressed state and toggling
Buttons will appear pressed (with a darker background, darker border, and inset shadow)
when the prop `presed` is set to `true`.
The `pressed` prop can be set to one of three values:
- `true`: Sets the `.active` class and adds the attribute `aria-pressed="true"`.
- `false`: Clears the `.active` class and adds the attribute `aria-pressed="false"`.
- `null`: (default) Neither the class `.active` nor the attribute `aria-pressed` will be set.
To create a button that can be toggled between active and non-active states, use
the `.sync` prop modifier (available in Vue 2.3+) on the `pressed` property
```html
<template>
<div>
<h5>Pressed and un-pressed state</h5>
<b-button :pressed="true" variant="success">Always Pressed</b-button>
<b-button :pressed="false" variant="success">Not Pressed</b-button>
<h5>Toggleable Button</h5>
<b-button :pressed.sync="myToggle0" variant="primary">Toggle Me</b-button>
<p>Pressed State: <strong>{{ myToggle }}</strong></p>
<h5>In a button group</h5>
<b-button-group size="sm">
<b-button v-for="btn in buttons" :pressed.sync="btn.state" :variant="btn.variant">
{{ btn.caption }}
</b-button>
</b-button-group>
<p>Pressed States: <strong>{{ btnStates }}</strong></p>
</div>
</template>
<script>
export default {
data: {
myToggle: false,
buttons: [
{ variant: 'primary', caption: 'Toggle 1', state: true },
{ variant: 'danger', caption: 'Toggle 2', state: false },
{ variant: 'warning', caption: 'Toggle 3', state: true },
{ variant: 'success', caption: 'No Toggle', state: null },
{ variant: 'outline-success', caption: 'Toggle 5', state: false },
{ variant: 'outline-primary', caption: 'Toggle 6', state: false }
]
},
computed: {
btnStates() {
return this.buttons.map(btn => btn.state);
}
}
}
</script>
<!-- button-4.vue -->
```
### Router links
Refer to [`vue-router`](https://router.vuejs.org/) docs for the various `<router-link>` related props.
Note the `tag` attribute for `<router-link>` is refered to as `router-tag` in `bootstrap-vue`.
### Alias
`<b-button>` can also be used by its shorter alias `<b-btn>`.
### See also
- [`<b-button-group>`](./button-group)
- [`<b-button-toolbar>`](./button-toolbar)
@@ -36,5 +36,12 @@
Can't touch this
</b-btn>
</div>
<div class="col-md-4 pb-2">
<b-btn variant="primary"
ref="btn_pressed"
:pressed.sync="btnToggle">
Toggle Me
</b-btn>
</div>
</div>
</div>
@@ -4,5 +4,8 @@ window.app = new Vue({
handleClick(event) {
alert('You clicked, I listened.')
},
},
data: {
btnToggle: null
}
});
Oops, something went wrong.

0 comments on commit 61a104f

Please sign in to comment.