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}
onClear={clear}
onClose={() => setShowReviewMode(false)}
onSelectListing={(id) => setSelectedListingId(id)}
getDecision={getDecision}
/>
)}

View file

@ -7,13 +7,14 @@ import type { PropertyFeature } from '@/types';
interface SwipeCardProps {
feature: PropertyFeature;
onSwipe: (direction: 'left' | 'right' | 'up') => void;
onTap?: () => void;
isTop: boolean;
stackIndex: number;
}
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 p = feature.properties;
@ -27,9 +28,14 @@ export function SwipeCard({ feature, onSwipe, isTop, stackIndex }: SwipeCardProp
}));
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 (tap) {
onTap?.();
return;
}
if (!active) {
const isSwipeRight = 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"
>
<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 */}
{isTop && (
<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 }}
/>
)}

View file

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