summaryrefslogtreecommitdiffstats
path: root/packages/gitbook-core/src/components/InjectedComponent.js
blob: cb50e028bedb231d4a774d65c0d54ebeeb7653ef (plain)
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
const React = require('react');
const ReactRedux = require('react-redux');
const { List } = require('immutable');

const { findMatchingComponents } = require('../actions/components');

/*
    Public: InjectedComponent makes it easy to include a set of dynamically registered
    components inside of your React render method. Rather than explicitly render
    an array of buttons, for example, you can use InjectedComponentSet:

    ```js
    <InjectedComponentSet className="message-actions"
                      matching={{role: 'ThreadActionButton'}}
                      props={{ a: 1 }}>
    ```

    InjectedComponentSet will look up components registered for the location you provide,
    render them inside a {Flexbox} and pass them `exposedProps`. By default, all injected
    children are rendered inside {UnsafeComponent} wrappers to prevent third-party code
    from throwing exceptions that break React renders.

    InjectedComponentSet monitors the ComponentStore for changes. If a new component
    is registered into the location you provide, InjectedComponentSet will re-render.
    If no matching components is found, the InjectedComponent renders an empty span.
 */

const Injection = React.createClass({
    propTypes: {
        component: React.PropTypes.func,
        props:     React.PropTypes.object,
        children:  React.PropTypes.node
    },

    render() {
        const Comp = this.props.component;
        const { props, children } = this.props;

        // TODO: try to render with an error handling for unsafe component
        return <Comp {...props}>{children}</Comp>;
    }
});

const InjectedComponentSet = React.createClass({
    propTypes: {
        components:    React.PropTypes.oneOfType([
            React.PropTypes.arrayOf(React.PropTypes.func),
            React.PropTypes.instanceOf(List)
        ]).isRequired,
        props:         React.PropTypes.object,
        withContainer: React.PropTypes.bool
    },

    render() {
        const { components, props, ...divProps } = this.props;

        const inner = components.map(Comp => <Injection key={Comp.displayName} component={Comp} props={props} />);

        return (
            <div {...divProps}>
                {inner}
            </div>
        );
    }
});

/**
 * Render only the first component matching
 */
const InjectedComponent = React.createClass({
    propTypes: {
        components:    React.PropTypes.oneOfType([
            React.PropTypes.arrayOf(React.PropTypes.func),
            React.PropTypes.instanceOf(List)
        ]).isRequired,
        props:    React.PropTypes.object,
        children: React.PropTypes.node
    },

    render() {
        const { components, props, children } = this.props;

        return components.reduce((inner, Comp) => {
            return (
                <Injection component={Comp} props={props}>
                    {inner}
                </Injection>
            );
        }, children);
    }
});

/**
 * Map Redux state to InjectedComponentSet's props
 */
function mapStateToProps(state, props) {
    const { components } = state;
    const { matching } = props;

    return {
        components: findMatchingComponents(components, matching)
    };
}

const connect = ReactRedux.connect(mapStateToProps);

module.exports = {
    InjectedComponent:    connect(InjectedComponent),
    InjectedComponentSet: connect(InjectedComponentSet)
};