Skip to content

Commit

Permalink
feat(Button): add role prop (#2752)
Browse files Browse the repository at this point in the history
  • Loading branch information
levithomason authored Apr 30, 2018
1 parent cc7d9ee commit 94e5684
Show file tree
Hide file tree
Showing 3 changed files with 91 additions and 93 deletions.
11 changes: 4 additions & 7 deletions src/elements/Button/Button.d.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,6 @@
import * as React from 'react';

import {
SemanticCOLORS,
SemanticFLOATS,
SemanticShorthandContent,
SemanticShorthandItem,
SemanticSIZES
} from '../..';
import { SemanticCOLORS, SemanticFLOATS, SemanticShorthandContent, SemanticShorthandItem, SemanticSIZES } from '../..';
import { LabelProps } from '../Label';
import { default as ButtonContent } from './ButtonContent';
import { default as ButtonGroup } from './ButtonGroup';
Expand Down Expand Up @@ -88,6 +82,9 @@ export interface ButtonProps {
/** A button can be formatted to show different levels of emphasis. */
primary?: boolean;

/** The role of the HTML element. */
role?: string;

/** A button can be formatted to show different levels of emphasis. */
secondary?: boolean;

Expand Down
62 changes: 26 additions & 36 deletions src/elements/Button/Button.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,16 +36,10 @@ class Button extends Component {
active: PropTypes.bool,

/** A button can animate to show hidden content. */
animated: PropTypes.oneOfType([
PropTypes.bool,
PropTypes.oneOf(['fade', 'vertical']),
]),
animated: PropTypes.oneOfType([PropTypes.bool, PropTypes.oneOf(['fade', 'vertical'])]),

/** A button can be attached to other content. */
attached: PropTypes.oneOfType([
PropTypes.bool,
PropTypes.oneOf(['left', 'right', 'top', 'bottom']),
]),
attached: PropTypes.oneOfType([PropTypes.bool, PropTypes.oneOf(['left', 'right', 'top', 'bottom'])]),

/** A basic button is less pronounced. */
basic: PropTypes.bool,
Expand Down Expand Up @@ -75,7 +69,13 @@ class Button extends Component {
/** A button can have different colors */
color: PropTypes.oneOf([
...SUI.COLORS,
'facebook', 'google plus', 'instagram', 'linkedin', 'twitter', 'vk', 'youtube',
'facebook',
'google plus',
'instagram',
'linkedin',
'twitter',
'vk',
'youtube',
]),

/** A button can reduce its padding to fit into tighter spaces. */
Expand All @@ -94,22 +94,13 @@ class Button extends Component {
fluid: PropTypes.bool,

/** Add an Icon by name, props object, or pass an <Icon />. */
icon: customPropTypes.some([
PropTypes.bool,
PropTypes.string,
PropTypes.object,
PropTypes.element,
]),
icon: customPropTypes.some([PropTypes.bool, PropTypes.string, PropTypes.object, PropTypes.element]),

/** A button can be formatted to appear on dark backgrounds. */
inverted: PropTypes.bool,

/** Add a Label by text, props object, or pass a <Label />. */
label: customPropTypes.some([
PropTypes.string,
PropTypes.object,
PropTypes.element,
]),
label: customPropTypes.some([PropTypes.string, PropTypes.object, PropTypes.element]),

/** A labeled button can format a Label or Icon to appear on the left or right. */
labelPosition: PropTypes.oneOf(['right', 'left']),
Expand All @@ -133,24 +124,25 @@ class Button extends Component {
/** A button can be formatted to show different levels of emphasis. */
primary: PropTypes.bool,

/** The role of the HTML element. */
role: PropTypes.string,

/** A button can be formatted to show different levels of emphasis. */
secondary: PropTypes.bool,

/** A button can have different sizes. */
size: PropTypes.oneOf(SUI.SIZES),

/** A button can receive focus. */
tabIndex: PropTypes.oneOfType([
PropTypes.number,
PropTypes.string,
]),
tabIndex: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),

/** A button can be formatted to toggle on and off. */
toggle: PropTypes.bool,
}

static defaultProps = {
as: 'button',
role: 'button',
}

static _meta = {
Expand Down Expand Up @@ -222,6 +214,7 @@ class Button extends Component {
positive,
primary,
secondary,
role,
size,
toggle,
} = this.props
Expand All @@ -245,13 +238,8 @@ class Button extends Component {
useKeyOrValueAndKey(animated, 'animated'),
useKeyOrValueAndKey(attached, 'attached'),
)
const labeledClasses = cx(
useKeyOrValueAndKey(labelPosition || !!label, 'labeled'),
)
const wrapperClasses = cx(
useKeyOnly(disabled, 'disabled'),
useValueAndKey(floated, 'floated'),
)
const labeledClasses = cx(useKeyOrValueAndKey(labelPosition || !!label, 'labeled'))
const wrapperClasses = cx(useKeyOnly(disabled, 'disabled'), useValueAndKey(floated, 'floated'))

const rest = getUnhandledProps(Button, this.props)
const ElementType = getElementType(Button, this.props, this.computeElementType)
Expand All @@ -260,10 +248,12 @@ class Button extends Component {
if (!_.isNil(label)) {
const buttonClasses = cx('ui', baseClasses, 'button', className)
const containerClasses = cx('ui', labeledClasses, 'button', className, wrapperClasses)
const labelElement = Label.create(label, { defaultProps: {
basic: true,
pointing: labelPosition === 'left' ? 'right' : 'left',
} })
const labelElement = Label.create(label, {
defaultProps: {
basic: true,
pointing: labelPosition === 'left' ? 'right' : 'left',
},
})

return (
<ElementType {...rest} className={containerClasses} onClick={this.handleClick}>
Expand All @@ -286,7 +276,7 @@ class Button extends Component {
disabled={(disabled && ElementType === 'button') || undefined}
onClick={this.handleClick}
ref={this.handleRef}
role='button'
role={role}
tabIndex={tabIndex}
>
{hasChildren && children}
Expand Down
111 changes: 61 additions & 50 deletions test/specs/elements/Button/Button-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,25 +66,21 @@ describe('Button', () => {

describe('attached', () => {
it('renders a div', () => {
shallow(<Button attached />)
.should.have.tagName('div')
shallow(<Button attached />).should.have.tagName('div')
})
})

describe('disabled', () => {
it('is not set by default', () => {
shallow(<Button />)
.should.not.have.prop('disabled')
shallow(<Button />).should.not.have.prop('disabled')
})

it('applied when defined', () => {
shallow(<Button disabled />)
.should.have.prop('disabled', true)
shallow(<Button disabled />).should.have.prop('disabled', true)
})

it("don't apply when the element's type isn't button", () => {
shallow(<Button as='div' disabled />)
.should.not.have.prop('disabled')
shallow(<Button as='div' disabled />).should.not.have.prop('disabled')
})

it('is not set by default when has a label', () => {
Expand Down Expand Up @@ -118,34 +114,27 @@ describe('Button', () => {

describe('icon', () => {
it('adds className icon', () => {
shallow(<Button icon='user' />)
.should.have.className('icon')
shallow(<Button icon='user' />).should.have.className('icon')
})

it('adds className icon when true', () => {
shallow(<Button icon />)
.should.have.className('icon')
shallow(<Button icon />).should.have.className('icon')
})

it('does not add className icon when there is content', () => {
shallow(<Button icon='user' content={0} />)
.should.not.have.className('icon')
shallow(<Button icon='user' content='Yo' />)
.should.not.have.className('icon')
shallow(<Button icon='user' content={0} />).should.not.have.className('icon')
shallow(<Button icon='user' content='Yo' />).should.not.have.className('icon')
})

it('adds className icon given labelPosition and content', () => {
shallow(<Button labelPosition='left' icon='user' content='My Account' />)
.should.have.className('icon')
shallow(<Button labelPosition='right' icon='user' content='My Account' />)
.should.have.className('icon')
shallow(<Button labelPosition='left' icon='user' content='My Account' />).should.have.className('icon')
shallow(<Button labelPosition='right' icon='user' content='My Account' />).should.have.className('icon')
})
})

describe('label', () => {
it('renders as a div', () => {
shallow(<Button label='http' />)
.should.have.tagName('div')
shallow(<Button label='http' />).should.have.tagName('div')
})
it('renders a div with a button and Label child', () => {
const wrapper = shallow(<Button label='hi' />)
Expand All @@ -155,8 +144,7 @@ describe('Button', () => {
wrapper.should.have.exactly(1).descendants('Label')
})
it('adds the labeled className to the root element', () => {
shallow(<Button label='hi' />)
.should.have.className('labeled')
shallow(<Button label='hi' />).should.have.className('labeled')
})
it('contains children without disabled class when disabled attribute is set', () => {
const wrapper = shallow(<Button label='hi' disabled />)
Expand All @@ -174,80 +162,103 @@ describe('Button', () => {
})
it('creates a basic pointing label', () => {
shallow(<Button label='foo' />)
.should.have.exactly(1).descendants('Label[basic][pointing]')
.should.have.exactly(1)
.descendants('Label[basic][pointing]')
})
it('is before the button and pointing="right" when labelPosition="left"', () => {
const wrapper = shallow(<Button labelPosition='left' label='foo' />)
wrapper.should.have.exactly(1).descendants('Label[pointing="right"]')

wrapper.children().at(0).shallow().should.match('.ui.label')
wrapper.children().at(1).should.match('button')
wrapper
.children()
.at(0)
.shallow()
.should.match('.ui.label')
wrapper
.children()
.at(1)
.should.match('button')
})
it('is after the button and pointing="left" when labelPosition="right"', () => {
const wrapper = shallow(<Button labelPosition='right' label='foo' />)
wrapper.should.have.exactly(1).descendants('Label[pointing="left"]')

wrapper.children().at(0).should.match('button')
wrapper.children().at(1).shallow().should.match('.ui.label')
wrapper
.children()
.at(0)
.should.match('button')
wrapper
.children()
.at(1)
.shallow()
.should.match('.ui.label')
})
it('is after the button and pointing="left" by default', () => {
const wrapper = shallow(<Button label='foo' />)
wrapper.should.have.exactly(1).descendants('Label[pointing="left"]')

wrapper.children().at(0).should.match('button')
wrapper.children().at(1).shallow().should.match('.ui.label')
wrapper
.children()
.at(0)
.should.match('button')
wrapper
.children()
.at(1)
.shallow()
.should.match('.ui.label')
})
})

describe('labelPosition', () => {
it('renders as a button when given an icon', () => {
shallow(<Button labelPosition='left' icon='user' />)
.should.have.tagName('button')
shallow(<Button labelPosition='right' icon='user' />)
.should.have.tagName('button')
shallow(<Button labelPosition='left' icon='user' />).should.have.tagName('button')
shallow(<Button labelPosition='right' icon='user' />).should.have.tagName('button')
})
})

describe('onClick', () => {
it('is called with (e, data) when clicked', () => {
const onClick = sandbox.spy()

shallow(<Button onClick={onClick} />)
.simulate('click', syntheticEvent)
shallow(<Button onClick={onClick} />).simulate('click', syntheticEvent)

onClick.should.have.been.calledOnce()
onClick.should.have.been.calledWithExactly(syntheticEvent, { onClick, as: 'button' })
onClick.should.have.been.calledWithExactly(syntheticEvent, { onClick, ...Button.defaultProps })
})

it('is not called when is disabled', () => {
const onClick = sandbox.spy()

shallow(<Button disabled onClick={onClick} />)
.simulate('click', syntheticEvent)
shallow(<Button disabled onClick={onClick} />).simulate('click', syntheticEvent)
onClick.should.have.callCount(0)
})
})

describe('role', () => {
it('defaults to a button', () => {
Button.defaultProps.role.should.equal('button')
shallow(<Button />).should.have.prop('role', 'button')
})
it('is configurable', () => {
shallow(<Button role='link' />).should.have.prop('role', 'link')
})
})

describe('tabIndex', () => {
it('is not set by default', () => {
shallow(<Button />)
.should.not.have.prop('tabIndex')
shallow(<Button />).should.not.have.prop('tabIndex')
})
it('defaults to 0 as div', () => {
shallow(<Button as='div' />)
.should.have.prop('tabIndex', 0)
shallow(<Button as='div' />).should.have.prop('tabIndex', 0)
})
it('defaults to -1 when disabled', () => {
shallow(<Button disabled />)
.should.have.prop('tabIndex', -1)
shallow(<Button disabled />).should.have.prop('tabIndex', -1)
})
it('can be set explicitly', () => {
shallow(<Button tabIndex={123} />)
.should.have.prop('tabIndex', 123)
shallow(<Button tabIndex={123} />).should.have.prop('tabIndex', 123)
})
it('can be set explicitly when disabled', () => {
shallow(<Button tabIndex={123} disabled />)
.should.have.prop('tabIndex', 123)
shallow(<Button tabIndex={123} disabled />).should.have.prop('tabIndex', 123)
})
})
})

0 comments on commit 94e5684

Please sign in to comment.