Skip to content

Commit

Permalink
Merge pull request #22 from glorious-codes/button
Browse files Browse the repository at this point in the history
Improve Button
  • Loading branch information
rafaelcamargo committed Aug 22, 2020
2 parents 592746c + 88ab928 commit 6270cc1
Show file tree
Hide file tree
Showing 15 changed files with 176 additions and 18 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Expand Up @@ -160,5 +160,5 @@ assets/js/
# Tests generated files
./coverage/
./screenshots/
/docs
spec/visual/results/
./docs/
2 changes: 1 addition & 1 deletion package.json
@@ -1,5 +1,5 @@
{
"name": "glorious-taslonic",
"name": "@glorious/taslonic",
"version": "0.1.0",
"description": "A glorious UI library available for React and Vue",
"files": [
Expand Down
27 changes: 25 additions & 2 deletions src/base/services/button/button.js
@@ -1,15 +1,34 @@
import propBasedCssClassService from '@base/services/prop-based-css-class/prop-based-css-class';
import propBasedTagNameService from '@base/services/prop-based-tag-name/prop-based-tag-name';

const _public = {};

_public.buildCssClasses = ({ theme, blocked } = {}) => {
const baseCssClass = getBaseCssClass();
const cssClasses = [baseCssClass];
propBasedCssClassService.handleProp(theme, isValidTheme, cssClasses, baseCssClass);
propBasedCssClassService.handleBooleanProp({ blocked }, isValidBooleanProp, cssClasses, baseCssClass);
propBasedCssClassService.handleProp(
theme,
isValidTheme,
cssClasses,
baseCssClass
);
propBasedCssClassService.handleBooleanProp(
{ blocked },
isValidBooleanProp,
cssClasses,
baseCssClass
);
return cssClasses.join(' ');
};

_public.buildTagName = propValue => {
return propBasedTagNameService.buildTagName(
propValue,
isOptionalTagNameValid,
{ defaultTagName: 'button'}
);
};

function isValidTheme(theme){
return ['primary','secondary'].includes(theme);
}
Expand All @@ -18,6 +37,10 @@ function isValidBooleanProp(propName){
return ['blocked'].includes(propName);
}

function isOptionalTagNameValid(tagName){
return ['a'].includes(tagName);
}

function getBaseCssClass(){
return 't-button';
}
Expand Down
15 changes: 15 additions & 0 deletions src/base/services/button/button.test.js
Expand Up @@ -25,4 +25,19 @@ describe('Button Service', () => {
const cssClasses = buttonService.buildCssClasses();
expect(cssClasses).toEqual('t-button');
});

it('should build tag name with prop value if tag prop value is valid', () => {
const tagName = buttonService.buildTagName('a');
expect(tagName).toEqual('a');
});

it('should build a button tag name if tag prop value has not been given', () => {
const tagName = buttonService.buildTagName();
expect(tagName).toEqual('button');
});

it('should build a button tag name if tag prop value is not valid', () => {
const tagName = buttonService.buildTagName('ul');
expect(tagName).toEqual('button');
});
});
7 changes: 7 additions & 0 deletions src/base/services/prop-based-tag-name/prop-based-tag-name.js
@@ -0,0 +1,7 @@
const _public = {};

_public.buildTagName = (propValue, isValidPropValue, { defaultTagName }) => {
return propValue && isValidPropValue(propValue) ? propValue : defaultTagName;
};

export default _public;
24 changes: 24 additions & 0 deletions src/base/services/prop-based-tag-name/prop-based-tag-name.test.js
@@ -0,0 +1,24 @@
import propBaseTagNameService from './prop-based-tag-name';

