Skip to content
This repository has been archived by the owner on Oct 19, 2021. It is now read-only.

Commit

Permalink
feat(Button): allows button to render arbitrary component as its root (
Browse files Browse the repository at this point in the history
…#1891)

* feat(Button): allows button to render arbitrary component as its root

leverages a render prop to allow consumers to render whatever they want in place of button or anchor

closes #1890

* refactor(Button): consolidates code to choose button node type

* fix(Button): fixes props to button and anchor components

* refactor(Button): renames render prop to as
  • Loading branch information
ZacharyStair authored and asudoh committed Feb 20, 2019
1 parent 9905920 commit 7323f86
Show file tree
Hide file tree
Showing 4 changed files with 64 additions and 50 deletions.
10 changes: 10 additions & 0 deletions src/components/Button/Button-story.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import AddFilled16 from '@carbon/icons-react/lib/add--filled/16';
import Search16 from '@carbon/icons-react/lib/search/16';
import { settings } from 'carbon-components';
import Button from '../Button';
import Link from '../Link';
import ButtonSkeleton from '../Button/Button.Skeleton';
import { componentsX } from '../../internal/FeatureFlags';

Expand Down Expand Up @@ -80,6 +81,15 @@ storiesOf('Buttons', module)
Link
</Button>
&nbsp;
<Button
{...regularProps}
as={Link}
kind="secondary"
href="#"
className="some-class">
Link
</Button>
&nbsp;
</div>
);
},
Expand Down
17 changes: 17 additions & 0 deletions src/components/Button/Button-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import React from 'react';
import { iconSearch } from 'carbon-icons';
import Button from '../Button';
import Link from '../Link';
import ButtonSkeleton from '../Button/Button.Skeleton';
import { shallow, mount } from 'enzyme';

Expand Down Expand Up @@ -97,6 +98,22 @@ describe('Button', () => {
});
});

describe('Renders arbitrary component with correct props', () => {
let wrapper;
beforeEach(() => {
wrapper = shallow(
<Button as={Link} data-foo="foo">
<div className="child">child</div>
<div className="child">child</div>
</Button>
);
});
it('renders as a Link with data attribute', () => {
expect(wrapper.is(Link)).toBe(true);
expect(wrapper.is('[data-foo="foo"]')).toBe(true);
});
});

describe('Renders icon buttons', () => {
const iconButton = mount(
<Button icon={iconSearch} iconDescription="Search">
Expand Down
61 changes: 37 additions & 24 deletions src/components/Button/Button.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,15 @@ const { prefix } = settings;

const Button = ({
children,
as,
className,
disabled,
small,
kind,
href,
tabIndex,
type,
inputref,
icon,
iconDescription,
...other
Expand All @@ -42,6 +44,7 @@ const Button = ({
const commonProps = {
tabIndex,
className: buttonClasses,
ref: inputref,
};
const buttonImage = (() => {
if (componentsX && icon && React.isValidElement(icon)) {
Expand All @@ -61,31 +64,35 @@ const Button = ({
return null;
})();

const button = (
<button
{...other}
{...commonProps}
disabled={disabled}
type={type}
ref={other.inputref}>
{children}
{buttonImage}
</button>
);

const anchor = (
<a
{...other}
{...commonProps}
href={href}
role="button"
ref={other.inputref}>
{children}
{buttonImage}
</a>
let component = 'button';
let otherProps = {
disabled,
type,
};
const anchorProps = {
role: 'button',
href,
};
if (as) {
component = as;
otherProps = {
...otherProps,
...anchorProps,
};
} else if (href) {
component = 'a';
otherProps = anchorProps;
}
return React.createElement(
component,
{
...other,
...commonProps,
...otherProps,
},
children,
buttonImage
);

return href ? anchor : button;
};

Button.propTypes = {
Expand All @@ -94,6 +101,12 @@ Button.propTypes = {
*/
children: PropTypes.node,

/**
* Specify how the button itself should be rendered.
* Make sure to apply all props to the root node and render children appropriately
*/
as: PropTypes.oneOfType([PropTypes.func, PropTypes.string]),

/**
* Specify an optional className to be added to your Button
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ exports[`ModalWrapper should render 1`] = `
Object {
"current": <button
class="btn-trigger bx--btn bx--btn--primary"
inputref="[object Object]"
tabindex="0"
type="button"
>
Expand All @@ -46,18 +45,6 @@ exports[`ModalWrapper should render 1`] = `
<button
className="btn-trigger bx--btn bx--btn--primary"
disabled={false}
inputref={
Object {
"current": <button
class="btn-trigger bx--btn bx--btn--primary"
inputref="[object Object]"
tabindex="0"
type="button"
>
Test Modal
</button>,
}
}
onClick={[Function]}
tabIndex={0}
type="button"
Expand Down Expand Up @@ -201,7 +188,6 @@ exports[`ModalWrapper should render 1`] = `
Object {
"current": <button
class="bx--btn bx--btn--primary"
inputref="[object Object]"
tabindex="0"
type="button"
>
Expand All @@ -218,18 +204,6 @@ exports[`ModalWrapper should render 1`] = `
<button
className="bx--btn bx--btn--primary"
disabled={false}
inputref={
Object {
"current": <button
class="bx--btn bx--btn--primary"
inputref="[object Object]"
tabindex="0"
type="button"
>
Save
</button>,
}
}
onClick={[Function]}
tabIndex={0}
type="button"
Expand Down

0 comments on commit 7323f86

Please sign in to comment.