Skip to content

Commit

Permalink
feat: button group
Browse files Browse the repository at this point in the history
  • Loading branch information
Vandish Gandhi committed Mar 29, 2019
1 parent 16f849c commit ba93ca6
Show file tree
Hide file tree
Showing 9 changed files with 366 additions and 0 deletions.
1 change: 1 addition & 0 deletions docs/assets/svg-symbols.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions docs/components/Layout/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import SearchResultCard from '../SearchResultCard';
import MigrationNote from '../MigrationNote';

import ButtonExample from '../../examples/ButtonExample';
import ButtonGroupExample from '../../examples/ButtonGroupExample';
import AlertInputExample from '../../examples/AlertInputExample';
import FilePickerExample from '../../examples/FilePickerExample';
import TextareaExample from '../../examples/TextareaExample';
Expand Down Expand Up @@ -75,6 +76,7 @@ ContentArea.propTypes = SidebarArea.propTypes;
const componentsBySection = {
'form-elements': [
'button',
'button-group',
'alert-input',
'file-picker',
'textarea',
Expand Down Expand Up @@ -175,6 +177,7 @@ class PageLayout extends React.Component {
<MigrationNote />
<PageTitle title="Form Elements" />
<ButtonExample />
<ButtonGroupExample />
<AlertInputExample />
<FilePickerExample />
<TextareaExample />
Expand Down
145 changes: 145 additions & 0 deletions docs/examples/ButtonGroupExample.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
import React from 'react';
import Example from '../components/Example';
import { Button, ButtonGroup, OverlayTrigger, Popover, SvgSymbol } from '../../src';
import { Overlay } from 'react-bootstrap';

class ButtonGroupExample extends React.PureComponent {
state = {
isDropdownOpen: false,
};

buttonRef = React.createRef();

render() {
return (
<React.Fragment>
<ButtonGroup bsStyle="success">
<Button>Approve</Button>
<OverlayTrigger
trigger={['click']}
placement="bottom"
overlay={<Popover id="popover-1">I am a popover on click!</Popover>}
>
<Button>
<SvgSymbol href="./docs/assets/svg-symbols.svg#caret-down" />
</Button>
</OverlayTrigger>
</ButtonGroup>
<ButtonGroup bsStyle="danger" inverse={true}>
<Button>Reject</Button>
<Button
onClick={() =>
this.setState(prevState => ({
isDropdownOpen: !prevState.isDropdownOpen,
}))
}
ref={this.buttonRef}
>
<SvgSymbol href="./docs/assets/svg-symbols.svg#caret-down" />
</Button>
<Overlay show={this.state.isDropdownOpen} target={this.buttonRef.current} placement="bottom">
<Popover id="popover-2">I am a Overlay on click!</Popover>
</Overlay>
</ButtonGroup>
<ButtonGroup bsStyle="primary">
<Button>Sign off</Button>
<Button onClick={() => alert('>I am a Alert on click!')}>
<SvgSymbol href="./docs/assets/svg-symbols.svg#caret-down" />
</Button>
</ButtonGroup>
<ButtonGroup bsStyle="warning" inverse={true} disabled={true}>
<Button>Disabled</Button>
<Button>
<SvgSymbol href="./docs/assets/svg-symbols.svg#caret-down" />
</Button>
</ButtonGroup>
</React.Fragment>
);
}
}

export const exampleProps = {
componentName: 'Button Group',
exampleCodeSnippet: `
<ButtonGroup bsStyle="success">
<Button>Approve</Button>
<OverlayTrigger
trigger={['click']}
placement="bottom"
overlay={<Popover id="popover-1">I am a popover on click!</Popover>}
>
<Button>
<SvgSymbol href="./docs/assets/svg-symbols.svg#caret-down" />
</Button>
</OverlayTrigger>
</ButtonGroup>
<ButtonGroup bsStyle="danger" inverse={true}>
<Button>Reject</Button>
<Button
onClick={() =>
this.setState(prevState => ({
isDropdownOpen: !prevState.isDropdownOpen,
}))
}
ref={this.buttonRef}
>
<SvgSymbol href="./docs/assets/svg-symbols.svg#caret-down" />
</Button>
<Overlay show={this.state.isDropdownOpen} target={this.buttonRef.current} placement="bottom">
<Popover id="popover-2">I am a Overlay on click!</Popover>
</Overlay>
</ButtonGroup>
<ButtonGroup bsStyle="primary">
<Button>Sign off</Button>
<Button onClick={() => alert('>I am a Alert on click!')}>
<SvgSymbol href="./docs/assets/svg-symbols.svg#caret-down" />
</Button>
</ButtonGroup>
<ButtonGroup bsStyle="warning" inverse={true} disabled={true}>
<Button>Disabled</Button>
<Button>
<SvgSymbol href="./docs/assets/svg-symbols.svg#caret-down" />
</Button>
</ButtonGroup>
`,
designNotes: (
<p>
<span className="text-bold">Button Groups</span> provides a layout for a two or more buttons to share common style
and functionality but with independent events.
</p>
),
propTypeSectionArray: [
{
propTypes: [
{
propType: 'bsStyle',
type: 'string, oneOf primary, default, success, info, warning or danger',
defaultValue: 'default',
},
{
propType: 'inverse',
type: 'bool',
note: 'Renders an inverse button. Can be used with bsStyle to create primary inverse buttons.',
defaultValue: 'false',
},
{
propType: 'disabled',
type: 'bool',
note: 'Disables entire button group',
defaultValue: 'false',
},
{
propType: 'bsSize',
type: 'string',
note: 'small, large',
},
],
},
],
};

export default () => (
<Example {...exampleProps}>
<ButtonGroupExample />
</Example>
);
19 changes: 19 additions & 0 deletions docs/examples/styles.scss
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,25 @@
margin-left: 20px;
}
}

&.button-group-example {
.adslot-ui-example {
display: flex;

.aui--button-group {
.svg-symbol-component {
display: flex;
fill: $color-white;
}

.btn-inverse {
.svg-symbol-component {
fill: $color-black;
}
}
}
}
}
}

