Skip to content

Commit 4c26e19

Browse files
committed
Feat: add CButton, CButtonClose, CButtonToolbar, CButtonGroup components.
1 parent 3168e5e commit 4c26e19

File tree

7 files changed

+221
-11
lines changed

7 files changed

+221
-11
lines changed

src/components/Button/CButton.js

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
import { mergeData } from 'vue-functional-data-merge'
2+
import pluckProps from '../../utils/pluck-props'
3+
import { assign } from '../../utils/object'
4+
import CLink, { propsFactory as linkPropsFactory } from '../Link/CLink'
5+
6+
const btnProps = {
7+
block: Boolean,
8+
pill: Boolean,
9+
square: Boolean,
10+
ghost: Boolean,
11+
size: {
12+
type: String,
13+
validator: (value) => ['sm','lg'].indexOf(value) !== -1
14+
},
15+
variant: {
16+
type: String,
17+
default: 'secondary'
18+
},
19+
type: {
20+
type: String,
21+
default: 'button'
22+
},
23+
pressed: {
24+
type: Boolean,
25+
default: null
26+
}
27+
}
28+
export const props = assign(linkPropsFactory(), btnProps)
29+
30+
// Helper functons to minimize runtime memory footprint when lots of buttons on page(TODO: check it)
31+
function isLink (props) {
32+
return Boolean(props.href || props.to)
33+
}
34+
function isToggle (props) {
35+
return props.pressed !== null
36+
}
37+
38+
function computeClasses (props) {
39+
return [
40+
`btn-${props.variant}`,
41+
props.pill ? 'btn-pill' : props.square ? 'btn-square' : '',
42+
{
43+
[`btn-${props.size}`]: Boolean(props.size),
44+
[`btn-ghost-${props.variant}`]: props.ghost,
45+
'btn-block': props.block,
46+
disabled: props.disabled,
47+
active: props.pressed
48+
}
49+
]
50+
}
51+
52+
function computePassedProps (props) {
53+
return isLink(props) ? pluckProps(linkPropsFactory(), props) : null
54+
}
55+
56+
function computeAttrs (props, data, link, toggle) {
57+
return {
58+
type: !link ? props.type : null,
59+
// in CLink disabled property works diffrently
60+
disabled: !link ? props.disabled : null,
61+
'aria-pressed': toggle ? String(props.pressed) : null,
62+
// autocomplete off is needed in toggle mode to prevent some browsers from
63+
// remembering the previous setting when using the back button.
64+
autocomplete: toggle ? 'off' : null,
65+
}
66+
}
67+
68+
export default {
69+
functional: true,
70+
name: 'CButton',
71+
props: props,
72+
render (h, { props, data, listeners, children }) {
73+
const toggle = isToggle(props)
74+
const link = isLink(props)
75+
const on = {
76+
click (e) {
77+
if (props.disabled && e instanceof Event) {
78+
e.stopPropagation()
79+
e.preventDefault()
80+
} else if (toggle && listeners && listeners['update:pressed']) {
81+
// Send .sync updates to any "pressed" prop (if .sync listeners)
82+
listeners['update:pressed'](!props.pressed)
83+
}
84+
}
85+
}
86+
const componentData = {
87+
staticClass: 'btn',
88+
class: computeClasses(props),
89+
props: computePassedProps(props),
90+
attrs: computeAttrs(props, data, link, toggle),
91+
on
92+
}
93+
return h(link ? CLink : 'button', mergeData(data, componentData), children)
94+
}
95+
}

src/components/Button/CButtonClose.js

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,17 @@ export default {
33
name: 'CButtonClose',
44
functional: true,
55
render (h, { data, props, slots }) {
6-
const componentData = {
7-
staticClass: 'close',
8-
attrs: {
9-
type: props.type || 'button',
10-
'aria-label': props.ariaLabel || 'Close'
11-
}
12-
}
13-
if (!slots().default)
14-
componentData.domProps = { innerHTML: '×' }
15-
return h('button', mergeData(data, componentData), slots().default)
6+
return h(
7+
'button',
8+
mergeData(data, {
9+
staticClass: 'close',
10+
attrs: {
11+
type: props.type || 'button',
12+
'aria-label': props.ariaLabel || 'Close'
13+
},
14+
domProps: !slots().default ? { innerHTML: '×' } : null
15+
}),
16+
slots().default
17+
)
1618
}
1719
}

src/components/Button/CButtonGroup.js

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { mergeData } from 'vue-functional-data-merge'
2+
export default {
3+
functional: true,
4+
name: 'CButtonGroup',
5+
props: {
6+
vertical: Boolean,
7+
size: String,
8+
},
9+
render (h, { props, data, children }) {
10+
return h(
11+
'div',
12+
mergeData(data, {
13+
class: [
14+
!props.vertical ? 'btn-group' : 'btn-group-vertical',
15+
{ [`btn-group-${props.size}`] : Boolean(props.size) }
16+
],
17+
attrs: {
18+
role: 'group'
19+
}
20+
}),
21+
children
22+
)
23+
}
24+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { mergeData } from 'vue-functional-data-merge'
2+
export default {
3+
functional: true,
4+
name: 'CButtonToolbar',
5+
props: {
6+
justify: Boolean,
7+
},
8+
render (h, { props, data, children }) {
9+
return h(
10+
'div',
11+
mergeData(data, {
12+
class: [
13+
'btn-toolbar',
14+
props.justify ? 'justify-content-between' : ''
15+
],
16+
attrs: {
17+
role: 'toolbar'
18+
}
19+
}),
20+
children
21+
)
22+
}
23+
}

src/components/Button/index.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
1+
import CButton from './CButton'
12
import CButtonClose from './CButtonClose'
3+
import CButtonGroup from './CButtonGroup'
4+
import CButtonToolbar from './CButtonToolbar'
25

36
export {
4-
CButtonClose
7+
CButton,
8+
CButtonClose,
9+
CButtonGroup,
10+
CButtonToolbar
511
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import { mount } from '@vue/test-utils'
2+
import Component from "../CButton";
3+
import {props} from "../CButton";
4+
5+
6+
const ComponentName = 'CButton'
7+
const defaultWrapper = mount(Component)
8+
const toggleWrapper = mount(Component, {
9+
context: {
10+
props: {
11+
pressed: true,
12+
type: 'input',
13+
size: 'lg',
14+
variant: 'info',
15+
ghost: true,
16+
block: true,
17+
square: true
18+
}
19+
},
20+
slots: {
21+
default: 'button'
22+
}
23+
})
24+
const routerLinkWrapper = mount(Component, {
25+
context: {
26+
props: {
27+
to: '/dashboard',
28+
size: 'sm',
29+
variant: 'success',
30+
ghost: true,
31+
pill: true
32+
}
33+
},
34+
slots: {
35+
default: 'button'
36+
}
37+
})
38+
39+
describe(ComponentName, () => {
40+
it('has a name', () => {
41+
expect(Component.name).toMatch(ComponentName)
42+
})
43+
it('renders correctly', () => {
44+
expect(defaultWrapper.element).toMatchSnapshot()
45+
})
46+
// it('renders correctly router link button', () => {
47+
// expect(routerLinkWrapper.element).toMatchSnapshot()
48+
// })
49+
// it('renders correctly toggle button', () => {
50+
// expect(toggleWrapper.element).toMatchSnapshot()
51+
// })
52+
})
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
// Jest Snapshot v1, https://goo.gl/fbAQLP
2+
3+
exports[`CButton renders correctly 1`] = `
4+
<button
5+
class="btn btn-secondary"
6+
type="button"
7+
/>
8+
`;

0 commit comments

Comments
 (0)