deploy
This commit is contained in:
@@ -0,0 +1 @@
|
||||
export { CatalogSlider } from './ui/CatalogSlider'
|
||||
@@ -0,0 +1,156 @@
|
||||
import { useEffect, useState } from 'react'
|
||||
import Box from '@mui/material/Box'
|
||||
import Paper from '@mui/material/Paper'
|
||||
import Stack from '@mui/material/Stack'
|
||||
import Typography from '@mui/material/Typography'
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import type { CatalogSliderSlide } from '@/entities/catalog-slider/api/catalog-slider-api'
|
||||
import { fetchCatalogSlider } from '@/entities/catalog-slider/api/catalog-slider-api'
|
||||
|
||||
const AUTO_MS = 5500
|
||||
|
||||
function CatalogSliderInner({ slides }: { slides: CatalogSliderSlide[] }) {
|
||||
const [index, setIndex] = useState(0)
|
||||
const [paused, setPaused] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
if (paused || slides.length <= 1) return undefined
|
||||
const id = window.setInterval(() => {
|
||||
setIndex((i) => (i + 1) % slides.length)
|
||||
}, AUTO_MS)
|
||||
return () => window.clearInterval(id)
|
||||
}, [paused, slides.length])
|
||||
|
||||
return (
|
||||
<Paper
|
||||
elevation={0}
|
||||
variant="outlined"
|
||||
sx={{
|
||||
borderRadius: 2,
|
||||
overflow: 'hidden',
|
||||
position: 'relative',
|
||||
}}
|
||||
onMouseEnter={() => setPaused(true)}
|
||||
onMouseLeave={() => setPaused(false)}
|
||||
>
|
||||
<Box
|
||||
component="section"
|
||||
aria-roledescription="carousel"
|
||||
aria-label="Фотогалерея каталога"
|
||||
aria-live={paused ? 'off' : 'polite'}
|
||||
sx={{
|
||||
position: 'relative',
|
||||
width: '100%',
|
||||
aspectRatio: { xs: '4/3', sm: '21/9' },
|
||||
maxHeight: { xs: 320, sm: 400 },
|
||||
bgcolor: 'action.hover',
|
||||
}}
|
||||
>
|
||||
{slides.map((slide, i) => (
|
||||
<Box
|
||||
key={slide.id}
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
inset: 0,
|
||||
opacity: i === index ? 1 : 0,
|
||||
transition: 'opacity 0.75s ease-in-out',
|
||||
pointerEvents: 'none',
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
component="img"
|
||||
src={slide.url}
|
||||
alt=""
|
||||
loading={i === 0 ? 'eager' : 'lazy'}
|
||||
sx={{
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
objectFit: 'cover',
|
||||
display: 'block',
|
||||
}}
|
||||
/>
|
||||
{slide.caption.trim() ? (
|
||||
<Box
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
px: 2,
|
||||
pt: 4,
|
||||
pb: slides.length > 1 ? 5 : 2,
|
||||
background: 'linear-gradient(to top, rgba(0,0,0,0.78) 0%, rgba(0,0,0,0.35) 55%, transparent 100%)',
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
color="common.white"
|
||||
variant="subtitle1"
|
||||
sx={{ fontWeight: 700, textShadow: '0 1px 4px rgba(0,0,0,0.6)' }}
|
||||
>
|
||||
{slide.caption.trim()}
|
||||
</Typography>
|
||||
</Box>
|
||||
) : null}
|
||||
</Box>
|
||||
))}
|
||||
|
||||
{slides.length > 1 && (
|
||||
<Stack
|
||||
direction="row"
|
||||
spacing={0.75}
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
bottom: 12,
|
||||
left: '50%',
|
||||
transform: 'translateX(-50%)',
|
||||
zIndex: 2,
|
||||
px: 1,
|
||||
py: 0.5,
|
||||
borderRadius: 2,
|
||||
bgcolor: 'rgba(0,0,0,0.35)',
|
||||
}}
|
||||
>
|
||||
{slides.map((slide, i) => (
|
||||
<Box
|
||||
key={slide.id}
|
||||
component="button"
|
||||
type="button"
|
||||
aria-label={`Слайд ${i + 1} из ${slides.length}`}
|
||||
aria-current={i === index ? 'true' : undefined}
|
||||
onClick={() => setIndex(i)}
|
||||
sx={{
|
||||
width: i === index ? 22 : 8,
|
||||
height: 8,
|
||||
p: 0,
|
||||
border: 'none',
|
||||
borderRadius: 99,
|
||||
bgcolor: i === index ? 'common.white' : 'rgba(255,255,255,0.45)',
|
||||
cursor: 'pointer',
|
||||
transition: 'width 0.25s, background-color 0.25s',
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</Stack>
|
||||
)}
|
||||
</Box>
|
||||
</Paper>
|
||||
)
|
||||
}
|
||||
|
||||
export function CatalogSlider() {
|
||||
const { data, isSuccess } = useQuery({
|
||||
queryKey: ['catalog-slider'],
|
||||
queryFn: fetchCatalogSlider,
|
||||
})
|
||||
|
||||
const slides = data?.slides ?? []
|
||||
const slideKey = slides.map((s) => s.id).join('|')
|
||||
|
||||
if (!isSuccess || slides.length === 0) return null
|
||||
|
||||
return (
|
||||
<Box sx={{ width: '100%', mb: 3 }}>
|
||||
<CatalogSliderInner key={slideKey} slides={slides} />
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user