All files / boundless/packages/boundless-progressive-disclosure index.js

100% Statements 17/17
93.75% Branches 15/16
100% Functions 8/8
100% Lines 16/16
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          27x 1x                                                                                                                                                                   14x     7x 3x         9x     9x 1x         3x 2x 2x       3x 1x         44x 15x                 44x                                          
import React, {PropTypes} from 'react';
import cx from 'classnames';
 
import omit from 'boundless-utils-omit-keys';
 
const isFunction = (x) => typeof x === 'function';
const noop = () => {};
 
/**
__Hide content until it's needed, with configurable teasers.__
 
Mechanically, hidden disclosure content is not rendered to the DOM until it is needed.
 */
export default class ProgressiveDisclosure extends React.PureComponent {
    static propTypes = {
        /**
         * any [React-supported attribute](https://facebook.github.io/react/docs/tags-and-attributes.html#html-attributes)
         */
        '*': PropTypes.any,
 
        /** if a function is passed, it will not be called until the disclosure content is due to be rendered */
        children: PropTypes.oneOfType([
            PropTypes.node,
            PropTypes.arrayOf(PropTypes.node),
            PropTypes.func,
        ]),
 
        /**
         * any valid HTML tag name
         */
        component: PropTypes.string,
 
        /**
         * controls the ProgressDisclosure "expanded" state declaratively
         */
        expanded: PropTypes.bool,
 
        /**
         * called when the content is shown; not called on initial render
         */
        onExpand: PropTypes.func,
 
        /**
         * called when the content is hidden; not called on initial render
         */
        onHide: PropTypes.func,
 
        /**
         * any valid HTML tag name
         */
        toggleComponent: PropTypes.string,
 
        /**
         * content to be shown next to the expansion toggle when the disclosure is in "contracted" state, e.g. "Show Advanced Options"
         */
        toggleContent: PropTypes.node,
 
        /**
         * content to be shown next to the expansion toggle when the disclosure is in "expanded" state, e.g. "Hide Advanced Options"
         */
        toggleExpandedContent: PropTypes.node,
 
        toggleProps: PropTypes.shape({
            /**
             * any [React-supported attribute](https://facebook.github.io/react/docs/tags-and-attributes.html#html-attributes)
             */
            '*': PropTypes.any,
        }),
    }
 
    static defaultProps = {
        children: null,
        component: 'div',
        expanded: false,
        onExpand: noop,
        onHide: noop,
        toggleComponent: 'div',
        toggleContent: null,
        toggleExpandedContent: null,
        toggleProps: {},
    }
 
    static internalKeys = Object.keys(ProgressiveDisclosure.defaultProps)
 
    state = {
        expanded: this.props.expanded,
    }
 
    fireStatefulCallback = () => this.props[this.state.expanded ? 'onExpand' : 'onHide']()
 
    componentWillReceiveProps(newProps) {
        if (newProps.expanded !== this.props.expanded) {
            this.setState({expanded: newProps.expanded}, this.fireStatefulCallback);
        }
    }
 
    handleClick = (event) => {
        this.setState({expanded: !this.state.expanded}, this.fireStatefulCallback);
 
        /* istanbul ignore else */
        if (isFunction(this.props.toggleProps.onClick)) {
            this.props.toggleProps.onClick(event);
        }
    }
 
    handleKeyDown = (event) => {
        if (event.key === 'Enter') {
            event.preventDefault();
            this.setState({expanded: !this.state.expanded}, this.fireStatefulCallback);
        }
 
        /* istanbul ignore else */
        if (isFunction(this.props.toggleProps.onKeyDown)) {
            this.props.toggleProps.onKeyDown(event);
        }
    }
 
    renderContent() {
        if (this.state.expanded) {
            return (
                <div className='b-disclosure-content'>
                    {isFunction(this.props.children) ? this.props.children() : this.props.children}
                </div>
            );
        }
    }
 
    render() {
        return (
            <this.props.component
                {...omit(this.props, ProgressiveDisclosure.internalKeys)}
                className={cx('b-disclosure', this.props.className, {
                   'b-disclosure-expanded': this.state.expanded,
                })}>
 
                <this.props.toggleComponent
                    {...this.props.toggleProps}
                    className={cx('b-disclosure-toggle', this.props.toggleProps.className)}
                    onClick={this.handleClick}
                    onKeyDown={this.handleKeyDown}
                    tabIndex='0'>
                    {this.state.expanded ? this.props.toggleExpandedContent || this.props.toggleContent : this.props.toggleContent}
                </this.props.toggleComponent>
 
                {this.renderContent()}
            </this.props.component>
        );
    }
}