Add tap-to-detail on swipe cards and fix color overlay alignment

- Add onTap callback to SwipeCard using useDrag's tap detection
- Wire through SwipeReviewMode to open ListingDetailSheet on tap
- Fix color overlay misalignment: add relative to card container so
  the absolute overlay positions within the rounded card, not the
  full-width outer wrapper
This commit is contained in:
Viktor Barzin 2026-02-21 21:13:32 +00:00
parent 9c954c0e43
commit eacdf24621
No known key found for this signature in database
GPG key ID: 0EB088298288D958
3 changed files with 14 additions and 4 deletions

View file

@ -625,6 +625,7 @@ function App() {
onDecide={decide} onDecide={decide}
onClear={clear} onClear={clear}
onClose={() => setShowReviewMode(false)} onClose={() => setShowReviewMode(false)}
onSelectListing={(id) => setSelectedListingId(id)}
getDecision={getDecision} getDecision={getDecision}
/> />
)} )}

View file

@ -7,13 +7,14 @@ import type { PropertyFeature } from '@/types';
interface SwipeCardProps { interface SwipeCardProps {
feature: PropertyFeature; feature: PropertyFeature;
onSwipe: (direction: 'left' | 'right' | 'up') => void; onSwipe: (direction: 'left' | 'right' | 'up') => void;
onTap?: () => void;
isTop: boolean; isTop: boolean;
stackIndex: number; stackIndex: number;
} }
const SWIPE_THRESHOLD = 100; const SWIPE_THRESHOLD = 100;
export function SwipeCard({ feature, onSwipe, isTop, stackIndex }: SwipeCardProps) { export function SwipeCard({ feature, onSwipe, onTap, isTop, stackIndex }: SwipeCardProps) {
const hasSwiped = useRef(false); const hasSwiped = useRef(false);
const p = feature.properties; const p = feature.properties;
@ -27,9 +28,14 @@ export function SwipeCard({ feature, onSwipe, isTop, stackIndex }: SwipeCardProp
})); }));
const bind = useDrag( const bind = useDrag(
({ active, movement: [mx, my], velocity: [vx, vy], direction: [dx, dy] }) => { ({ active, movement: [mx, my], velocity: [vx, vy], direction: [dx, dy], tap }) => {
if (!isTop || hasSwiped.current) return; if (!isTop || hasSwiped.current) return;
if (tap) {
onTap?.();
return;
}
if (!active) { if (!active) {
const isSwipeRight = mx > SWIPE_THRESHOLD || (vx > 0.5 && dx > 0); const isSwipeRight = mx > SWIPE_THRESHOLD || (vx > 0.5 && dx > 0);
const isSwipeLeft = mx < -SWIPE_THRESHOLD || (vx > 0.5 && dx < 0); const isSwipeLeft = mx < -SWIPE_THRESHOLD || (vx > 0.5 && dx < 0);
@ -81,11 +87,11 @@ export function SwipeCard({ feature, onSwipe, isTop, stackIndex }: SwipeCardProp
}} }}
className="cursor-grab active:cursor-grabbing" className="cursor-grab active:cursor-grabbing"
> >
<div className="bg-background rounded-2xl border shadow-lg overflow-hidden mx-4"> <div className="relative bg-background rounded-2xl border shadow-lg overflow-hidden mx-4">
{/* Color overlay */} {/* Color overlay */}
{isTop && ( {isTop && (
<animated.div <animated.div
className="absolute inset-0 z-10 rounded-2xl pointer-events-none" className="absolute inset-0 z-10 pointer-events-none"
style={{ backgroundColor: overlayColor, opacity: overlayOpacity }} style={{ backgroundColor: overlayColor, opacity: overlayOpacity }}
/> />
)} )}

View file

@ -9,6 +9,7 @@ interface SwipeReviewModeProps {
onDecide: (listingId: number, decision: DecisionType, listingType?: 'RENT' | 'BUY') => void; onDecide: (listingId: number, decision: DecisionType, listingType?: 'RENT' | 'BUY') => void;
onClear: (listingId: number, listingType?: 'RENT' | 'BUY') => void; onClear: (listingId: number, listingType?: 'RENT' | 'BUY') => void;
onClose: () => void; onClose: () => void;
onSelectListing?: (listingId: number) => void;
getDecision: (listingId: number, listingType?: string) => DecisionType | undefined; getDecision: (listingId: number, listingType?: string) => DecisionType | undefined;
} }
@ -33,6 +34,7 @@ export function SwipeReviewMode({
onDecide, onDecide,
onClear, onClear,
onClose, onClose,
onSelectListing,
getDecision, getDecision,
}: SwipeReviewModeProps) { }: SwipeReviewModeProps) {
// Filter to only undecided features // Filter to only undecided features
@ -128,6 +130,7 @@ export function SwipeReviewMode({
key={feature.properties.url} key={feature.properties.url}
feature={feature} feature={feature}
onSwipe={handleSwipe} onSwipe={handleSwipe}
onTap={() => onSelectListing?.(getListingId(feature))}
isTop={i === 0} isTop={i === 0}
stackIndex={i} stackIndex={i}
/> />