1 | import React from 'react';
|
2 | import PropTypes from 'prop-types';
|
3 | import ReactDOM from 'react-dom';
|
4 | import classNames from 'classnames';
|
5 | import { Popper as ReactPopper } from 'react-popper';
|
6 | import {
|
7 | getTarget,
|
8 | targetPropType,
|
9 | mapToCssModules,
|
10 | DOMElement,
|
11 | tagPropType,
|
12 | } from './utils';
|
13 | import Fade from './Fade';
|
14 |
|
15 | function noop() {}
|
16 |
|
17 | const propTypes = {
|
18 | children: PropTypes.oneOfType([PropTypes.node, PropTypes.func]).isRequired,
|
19 | popperClassName: PropTypes.string,
|
20 | placement: PropTypes.string,
|
21 | placementPrefix: PropTypes.string,
|
22 | arrowClassName: PropTypes.string,
|
23 | hideArrow: PropTypes.bool,
|
24 | tag: tagPropType,
|
25 | isOpen: PropTypes.bool,
|
26 | cssModule: PropTypes.object,
|
27 | offset: PropTypes.arrayOf(PropTypes.number),
|
28 | fallbackPlacements: PropTypes.array,
|
29 | flip: PropTypes.bool,
|
30 | container: targetPropType,
|
31 | target: targetPropType.isRequired,
|
32 | modifiers: PropTypes.array,
|
33 | strategy: PropTypes.string,
|
34 | boundariesElement: PropTypes.oneOfType([PropTypes.string, DOMElement]),
|
35 | onClosed: PropTypes.func,
|
36 | fade: PropTypes.bool,
|
37 | transition: PropTypes.shape(Fade.propTypes),
|
38 | };
|
39 |
|
40 | const defaultProps = {
|
41 | boundariesElement: 'scrollParent',
|
42 | placement: 'auto',
|
43 | hideArrow: false,
|
44 | isOpen: false,
|
45 | offset: [0, 0],
|
46 | flip: true,
|
47 | container: 'body',
|
48 | modifiers: [],
|
49 | onClosed: noop,
|
50 | fade: true,
|
51 | transition: {
|
52 | ...Fade.defaultProps,
|
53 | },
|
54 | };
|
55 |
|
56 | class PopperContent extends React.Component {
|
57 | constructor(props) {
|
58 | super(props);
|
59 |
|
60 | this.setTargetNode = this.setTargetNode.bind(this);
|
61 | this.getTargetNode = this.getTargetNode.bind(this);
|
62 | this.getRef = this.getRef.bind(this);
|
63 | this.onClosed = this.onClosed.bind(this);
|
64 | this.state = { isOpen: props.isOpen };
|
65 | }
|
66 |
|
67 | static getDerivedStateFromProps(props, state) {
|
68 | if (props.isOpen && !state.isOpen) {
|
69 | return { isOpen: props.isOpen };
|
70 | }
|
71 | return null;
|
72 | }
|
73 |
|
74 | componentDidUpdate() {
|
75 | if (
|
76 | this._element &&
|
77 | this._element.childNodes &&
|
78 | this._element.childNodes[0] &&
|
79 | this._element.childNodes[0].focus
|
80 | ) {
|
81 | this._element.childNodes[0].focus();
|
82 | }
|
83 | }
|
84 |
|
85 | onClosed() {
|
86 | this.props.onClosed();
|
87 | this.setState({ isOpen: false });
|
88 | }
|
89 |
|
90 | getTargetNode() {
|
91 | return this.targetNode;
|
92 | }
|
93 |
|
94 | getContainerNode() {
|
95 | return getTarget(this.props.container);
|
96 | }
|
97 |
|
98 | getRef(ref) {
|
99 | this._element = ref;
|
100 | }
|
101 |
|
102 | setTargetNode(node) {
|
103 | this.targetNode = typeof node === 'string' ? getTarget(node) : node;
|
104 | }
|
105 |
|
106 | renderChildren() {
|
107 | const {
|
108 | cssModule,
|
109 | children,
|
110 | isOpen,
|
111 | flip,
|
112 | target,
|
113 | offset,
|
114 | fallbackPlacements,
|
115 | placementPrefix,
|
116 | arrowClassName: _arrowClassName,
|
117 | hideArrow,
|
118 | popperClassName: _popperClassName,
|
119 | tag,
|
120 | container,
|
121 | modifiers,
|
122 | strategy,
|
123 | boundariesElement,
|
124 | onClosed,
|
125 | fade,
|
126 | transition,
|
127 | placement,
|
128 | ...attrs
|
129 | } = this.props;
|
130 | const arrowClassName = mapToCssModules(
|
131 | classNames('arrow', _arrowClassName),
|
132 | cssModule,
|
133 | );
|
134 | const popperClassName = mapToCssModules(
|
135 | classNames(
|
136 | _popperClassName,
|
137 | placementPrefix ? `${placementPrefix}-auto` : '',
|
138 | ),
|
139 | this.props.cssModule,
|
140 | );
|
141 |
|
142 | const modifierNames = modifiers.map((m) => m.name);
|
143 | const baseModifiers = [
|
144 | {
|
145 | name: 'offset',
|
146 | options: {
|
147 | offset,
|
148 | },
|
149 | },
|
150 | {
|
151 | name: 'flip',
|
152 | enabled: flip,
|
153 | options: {
|
154 | fallbackPlacements,
|
155 | },
|
156 | },
|
157 | {
|
158 | name: 'preventOverflow',
|
159 | options: {
|
160 | boundary: boundariesElement,
|
161 | },
|
162 | },
|
163 | ].filter((m) => !modifierNames.includes(m.name));
|
164 | const extendedModifiers = [...baseModifiers, ...modifiers];
|
165 |
|
166 | const popperTransition = {
|
167 | ...Fade.defaultProps,
|
168 | ...transition,
|
169 | baseClass: fade ? transition.baseClass : '',
|
170 | timeout: fade ? transition.timeout : 0,
|
171 | };
|
172 |
|
173 | return (
|
174 | <Fade
|
175 | {...popperTransition}
|
176 | {...attrs}
|
177 | in={isOpen}
|
178 | onExited={this.onClosed}
|
179 | tag={tag}
|
180 | >
|
181 | <ReactPopper
|
182 | referenceElement={this.targetNode}
|
183 | modifiers={extendedModifiers}
|
184 | placement={placement}
|
185 | strategy={strategy}
|
186 | >
|
187 | {({
|
188 | ref,
|
189 | style,
|
190 | placement: popperPlacement,
|
191 | isReferenceHidden,
|
192 | arrowProps,
|
193 | update,
|
194 | }) => (
|
195 | <div
|
196 | ref={ref}
|
197 | style={style}
|
198 | className={popperClassName}
|
199 | data-popper-placement={popperPlacement}
|
200 | data-popper-reference-hidden={
|
201 | isReferenceHidden ? 'true' : undefined
|
202 | }
|
203 | >
|
204 | {typeof children === 'function' ? children({ update }) : children}
|
205 | {!hideArrow && (
|
206 | <span
|
207 | ref={arrowProps.ref}
|
208 | className={arrowClassName}
|
209 | style={arrowProps.style}
|
210 | />
|
211 | )}
|
212 | </div>
|
213 | )}
|
214 | </ReactPopper>
|
215 | </Fade>
|
216 | );
|
217 | }
|
218 |
|
219 | render() {
|
220 | this.setTargetNode(this.props.target);
|
221 |
|
222 | if (this.state.isOpen) {
|
223 | return this.props.container === 'inline'
|
224 | ? this.renderChildren()
|
225 | : ReactDOM.createPortal(
|
226 | <div ref={this.getRef}>{this.renderChildren()}</div>,
|
227 | this.getContainerNode(),
|
228 | );
|
229 | }
|
230 |
|
231 | return null;
|
232 | }
|
233 | }
|
234 |
|
235 | PopperContent.propTypes = propTypes;
|
236 | PopperContent.defaultProps = defaultProps;
|
237 |
|
238 | export default PopperContent;
|