/
DropdownMenu.tsx
165 lines (145 loc) · 4.67 KB
/
DropdownMenu.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
import classNames from 'classnames';
import PropTypes from 'prop-types';
import React, { useContext } from 'react';
import {
useDropdownMenu,
UseDropdownMenuValue,
UseDropdownMenuOptions,
} from 'react-overlays/DropdownMenu';
import useMergedRefs from '@restart/hooks/useMergedRefs';
import NavbarContext from './NavbarContext';
import { useBootstrapPrefix } from './ThemeProvider';
import useWrappedRefWithWarning from './useWrappedRefWithWarning';
import usePopperMarginModifiers from './usePopperMarginModifiers';
import {
BsPrefixPropsWithChildren,
BsPrefixRefForwardingComponent,
SelectCallback,
} from './helpers';
export interface DropdownMenuProps extends BsPrefixPropsWithChildren {
show?: boolean;
renderOnMount?: boolean;
flip?: boolean;
alignRight?: boolean;
onSelect?: SelectCallback;
rootCloseEvent?: 'click' | 'mousedown';
popperConfig?: UseDropdownMenuOptions['popperConfig'];
}
type DropdownMenu = BsPrefixRefForwardingComponent<'div', DropdownMenuProps>;
const propTypes = {
/**
* @default 'dropdown-menu'
*/
bsPrefix: PropTypes.string,
/** Controls the visibility of the Dropdown menu */
show: PropTypes.bool,
/** Whether to render the dropdown menu in the DOM before the first time it is shown */
renderOnMount: PropTypes.bool,
/** Have the dropdown switch to it's opposite placement when necessary to stay on screen. */
flip: PropTypes.bool,
/** Aligns the Dropdown menu to the right of it's container. */
alignRight: PropTypes.bool,
onSelect: PropTypes.func,
/**
* Which event when fired outside the component will cause it to be closed
*
* *Note: For custom dropdown components, you will have to pass the
* `rootCloseEvent` to `<RootCloseWrapper>` in your custom dropdown menu
* component ([similarly to how it is implemented in `<Dropdown.Menu>`](https://github.com/react-bootstrap/react-bootstrap/blob/v0.31.5/src/DropdownMenu.js#L115-L119)).*
*/
rootCloseEvent: PropTypes.oneOf(['click', 'mousedown']),
/**
* Control the rendering of the DropdownMenu. All non-menu props
* (listed here) are passed through to the `as` Component.
*
* If providing a custom, non DOM, component. the `show`, `close` and `alignRight` props
* are also injected and should be handled appropriately.
*/
as: PropTypes.elementType,
/**
* A set of popper options and props passed directly to Popper.
*/
popperConfig: PropTypes.object,
};
const defaultProps = {
alignRight: false,
flip: true,
};
// TODO: remove this hack
type UseDropdownMenuValueHack = UseDropdownMenuValue & { placement: any };
const DropdownMenu: DropdownMenu = React.forwardRef(
(
{
bsPrefix,
className,
alignRight,
rootCloseEvent,
flip,
show: showProps,
renderOnMount,
// Need to define the default "as" during prop destructuring to be compatible with styled-components github.com/react-bootstrap/react-bootstrap/issues/3595
as: Component = 'div',
popperConfig,
...props
}: DropdownMenuProps,
ref,
) => {
const isNavbar = useContext(NavbarContext);
const prefix = useBootstrapPrefix(bsPrefix, 'dropdown-menu');
const [popperRef, marginModifiers] = usePopperMarginModifiers();
const {
hasShown,
placement,
show,
alignEnd,
close,
props: menuProps,
} = useDropdownMenu({
flip,
rootCloseEvent,
show: showProps,
alignEnd: alignRight,
usePopper: !isNavbar,
popperConfig: {
...popperConfig,
modifiers: marginModifiers.concat(popperConfig?.modifiers || []),
},
}) as UseDropdownMenuValueHack;
menuProps.ref = useMergedRefs(
popperRef,
useMergedRefs(
useWrappedRefWithWarning(ref, 'DropdownMenu'),
menuProps.ref,
),
);
if (!hasShown && !renderOnMount) return null;
// For custom components provide additional, non-DOM, props;
if (typeof Component !== 'string') {
(menuProps as any).show = show;
(menuProps as any).close = close;
(menuProps as any).alignRight = alignEnd;
}
if (placement) {
// we don't need the default popper style,
// menus are display: none when not shown.
(props as any).style = { ...(props as any).style, ...menuProps.style };
props['x-placement'] = placement;
}
return (
<Component
{...props}
{...menuProps}
className={classNames(
className,
prefix,
show && 'show',
alignEnd && `${prefix}-right`,
)}
/>
);
},
);
DropdownMenu.displayName = 'DropdownMenu';
DropdownMenu.propTypes = propTypes;
DropdownMenu.defaultProps = defaultProps;
export default DropdownMenu;