UNPKG

7.86 kBJavaScriptView Raw
1import _extends from "@babel/runtime/helpers/esm/extends";
2import _objectWithoutPropertiesLoose from "@babel/runtime/helpers/esm/objectWithoutPropertiesLoose";
3import PropTypes from 'prop-types';
4import React, { useState } from 'react';
5import ReactDOM from 'react-dom';
6import useCallbackRef from '@restart/hooks/useCallbackRef';
7import useMergedRefs from '@restart/hooks/useMergedRefs';
8import { placements } from './popper';
9import usePopper, { toModifierMap } from './usePopper';
10import useRootClose from './useRootClose';
11import useWaitForDOMRef from './useWaitForDOMRef';
12
13/**
14 * Built on top of `Popper.js`, the overlay component is
15 * great for custom tooltip overlays.
16 */
17var Overlay = React.forwardRef(function (props, outerRef) {
18 var _modifiers$preventOve, _modifiers$arrow;
19
20 var flip = props.flip,
21 placement = props.placement,
22 _props$containerPaddi = props.containerPadding,
23 containerPadding = _props$containerPaddi === void 0 ? 5 : _props$containerPaddi,
24 _props$popperConfig = props.popperConfig,
25 popperConfig = _props$popperConfig === void 0 ? {} : _props$popperConfig,
26 Transition = props.transition;
27
28 var _useCallbackRef = useCallbackRef(),
29 rootElement = _useCallbackRef[0],
30 attachRef = _useCallbackRef[1];
31
32 var _useCallbackRef2 = useCallbackRef(),
33 arrowElement = _useCallbackRef2[0],
34 attachArrowRef = _useCallbackRef2[1];
35
36 var mergedRef = useMergedRefs(attachRef, outerRef);
37 var container = useWaitForDOMRef(props.container);
38 var target = useWaitForDOMRef(props.target);
39
40 var _useState = useState(!props.show),
41 exited = _useState[0],
42 setExited = _useState[1];
43
44 var modifiers = toModifierMap(popperConfig.modifiers);
45
46 var _usePopper = usePopper(target, rootElement, _extends({}, popperConfig, {
47 placement: placement || 'bottom',
48 modifiers: _extends({}, modifiers, {
49 eventListeners: {
50 enabled: !!props.show
51 },
52 preventOverflow: _extends({}, modifiers.preventOverflow, {
53 options: _extends({
54 padding: containerPadding || 5
55 }, (_modifiers$preventOve = modifiers.preventOverflow) == null ? void 0 : _modifiers$preventOve.options)
56 }),
57 arrow: _extends({}, modifiers.arrow, {
58 enabled: !!arrowElement,
59 options: _extends({}, (_modifiers$arrow = modifiers.arrow) == null ? void 0 : _modifiers$arrow.options, {
60 element: arrowElement
61 })
62 }),
63 flip: _extends({
64 enabled: !!flip
65 }, modifiers.flip)
66 })
67 })),
68 styles = _usePopper.styles,
69 arrowStyles = _usePopper.arrowStyles,
70 popper = _objectWithoutPropertiesLoose(_usePopper, ["styles", "arrowStyles"]);
71
72 if (props.show) {
73 if (exited) setExited(false);
74 } else if (!props.transition && !exited) {
75 setExited(true);
76 }
77
78 var handleHidden = function handleHidden() {
79 setExited(true);
80
81 if (props.onExited) {
82 props.onExited.apply(props, arguments);
83 }
84 }; // Don't un-render the overlay while it's transitioning out.
85
86
87 var mountOverlay = props.show || Transition && !exited;
88 useRootClose(rootElement, props.onHide, {
89 disabled: !props.rootClose || props.rootCloseDisabled,
90 clickTrigger: props.rootCloseEvent
91 });
92
93 if (!mountOverlay) {
94 // Don't bother showing anything if we don't have to.
95 return null;
96 }
97
98 var child = props.children(_extends({}, popper, {
99 show: !!props.show,
100 props: {
101 style: styles,
102 ref: mergedRef
103 },
104 arrowProps: {
105 style: arrowStyles,
106 ref: attachArrowRef
107 }
108 }));
109
110 if (Transition) {
111 var onExit = props.onExit,
112 onExiting = props.onExiting,
113 onEnter = props.onEnter,
114 onEntering = props.onEntering,
115 onEntered = props.onEntered;
116 child = /*#__PURE__*/React.createElement(Transition, {
117 "in": props.show,
118 appear: true,
119 onExit: onExit,
120 onExiting: onExiting,
121 onExited: handleHidden,
122 onEnter: onEnter,
123 onEntering: onEntering,
124 onEntered: onEntered
125 }, child);
126 }
127
128 return container ? ReactDOM.createPortal(child, container) : null;
129});
130Overlay.displayName = 'Overlay';
131Overlay.propTypes = {
132 /**
133 * Set the visibility of the Overlay
134 */
135 show: PropTypes.bool,
136
137 /** Specify where the overlay element is positioned in relation to the target element */
138 placement: PropTypes.oneOf(placements),
139
140 /**
141 * A DOM Element, Ref to an element, or function that returns either. The `target` element is where
142 * the overlay is positioned relative to.
143 */
144 target: PropTypes.any,
145
146 /**
147 * A DOM Element, Ref to an element, or function that returns either. The `container` will have the Portal children
148 * appended to it.
149 */
150 container: PropTypes.any,
151
152 /**
153 * Enables the Popper.js `flip` modifier, allowing the Overlay to
154 * automatically adjust it's placement in case of overlap with the viewport or toggle.
155 * Refer to the [flip docs](https://popper.js.org/popper-documentation.html#modifiers..flip.enabled) for more info
156 */
157 flip: PropTypes.bool,
158
159 /**
160 * A render prop that returns an element to overlay and position. See
161 * the [react-popper documentation](https://github.com/FezVrasta/react-popper#children) for more info.
162 *
163 * @type {Function ({
164 * show: boolean,
165 * placement: Placement,
166 * outOfBoundaries: ?boolean,
167 * scheduleUpdate: () => void,
168 * props: {
169 * ref: (?HTMLElement) => void,
170 * style: { [string]: string | number },
171 * aria-labelledby: ?string
172 * },
173 * arrowProps: {
174 * ref: (?HTMLElement) => void,
175 * style: { [string]: string | number },
176 * },
177 * }) => React.Element}
178 */
179 children: PropTypes.func.isRequired,
180
181 /**
182 * Control how much space there is between the edge of the boundary element and overlay.
183 * A convenience shortcut to setting `popperConfig.modfiers.preventOverflow.padding`
184 */
185 containerPadding: PropTypes.number,
186
187 /**
188 * A set of popper options and props passed directly to react-popper's Popper component.
189 */
190 popperConfig: PropTypes.object,
191
192 /**
193 * Specify whether the overlay should trigger `onHide` when the user clicks outside the overlay
194 */
195 rootClose: PropTypes.bool,
196
197 /**
198 * Specify event for toggling overlay
199 */
200 rootCloseEvent: PropTypes.oneOf(['click', 'mousedown']),
201
202 /**
203 * Specify disabled for disable RootCloseWrapper
204 */
205 rootCloseDisabled: PropTypes.bool,
206
207 /**
208 * A Callback fired by the Overlay when it wishes to be hidden.
209 *
210 * __required__ when `rootClose` is `true`.
211 *
212 * @type func
213 */
214 onHide: function onHide(props) {
215 for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
216 args[_key - 1] = arguments[_key];
217 }
218
219 if (props.rootClose) {
220 var _PropTypes$func;
221
222 return (_PropTypes$func = PropTypes.func).isRequired.apply(_PropTypes$func, [props].concat(args));
223 }
224
225 return PropTypes.func.apply(PropTypes, [props].concat(args));
226 },
227
228 /**
229 * A `react-transition-group@2.0.0` `<Transition/>` component
230 * used to animate the overlay as it changes visibility.
231 */
232 // @ts-ignore
233 transition: PropTypes.elementType,
234
235 /**
236 * Callback fired before the Overlay transitions in
237 */
238 onEnter: PropTypes.func,
239
240 /**
241 * Callback fired as the Overlay begins to transition in
242 */
243 onEntering: PropTypes.func,
244
245 /**
246 * Callback fired after the Overlay finishes transitioning in
247 */
248 onEntered: PropTypes.func,
249
250 /**
251 * Callback fired right before the Overlay transitions out
252 */
253 onExit: PropTypes.func,
254
255 /**
256 * Callback fired as the Overlay begins to transition out
257 */
258 onExiting: PropTypes.func,
259
260 /**
261 * Callback fired after the Overlay finishes transitioning out
262 */
263 onExited: PropTypes.func
264};
265export default Overlay;
\No newline at end of file