UNPKG

4.11 kBJavaScriptView Raw
1import React from 'react';
2import PropTypes from 'prop-types';
3import ReactDOM from 'react-dom';
4import classNames from 'classnames';
5import { Popper } from 'react-popper';
6import { DropdownContext } from './DropdownContext';
7import {
8 mapToCssModules,
9 tagPropType,
10 targetPropType,
11 getTarget,
12 deprecated,
13} from './utils';
14
15const propTypes = {
16 tag: tagPropType,
17 children: PropTypes.node.isRequired,
18 dark: PropTypes.bool,
19 end: PropTypes.bool,
20 /** Flips the menu to the opposite side if there is not enough space to fit */
21 flip: PropTypes.bool,
22 modifiers: PropTypes.array,
23 className: PropTypes.string,
24 cssModule: PropTypes.object,
25 style: PropTypes.object,
26 persist: PropTypes.bool,
27 strategy: PropTypes.string,
28 container: targetPropType,
29 /** Update popper layout when a click event comes up. This leverages event bubbling. */
30 updateOnSelect: PropTypes.bool,
31 right: deprecated(PropTypes.bool, 'Please use "end" instead.'),
32};
33
34const directionPositionMap = {
35 up: 'top',
36 left: 'left',
37 right: 'right',
38 start: 'left',
39 end: 'right',
40 down: 'bottom',
41};
42
43class DropdownMenu extends React.Component {
44 getRole() {
45 if (this.context.menuRole === 'listbox') {
46 return 'listbox';
47 }
48 return 'menu';
49 }
50
51 render() {
52 const {
53 className,
54 cssModule,
55 dark,
56 end,
57 right,
58 tag = 'div',
59 flip = true,
60 modifiers = [],
61 persist,
62 strategy,
63 container,
64 updateOnSelect,
65 ...attrs
66 } = this.props;
67
68 const classes = mapToCssModules(
69 classNames(className, 'dropdown-menu', {
70 'dropdown-menu-dark': dark,
71 'dropdown-menu-end': end || right,
72 show: this.context.isOpen,
73 }),
74 cssModule,
75 );
76
77 const Tag = tag;
78
79 if (persist || (this.context.isOpen && !this.context.inNavbar)) {
80 const position1 =
81 directionPositionMap[this.context.direction] || 'bottom';
82 const position2 = end || right ? 'end' : 'start';
83 const poperPlacement = `${position1}-${position2}`;
84 const poperModifiers = [
85 ...modifiers,
86 {
87 name: 'flip',
88 enabled: !!flip,
89 },
90 ];
91
92 const persistStyles = {};
93 if (persist) {
94 persistStyles.display = 'block';
95 persistStyles.visibility = this.context.isOpen ? 'visible' : 'hidden';
96 }
97
98 const popper = (
99 <Popper
100 placement={poperPlacement}
101 modifiers={poperModifiers}
102 strategy={strategy}
103 >
104 {({ ref, style, placement, update }) => {
105 let combinedStyle = {
106 ...this.props.style,
107 ...persistStyles,
108 ...style,
109 };
110
111 const handleRef = (tagRef) => {
112 // Send the ref to `react-popper`
113 ref(tagRef);
114 // Send the ref to the parent Dropdown so that clicks outside
115 // it will cause it to close
116 const { onMenuRef } = this.context;
117 if (onMenuRef) onMenuRef(tagRef);
118 };
119
120 return (
121 <Tag
122 tabIndex="-1"
123 role={this.getRole()}
124 ref={handleRef}
125 {...attrs}
126 style={combinedStyle}
127 aria-hidden={!this.context.isOpen}
128 className={classes}
129 data-popper-placement={placement}
130 onClick={() => updateOnSelect && update()}
131 />
132 );
133 }}
134 </Popper>
135 );
136
137 if (container) {
138 return ReactDOM.createPortal(popper, getTarget(container));
139 }
140 return popper;
141 }
142 const { onMenuRef } = this.context;
143
144 return (
145 <Tag
146 tabIndex="-1"
147 role={this.getRole()}
148 {...attrs}
149 ref={onMenuRef}
150 aria-hidden={!this.context.isOpen}
151 className={classes}
152 data-popper-placement={attrs.placement}
153 data-bs-popper="static"
154 />
155 );
156 }
157}
158
159DropdownMenu.propTypes = propTypes;
160DropdownMenu.contextType = DropdownContext;
161
162export default DropdownMenu;