From 9e6e0902ab276e1c131fd1c915e41854c4fa6355 Mon Sep 17 00:00:00 2001 From: Gilles Soulier Date: Sun, 24 May 2026 12:07:38 +0200 Subject: [PATCH] feat(todos): composant SwipeableRow (swipe touch, seuil 80px) --- .../src/components/todos/SwipeableRow.tsx | 77 +++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 frontend/src/components/todos/SwipeableRow.tsx diff --git a/frontend/src/components/todos/SwipeableRow.tsx b/frontend/src/components/todos/SwipeableRow.tsx new file mode 100644 index 0000000..2a34ee5 --- /dev/null +++ b/frontend/src/components/todos/SwipeableRow.tsx @@ -0,0 +1,77 @@ +import { useRef, useState } from 'react' + +interface SwipeableRowProps { + children: React.ReactNode + rightContent: React.ReactNode // actions révélées par swipe gauche + onSwipeRight?: () => void // callback swipe droit (marquer done) +} + +const THRESHOLD = 80 // pixels pour déclencher une action + +export default function SwipeableRow({ children, rightContent, onSwipeRight }: SwipeableRowProps) { + const [offsetX, setOffsetX] = useState(0) + const startX = useRef(null) + const dragging = useRef(false) + + function onTouchStart(e: React.TouchEvent) { + startX.current = e.touches[0].clientX + dragging.current = false + } + + function onTouchMove(e: React.TouchEvent) { + if (startX.current === null) return + dragging.current = true + const dx = e.touches[0].clientX - startX.current + // Clamp : +120px à droite, -160px à gauche (largeur des boutons) + setOffsetX(Math.max(Math.min(dx, 120), -160)) + } + + function onTouchEnd() { + if (offsetX > THRESHOLD && onSwipeRight) { + onSwipeRight() + } + setOffsetX(0) + startX.current = null + dragging.current = false + } + + const revealActions = offsetX < -(THRESHOLD / 2) + + return ( +
+ {/* Boutons d'action révélés à droite (swipe gauche) */} +
+ {rightContent} +
+ + {/* Rangée principale déplaçable */} +
THRESHOLD / 2 ? 'var(--ok)' : 'var(--bg-3)', + position: 'relative', + zIndex: 1, + }} + > + {children} +
+
+ ) +}