import React, { useState, useEffect, useCallback, useMemo, useRef } from 'react'; import { VariableProduct } from '../types'; import { ChevronLeft, ChevronRight, Eye, Sparkles, Shuffle, Star, Heart, FileCode } from 'lucide-react'; interface ProductCascadeWidgetProps { products: VariableProduct[]; onSelectProduct: (product: VariableProduct) => void; favoriteIds?: string[]; onToggleFavorite?: (productId: string) => void; title?: string; subtitle?: string; onOpenSnippetModal?: () => void; } export const ProductCascadeWidget: React.FC = ({ products, onSelectProduct, favoriteIds = [], onToggleFavorite, title = 'Особливі та вибрані речі', subtitle = 'Гортайте пальцем в сторони: плавний рух каскаду з виділеним центральним фото', onOpenSnippetModal }) => { const [filterMode, setFilterMode] = useState<'featured' | 'random' | 'recent'>('featured'); const [activeIndex, setActiveIndex] = useState(0); const scrollContainerRef = useRef(null); const isMouseDownRef = useRef(false); const startMouseXRef = useRef(0); const startScrollLeftRef = useRef(0); const hasMovedMouseRef = useRef(false); const rafIdRef = useRef(null); // Responsive screen width detection const [windowWidth, setWindowWidth] = useState(() => typeof window !== 'undefined' ? window.innerWidth : 1200 ); useEffect(() => { const handleResize = () => setWindowWidth(window.innerWidth); window.addEventListener('resize', handleResize); return () => window.removeEventListener('resize', handleResize); }, []); // Responsive card dimensions to fill the viewport seamlessly const cardWidth = useMemo(() => { if (windowWidth < 640) return 240; if (windowWidth < 1024) return 270; if (windowWidth < 1600) return 300; return 330; }, [windowWidth]); // Base filtered products const baseItems = useMemo(() => { if (!products || products.length === 0) return []; if (filterMode === 'recent') { return [...products].sort((a, b) => b.id.localeCompare(a.id)); } if (filterMode === 'random') { return [...products].sort(() => 0.5 - Math.random()); } return products; }, [products, filterMode]); // Repeat items so on ultra-wide screens the carousel extends from edge to edge without empty space const displayItems = useMemo(() => { if (baseItems.length === 0) return []; return [ ...baseItems.map((p) => ({ ...p, uniqueId: `${p.id}-s1` })), ...baseItems.map((p) => ({ ...p, uniqueId: `${p.id}-s2` })), ...baseItems.map((p) => ({ ...p, uniqueId: `${p.id}-s3` })), ...baseItems.map((p) => ({ ...p, uniqueId: `${p.id}-s4` })) ]; }, [baseItems]); const total = displayItems.length; // Center scroll on item by index const scrollToItem = useCallback( (index: number, behavior: ScrollBehavior = 'smooth') => { const container = scrollContainerRef.current; if (!container) return; const cardElements = container.querySelectorAll('.gidna-cascade-card'); if (cardElements[index]) { const card = cardElements[index]; const containerWidth = container.clientWidth; const targetScrollLeft = card.offsetLeft - containerWidth / 2 + card.clientWidth / 2; container.scrollTo({ left: targetScrollLeft, behavior }); } }, [] ); // Initial centering on mount or filter change useEffect(() => { const timer = setTimeout(() => { if (baseItems.length > 0) { // Start near the beginning of set 2 const initialIdx = Math.min(baseItems.length, total - 1); setActiveIndex(initialIdx); scrollToItem(initialIdx, 'auto'); } }, 60); return () => clearTimeout(timer); }, [filterMode, baseItems.length, total, scrollToItem]); // Track center item during scroll with requestAnimationFrame for 60fps performance const handleScroll = useCallback(() => { const container = scrollContainerRef.current; if (!container) return; const containerCenter = container.scrollLeft + container.clientWidth / 2; const cardElements = container.querySelectorAll('.gidna-cascade-card'); let closestIndex = 0; let minDistance = Infinity; cardElements.forEach((card, index) => { const cardCenter = card.offsetLeft + card.clientWidth / 2; const distance = Math.abs(containerCenter - cardCenter); if (distance < minDistance) { minDistance = distance; closestIndex = index; } }); if (closestIndex !== activeIndex) { setActiveIndex(closestIndex); } }, [activeIndex]); const onScrollThrottled = useCallback(() => { if (rafIdRef.current !== null) return; rafIdRef.current = requestAnimationFrame(() => { handleScroll(); rafIdRef.current = null; }); }, [handleScroll]); // Previous and Next buttons const handlePrev = useCallback(() => { if (activeIndex > 0) { scrollToItem(activeIndex - 1); } }, [activeIndex, scrollToItem]); const handleNext = useCallback(() => { if (activeIndex < total - 1) { scrollToItem(activeIndex + 1); } }, [activeIndex, total, scrollToItem]); // Keyboard navigation useEffect(() => { const handleKeyDown = (e: KeyboardEvent) => { if (e.key === 'ArrowLeft') handlePrev(); if (e.key === 'ArrowRight') handleNext(); }; window.addEventListener('keydown', handleKeyDown); return () => window.removeEventListener('keydown', handleKeyDown); }, [handlePrev, handleNext]); // Desktop Mouse Drag Handlers const handleMouseDown = (e: React.MouseEvent) => { const container = scrollContainerRef.current; if (!container) return; isMouseDownRef.current = true; hasMovedMouseRef.current = false; startMouseXRef.current = e.pageX - container.offsetLeft; startScrollLeftRef.current = container.scrollLeft; }; const handleMouseMove = (e: React.MouseEvent) => { const container = scrollContainerRef.current; if (!isMouseDownRef.current || !container) return; e.preventDefault(); const x = e.pageX - container.offsetLeft; const walk = (x - startMouseXRef.current) * 1.5; if (Math.abs(walk) > 6) { hasMovedMouseRef.current = true; } container.scrollLeft = startScrollLeftRef.current - walk; }; const handleMouseUpOrLeave = () => { isMouseDownRef.current = false; }; if (!displayItems || displayItems.length === 0) { return null; } const activeProduct = displayItems[activeIndex] || displayItems[0]; const isFavoriteActive = activeProduct ? favoriteIds.includes(activeProduct.id) : false; return (
{/* 1. Header with Mode Switcher (Особливі / Рандомні / Останні новинки) */}
Добірка речей магазину «Гідна»

{filterMode === 'featured' && 'Особливі та вибрані речі'} {filterMode === 'random' && 'Випадкова добірка для вас'} {filterMode === 'recent' && 'Останні надходження сезону'}

{subtitle}

{/* Controls: Mode Tabs & Snippet Button */}
{onOpenSnippetModal && ( )} {/* 3 Quick Mode Tabs */}
{/* 2. FULL-WIDTH EDGE-TO-EDGE SWIPE / SCROLL STREAM */}
{/* Navigation Arrow Left (Desktop) */} {/* Navigation Arrow Right (Desktop) */} {/* HORIZONTAL SCROLL/SWIPE TRACK: - Spans 100% of the screen width (edge to edge) - Native iOS/Android touch swipe with buttery smooth inertia - Snaps softly to center without abrupt jumps - Mouse drag supported for desktop */}
{displayItems.map((product, index) => { const isCenter = index === activeIndex; const activeImage = product.gallery[0]; const isFavorite = favoriteIds.includes(product.id); return (
{ if (hasMovedMouseRef.current) return; if (isCenter) { onSelectProduct(product); } else { scrollToItem(index); } }} className={`gidna-cascade-card shrink-0 transition-all duration-300 ease-out ${ isCenter ? 'z-30' : 'z-10' }`} style={{ width: `${cardWidth}px`, scrollSnapAlign: 'center', transform: isCenter ? 'scale(1.05)' : 'scale(0.92)', opacity: isCenter ? 1 : 0.85 }} > {/* PHOTO CARD WITH VINTAGE CORNERS: Notice: NO titles and NO prices on the photo cards! Clean, luxurious, high-fashion presentation. */}
{/* 4 Authentic Photo Album Corners */} {/* 3. DEDICATED HIGHLIGHTED PRESENTATION: Exclusively displays title, price, and actions ONLY FOR THE CENTRAL HIGHLIGHTED PHOTO */} {activeProduct && (
{activeProduct.category} ✦ Виділений товар

{activeProduct.title}

{activeProduct.basePrice} грн
)} {/* 4. Pagination Dots for base unique items */} {baseItems.length > 1 && (
{baseItems.map((item, idx) => { const isDotActive = activeProduct?.id === item.id; return (
)}
); };
Г ідна ТОП

Батник жіночий на флісі з новорічним принтом

856  грн

Колір: Білий
Оберіть розмір: 📐 Таблиця замірів
Обрано: Не обрано (наявний на складі)
🚀 Швидка доставка Новою Поштою або Укрпоштою
💳 Накладений платіж без будь-яких передоплат
🔄 Гарантований обмін/повернення протягом 14 днів
Артикул: 241R4002
Артикул: 241R4002 Категорія:

Характеристики моделі:

• Країна виробник:
Китай

• Виріз:
круглий

• Декорування:
Однотонні

• Довжина рукава:
З довгими рукавами

• Тип тканини:
Трьохнитка

• Особливості тканини:
Тягнеться

• Сезон:
Осінній; Зимовий; Демісезонний

• Склад:
98% бавовна, 2% спандекс

• Стиль:
Повсякденний (casual)

• Особливості крою:
Вільний

• Розмірна група:
Норма; Напівбатал

Заміри (фактичні заміри можуть відрізнятись +- 2 см)
Розмір L-XL S-M
Довжина виробу 54 см 56 см
Довжина рукава (см) 51 см 53 см
Напівобхват грудей 62 см 66 см
Ширина плечей 65 см 66 см

Відгуки

Відгуків немає, поки що.

Будьте першим, хто залишив відгук на “Батник жіночий на флісі з новорічним принтом”

Ваша e-mail адреса не оприлюднюватиметься. Обов’язкові поля позначені *