veza/apps/web/src/features/playlists/hooks/useTouchGestures.ts
2025-12-12 21:34:34 -05:00

155 lines
3.9 KiB
TypeScript

/**
* Hook pour gérer les gestes tactiles (touch gestures)
* T0504: Create Playlist Mobile Responsive
*/
import { useRef, useCallback, useState } from 'react';
export interface SwipeDirection {
direction: 'left' | 'right' | 'up' | 'down' | null;
distance: number;
}
export interface TouchGestureHandlers {
onSwipeLeft?: () => void;
onSwipeRight?: () => void;
onSwipeUp?: () => void;
onSwipeDown?: () => void;
onLongPress?: () => void;
onTap?: () => void;
swipeThreshold?: number;
longPressDelay?: number;
}
export function useTouchGestures(handlers: TouchGestureHandlers = {}) {
const {
onSwipeLeft,
onSwipeRight,
onSwipeUp,
onSwipeDown,
onLongPress,
onTap,
swipeThreshold = 50,
longPressDelay = 500,
} = handlers;
const touchStartRef = useRef<{ x: number; y: number; time: number } | null>(
null,
);
const longPressTimerRef = useRef<NodeJS.Timeout | null>(null);
const [isLongPressing, setIsLongPressing] = useState(false);
const handleTouchStart = useCallback(
(e: React.TouchEvent) => {
const touch = e.touches[0];
touchStartRef.current = {
x: touch.clientX,
y: touch.clientY,
time: Date.now(),
};
// Démarrer le timer pour le long press
if (onLongPress) {
longPressTimerRef.current = setTimeout(() => {
setIsLongPressing(true);
onLongPress();
}, longPressDelay);
}
},
[onLongPress, longPressDelay],
);
const handleTouchMove = useCallback((_e: React.TouchEvent) => {
// Annuler le long press si l'utilisateur bouge
if (longPressTimerRef.current) {
clearTimeout(longPressTimerRef.current);
longPressTimerRef.current = null;
}
setIsLongPressing(false);
}, []);
const handleTouchEnd = useCallback(
(e: React.TouchEvent) => {
// Annuler le long press timer
if (longPressTimerRef.current) {
clearTimeout(longPressTimerRef.current);
longPressTimerRef.current = null;
}
if (!touchStartRef.current) return;
const touch = e.changedTouches[0];
const deltaX = touch.clientX - touchStartRef.current.x;
const deltaY = touch.clientY - touchStartRef.current.y;
const distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
const time = Date.now() - touchStartRef.current.time;
// Si c'était un long press, ne pas traiter comme un swipe
if (isLongPressing) {
setIsLongPressing(false);
touchStartRef.current = null;
return;
}
// Si la distance est trop petite, considérer comme un tap
if (distance < swipeThreshold) {
if (onTap && time < 300) {
onTap();
}
touchStartRef.current = null;
return;
}
// Déterminer la direction du swipe
const absX = Math.abs(deltaX);
const absY = Math.abs(deltaY);
if (absX > absY) {
// Swipe horizontal
if (deltaX > 0 && onSwipeRight) {
onSwipeRight();
} else if (deltaX < 0 && onSwipeLeft) {
onSwipeLeft();
}
} else {
// Swipe vertical
if (deltaY > 0 && onSwipeDown) {
onSwipeDown();
} else if (deltaY < 0 && onSwipeUp) {
onSwipeUp();
}
}
touchStartRef.current = null;
setIsLongPressing(false);
},
[
swipeThreshold,
onSwipeLeft,
onSwipeRight,
onSwipeUp,
onSwipeDown,
onTap,
isLongPressing,
],
);
const handleTouchCancel = useCallback(() => {
if (longPressTimerRef.current) {
clearTimeout(longPressTimerRef.current);
longPressTimerRef.current = null;
}
touchStartRef.current = null;
setIsLongPressing(false);
}, []);
return {
touchHandlers: {
onTouchStart: handleTouchStart,
onTouchMove: handleTouchMove,
onTouchEnd: handleTouchEnd,
onTouchCancel: handleTouchCancel,
},
isLongPressing,
};
}