This commit is contained in:
egortriston
2025-07-16 14:28:47 +03:00
parent 3e98f8fed6
commit 2703137ca1
7 changed files with 368 additions and 275 deletions

View File

@ -1,4 +1,4 @@
import React, { useState, useEffect } from 'react';
import React, { useState, useEffect, useRef } from 'react';
import { useQuery } from '@apollo/client';
import { GET_HERO_BANNERS } from '@/lib/graphql';
import Link from 'next/link';
@ -13,20 +13,94 @@ interface HeroBanner {
sortOrder: number;
}
// Добавим CSS для стрелок
const arrowStyles = `
.pod-slider-arrow {
width: 40px;
height: 40px;
border: none;
background: none;
padding: 0;
position: absolute;
top: 50%;
transform: translateY(-50%);
z-index: 10;
display: flex;
align-items: center;
justify-content: center;
opacity: 1;
transition: opacity 0.2s;
cursor: pointer;
}
.pod-slider-arrow-left { left: 12px; }
.pod-slider-arrow-right { right: 12px; }
.pod-slider-arrow .arrow-circle {
width: 36px;
height: 36px;
border-radius: 50%;
background: rgba(255,255,255,0.85);
display: flex;
align-items: center;
justify-content: center;
transition: background 0.2s;
}
.pod-slider-arrow:hover .arrow-circle,
.pod-slider-arrow:focus .arrow-circle {
background: #ec1c24;
}
.pod-slider-arrow .arrow-svg {
width: 20px;
height: 20px;
display: block;
transition: stroke 0.2s;
stroke: #222;
}
.pod-slider-arrow:hover .arrow-svg,
.pod-slider-arrow:focus .arrow-svg {
stroke: #fff;
}
`;
const slideStyles = `
.pod-slider-slide {
position: absolute;
top: 0; left: 0;
opacity: 0;
transform: translateX(40px) scale(0.98);
transition: opacity 0.5s cubic-bezier(.4,0,.2,1), transform 0.5s cubic-bezier(.4,0,.2,1);
pointer-events: none;
z-index: 1;
}
.pod-slider-slide.active {
opacity: 1;
transform: translateX(0) scale(1);
pointer-events: auto;
z-index: 2;
}
.pod-slider-slide.prev {
opacity: 0;
transform: translateX(-40px) scale(0.98);
z-index: 1;
}
.pod-slider-slide.next {
opacity: 0;
transform: translateX(40px) scale(0.98);
z-index: 1;
}
.mask.w-slider-mask { position: relative; }
`;
const ProductOfDayBanner: React.FC = () => {
const [currentSlide, setCurrentSlide] = useState(0);
const { data, loading, error } = useQuery(GET_HERO_BANNERS, {
errorPolicy: 'all'
});
const [showArrows, setShowArrows] = useState(false);
const sliderRef = useRef<HTMLDivElement>(null);
const { data } = useQuery(GET_HERO_BANNERS, { errorPolicy: 'all' });
// Фильтруем только активные баннеры и сортируем их
const banners: HeroBanner[] = data?.heroBanners
?.filter((banner: HeroBanner) => banner.isActive)
?.slice()
?.sort((a: HeroBanner, b: HeroBanner) => a.sortOrder - b.sortOrder) || [];
// Если нет баннеров из админки, показываем дефолтный
const allBanners = banners.length > 0 ? banners : [{
id: 'default',
title: 'ДОСТАВИМ БЫСТРО!',
@ -37,18 +111,15 @@ const ProductOfDayBanner: React.FC = () => {
sortOrder: 0
}];
// Автопрокрутка слайдов
useEffect(() => {
if (allBanners.length > 1) {
const interval = setInterval(() => {
setCurrentSlide(prev => (prev + 1) % allBanners.length);
}, 5000); // Меняем слайд каждые 5 секунд
}, 5000);
return () => clearInterval(interval);
}
}, [allBanners.length]);
// Сброс текущего слайда если он вне диапазона
useEffect(() => {
if (currentSlide >= allBanners.length) {
setCurrentSlide(0);
@ -67,141 +138,109 @@ const ProductOfDayBanner: React.FC = () => {
setCurrentSlide(index);
};
const currentBanner = allBanners[currentSlide];
const renderBannerContent = (banner: HeroBanner) => {
return (
<div className="div-block-128" style={{
backgroundImage: `url(${banner.imageUrl})`,
backgroundSize: 'cover',
backgroundPosition: 'center',
backgroundRepeat: 'no-repeat',
position: 'relative',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
minHeight: '200px',
width: '100%',
height: '100%',
borderRadius: '20px',
overflow: 'hidden'
}}>
{(banner.title || banner.subtitle) && (
<div style={{
position: 'absolute',
bottom: '20px',
left: '20px',
right: '20px',
color: 'white',
textShadow: '0 2px 4px rgba(0,0,0,0.5)'
}}>
{banner.title && (
<h3 style={{ margin: 0, fontSize: '18px', fontWeight: 'bold' }}>
{banner.title}
</h3>
)}
{banner.subtitle && (
<p style={{ margin: '5px 0 0 0', fontSize: '14px' }}>
{banner.subtitle}
</p>
)}
</div>
)}
</div>
);
};
const bannerContent = renderBannerContent(currentBanner);
const finalContent = currentBanner.linkUrl ? (
<Link href={currentBanner.linkUrl} style={{ cursor: 'pointer', display: 'block', width: '100%', height: '100%' }}>
{bannerContent}
</Link>
) : bannerContent;
// Показывать стрелки при наведении на слайдер или стрелки
const handleMouseEnter = () => setShowArrows(true);
const handleMouseLeave = () => setShowArrows(false);
return (
<div style={{ position: 'relative', width: '100%', height: '100%' }}>
{finalContent}
{/* Навигация стрелками (показываем только если баннеров больше 1) */}
{allBanners.length > 1 && (
<>
<div
onClick={handlePrevSlide}
style={{
position: 'absolute',
left: '10px',
top: '50%',
transform: 'translateY(-50%)',
background: 'rgba(0,0,0,0.5)',
color: 'white',
border: 'none',
borderRadius: '50%',
width: '40px',
height: '40px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
cursor: 'pointer',
zIndex: 10,
fontSize: '18px'
}}
>
</div>
<div
onClick={handleNextSlide}
style={{
position: 'absolute',
right: '10px',
top: '50%',
transform: 'translateY(-50%)',
background: 'rgba(0,0,0,0.5)',
color: 'white',
border: 'none',
borderRadius: '50%',
width: '40px',
height: '40px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
cursor: 'pointer',
zIndex: 10,
fontSize: '18px'
}}
>
</div>
</>
)}
{/* Индикаторы слайдов (показываем только если баннеров больше 1) */}
{allBanners.length > 1 && (
<div style={{
position: 'absolute',
bottom: '10px',
left: '50%',
transform: 'translateX(-50%)',
display: 'flex',
gap: '8px',
zIndex: 10
}}>
{allBanners.map((_, index) => (
<div
className="slider w-slider"
ref={sliderRef}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
tabIndex={0}
style={{ position: 'relative' }}
>
{/* Вставляем стили для стрелок */}
<style>{arrowStyles}{slideStyles}</style>
<div className="mask w-slider-mask">
{allBanners.map((banner, idx) => {
let slideClass = 'pod-slider-slide';
if (idx === currentSlide) slideClass += ' active';
else if (idx === (currentSlide === 0 ? allBanners.length - 1 : currentSlide - 1)) slideClass += ' prev';
else if (idx === (currentSlide + 1) % allBanners.length) slideClass += ' next';
const slideContent = (
<div
key={index}
onClick={() => handleSlideIndicator(index)}
className="div-block-128"
style={{
width: '10px',
height: '10px',
borderRadius: '50%',
background: index === currentSlide ? 'white' : 'rgba(255,255,255,0.5)',
cursor: 'pointer',
transition: 'background 0.3s'
backgroundImage: `url(${banner.imageUrl})`,
// backgroundSize: 'cover',
// backgroundPosition: 'center',
// backgroundRepeat: 'no-repeat',
}}
/>
))}
</div>
)}
>
{/* Можно добавить текст поверх баннера, если нужно */}
</div>
);
return (
<div
className={slideClass + ' slide w-slide'}
key={banner.id}
// style={{ display: idx === currentSlide ? 'block' : 'none', position: 'relative' }}
style={{ position: 'absolute', top: 0, left: 0, width: '100%', height: '100%' }}
>
{banner.linkUrl ? (
<Link href={banner.linkUrl} style={{ display: 'block', width: '100%', height: '100%' }}>{slideContent}</Link>
) : slideContent}
</div>
);
})}
</div>
{/* SVG-стрелки как в Webflow, поверх баннера, с hover-эффектом */}
<button
className="pod-slider-arrow pod-slider-arrow-left"
onClick={handlePrevSlide}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
style={{
opacity: showArrows ? 1 : 0,
pointerEvents: showArrows ? 'auto' : 'none',
}}
tabIndex={-1}
aria-label="Предыдущий баннер"
>
<span className="arrow-circle">
<svg className="arrow-svg" width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M16.6673 10H3.33398M3.33398 10L8.33398 5M3.33398 10L8.33398 15" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
</svg>
</span>
</button>
<button
className="pod-slider-arrow pod-slider-arrow-right"
onClick={handleNextSlide}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
style={{
opacity: showArrows ? 1 : 0,
pointerEvents: showArrows ? 'auto' : 'none',
}}
tabIndex={-1}
aria-label="Следующий баннер"
>
<span className="arrow-circle">
<svg className="arrow-svg" width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M3.33398 10H16.6673M16.6673 10L11.6673 5M16.6673 10L11.6673 15" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
</svg>
</span>
</button>
<div className="slide-nav w-slider-nav w-slider-nav-invert w-round">
{allBanners.map((_, idx) => (
<div
key={idx}
className="w-slider-dot"
style={{
background: idx === currentSlide ? 'white' : 'rgba(255,255,255,0.5)',
borderRadius: '50%',
width: 10,
height: 10,
margin: 4,
display: 'inline-block',
cursor: 'pointer'
}}
onClick={() => handleSlideIndicator(idx)}
/>
))}
</div>
</div>
);
};