77 UseDropdownMenuOptions ,
88} from 'react-overlays/DropdownMenu' ;
99import useMergedRefs from '@restart/hooks/useMergedRefs' ;
10+ import warning from 'warning' ;
1011import NavbarContext from './NavbarContext' ;
1112import { useBootstrapPrefix } from './ThemeProvider' ;
1213import useWrappedRefWithWarning from './useWrappedRefWithWarning' ;
@@ -17,10 +18,21 @@ import {
1718 SelectCallback ,
1819} from './helpers' ;
1920
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+
2031export interface DropdownMenuProps extends BsPrefixPropsWithChildren {
2132 show ?: boolean ;
2233 renderOnMount ?: boolean ;
2334 flip ?: boolean ;
35+ align ?: AlignType ;
2436 alignRight ?: boolean ;
2537 onSelect ?: SelectCallback ;
2638 rootCloseEvent ?: 'click' | 'mousedown' ;
@@ -29,6 +41,16 @@ export interface DropdownMenuProps extends BsPrefixPropsWithChildren {
2941
3042type DropdownMenu = BsPrefixRefForwardingComponent < 'div' , DropdownMenuProps > ;
3143
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+
3254const propTypes = {
3355 /**
3456 * @default 'dropdown-menu'
@@ -44,7 +66,22 @@ const propTypes = {
4466 /** Have the dropdown switch to it's opposite placement when necessary to stay on screen. */
4567 flip : PropTypes . bool ,
4668
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+ */
4885 alignRight : PropTypes . bool ,
4986
5087 onSelect : PropTypes . func ,
@@ -73,7 +110,8 @@ const propTypes = {
73110 popperConfig : PropTypes . object ,
74111} ;
75112
76- const defaultProps = {
113+ const defaultProps : Partial < DropdownMenuProps > = {
114+ align : 'left' ,
77115 alignRight : false ,
78116 flip : true ,
79117} ;
@@ -86,6 +124,9 @@ const DropdownMenu: DropdownMenu = React.forwardRef(
86124 {
87125 bsPrefix,
88126 className,
127+ align,
128+ // When we remove alignRight from API, use the var locally to toggle
129+ // .dropdown-menu-right class below.
89130 alignRight,
90131 rootCloseEvent,
91132 flip,
@@ -102,6 +143,31 @@ const DropdownMenu: DropdownMenu = React.forwardRef(
102143 const prefix = useBootstrapPrefix ( bsPrefix , 'dropdown-menu' ) ;
103144 const [ popperRef , marginModifiers ] = usePopperMarginModifiers ( ) ;
104145
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+
105171 const {
106172 hasShown,
107173 placement,
@@ -114,7 +180,7 @@ const DropdownMenu: DropdownMenu = React.forwardRef(
114180 rootCloseEvent,
115181 show : showProps ,
116182 alignEnd : alignRight ,
117- usePopper : ! isNavbar ,
183+ usePopper : ! isNavbar && alignClasses . length === 0 ,
118184 popperConfig : {
119185 ...popperConfig ,
120186 modifiers : marginModifiers . concat ( popperConfig ?. modifiers || [ ] ) ,
@@ -137,12 +203,14 @@ const DropdownMenu: DropdownMenu = React.forwardRef(
137203 ( menuProps as any ) . close = close ;
138204 ( menuProps as any ) . alignRight = alignEnd ;
139205 }
206+
140207 if ( placement ) {
141208 // we don't need the default popper style,
142209 // menus are display: none when not shown.
143210 ( props as any ) . style = { ...( props as any ) . style , ...menuProps . style } ;
144211 props [ 'x-placement' ] = placement ;
145212 }
213+
146214 return (
147215 < Component
148216 { ...props }
@@ -152,6 +220,7 @@ const DropdownMenu: DropdownMenu = React.forwardRef(
152220 prefix ,
153221 show && 'show' ,
154222 alignEnd && `${ prefix } -right` ,
223+ ...alignClasses ,
155224 ) }
156225 />
157226 ) ;
0 commit comments