.full-width {
Expand Down
60 changes: 60 additions & 0 deletions src/components/adslot-ui/ButtonGroup/index.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import _ from 'lodash';
import { expandDts } from 'lib/utils';
import React from 'react';
import PropTypes from 'prop-types';
import './styles.scss';
import { Button } from '../../third-party';

class ButtonGroup extends React.Component {
static propTypes = {
dts: PropTypes.string,
children: PropTypes.node,
bsStyle: PropTypes.string,
inverse: PropTypes.bool,
disabled: PropTypes.bool,
bsSize: PropTypes.string,
};

injectProps(children) {
return React.Children.map(children, child => {
if (React.isValidElement(child)) {
const buttonProps = {
...(this.props.bsStyle ? { bsStyle: this.props.bsStyle } : {}),
...(!_.isNil(this.props.inverse) ? { inverse: this.props.inverse } : {}),
...(!_.isNil(this.props.disabled) ? { disabled: this.props.disabled } : {}),
...(this.props.bsSize ? { bsSize: this.props.bsSize } : {}),
};

const childNodes = React.Children.map(child.props.children, childNode =>
React.isValidElement(childNode)
? React.cloneElement(childNode, {
...childNode.props,
...(childNode.type.name === Button.name ? buttonProps : {}),
})
: childNode
);

return React.cloneElement(child, {
...child.props,
...(child.type.name === Button.name ? buttonProps : {}),
...(!_.isEmpty(childNodes) ? { children: childNodes.length === 1 ? childNodes[0] : childNodes } : {}),
});
}

return child;
});
}

render() {
const { children, dts } = this.props;
const content = this.injectProps(children);

return (
<div {...expandDts(dts)} className="aui--button-group">
{content}
</div>
);
}
}

export default ButtonGroup;
85 changes: 85 additions & 0 deletions src/components/adslot-ui/ButtonGroup/index.spec.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import React from 'react';
import { mount } from 'enzyme';
import ButtonGroup from 'adslot-ui/ButtonGroup';
import Button from 'third-party/Button';

describe('ButtonGroupComponent', () => {
it('should render Button Group', () => {
const wrapper = mount(
<ButtonGroup>
<Button>Test1</Button>
<Button>Test2</Button>
</ButtonGroup>
);
expect(wrapper.find(ButtonGroup)).to.have.length(1);
});

it('should override child Button style', () => {
const wrapper = mount(
<ButtonGroup bsStyle="link" inverse>
<Button bsStyle="primary">Test1</Button>
<Button inverse={false}>Test2</Button>
</ButtonGroup>
);
expect(
wrapper
.find(Button)
.at(0)
.props().bsStyle
).to.equal('link');
expect(
wrapper
.find(Button)
.at(1)
.props().inverse
).to.equal(true);
});

it('should disable child buttons', () => {
const wrapper = mount(
<ButtonGroup disabled>
<Button bsStyle="primary">Test1</Button>
<Button inverse={false}>Test2</Button>
</ButtonGroup>
);
expect(
wrapper
.find(Button)
.at(0)
.props().disabled
).to.equal(true);
expect(
wrapper
.find(Button)
.at(1)
.props().disabled
).to.equal(true);
});

it('should inject props to Button at any nested level', () => {
const wrapper = mount(
<ButtonGroup disabled bsSize="large">
<div>
<div>foo</div>
<Button bsStyle="primary">Test1</Button>
</div>
</ButtonGroup>
);
expect(
wrapper
.find(Button)
.at(0)
.props().disabled
).to.equal(true);
});

it('should not crash when child is null', () => {
const wrapper = mount(
<ButtonGroup disabled>
<div />
{null}
</ButtonGroup>
);
expect(wrapper.find(ButtonGroup)).to.have.length(1);
});
});
49 changes: 49 additions & 0 deletions src/components/adslot-ui/ButtonGroup/styles.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
@import '~styles/color';

.aui--button-group {
display: flex;

.button-component {
margin-right: 0;
border-radius: 0;
box-shadow: none;

&.btn-inverse:hover,
&.btn-inverse:active,
&.btn-inverse:focus {
border: 1px solid $color-gray-light;
background-color: $color-gray-lighter;
}

&.btn,
&.btn:hover {
border: 1px solid $color-gray-light;
border-right-width: 0;
}

&:first-child {
border-top-left-radius: 2px;
border-bottom-left-radius: 2px;
}

&:last-child,
&:last-child:hover,
&:last-child:active {
border-right-width: 1px;
border-top-right-radius: 2px;
border-bottom-right-radius: 2px;
}
}

+ .aui--button-group {
margin-left: 20px;
}

+ button {
margin-right: 20px;
}
}

button + .aui--button-group {
margin-left: 20px;
}
Loading

0 comments on commit ba93ca6

Please sign in to comment.