7
7
UseDropdownMenuOptions ,
8
8
} from 'react-overlays/DropdownMenu' ;
9
9
import useMergedRefs from '@restart/hooks/useMergedRefs' ;
10
+ import warning from 'warning' ;
10
11
import NavbarContext from './NavbarContext' ;
11
12
import { useBootstrapPrefix } from './ThemeProvider' ;
12
13
import useWrappedRefWithWarning from './useWrappedRefWithWarning' ;
@@ -17,10 +18,21 @@ import {
17
18
SelectCallback ,
18
19
} from './helpers' ;
19
20
21
+ export type AlignDirection = 'left' | 'right' ;
22
+
23
+ export type ResponsiveAlignProp =
24
+ | { sm : AlignDirection }
25
+ | { md : AlignDirection }
26
+ | { lg : AlignDirection }
27
+ | { xl : AlignDirection } ;
28
+
29
+ export type AlignType = AlignDirection | ResponsiveAlignProp ;
30
+
20
31
export interface DropdownMenuProps extends BsPrefixPropsWithChildren {
21
32
show ?: boolean ;
22
33
renderOnMount ?: boolean ;
23
34
flip ?: boolean ;
35
+ align ?: AlignType ;
24
36
alignRight ?: boolean ;
25
37
onSelect ?: SelectCallback ;
26
38
rootCloseEvent ?: 'click' | 'mousedown' ;
@@ -29,6 +41,16 @@ export interface DropdownMenuProps extends BsPrefixPropsWithChildren {
29
41
30
42
type DropdownMenu = BsPrefixRefForwardingComponent < 'div' , DropdownMenuProps > ;
31
43
44
+ const alignDirection = PropTypes . oneOf ( [ 'left' , 'right' ] ) ;
45
+
46
+ export const alignPropType = PropTypes . oneOfType ( [
47
+ alignDirection ,
48
+ PropTypes . shape ( { sm : alignDirection } ) ,
49
+ PropTypes . shape ( { md : alignDirection } ) ,
50
+ PropTypes . shape ( { lg : alignDirection } ) ,
51
+ PropTypes . shape ( { xl : alignDirection } ) ,
52
+ ] ) ;
53
+
32
54
const propTypes = {
33
55
/**
34
56
* @default 'dropdown-menu'
@@ -44,7 +66,22 @@ const propTypes = {
44
66
/** Have the dropdown switch to it's opposite placement when necessary to stay on screen. */
45
67
flip : PropTypes . bool ,
46
68
47
- /** Aligns the Dropdown menu to the right of it's container. */
69
+ /**
70
+ * Aligns the dropdown menu to the specified side of the container. You can also align
71
+ * the menu responsively for breakpoints starting at `sm` and up. The alignment
72
+ * direction will affect the specified breakpoint or larger.
73
+ *
74
+ * *Note: Using responsive alignment will disable Popper usage for positioning.*
75
+ *
76
+ * @type {"left"|"right"|{ sm: "left"|"right" }|{ md: "left"|"right" }|{ lg: "left"|"right" }|{ xl: "left"|"right"} }
77
+ */
78
+ align : alignPropType ,
79
+
80
+ /**
81
+ * Aligns the Dropdown menu to the right of it's container.
82
+ *
83
+ * @deprecated Use align="right"
84
+ */
48
85
alignRight : PropTypes . bool ,
49
86
50
87
onSelect : PropTypes . func ,
@@ -73,7 +110,8 @@ const propTypes = {
73
110
popperConfig : PropTypes . object ,
74
111
} ;
75
112
76
- const defaultProps = {
113
+ const defaultProps : Partial < DropdownMenuProps > = {
114
+ align : 'left' ,
77
115
alignRight : false ,
78
116
flip : true ,
79
117
} ;
@@ -86,6 +124,9 @@ const DropdownMenu: DropdownMenu = React.forwardRef(
86
124
{
87
125
bsPrefix,
88
126
className,
127
+ align,
128
+ // When we remove alignRight from API, use the var locally to toggle
129
+ // .dropdown-menu-right class below.
89
130
alignRight,
90
131
rootCloseEvent,
91
132
flip,
@@ -102,6 +143,31 @@ const DropdownMenu: DropdownMenu = React.forwardRef(
102
143
const prefix = useBootstrapPrefix ( bsPrefix , 'dropdown-menu' ) ;
103
144
const [ popperRef , marginModifiers ] = usePopperMarginModifiers ( ) ;
104
145
146
+ const alignClasses : string [ ] = [ ] ;
147
+ if ( align ) {
148
+ if ( typeof align === 'object' ) {
149
+ const keys = Object . keys ( align ) ;
150
+
151
+ warning (
152
+ keys . length === 1 ,
153
+ 'There should only be 1 breakpoint when passing an object to `align`' ,
154
+ ) ;
155
+
156
+ if ( keys . length ) {
157
+ const brkPoint = keys [ 0 ] ;
158
+ const direction = align [ brkPoint ] ;
159
+
160
+ // .dropdown-menu-right is required for responsively aligning
161
+ // left in addition to align left classes.
162
+ // Reuse alignRight to toggle the class below.
163
+ alignRight = direction === 'left' ;
164
+ alignClasses . push ( `${ prefix } -${ brkPoint } -${ direction } ` ) ;
165
+ }
166
+ } else if ( align === 'right' ) {
167
+ alignRight = true ;
168
+ }
169
+ }
170
+
105
171
const {
106
172
hasShown,
107
173
placement,
@@ -114,7 +180,7 @@ const DropdownMenu: DropdownMenu = React.forwardRef(
114
180
rootCloseEvent,
115
181
show : showProps ,
116
182
alignEnd : alignRight ,
117
- usePopper : ! isNavbar ,
183
+ usePopper : ! isNavbar && alignClasses . length === 0 ,
118
184
popperConfig : {
119
185
...popperConfig ,
120
186
modifiers : marginModifiers . concat ( popperConfig ?. modifiers || [ ] ) ,
@@ -137,12 +203,14 @@ const DropdownMenu: DropdownMenu = React.forwardRef(
137
203
( menuProps as any ) . close = close ;
138
204
( menuProps as any ) . alignRight = alignEnd ;
139
205
}
206
+
140
207
if ( placement ) {
141
208
// we don't need the default popper style,
142
209
// menus are display: none when not shown.
143
210
( props as any ) . style = { ...( props as any ) . style , ...menuProps . style } ;
144
211
props [ 'x-placement' ] = placement ;
145
212
}
213
+
146
214
return (
147
215
< Component
148
216
{ ...props }
@@ -152,6 +220,7 @@ const DropdownMenu: DropdownMenu = React.forwardRef(
152
220
prefix ,
153
221
show && 'show' ,
154
222
alignEnd && `${ prefix } -right` ,
223
+ ...alignClasses ,
155
224
) }
156
225
/>
157
226
) ;
0 commit comments