From 623c3783b506b17918d16012d40458c36c6b2650 Mon Sep 17 00:00:00 2001 From: Maddox Date: Tue, 7 Apr 2026 20:22:12 -0400 Subject: [PATCH] Switch rolodex to vertical swipe (avoids Pixel back gesture conflict) Swipe up/down instead of left/right. rotateX drum effect instead of rotateY. ChevronUp/Down buttons. No horizontal touch tracking. Co-Authored-By: Claude Sonnet 4.6 --- src/components/MobileRolodex.tsx | 56 ++++++++++++-------------------- 1 file changed, 20 insertions(+), 36 deletions(-) diff --git a/src/components/MobileRolodex.tsx b/src/components/MobileRolodex.tsx index 98da624..f4e33a8 100644 --- a/src/components/MobileRolodex.tsx +++ b/src/components/MobileRolodex.tsx @@ -1,5 +1,5 @@ import { useState, useRef } from 'react' -import { Pencil, Trash2, ChevronLeft, ChevronRight } from 'lucide-react' +import { Pencil, Trash2, ChevronUp, ChevronDown } from 'lucide-react' import type { Store } from '../types' import { RouteBadge } from './RouteBadge' import { TagChip } from './TagChip' @@ -17,7 +17,7 @@ export function MobileRolodex({ stores, onEdit, onDelete }: Props) { const [dragOffset, setDragOffset] = useState(0) const touchStartX = useRef(0) const touchStartY = useRef(0) - const isHorizontalDrag = useRef(null) + const isVerticalDrag = useRef(null) const total = stores.length @@ -29,7 +29,7 @@ export function MobileRolodex({ stores, onEdit, onDelete }: Props) { function onTouchStart(e: React.TouchEvent) { touchStartX.current = e.touches[0].clientX touchStartY.current = e.touches[0].clientY - isHorizontalDrag.current = null + isVerticalDrag.current = null setDragging(true) } @@ -38,41 +38,41 @@ export function MobileRolodex({ stores, onEdit, onDelete }: Props) { const dy = e.touches[0].clientY - touchStartY.current // Determine drag axis on first significant move - if (isHorizontalDrag.current === null && (Math.abs(dx) > 4 || Math.abs(dy) > 4)) { - isHorizontalDrag.current = Math.abs(dx) > Math.abs(dy) + if (isVerticalDrag.current === null && (Math.abs(dx) > 4 || Math.abs(dy) > 4)) { + isVerticalDrag.current = Math.abs(dy) > Math.abs(dx) } - if (isHorizontalDrag.current) { + if (isVerticalDrag.current) { e.preventDefault() // Dampen drag at edges - const atStart = index === 0 && dx > 0 - const atEnd = index === total - 1 && dx < 0 - setDragOffset(atStart || atEnd ? dx * 0.2 : dx) + const atStart = index === 0 && dy > 0 + const atEnd = index === total - 1 && dy < 0 + setDragOffset(atStart || atEnd ? dy * 0.2 : dy) } } function onTouchEnd() { setDragging(false) - if (isHorizontalDrag.current && Math.abs(dragOffset) > 60) { + if (isVerticalDrag.current && Math.abs(dragOffset) > 60) { dragOffset < 0 ? goTo(index + 1) : goTo(index - 1) } else { setDragOffset(0) } - isHorizontalDrag.current = null + isVerticalDrag.current = null } if (total === 0) return null // Positions relative to current index function getCardStyle(offset: number): React.CSSProperties { - const x = offset * 88 + dragOffset * (offset === 0 ? 1 : 0.15) + const y = offset * 88 + dragOffset * (offset === 0 ? 1 : 0.15) const scale = offset === 0 ? 1 : 0.88 const opacity = Math.abs(offset) > 1 ? 0 : offset === 0 ? 1 : 0.5 const zIndex = offset === 0 ? 10 : Math.abs(offset) === 1 ? 5 : 0 - const rotateY = offset === 0 ? (dragOffset * -0.04) : (offset > 0 ? -6 : 6) + const rotateX = offset === 0 ? (dragOffset * 0.04) : (offset > 0 ? 6 : -6) return { - transform: `translateX(${x}%) scale(${scale}) rotateY(${rotateY}deg)`, + transform: `translateY(${y}%) scale(${scale}) rotateX(${rotateX}deg)`, opacity, zIndex, transition: dragging ? 'none' : 'all 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94)', @@ -149,36 +149,20 @@ export function MobileRolodex({ stores, onEdit, onDelete }: Props) { disabled={index === 0} className="w-9 h-9 flex items-center justify-center rounded-xl glass text-slate-400 hover:text-slate-200 disabled:opacity-25 disabled:cursor-not-allowed transition-colors" > - + - {/* Dot indicators — show up to 9, scroll window for larger sets */} -
- {total <= 9 ? ( - Array.from({ length: total }, (_, i) => ( -
+ {/* Counter */} + + {index + 1} / {total} +