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 } from 'react-popper';
|
6 | import { DropdownContext } from './DropdownContext';
|
7 | import {
|
8 | mapToCssModules,
|
9 | tagPropType,
|
10 | targetPropType,
|
11 | getTarget,
|
12 | deprecated,
|
13 | } from './utils';
|
14 |
|
15 | const propTypes = {
|
16 | tag: tagPropType,
|
17 | children: PropTypes.node.isRequired,
|
18 | dark: PropTypes.bool,
|
19 | end: PropTypes.bool,
|
20 |
|
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 |
|
30 | updateOnSelect: PropTypes.bool,
|
31 | right: deprecated(PropTypes.bool, 'Please use "end" instead.'),
|
32 | };
|
33 |
|
34 | const directionPositionMap = {
|
35 | up: 'top',
|
36 | left: 'left',
|
37 | right: 'right',
|
38 | start: 'left',
|
39 | end: 'right',
|
40 | down: 'bottom',
|
41 | };
|
42 |
|
43 | class 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 |
|
113 | ref(tagRef);
|
114 |
|
115 |
|
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 |
|
159 | DropdownMenu.propTypes = propTypes;
|
160 | DropdownMenu.contextType = DropdownContext;
|
161 |
|
162 | export default DropdownMenu;
|