From ba93ca6f923b37330706dd90efbd1ca399b25888 Mon Sep 17 00:00:00 2001 From: Vandish Gandhi Date: Mon, 25 Mar 2019 14:24:07 +1100 Subject: [PATCH] feat: button group --- docs/assets/svg-symbols.svg | 1 + docs/components/Layout/index.jsx | 3 + docs/examples/ButtonGroupExample.jsx | 145 ++++++++++++++++++ docs/examples/styles.scss | 19 +++ .../adslot-ui/ButtonGroup/index.jsx | 60 ++++++++ .../adslot-ui/ButtonGroup/index.spec.jsx | 85 ++++++++++ .../adslot-ui/ButtonGroup/styles.scss | 49 ++++++ src/components/adslot-ui/index.js | 2 + src/index.js | 2 + 9 files changed, 366 insertions(+) create mode 100644 docs/examples/ButtonGroupExample.jsx create mode 100644 src/components/adslot-ui/ButtonGroup/index.jsx create mode 100644 src/components/adslot-ui/ButtonGroup/index.spec.jsx create mode 100644 src/components/adslot-ui/ButtonGroup/styles.scss diff --git a/docs/assets/svg-symbols.svg b/docs/assets/svg-symbols.svg index bf7b74c6a..54c541dcd 100644 --- a/docs/assets/svg-symbols.svg +++ b/docs/assets/svg-symbols.svg @@ -6,4 +6,5 @@ + diff --git a/docs/components/Layout/index.jsx b/docs/components/Layout/index.jsx index 5be3f98bb..1290bff8f 100644 --- a/docs/components/Layout/index.jsx +++ b/docs/components/Layout/index.jsx @@ -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'; @@ -75,6 +76,7 @@ ContentArea.propTypes = SidebarArea.propTypes; const componentsBySection = { 'form-elements': [ 'button', + 'button-group', 'alert-input', 'file-picker', 'textarea', @@ -175,6 +177,7 @@ class PageLayout extends React.Component { + diff --git a/docs/examples/ButtonGroupExample.jsx b/docs/examples/ButtonGroupExample.jsx new file mode 100644 index 000000000..138e23660 --- /dev/null +++ b/docs/examples/ButtonGroupExample.jsx @@ -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 ( + + + + I am a popover on click!} + > + + + + + + + + I am a Overlay on click! + + + + + + + + + + + + ); + } +} + +export const exampleProps = { + componentName: 'Button Group', + exampleCodeSnippet: ` + + + I am a popover on click!} + > + + + + + + + + I am a Overlay on click! + + + + + + + + + + + `, + designNotes: ( +

+ Button Groups provides a layout for a two or more buttons to share common style + and functionality but with independent events. +

+ ), + 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 () => ( + + + +); diff --git a/docs/examples/styles.scss b/docs/examples/styles.scss index 1d3efe79a..aca7bfb67 100644 --- a/docs/examples/styles.scss +++ b/docs/examples/styles.scss @@ -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 { diff --git a/src/components/adslot-ui/ButtonGroup/index.jsx b/src/components/adslot-ui/ButtonGroup/index.jsx new file mode 100644 index 000000000..73dcac8c9 --- /dev/null +++ b/src/components/adslot-ui/ButtonGroup/index.jsx @@ -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 ( +
+ {content} +
+ ); + } +} + +export default ButtonGroup; diff --git a/src/components/adslot-ui/ButtonGroup/index.spec.jsx b/src/components/adslot-ui/ButtonGroup/index.spec.jsx new file mode 100644 index 000000000..a8ffd6f0c --- /dev/null +++ b/src/components/adslot-ui/ButtonGroup/index.spec.jsx @@ -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( + + + + + ); + expect(wrapper.find(ButtonGroup)).to.have.length(1); + }); + + it('should override child Button style', () => { + const wrapper = mount( + + + + + ); + 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( + + + + + ); + 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( + +
+
foo
+ +
+
+ ); + expect( + wrapper + .find(Button) + .at(0) + .props().disabled + ).to.equal(true); + }); + + it('should not crash when child is null', () => { + const wrapper = mount( + +
+ {null} + + ); + expect(wrapper.find(ButtonGroup)).to.have.length(1); + }); +}); diff --git a/src/components/adslot-ui/ButtonGroup/styles.scss b/src/components/adslot-ui/ButtonGroup/styles.scss new file mode 100644 index 000000000..c3bed474d --- /dev/null +++ b/src/components/adslot-ui/ButtonGroup/styles.scss @@ -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; +} diff --git a/src/components/adslot-ui/index.js b/src/components/adslot-ui/index.js index 1944d101f..c54f7af24 100644 --- a/src/components/adslot-ui/index.js +++ b/src/components/adslot-ui/index.js @@ -1,5 +1,6 @@ import Accordion from 'adslot-ui/Accordion'; import AlertInput from 'adslot-ui/AlertInput'; +import ButtonGroup from 'adslot-ui/ButtonGroup'; import Carousel from 'adslot-ui/Carousel'; import Checkbox from 'adslot-ui/Checkbox'; import CheckboxGroup from 'adslot-ui/CheckboxGroup'; @@ -35,6 +36,7 @@ import Nav from 'adslot-ui/Navigation'; export { Accordion, AlertInput, + ButtonGroup, Carousel, Checkbox, CheckboxGroup, diff --git a/src/index.js b/src/index.js index c805e4062..d4e6ac5d8 100644 --- a/src/index.js +++ b/src/index.js @@ -45,6 +45,7 @@ import { import { Accordion, AlertInput, + ButtonGroup, Carousel, Checkbox, CheckboxGroup, @@ -86,6 +87,7 @@ export { BorderedWell, Breadcrumb, Button, + ButtonGroup, Card, Carousel, Checkbox,