import React, { createContext, forwardRef, useCallback, useContext, useLayoutEffect, useMemo, useState, } from 'react' import { Dropdown as BS5Dropdown, DropdownToggle as BS5DropdownToggle, DropdownMenu as BS5DropdownMenu, DropdownItem as BS5DropdownItem, DropdownDivider as BS5DropdownDivider, DropdownHeader as BS5DropdownHeader, Button as BS5Button, } from 'react-bootstrap' import type { DropdownProps, DropdownItemProps, DropdownToggleProps, DropdownMenuProps, DropdownDividerProps, DropdownHeaderProps, } from '@/shared/components/types/dropdown-menu-props' import MaterialIcon from '@/shared/components/material-icon' import { fixedForwardRef } from '@/utils/react' import classnames from 'classnames' // 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( { active, children, className, description, leadingIcon, trailingIcon, ...props }: DropdownItemProps, ref: React.ForwardedRef ) { let leadingIconComponent = null if (leadingIcon) { if (typeof leadingIcon === 'string') { leadingIconComponent = ( ) } else { leadingIconComponent = ( ) } } let trailingIconComponent = null if (trailingIcon) { if (typeof trailingIcon === 'string') { const trailingIconType = active ? 'check' : trailingIcon trailingIconComponent = ( ) } else { trailingIconComponent = ( ) } } return ( {leadingIconComponent} {description ? ( {children} {description} ) : ( children )} {trailingIconComponent} ) } function EmptyLeadingIcon() { return } const ForwardReferredDropdownItem = fixedForwardRef(DropdownItem, { EmptyLeadingIcon, }) export { ForwardReferredDropdownItem as DropdownItem } export const DropdownToggleCustom = forwardRef< HTMLButtonElement, React.ComponentProps >(({ children, className, ...props }, ref) => ( {children} )) DropdownToggleCustom.displayName = 'DropdownToggleCustom' export const DropdownToggle = forwardRef< typeof BS5DropdownToggle, DropdownToggleProps >((props, ref) => { const registerToggle = useContext(DropdownInternalContext)?.registerToggle useLayoutEffect(() => registerToggle?.(), [registerToggle]) return }) DropdownToggle.displayName = 'DropdownToggle' export const DropdownMenu = forwardRef< typeof BS5DropdownMenu, DropdownMenuProps >(({ as = 'ul', className, ...props }, ref) => { const context = useContext(DropdownInternalContext) return ( ) }) DropdownMenu.displayName = 'DropdownMenu' export function DropdownDivider({ as = 'li', ...props }: DropdownDividerProps) { return } export function DropdownHeader({ as = 'li', ...props }: DropdownHeaderProps) { return }