Skip to content

Commit

Permalink
feat(Button): add close icon support (#1206)
Browse files Browse the repository at this point in the history
Closes #1182
  • Loading branch information
median-man authored and TheSharpieOne committed Sep 25, 2018
1 parent bd8b2e0 commit 02f5e9a
Show file tree
Hide file tree
Showing 4 changed files with 96 additions and 3 deletions.
22 changes: 21 additions & 1 deletion docs/lib/Components/ButtonsPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ const ButtonOutlineSource = require('!!raw!../examples/ButtonOutline');
import ButtonStateful from '../examples/ButtonStateful';
const ButtonStatefulSource = require('!!raw!../examples/ButtonStateful');

import ButtonCloseIcon from '../examples/ButtonCloseIcon';
const ButtonCloseIconSource = require('!!raw!../examples/ButtonCloseIcon');

export default class ButtonsPage extends React.Component {
render() {
return (
Expand Down Expand Up @@ -44,7 +47,10 @@ export default class ButtonsPage extends React.Component {
innerRef: PropTypes.oneOfType([PropTypes.func, PropTypes.string]),
onClick: PropTypes.func,
size: PropTypes.string
size: PropTypes.string,
// use close prop for BS4 close icon utility
close: PropTypes.bool,
}`}
</PrismCode>
</pre>
Expand Down Expand Up @@ -123,6 +129,20 @@ export default class ButtonsPage extends React.Component {
{ButtonStatefulSource}
</PrismCode>
</pre>

<SectionTitle>Close icon</SectionTitle>
<p>
Use a generic close icon to dismiss content. Use <code>&lt;Button close /&gt;</code> for the default icon. Otherwise, custom content for the button
may be defined. (e.g. JSX: <code>&lt;Button close&gt;&lt;span aria-hidden="true"&gt;&ndash;&lt;/span&gt;&lt;/Button&gt;</code>) The default aria-label is "Close".
</p>
<div className="docs-example">
<ButtonCloseIcon />
</div>
<pre>
<PrismCode className="language-jsx">
{ButtonCloseIconSource}
</PrismCode>
</pre>
</div>
);
}
Expand Down
38 changes: 38 additions & 0 deletions docs/lib/examples/ButtonCloseIcon.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import React, { Component } from 'react';
import { Button, Card, CardBody, CardText, CardGroup, CardTitle } from 'reactstrap';

const Example = () => (
<div>
<CardGroup>
<Card>
<CardBody>
<CardTitle>

<Button close />

</CardTitle>
<CardText>Default close icon</CardText>
</CardBody>
</Card>

<Card>
<CardBody>
<CardTitle>

<Button close aria-label="Cancel">
<span aria-hidden>&ndash;</span>
</Button>

</CardTitle>
<CardText>
Custom content and aria-label
</CardText>
</CardBody>
</Card>
</CardGroup>


</div>
);

export default Example;
18 changes: 16 additions & 2 deletions src/Button.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { mapToCssModules } from './utils';

const propTypes = {
active: PropTypes.bool,
'aria-label': PropTypes.string,
block: PropTypes.bool,
color: PropTypes.string,
disabled: PropTypes.bool,
Expand All @@ -16,6 +17,7 @@ const propTypes = {
children: PropTypes.node,
className: PropTypes.string,
cssModule: PropTypes.object,
close: PropTypes.bool,
};

const defaultProps = {
Expand Down Expand Up @@ -44,8 +46,10 @@ class Button extends React.Component {
render() {
let {
active,
'aria-label': ariaLabel,
block,
className,
close,
cssModule,
color,
outline,
Expand All @@ -55,10 +59,17 @@ class Button extends React.Component {
...attributes
} = this.props;

if (close && typeof attributes.children === 'undefined') {
attributes.children = <span aria-hidden>×</span>;
}

const btnOutlineColor = `btn${outline ? '-outline' : ''}-${color}`;

const classes = mapToCssModules(classNames(
className,
'btn',
`btn${outline ? '-outline' : ''}-${color}`,
{ close },
close || 'btn',
close || btnOutlineColor,
size ? `btn-${size}` : false,
block ? 'btn-block' : false,
{ active, disabled: this.props.disabled }
Expand All @@ -68,13 +79,16 @@ class Button extends React.Component {
Tag = 'a';
}

const defaultAriaLabel = close ? 'Close' : null;

return (
<Tag
type={(Tag === 'button' && attributes.onClick) ? 'button' : undefined}
{...attributes}
className={classes}
ref={innerRef}
onClick={this.onClick}
aria-label={ariaLabel || defaultAriaLabel}
/>
);
}
Expand Down
21 changes: 21 additions & 0 deletions src/__tests__/Button.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,27 @@ describe('Button', () => {
expect(block.hasClass('btn-block')).toBe(true);
});

it('should render close icon utility with default props', () => {
const times = '×'; // unicode: U+00D7 MULTIPLICATION SIGN
const expectedInnerHTML = `<span aria-hidden="true">${times}</span>`;

const wrapper = shallow(<Button close />);
const actualInnerHTML = wrapper.children().html();

expect(wrapper.find('.close').length).toBe(1);
expect(wrapper.find('.btn').length).toBe(0);
expect(wrapper.find('.btn-secondary').length).toBe(0);
expect(wrapper.find('button').prop('aria-label')).toMatch(/close/i);
expect(actualInnerHTML).toBe(expectedInnerHTML);
});

it('should render close icon with custom child and props', () => {
const testChild = 'close this thing';
const wrapper = shallow(<Button close>{testChild}</Button>);

expect(wrapper.contains(testChild));
});

describe('onClick', () => {
it('calls props.onClick if it exists', () => {
const onClick = jest.fn();
Expand Down

0 comments on commit 02f5e9a

Please sign in to comment.