| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188 | 'use client'import React from 'react'import {  FloatingPortal,  autoUpdate,  flip,  offset,  shift,  size,  useDismiss,  useFloating,  useFocus,  useHover,  useInteractions,  useMergeRefs,  useRole,} from '@floating-ui/react'import type { OffsetOptions, Placement } from '@floating-ui/react'import cn from '@/utils/classnames'export type PortalToFollowElemOptions = {  /*  * top, bottom, left, right  * start, end. Default is middle  * combine: top-start, top-end  */  placement?: Placement  open?: boolean  offset?: number | OffsetOptions  onOpenChange?: (open: boolean) => void  triggerPopupSameWidth?: boolean}export function usePortalToFollowElem({  placement = 'bottom',  open,  offset: offsetValue = 0,  onOpenChange: setControlledOpen,  triggerPopupSameWidth,}: PortalToFollowElemOptions = {}) {  const setOpen = setControlledOpen  const data = useFloating({    placement,    open,    onOpenChange: setOpen,    whileElementsMounted: autoUpdate,    middleware: [      offset(offsetValue),      flip({        crossAxis: placement.includes('-'),        fallbackAxisSideDirection: 'start',        padding: 5,      }),      shift({ padding: 5 }),      size({        apply({ rects, elements }) {          if (triggerPopupSameWidth)            elements.floating.style.width = `${rects.reference.width}px`        },      }),    ],  })  const context = data.context  const hover = useHover(context, {    move: false,    enabled: open == null,  })  const focus = useFocus(context, {    enabled: open == null,  })  const dismiss = useDismiss(context)  const role = useRole(context, { role: 'tooltip' })  const interactions = useInteractions([hover, focus, dismiss, role])  return React.useMemo(    () => ({      open,      setOpen,      ...interactions,      ...data,    }),    [open, setOpen, interactions, data],  )}type ContextType = ReturnType<typeof usePortalToFollowElem> | nullconst PortalToFollowElemContext = React.createContext<ContextType>(null)export function usePortalToFollowElemContext() {  const context = React.useContext(PortalToFollowElemContext)  if (context == null)    throw new Error('PortalToFollowElem components must be wrapped in <PortalToFollowElem />')  return context}export function PortalToFollowElem({  children,  ...options}: { children: React.ReactNode } & PortalToFollowElemOptions) {  // This can accept any props as options, e.g. `placement`,  // or other positioning options.  const tooltip = usePortalToFollowElem(options)  return (    <PortalToFollowElemContext.Provider value={tooltip}>      {children}    </PortalToFollowElemContext.Provider>  )}export const PortalToFollowElemTrigger = (  {    ref: propRef,    children,    asChild = false,    ...props  }: React.HTMLProps<HTMLElement> & { ref?: React.RefObject<HTMLElement>, asChild?: boolean },) => {  const context = usePortalToFollowElemContext()  const childrenRef = (children as any).props?.ref  const ref = useMergeRefs([context.refs.setReference, propRef, childrenRef])  // `asChild` allows the user to pass any element as the anchor  if (asChild && React.isValidElement(children)) {    return React.cloneElement(      children,      context.getReferenceProps({        ref,        ...props,        ...children.props,        'data-state': context.open ? 'open' : 'closed',      }),    )  }  return (    <div      ref={ref}      className={cn('inline-block', props.className)}      // The user can style the trigger based on the state      data-state={context.open ? 'open' : 'closed'}      {...context.getReferenceProps(props)}    >      {children}    </div>  )}PortalToFollowElemTrigger.displayName = 'PortalToFollowElemTrigger'export const PortalToFollowElemContent = (  {    ref: propRef,    style,    ...props  }: React.HTMLProps<HTMLDivElement> & {    ref?: React.RefObject<HTMLDivElement>;  },) => {  const context = usePortalToFollowElemContext()  const ref = useMergeRefs([context.refs.setFloating, propRef])  if (!context.open)    return null  const body = document.body  return (    <FloatingPortal root={body}>      <div        ref={ref}        style={{          ...context.floatingStyles,          ...style,        }}        {...context.getFloatingProps(props)}      />    </FloatingPortal>  )}PortalToFollowElemContent.displayName = 'PortalToFollowElemContent'
 |