diff --git a/services/web/frontend/js/shared/components/dropdown/dropdown-menu.tsx b/services/web/frontend/js/shared/components/dropdown/dropdown-menu.tsx
index 291a2ac07d..c978085ff0 100644
--- a/services/web/frontend/js/shared/components/dropdown/dropdown-menu.tsx
+++ b/services/web/frontend/js/shared/components/dropdown/dropdown-menu.tsx
@@ -1,4 +1,12 @@
-import React, { forwardRef } from 'react'
+import React, {
+ createContext,
+ forwardRef,
+ useCallback,
+ useContext,
+ useLayoutEffect,
+ useMemo,
+ useState,
+} from 'react'
import {
Dropdown as BS5Dropdown,
DropdownToggle as BS5DropdownToggle,
@@ -20,8 +28,34 @@ import MaterialIcon from '@/shared/components/material-icon'
import { fixedForwardRef } from '@/utils/react'
import classnames from 'classnames'
-export function Dropdown({ ...props }: DropdownProps) {
- return
+// Lets DropdownToggle register its presence with the parent Dropdown so that
+// DropdownMenu can know whether a toggle exists (and therefore whether Popper
+// will run to position the menu).
+type DropdownInternalContextValue = {
+ registerToggle: () => () => void
+ hasToggle: boolean
+}
+const DropdownInternalContext =
+ createContext(null)
+
+export function Dropdown({ children, ...props }: DropdownProps) {
+ const [toggleCount, setToggleCount] = useState(0)
+
+ const registerToggle = useCallback(() => {
+ setToggleCount(c => c + 1)
+ return () => setToggleCount(c => c - 1)
+ }, [])
+
+ const value = useMemo(
+ () => ({ registerToggle, hasToggle: toggleCount > 0 }),
+ [registerToggle, toggleCount]
+ )
+
+ return (
+
+ {children}
+
+ )
}
function DropdownItem(
@@ -124,14 +158,32 @@ DropdownToggleCustom.displayName = 'DropdownToggleCustom'
export const DropdownToggle = forwardRef<
typeof BS5DropdownToggle,
DropdownToggleProps
->((props, ref) => )
+>((props, ref) => {
+ const registerToggle = useContext(DropdownInternalContext)?.registerToggle
+
+ useLayoutEffect(() => registerToggle?.(), [registerToggle])
+
+ return
+})
DropdownToggle.displayName = 'DropdownToggle'
export const DropdownMenu = forwardRef<
typeof BS5DropdownMenu,
DropdownMenuProps
->(({ as = 'ul', ...props }, ref) => {
- return
+>(({ as = 'ul', className, ...props }, ref) => {
+ const context = useContext(DropdownInternalContext)
+
+ return (
+
+ )
})
DropdownMenu.displayName = 'DropdownMenu'
diff --git a/services/web/frontend/stylesheets/components/dropdown-menu.scss b/services/web/frontend/stylesheets/components/dropdown-menu.scss
index aa2161375a..c5ba8d387e 100644
--- a/services/web/frontend/stylesheets/components/dropdown-menu.scss
+++ b/services/web/frontend/stylesheets/components/dropdown-menu.scss
@@ -103,6 +103,11 @@ $dropdown-item-min-height: 36px;
.subdued {
color: var(--content-disabled);
}
+
+ &.show.dropdown-menu-popper:not([data-popper-placement], [data-bs-popper]) {
+ // Prevents flickering before Popper positions the menu
+ visibility: hidden;
+ }
}
.dropdown-item {