All files / boundless/packages/boundless-segmented-control index.js

95.45% Statements 21/22
87.5% Branches 14/16
93.75% Functions 15/16
95% Lines 19/20
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 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178                    19x 56x 1x 1x       19x                                                                                                                                                                           54x       18x       4x           2x   2x 2x 2x               1x         1x         1x         2x         1x     27x             81x                                    
import React, {PropTypes} from 'react';
import cx from 'classnames';
 
import ArrowKeyNavigation from 'boundless-arrow-key-navigation';
import Button from 'boundless-button';
import omit from 'boundless-utils-omit-keys';
 
function findIndex(arr, test) {
    let found;
 
    for (let i = 0, len = arr.length; i < len; i += 1) {
        if (test(arr[i])) {
            found = i;
            break;
        }
    }
 
    return found;
}
 
/**
__A control containing multiple buttons, only one of which can be active at a time.__
 
SegmentedControl has many potential uses, the most common being:
 
1. The controls for a tabbed view
2. A mode switch
 
Essentially, it behaves like a radio group without actually using input controls. Only one option can be selected at a time.
 
### Component Instance Methods
 
- `getSelectedOption()` retrieves the option that is selected
- `getSelectedOptionIndex()` retrieves the index of the option that is selected
- `selectOption(option)` allows for programmatic switching of the active SegmentedControl option
- `selectOptionByKey(key, value)` allows for programmatic switching of the active SegmentedControl option using a unique key
- `selectOptionIndex(index)` allows for programmatic switching of the active SegmentedControl option by index
 */
export default class SegmentedControl extends React.PureComponent {
    static propTypes = {
        /**
         * any [React-supported attribute](https://facebook.github.io/react/docs/tags-and-attributes.html#html-attributes)
         */
        '*': PropTypes.any,
 
        /**
         * sets the initial selected option on first mount
         */
        defaultOptionSelectedIndex: PropTypes.number,
 
        /**
         * called when a child element becomes selected with the option and option index
         */
        onOptionSelected: PropTypes.func,
 
        /**
         * provide a customized component type if desired, either a HTML element name or ReactComponent
         */
        optionComponent: PropTypes.oneOfType([
            PropTypes.string,
            PropTypes.func,
        ]),
 
        /**
         * prop objects to be applied against the SegmentedControl buttons, accepts any valid React props
         *
         * #### Example
         *
         * ```jsx
         * options={[{
         *     children: 'Foo',
         *     className: 'foo',
         * }, {
         *     children: <span>Bar</span>,
         *     'data-id': 'bar',
         * }]}
         * ```
         */
        options: PropTypes.arrayOf(
            PropTypes.shape({
                /**
                 * any [React-supported attribute](https://facebook.github.io/react/docs/tags-and-attributes.html#html-attributes)
                 */
                '*': PropTypes.any,
                children: PropTypes.node,
            })
        ).isRequired,
    }
 
    static defaultProps = {
        defaultOptionSelectedIndex: 0,
        onOptionSelected: () => {},
        optionComponent: 'button',
        options: [],
    }
 
    static internalKeys = Object.keys(SegmentedControl.defaultProps)
 
    state = {
        selectedIndex: null,
    }
 
    inferSelectedOptionIndex(props = this.props, state = this.state) {
        return findIndex(props.options, (option) => option.pressed) || state.selectedIndex;
    }
 
    componentWillMount() {
        this.setState({selectedIndex: this.inferSelectedOptionIndex() || this.props.defaultOptionSelectedIndex});
    }
 
    componentWillReceiveProps(nextProps) {
        Iif (nextProps.options !== this.props.options) {
            this.setState({selectedIndex: this.inferSelectedOptionIndex(nextProps)});
        }
    }
 
    handleOptionSelection = (event) => {
        const index = Array.prototype.indexOf.call(event.target.parentElement.children, event.target);
 
        Eif (this.state.selectedIndex !== index) {
            this.setState({selectedIndex: index}, () => {
                this.props.onOptionSelected(this.props.options[this.state.selectedIndex], this.state.selectedIndex);
            });
        }
    }
 
    /**
     * @public
     */
    getSelectedOption = () => this.props.options[this.state.selectedIndex]
 
    /**
     * @public
     */
    getSelectedOptionIndex = () => this.state.selectedIndex
 
    /**
     * @public
     */
    selectOption = (option) => this.setState({selectedIndex: this.props.options.indexOf(option)})
 
    /**
     * @public
     */
    selectOptionByKey = (k, v) => this.setState({selectedIndex: findIndex(this.props.options, (option) => option[k] === v)})
 
    /**
     * @public
     */
    selectOptionIndex = (index) => this.setState({selectedIndex: index})
 
    render() {
        return (
            <ArrowKeyNavigation
                {...omit(this.props, SegmentedControl.internalKeys)}
                role='radiogroup'
                className={cx('b-segmented-control', this.props.className)}
                mode={ArrowKeyNavigation.mode.HORIZONTAL}>
                {this.props.options.map((props, index) => (
                    <Button
                        {...props}
                        key={props.key || index}
                        aria-checked={index === this.state.selectedIndex}
                        component={props.component || this.props.optionComponent}
                        className={cx('b-segmented-control-option', props.className, {
                            'b-segmented-control-option-selected': index === this.state.selectedIndex,
                        })}
                        onPressed={this.handleOptionSelection}
                        pressed={index === this.state.selectedIndex}
                        role='radio'>
                        {props.children}
                    </Button>
                ))}
            </ArrowKeyNavigation>
        );
    }
}