UNPKG

6.39 kBJavaScriptView Raw
1import _objectWithoutPropertiesLoose from "@babel/runtime/helpers/esm/objectWithoutPropertiesLoose";
2import _extends from "@babel/runtime/helpers/esm/extends";
3import PropTypes from 'prop-types';
4import React, { useContext, useRef } from 'react';
5import useCallbackRef from '@restart/hooks/useCallbackRef';
6import DropdownContext from './DropdownContext';
7import usePopper, { toModifierMap } from './usePopper';
8import useRootClose from './useRootClose';
9
10var noop = function noop() {};
11/**
12 * @memberOf Dropdown
13 * @param {object} options
14 * @param {boolean} options.flip Automatically adjust the menu `drop` position based on viewport edge detection
15 * @param {boolean} options.show Display the menu manually, ignored in the context of a `Dropdown`
16 * @param {boolean} options.usePopper opt in/out of using PopperJS to position menus. When disabled you must position it yourself.
17 * @param {string} options.rootCloseEvent The pointer event to listen for when determining "clicks outside" the menu for triggering a close.
18 * @param {object} options.popperConfig Options passed to the [`usePopper`](/api/usePopper) hook.
19 */
20
21
22export function useDropdownMenu(options) {
23 var _modifiers$arrow;
24
25 if (options === void 0) {
26 options = {};
27 }
28
29 var context = useContext(DropdownContext);
30
31 var _useCallbackRef = useCallbackRef(),
32 arrowElement = _useCallbackRef[0],
33 attachArrowRef = _useCallbackRef[1];
34
35 var hasShownRef = useRef(false);
36 var _options = options,
37 flip = _options.flip,
38 rootCloseEvent = _options.rootCloseEvent,
39 _options$popperConfig = _options.popperConfig,
40 popperConfig = _options$popperConfig === void 0 ? {} : _options$popperConfig,
41 _options$usePopper = _options.usePopper,
42 shouldUsePopper = _options$usePopper === void 0 ? !!context : _options$usePopper;
43 var show = (context == null ? void 0 : context.show) == null ? options.show : context.show;
44 var alignEnd = (context == null ? void 0 : context.alignEnd) == null ? options.alignEnd : context.alignEnd;
45
46 if (show && !hasShownRef.current) {
47 hasShownRef.current = true;
48 }
49
50 var handleClose = function handleClose(e) {
51 context == null ? void 0 : context.toggle(false, e);
52 };
53
54 var _ref = context || {},
55 drop = _ref.drop,
56 setMenu = _ref.setMenu,
57 menuElement = _ref.menuElement,
58 toggleElement = _ref.toggleElement;
59
60 var placement = alignEnd ? 'bottom-end' : 'bottom-start';
61 if (drop === 'up') placement = alignEnd ? 'top-end' : 'top-start';else if (drop === 'right') placement = alignEnd ? 'right-end' : 'right-start';else if (drop === 'left') placement = alignEnd ? 'left-end' : 'left-start';
62 var modifiers = toModifierMap(popperConfig.modifiers);
63 var popper = usePopper(toggleElement, menuElement, _extends({}, popperConfig, {
64 placement: placement,
65 enabled: !!(shouldUsePopper && show),
66 modifiers: _extends({}, modifiers, {
67 eventListeners: {
68 enabled: !!show
69 },
70 arrow: _extends({}, modifiers.arrow, {
71 enabled: !!arrowElement,
72 options: _extends({}, (_modifiers$arrow = modifiers.arrow) == null ? void 0 : _modifiers$arrow.options, {
73 element: arrowElement
74 })
75 }),
76 flip: _extends({
77 enabled: !!flip
78 }, modifiers.flip)
79 })
80 }));
81 var menu;
82 var menuProps = {
83 ref: setMenu || noop,
84 'aria-labelledby': toggleElement == null ? void 0 : toggleElement.id
85 };
86 var childArgs = {
87 show: show,
88 alignEnd: alignEnd,
89 hasShown: hasShownRef.current,
90 close: handleClose
91 };
92
93 if (!shouldUsePopper) {
94 menu = _extends({}, childArgs, {
95 props: menuProps
96 });
97 } else {
98 menu = _extends({}, popper, {}, childArgs, {
99 props: _extends({}, menuProps, {
100 style: popper.styles
101 }),
102 arrowProps: {
103 ref: attachArrowRef,
104 style: popper.arrowStyles
105 }
106 });
107 }
108
109 useRootClose(menuElement, handleClose, {
110 clickTrigger: rootCloseEvent,
111 disabled: !(menu && show)
112 });
113 return menu;
114}
115var propTypes = {
116 /**
117 * A render prop that returns a Menu element. The `props`
118 * argument should spread through to **a component that can accept a ref**.
119 *
120 * @type {Function ({
121 * show: boolean,
122 * alignEnd: boolean,
123 * close: (?SyntheticEvent) => void,
124 * placement: Placement,
125 * outOfBoundaries: ?boolean,
126 * scheduleUpdate: () => void,
127 * props: {
128 * ref: (?HTMLElement) => void,
129 * style: { [string]: string | number },
130 * aria-labelledby: ?string
131 * },
132 * arrowProps: {
133 * ref: (?HTMLElement) => void,
134 * style: { [string]: string | number },
135 * },
136 * }) => React.Element}
137 */
138 children: PropTypes.func.isRequired,
139
140 /**
141 * Controls the visible state of the menu, generally this is
142 * provided by the parent `Dropdown` component,
143 * but may also be specified as a prop directly.
144 */
145 show: PropTypes.bool,
146
147 /**
148 * Aligns the dropdown menu to the 'end' of it's placement position.
149 * Generally this is provided by the parent `Dropdown` component,
150 * but may also be specified as a prop directly.
151 */
152 alignEnd: PropTypes.bool,
153
154 /**
155 * Enables the Popper.js `flip` modifier, allowing the Dropdown to
156 * automatically adjust it's placement in case of overlap with the viewport or toggle.
157 * Refer to the [flip docs](https://popper.js.org/popper-documentation.html#modifiers..flip.enabled) for more info
158 */
159 flip: PropTypes.bool,
160 usePopper: PropTypes.oneOf([true, false]),
161
162 /**
163 * A set of popper options and props passed directly to react-popper's Popper component.
164 */
165 popperConfig: PropTypes.object,
166
167 /**
168 * Override the default event used by RootCloseWrapper.
169 */
170 rootCloseEvent: PropTypes.string
171};
172var defaultProps = {
173 usePopper: true
174};
175
176/**
177 * Also exported as `<Dropdown.Menu>` from `Dropdown`.
178 *
179 * @displayName DropdownMenu
180 * @memberOf Dropdown
181 */
182function DropdownMenu(_ref2) {
183 var children = _ref2.children,
184 options = _objectWithoutPropertiesLoose(_ref2, ["children"]);
185
186 var args = useDropdownMenu(options);
187 return /*#__PURE__*/React.createElement(React.Fragment, null, args.hasShown ? children(args) : null);
188}
189
190DropdownMenu.displayName = 'ReactOverlaysDropdownMenu';
191DropdownMenu.propTypes = propTypes;
192DropdownMenu.defaultProps = defaultProps;
193/** @component */
194
195export default DropdownMenu;
\No newline at end of file