UNPKG

5.57 kBJavaScriptView Raw
1import React from 'react';
2import PropTypes from 'prop-types';
3import ReactDOM from 'react-dom';
4import classNames from 'classnames';
5import { Popper as ReactPopper } from 'react-popper';
6import {
7 getTarget,
8 targetPropType,
9 mapToCssModules,
10 DOMElement,
11 tagPropType,
12} from './utils';
13import Fade from './Fade';
14
15function noop() {}
16
17const 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
40const 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
56class 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
235PopperContent.propTypes = propTypes;
236PopperContent.defaultProps = defaultProps;
237
238export default PopperContent;