useLongPress

UI/Interaction

A React hook that handles both normal press and long press interactions, with progress tracking and customizable callbacks.

Demo

Try pressing the button normally, holding it for a long press, or canceling a long press by releasing early:

Press and hold the button

0
Normal Presses
0
Long Presses
0
Canceled

Installation

npm install @thibault.sh/hooks

Usage

import { useLongPress } from '@thibault.sh/hooks/useLongPress';

function LongPressButton() {
  const { handlers, state } = useLongPress({
    delay: 500,
    onPress: () => console.log('Normal press'),
    onLongPress: () => console.log('Long press completed'),
    onLongPressCanceled: () => console.log('Long press canceled')
  });

  return (
    <button {...handlers}>
      {state.isLongPressed
        ? 'Long Press!'
        : `Hold me (${Math.round(state.progress * 100)}%)`}
    </button>
  );
}

API

Returns

Object containing event handlers and current press state

Features

  • Progress Tracking

    Real-time progress tracking of the long press duration with smooth animation frames

  • Touch & Mouse Support

    Works with both touch and mouse events for broad device compatibility

  • Customizable Timing

    Adjustable delay threshold for long press detection

  • Rich Event Callbacks

    Comprehensive set of callbacks for different press states and events

Context Menu Example

import { useLongPress } from '@thibault.sh/hooks/useLongPress';

function ContextMenuButton() {
  const [menuPosition, setMenuPosition] = useState<{ x: number; y: number } | null>(null);

  const { handlers } = useLongPress({
    delay: 500,
    preventContext: true,
    onPress: () => setMenuPosition(null),
    onLongPress: (event) => {
      // Position context menu at press location
      const { clientX, clientY } = event instanceof TouchEvent 
        ? event.touches[0] 
        : event;
      setMenuPosition({ x: clientX, y: clientY });
    }
  });

  return (
    <>
      <button {...handlers}>
        Long press for context menu
      </button>
      {menuPosition && (
        <div
          style={{
            position: 'fixed',
            top: menuPosition.y,
            left: menuPosition.x,
          }}
        >
          {/* Context menu content */}
        </div>
      )}
    </>
  );
}

Delete Confirmation Example

import { useLongPress } from '@thibault.sh/hooks/useLongPress';

function DeleteButton({ onDelete }: { onDelete: () => void }) {
  const { handlers, state } = useLongPress({
    delay: 2000,
    onLongPress: onDelete,
    onLongPressCanceled: () => console.log('Delete canceled')
  });

  return (
    <button
      {...handlers}
      className={`${
        state.isPressed
          ? 'bg-red-500'
          : 'bg-gray-200'
      } relative overflow-hidden`}
    >
      {state.isLongPressed ? (
        'Deleted!'
      ) : (
        <>
          Hold to Delete
          {state.isPressed && (
            <div
              className="absolute bottom-0 left-0 h-1 bg-red-700"
              style={{ width: `${state.progress * 100}%` }}
            />
          )}
        </>
      )}
    </button>
  );
}