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:
parent
9c954c0e43
commit
eacdf24621
3 changed files with 14 additions and 4 deletions
|
|
@ -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}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -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 }}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -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}
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue