wrongmove/frontend/src/components/SwipeableCardRow.tsx

88 lines
3.1 KiB
TypeScript
Raw Normal View History

import { useRef, useEffect, useCallback } from 'react';
import { PropertyCardCompact } from './PropertyCardCompact';
import type { PropertyFeature } from '@/types';
interface SwipeableCardRowProps {
features: PropertyFeature[];
activeIndex: number;
onActiveIndexChange: (index: number) => void;
avgPricePerSqm: number;
highlightedPropertyUrl?: string | null;
onCardClick?: (feature: PropertyFeature) => void;
}
export function SwipeableCardRow({
features,
activeIndex,
onActiveIndexChange,
avgPricePerSqm,
highlightedPropertyUrl,
onCardClick,
}: SwipeableCardRowProps) {
const scrollRef = useRef<HTMLDivElement>(null);
const isScrollingProgrammatically = useRef(false);
// Scroll to active index when it changes externally (e.g., from map marker tap)
useEffect(() => {
const container = scrollRef.current;
if (!container) return;
const cardWidth = 280 + 12; // card width + gap
const targetScroll = activeIndex * cardWidth - (container.clientWidth - 280) / 2;
isScrollingProgrammatically.current = true;
container.scrollTo({ left: targetScroll, behavior: 'smooth' });
// Reset flag after scroll completes
const timer = setTimeout(() => {
isScrollingProgrammatically.current = false;
}, 500);
return () => clearTimeout(timer);
}, [activeIndex]);
// Detect which card is centered after user scrolls
const handleScroll = useCallback(() => {
if (isScrollingProgrammatically.current) return;
const container = scrollRef.current;
if (!container) return;
const cardWidth = 280 + 12;
const centerX = container.scrollLeft + container.clientWidth / 2;
const newIndex = Math.round((centerX - 280 / 2) / cardWidth);
const clampedIndex = Math.max(0, Math.min(newIndex, features.length - 1));
if (clampedIndex !== activeIndex) {
onActiveIndexChange(clampedIndex);
}
}, [activeIndex, features.length, onActiveIndexChange]);
// Debounced scroll handler
const scrollTimerRef = useRef<number | null>(null);
const debouncedScroll = useCallback(() => {
if (scrollTimerRef.current) clearTimeout(scrollTimerRef.current);
scrollTimerRef.current = window.setTimeout(handleScroll, 100);
}, [handleScroll]);
if (features.length === 0) return null;
return (
<div
ref={scrollRef}
onScroll={debouncedScroll}
className="flex gap-3 overflow-x-auto snap-x snap-mandatory px-4 py-2 scrollbar-none"
style={{ WebkitOverflowScrolling: 'touch' }}
>
{features.map((feature, index) => (
<PropertyCardCompact
key={feature.properties.url}
property={feature.properties}
isActive={index === activeIndex}
isHighlighted={feature.properties.url === highlightedPropertyUrl}
avgPricePerSqm={avgPricePerSqm}
onClick={() => onCardClick?.(feature)}
/>
))}
</div>
);
}