All files / components/ButtonGroup ButtonGroup.jsx

100% Statements 13/13
100% Branches 2/2
100% Functions 1/1
100% Lines 13/13
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129              120x             120x                 120x                                                                                       120x                 2x 2x         2x 1x     2x                 10x   10x   10x           21x                                              
import _ from 'lodash';
import Button from '../Button/Button';
import React from 'react';
import { lucidClassNames } from '../../util/style-helpers';
import { createClass, findTypes, omitProps }  from '../../util/component-types';
import reducers from './ButtonGroup.reducers';
 
const cx = lucidClassNames.bind('&-ButtonGroup');
 
const {
	any,
	func,
	arrayOf,
	number,
} = React.PropTypes;
 
/**
 *
 * {"categories": ["controls", "buttons"], "madeFrom": ["Button"]}
 *
 * Button groups allow you to pair buttons together to form a seamless cluster.
 * Any props not explicitly called out are spread on to the root component.
 */
const ButtonGroup = createClass({
	displayName: 'ButtonGroup',
 
	components: {
		/**
		 * Renders a `<Button`> inside the `ButtonGroup`.
		 */
		Button: createClass({
			displayName: 'ButtonGroup.Button',
		}),
	},
 
	reducers: reducers,
 
	propTypes: {
		/**
		 * A function that is called with the index of the child button clicked.
		 * `props` refers to the child button props.
		 *
		 * Signature: `(selectedIndex, { event, props }) => {}`
		 */
		onSelect: func,
 
		/**
		 * Appended to the component-specific class names set on the root
		 * element. Value is run through the `classnames` library.
		 */
		className: any,
 
		/**
		 * All children should be `ButtonGroup.Button`s and they support the same
		 * props as `Button`s.
		 */
		children: any,
 
		/**
		 * An array of currently selected `ButtonGroup.Button`s indices. You can
		 * also pass the prop `isActive` to individual `ButtonGroup.Button`
		 * components.
		 */
		selectedIndices: arrayOf(number),
	},
 
	getDefaultProps() {
		return {
			onSelect: _.noop,
			className: null,
			children: null,
			selectedIndices: [],
		};
	},
 
	handleSelect({ event, props: childProps }) {
		const { callbackId } = childProps;
		const clickedButtonProps = _.get(findTypes(this.props, ButtonGroup.Button)[callbackId], 'props', {});
 
		// If the consumer passed in an `onClick` to the child `ButtonGroup.Button`
		// component, we should make sure to call that in addition to the
		// `ButtonGroup`'s `onSelect`.
		if (_.isFunction(clickedButtonProps.onClick)) {
			clickedButtonProps.onClick({ event, props: childProps });
		}
 
		this.props.onSelect(callbackId, { event, props: childProps });
	},
 
	render() {
		const {
			selectedIndices,
			className,
			children,
			...passThroughs,
		} = this.props;
 
		const buttonChildProps = _.map(findTypes(this.props, ButtonGroup.Button), 'props');
 
		return (
			<span
				{...omitProps(passThroughs, ButtonGroup)}
				className={cx('&', className)}
			>
				{_.map(buttonChildProps, (buttonChildProp, index) => {
					return (
						// The order of the spread operator below is important. If the
						// consumer puts `isActive` directly on a `ButtonGroup.Button`, we
						// want that to take precedence over the `selectedIndices` prop on
						// the parent `ButtonGroup`. However, we want our `onClick` at the
						// bottom because we manually handle passing the event to the
						// `ButtonGroup.Button`'s `onClick` if it exists.
						<Button
							isActive={_.includes(selectedIndices, index)}
							{...buttonChildProp}
							key={index}
							callbackId={index}
							onClick={this.handleSelect}
						/>
					);
				})}
				{children}
			</span>
		);
	},
});
 
export default ButtonGroup;