describe('Prop Based Tag Name Service', () => {
it('should return prop value if given prop is valid', () => {
const defaultTagName = 'button';
const isValidTagName = prop => ['a', 'button'].includes(prop);
const tagName = propBaseTagNameService.buildTagName('a', isValidTagName, { defaultTagName });
expect(tagName).toEqual('a');
});

it('should return default tag name if given prop is not valid', () => {
const defaultTagName = 'button';
const isValidTagName = prop => ['a', 'button'].includes(prop);
const tagName = propBaseTagNameService.buildTagName('ul', isValidTagName, { defaultTagName });
expect(tagName).toEqual('button');
});

it('should return default tag name if prop has not been given', () => {
const defaultTagName = 'button';
const isValidTagName = prop => ['a', 'button'].includes(prop);
const tagName = propBaseTagNameService.buildTagName(undefined, isValidTagName, { defaultTagName });
expect(tagName).toEqual('button');
});
});
4 changes: 4 additions & 0 deletions src/base/styles/_mixins.styl
Expand Up @@ -38,6 +38,10 @@ col-size($size)
flex-basis $size
max-width $size

user-select($prop)
-webkit-user-select $prop
user-select $prop

breakpoint($size)
if($size == 'xs')
@media screen and (min-width 0)
Expand Down
16 changes: 11 additions & 5 deletions src/base/styles/button.styl
@@ -1,3 +1,5 @@
@require '_mixins'

