FezVrasta/popper.js

View on GitHub
website/lib/components/Floating.js

Summary

Maintainability
A
2 hrs
Test Coverage
import * as FloatingUI from '@floating-ui/react';
import {cloneElement, useEffect, useRef, useState} from 'react';
import {flushSync} from 'react-dom';

import {useChromeContext} from './Chrome';

export function Floating({
  children,
  content,
  strategy: strategyOption,
  tooltipStyle = {},
  middleware = [],
  portaled,
  bounded,
  minHeight,
  transition,
  arrow,
  lockedFromArrow,
  ...options
}) {
  const boundaryRef = useChromeContext();

  const arrowRef = useRef();
  const {
    middlewareData,
    refs,
    floatingStyles,
    placement,
    isPositioned,
  } = FloatingUI.useFloating({
    whileElementsMounted: FloatingUI.autoUpdate,
    strategy: strategyOption,
    middleware:
      [
        ...middleware,
        ...(arrow
          ? [
              {
                name: 'arrow',
                options: {element: arrowRef, padding: 5},
              },
            ]
          : []),
        ...(lockedFromArrow
          ? [
              {
                name: 'shift',
                options: middleware.find(
                  (m) => m.name === 'shift',
                )?.options,
              },
            ]
          : []),
      ]
        ?.map(({name, options}) => {
          if (name === 'size') {
            return FloatingUI.size?.({
              ...options,
              apply: ({availableHeight}) => {
                Object.assign(
                  refs.floating.current.style ?? {},
                  {
                    maxHeight: minHeight
                      ? `${Math.max(
                          availableHeight,
                          minHeight,
                        )}px`
                      : `${Math.max(availableHeight, 0)}px`,
                  },
                );
              },
            });
          }

          if (name === 'hide') {
            return [
              FloatingUI.hide(),
              FloatingUI.hide({strategy: 'escaped'}),
            ];
          }

          return FloatingUI[name]?.(
            name === 'shift'
              ? {
                  ...options,
                  boundary: bounded
                    ? boundaryRef.current || undefined
                    : undefined,
                }
              : options,
          );
        })
        .flat()
        .filter((v) => v) ?? [],
    ...options,
  });

  const [moveTransition, setMoveTransition] = useState(false);

  useEffect(() => {
    function handleResize() {
      flushSync(() => setMoveTransition(false));
      requestAnimationFrame(() => setMoveTransition(transition));
    }

    if (isPositioned && transition) {
      setMoveTransition(true);
    }

    window.addEventListener('resize', handleResize);

    return () => {
      window.removeEventListener('resize', handleResize);
    };
  }, [isPositioned, transition, refs.floating]);

  const staticSide = {
    left: 'right',
    right: 'left',
    top: 'bottom',
    bottom: 'top',
  }[placement.split('-')[0]];

  const tooltipJsx = (
    <div
      className="z-10 grid place-items-center bg-rose-500 text-base font-semibold text-gray-50"
      ref={refs.setFloating}
      style={{
        ...tooltipStyle,
        ...floatingStyles,
        maxWidth: bounded ? '60vw' : undefined,
        opacity: middlewareData.hide?.escaped
          ? '0.5'
          : undefined,
        visibility:
          middlewareData.hide?.referenceHidden || !isPositioned
            ? 'hidden'
            : undefined,
        transition: moveTransition
          ? 'transform 0.65s cubic-bezier(0.43, 0.33, 0.14, 1.01)'
          : undefined,
      }}
    >
      <div className="px-2 py-2">{content ?? 'Floating'}</div>
      {arrow && (
        <div
          ref={arrowRef}
          className="h-4 w-4 bg-gray-800 [left:-0.5rem]"
          style={{
            position: 'absolute',
            left:
              middlewareData.arrow?.x != null
                ? middlewareData.arrow.x
                : undefined,
            top:
              middlewareData.arrow?.y != null
                ? middlewareData.arrow.y
                : undefined,
            right: undefined,
            bottom: undefined,
            [staticSide]: lockedFromArrow ? '-0.5rem' : '-1rem',
            background: lockedFromArrow ? 'inherit' : 'inherit',
            transition: lockedFromArrow
              ? 'transform 0.2s ease'
              : 'none',
            transform: lockedFromArrow
              ? middlewareData.arrow?.centerOffset !== 0
                ? 'translateX(1rem) rotate(45deg)'
                : 'rotate(45deg)'
              : '',
          }}
        />
      )}
    </div>
  );

  return (
    <>
      {cloneElement(children, {ref: refs.setReference})}
      {portaled ? (
        <FloatingUI.FloatingPortal id="floating-container">
          {tooltipJsx}
        </FloatingUI.FloatingPortal>
      ) : (
        tooltipJsx
      )}
    </>
  );
}