import { arrow, autoUpdate, flip, offset as offsetMiddleware, OffsetOptions, Placement, ReferenceType, shift, size, useFloating, } from '@floating-ui/react-dom'; import {CSSProperties, Ref, useMemo, useRef} from 'react'; import {mergeRefs} from 'react-merge-refs'; import {UseFloatingOptions} from '@floating-ui/react-dom/src/types'; interface Props { floatingWidth?: 'auto' | 'matchTrigger'; ref?: Ref; disablePositioning?: boolean; placement?: Placement; offset?: OffsetOptions; showArrow?: boolean; maxHeight?: number; shiftCrossAxis?: boolean; fallbackPlacements?: Placement[]; } export function useFloatingPosition({ floatingWidth, ref, disablePositioning = false, placement = 'bottom', offset = 2, showArrow = false, maxHeight, shiftCrossAxis = true, fallbackPlacements, }: Props) { const arrowRef = useRef(null); const floatingConfig: UseFloatingOptions = {placement, strategy: 'fixed'}; if (!disablePositioning) { floatingConfig.whileElementsMounted = autoUpdate; floatingConfig.middleware = [ offsetMiddleware(offset), shift({padding: 16, crossAxis: shiftCrossAxis, mainAxis: true}), flip({ padding: 16, fallbackPlacements, }), size({ apply({rects, availableHeight, availableWidth, elements}) { if (floatingWidth === 'matchTrigger' && maxHeight != null) { Object.assign(elements.floating.style, { width: `${rects.reference.width}px`, maxWidth: `${availableWidth}`, maxHeight: `${Math.min(availableHeight, maxHeight)}px`, }); } else if (maxHeight != null) { Object.assign(elements.floating.style, { maxHeight: `${Math.min(availableHeight, maxHeight)}px`, }); } }, padding: 16, }), ]; if (showArrow) { floatingConfig.middleware.push(arrow({element: arrowRef})); } } const floatingProps = useFloating(floatingConfig); const mergedReferenceRef = useMemo( () => mergeRefs([ref!, floatingProps.refs.setReference]), [floatingProps.refs.setReference, ref] ); const {x: arrowX, y: arrowY} = floatingProps.middlewareData.arrow || {}; const staticSide = { top: 'bottom', right: 'left', bottom: 'top', left: 'right', }[floatingProps.placement.split('-')[0]]!; const arrowStyle: CSSProperties = { left: arrowX, top: arrowY, right: '', bottom: '', [staticSide]: '-4px', }; return { ...floatingProps, reference: mergedReferenceRef, arrowRef, arrowStyle, }; }