.t-button
--t-button-border-radius: 4px
padding 11px
Expand All @@ -6,6 +8,7 @@
font-size var(--t-font-size-xs)
font-weight bold
text-align center
text-decoration none
line-height var(--t-font-size-sm)
border 1px solid var(--t-color-grey)
border-radius var(--t-button-border-radius)
Expand All @@ -16,13 +19,14 @@
transition-duration 200ms
transition-timing-function ease-in-out
outline 0
user-select none
user-select(none)
-webkit-appearance none
&:hover
background-color var(--t-color-grey-lighter)
&:focus
&:focus,
&:active
background-color var(--t-color-grey-lighter)
box-shadow: 0 0 0 4px var(--t-color-purple-x-lighter)
box-shadow 0 0 0 4px var(--t-color-purple-x-lighter)
&[disabled]
background-color var(--t-color-grey-lighter)
color var(--t-color-grey)
Expand All @@ -35,7 +39,8 @@
color white
border-color var(--t-color-purple)
&:hover,
&:focus
&:focus,
&:active
background-color var(--t-color-purple-light)
border-color var(--t-color-purple-light)
&[disabled]
Expand All @@ -46,7 +51,8 @@
color var(--t-color-purple)
border-color var(--t-color-purple)
&:hover,
&:focus
&:focus,
&:active
border-color var(--t-color-purple-lighter)
&[disabled]
color var(--t-color-purple-lighter)
Expand Down
26 changes: 25 additions & 1 deletion src/react/components/button/button.doc.js
@@ -1,12 +1,17 @@
module.exports = {
name: 'Button',
description: 'Abstraction of a native button',
description: 'Abstraction of a native button.',
properties: [
{
name: 'blocked',
type: 'Boolean, String',
values: 'true, false'
},
{
name: 'tag',
type: 'String',
values: 'a'
},
{
name: 'theme',
type: 'String',
Expand Down Expand Up @@ -51,6 +56,25 @@ module.exports = {
}
}
},
{
title: 'Button tag',
description: 'You can optionally render a button as anchor.',
controller: function(){
const { Button, Col, Row } = taslonicReact;

return function(){
return (
<Row>
<Col sm="6">
<Button tag="a" href="https://github.com/glorious-codes/glorious-taslonic" target="_blank">
Anchor Button
</Button>
</Col>
</Row>
);
}
}
},
{
title: 'Button theme',
controller: function(){
Expand Down
7 changes: 4 additions & 3 deletions src/react/components/button/button.js
@@ -1,11 +1,12 @@
import buttonService from '@base/services/button/button';
import React from 'react';

export const Button = ({ theme, blocked, children, ...rest }) => {
export const Button = ({ theme, blocked, tag, children, ...rest }) => {
const TagName = buttonService.buildTagName(tag);
return (
<button className={buildCssClasses(theme, blocked)} { ...rest }>
<TagName className={buildCssClasses(theme, blocked)} { ...rest }>
{ children }
</button>
</TagName>
);
};

Expand Down
24 changes: 23 additions & 1 deletion src/react/components/button/button.test.js
Expand Up @@ -5,7 +5,11 @@ import { Button } from './button';
describe('Button', () => {
function mount(props = {}){
return shallow(
<Button theme={ props.theme } blocked={ props.blocked } { ...props }>
<Button
theme={ props.theme }
blocked={ props.blocked }
tag={props.tag}
{ ...props }>
{ props.content }
</Button>
);
Expand All @@ -16,6 +20,16 @@ describe('Button', () => {
expect(wrapper.prop('className')).toEqual('t-button');
});

it('should render a button using a button tag name by default', () => {
const wrapper = mount();
expect(wrapper.name().toLowerCase()).toEqual('button');
});

it('should optionally render a button using a anchor tag name', () => {
const wrapper = mount({ tag: 'a' });
expect(wrapper.name().toLowerCase()).toEqual('a');
});

it('should optionally set a primary theme', () => {
const wrapper = mount({ theme: 'primary' });
expect(wrapper.prop('className').includes('t-button-primary')).toEqual(true);
Expand All @@ -31,6 +45,14 @@ describe('Button', () => {
expect(wrapper.prop('className').includes('t-button-blocked')).toEqual(true);
});

it('should optionally render custom attributes', () => {
const href = 'https://rafaelcamargo.com';
const target = '_blank';
const wrapper = mount({ href , target });
expect(wrapper.prop('href')).toEqual(href);
expect(wrapper.prop('target')).toEqual(target);
});

it('should render some content', () => {
const wrapper = mount({ content: <span>Click</span> });
expect(wrapper.find('span').text()).toEqual('Click');
Expand Down
20 changes: 19 additions & 1 deletion src/vue/components/button/button.doc.js
@@ -1,12 +1,17 @@
module.exports = {
name: 'Button',
description: 'Abstraction of a native button',
description: 'Abstraction of a native button.',
properties: [
{
name: 'blocked',
type: 'Boolean, String',
values: 'true, false'
},
{
name: 'tag',
type: 'String',
values: 'a'
},
{
name: 'theme',
type: 'String',
Expand Down Expand Up @@ -39,6 +44,19 @@ module.exports = {
</t-row>
`
},
{
title: 'Button tag',
description: 'You can optionally render a button as anchor.',
template: `
<t-row>
<t-col sm="6">
<t-button tag="a" href="https://github.com/glorious-codes/glorious-taslonic" target="_blank">
Anchor Button
</t-button>
</t-col>
</t-row>
`
},
{
title: 'Button theme',
template: `
Expand Down
4 changes: 2 additions & 2 deletions src/vue/components/button/button.html
@@ -1,3 +1,3 @@
<button :class="classes">
<component :is="tagName" :class="classes">
<slot></slot>
</button>
</component>
6 changes: 5 additions & 1 deletion src/vue/components/button/button.js
Expand Up @@ -3,11 +3,15 @@ import template from './button.html';

export const button = {
name: 't-button',
props: ['theme', 'blocked'],
props: ['theme', 'blocked', 'tag'],
computed: {
classes(){
const { theme, blocked } = this;
return buttonService.buildCssClasses({ theme, blocked });
},
tagName(){
const { tag } = this;
return buttonService.buildTagName(tag);
}
},
template
Expand Down
10 changes: 10 additions & 0 deletions src/vue/components/button/button.test.js
Expand Up @@ -11,6 +11,16 @@ describe('Button', () => {
expect(wrapper.classes()).toContain('t-button');
});

it('should render a button using a button tag name by default', () => {
const wrapper = mount();
expect(wrapper.vm.$el.tagName.toLowerCase()).toEqual('button');
});

it('should optionally render a button using a anchor tag name', () => {
const wrapper = mount({ tag: 'a' });
expect(wrapper.vm.$el.tagName.toLowerCase()).toEqual('a');
});

it('should optionally set a primary theme', () => {
const wrapper = mount({ theme: 'primary' });
expect(wrapper.classes()).toContain('t-button-primary');
Expand Down

0 comments on commit 6270cc1

Please sign in to comment.