Merge pull request #33542 from overleaf/ii-dropdown-flicker
Fix dropdown menu flicker on open GitOrigin-RevId: ddf826b30fcac3322d86067e5950731e7dc1a2d4
This commit is contained in:
@@ -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 <BS5Dropdown {...props} />
|
||||
// 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<DropdownInternalContextValue | null>(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 (
|
||||
<DropdownInternalContext.Provider value={value}>
|
||||
<BS5Dropdown {...props}>{children}</BS5Dropdown>
|
||||
</DropdownInternalContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
function DropdownItem(
|
||||
@@ -124,14 +158,32 @@ DropdownToggleCustom.displayName = 'DropdownToggleCustom'
|
||||
export const DropdownToggle = forwardRef<
|
||||
typeof BS5DropdownToggle,
|
||||
DropdownToggleProps
|
||||
>((props, ref) => <BS5DropdownToggle {...props} ref={ref} />)
|
||||
>((props, ref) => {
|
||||
const registerToggle = useContext(DropdownInternalContext)?.registerToggle
|
||||
|
||||
useLayoutEffect(() => registerToggle?.(), [registerToggle])
|
||||
|
||||
return <BS5DropdownToggle {...props} ref={ref} />
|
||||
})
|
||||
DropdownToggle.displayName = 'DropdownToggle'
|
||||
|
||||
export const DropdownMenu = forwardRef<
|
||||
typeof BS5DropdownMenu,
|
||||
DropdownMenuProps
|
||||
>(({ as = 'ul', ...props }, ref) => {
|
||||
return <BS5DropdownMenu as={as} role="menu" {...props} ref={ref} />
|
||||
>(({ as = 'ul', className, ...props }, ref) => {
|
||||
const context = useContext(DropdownInternalContext)
|
||||
|
||||
return (
|
||||
<BS5DropdownMenu
|
||||
as={as}
|
||||
role="menu"
|
||||
className={classnames(className, {
|
||||
'dropdown-menu-popper': !!context?.hasToggle,
|
||||
})}
|
||||
{...props}
|
||||
ref={ref}
|
||||
/>
|
||||
)
|
||||
})
|
||||
DropdownMenu.displayName = 'DropdownMenu'
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user