import * as React from "react" ; import { createPopperLite } from "@popperjs/core" ; import useLayoutEffect from "use-isomorphic-layout-effect" ; import { fromEntries } from "@design-systems/utils" ; import type { Options as PopperOptions , Instance , State as PopperState , } from "@popperjs/core" ; import isEqual from "react-fast-compare" ; const defaultPopperOptions = { placement : "bottom" , strategy : "absolute" , modifiers : [ ] , } ; param referenceElement param popperElement param options example export const usePopper = ( referenceElement , popperElement , options = { } ) => { const prevOptions = React . useRef < PopperOptions > ( ) ; const optionsWithDefaults = { ... defaultPopperOptions , ... options , } ; const [ state , setState ] = React . useState < Pick < PopperState , "styles" | "attributes" > > ( { styles : { popper : { position : optionsWithDefaults . strategy , left : "0" , top : "0" , } , } , attributes : { } , } ) ; const updateStateModifier = React . useMemo ( ( ) => ( { name : "updateState" as const , enabled : true , phase : "write" as const , fn : ( { state : newState , } ) => { const elements = Object . keys ( newState . elements ) ; setState ( { styles : fromEntries ( elements . map ( ( element ) => [ element , newState . styles [ element ] || { } ] ) ) , attributes : fromEntries ( elements . map ( ( element ) => [ element , newState . attributes [ element ] ] ) ) , } ) ; } , requires : [ "computeStyles" ] , } ) , [ ] ) ; const popperOptions = React . useMemo ( ( ) => { const newOptions = { ... optionsWithDefaults , modifiers : [ ... optionsWithDefaults . modifiers , updateStateModifier , { name : "applyStyles" , enabled : false } , ] , } ; if ( isEqual ( prevOptions . current , newOptions ) ) { return prevOptions . current || newOptions ; } prevOptions . current = newOptions ; return newOptions ; } , [ optionsWithDefaults , updateStateModifier ] ) ; const popperInstanceRef = React . useRef < Instance > ( ) ; /** If the options change on the hook change them on the popper instance too */ useLayoutEffect ( ( ) => { popperInstanceRef . current ?. setOptions ( popperOptions ) ; } , [ popperOptions ] ) ; /** Create the popper instance. Recreate it when the reference/popper elements change */ useLayoutEffect ( ( ) => { if ( ! referenceElement || ! popperElement ) { return ; } const popperInstance = createPopperLite ( referenceElement , popperElement , popperOptions ) ; popperInstanceRef . current = popperInstance ; return ( ) => { popperInstance . destroy ( ) ; popperInstanceRef . current = undefined ; } ; } , [ referenceElement , popperElement , createPopperLite ] ) ; return { styles : state . styles as { [ key ] : React . CSSProperties ; } , attributes : state . attributes , popperInstanceRef , } ; } ;