Use Touch Dialog Drag

ArchitectureReact Client Component
Dependencies

Usage

You can check out the following components for usage examples of this hook:

Install Instructions

Copy and paste the code into that file

import { type TouchEventHandler, useCallback, useRef, useState } from "react";

const simpleClamp = (delta: number, maxScroll: number) => {
  const windowHeight = window.innerHeight;
  const movedPercent = delta / windowHeight / 2;

  return movedPercent * maxScroll;
};

export const useTouchDialogDrag = ({
  onClose,
  animateOpacity = false,
  maxScroll = 200,
}: {
  onClose?: (() => void) | undefined;
  maxScroll?: number;
  animateOpacity?: boolean;
}) => {
  const [startingPos, setStartingPos] = useState<null | number>(null);
  const [lastPos, setLastPos] = useState<null | number>(null);
  const ref = useRef<HTMLDialogElement>(null);

  const onTouchMove: TouchEventHandler<HTMLElement> = useCallback(
    (e) => {
      const { current } = ref;
      if (current === null) return;

      const { touches, target } = e;

      if ((target as HTMLElement).getAttribute("data-draggable") === null)
        return;

      if (startingPos === null) {
        setStartingPos(touches[0]!.screenY);
      } else {
        const delta = touches[0]!.screenY - startingPos;

        // Incase drag direction is in opposite to closing we will clamp it
        const movedBy = delta < 0 ? simpleClamp(delta, maxScroll) : delta;

        current.style.setProperty("transform", `translateY(${movedBy}px)`);
        if (animateOpacity && delta > 0)
          current.style.setProperty("opacity", `${1 - delta / 100}`);

        current.style.setProperty("transition", "none");
        setLastPos(touches[0]!.screenY);
      }
    },
    [startingPos, maxScroll, animateOpacity]
  );

  const onTouchEnd: TouchEventHandler<HTMLElement> = useCallback(() => {
    const { current } = ref;
    if (current === null) return;

    current.style.removeProperty("transform");
    animateOpacity && current.style.removeProperty("opacity");
    current.style.removeProperty("transition");

    if (
      lastPos !== null &&
      startingPos !== null &&
      startingPos - lastPos < -100
    ) {
      onClose && onClose();
    }

    setStartingPos(null);
    setLastPos(null);
  }, [startingPos, lastPos, onClose, animateOpacity]);

  return {
    onTouchMove,
    onTouchEnd,
    ref,
  };
};

Loading...