Files
gsap-carousel/src/widgets/TimeFrameSlider/ui/EventsCarousel/EventsCarousel.tsx

164 lines
4.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* EventsCarousel Component
* Карусель событий с использованием Swiper
* Отображает список исторических событий в виде слайдера
*/
import { useGSAP } from '@gsap/react'
import classNames from 'classnames'
import { gsap } from 'gsap'
import { memo, useRef, useState } from 'react'
import { Swiper, SwiperSlide } from 'swiper/react'
import 'swiper/css'
import 'swiper/css/navigation'
import 'swiper/css/pagination'
import ChevronSvg from '@/shared/assets/chevron--left.svg'
import { Button } from '@/shared/ui/Button'
import { Card } from '@/shared/ui/Card'
import {
EVENT_CAROUSEL_CONFIG,
HIDE_DURATION,
SHOW_DELAY,
SHOW_DURATION,
SHOW_Y_OFFSET,
} from './constants'
import styles from './EventsCarousel.module.scss'
import type { HistoricalEvent } from '@/entities/TimePeriod'
import type { Swiper as SwiperType } from 'swiper'
export interface EventsCarouselProps {
/**
* Массив исторических событий для отображения
*/
readonly events: readonly HistoricalEvent[]
/**
* Флаг видимости карусели (управляет анимацией появления/исчезновения)
*/
readonly visible: boolean
}
/**
* Компонент карусели исторических событий
*
* Использует Swiper для создания слайдера с кастомной навигацией.
* Поддерживает адаптивное количество слайдов на разных размерах экрана.
* Анимирует появление/исчезновение с помощью GSAP useGSAP hook.
*
* @example
* ```tsx
* <EventsCarousel
* events={ HISTORICAL_PERIODS[0].events }
* visible={ true }
* />
* ```
*/
export const EventsCarousel = memo(
({ events, visible }: EventsCarouselProps) => {
const containerRef = useRef<HTMLDivElement>(null)
const [isBeginning, setIsBeginning] = useState(true)
const [isEnd, setIsEnd] = useState(false)
/**
* Анимация появления/исчезновения карусели
* Использует useGSAP hook для автоматической очистки анимаций
*/
useGSAP(
() => {
if (visible) {
gsap.fromTo(
containerRef.current,
{ opacity: 0, y: SHOW_Y_OFFSET },
{
opacity: 1,
y: 0,
duration: SHOW_DURATION,
delay: SHOW_DELAY,
}
)
} else {
gsap.to(containerRef.current, {
opacity: 0,
duration: HIDE_DURATION,
})
}
},
{ scope: containerRef, dependencies: [visible, events] }
)
/**
* Обработчик инициализации Swiper
* Устанавливает начальное состояние кнопок навигации
*/
const handleSwiperInit = (swiper: SwiperType) => {
setIsBeginning(swiper.isBeginning)
setIsEnd(swiper.isEnd)
}
/**
* Обработчик изменения состояния Swiper
* Обновляет состояние кнопок навигации
*/
const handleSlideChange = (swiper: SwiperType) => {
setIsBeginning(swiper.isBeginning)
setIsEnd(swiper.isEnd)
}
return (
<div className={styles.container} ref={containerRef}>
<div
className={classNames(styles.prevButtonWrapper, {
[styles.hidden]: isBeginning,
})}
>
<Button
variant='round'
size='small'
colorScheme='secondary'
className='swiper-button-prev-custom'
aria-label='Предыдущий слайд'
>
<ChevronSvg width={6} height={9} stroke='#3877EE' />
</Button>
</div>
<div
className={classNames(styles.nextButtonWrapper, {
[styles.hidden]: isEnd,
})}
>
<Button
variant='round'
size='small'
colorScheme='secondary'
className='swiper-button-next-custom'
aria-label='Следующий слайд'
>
<ChevronSvg width={6} height={9} stroke='#3877EE' />
</Button>
</div>
<Swiper
{...EVENT_CAROUSEL_CONFIG}
watchSlidesProgress
onInit={handleSwiperInit}
onSlideChange={handleSlideChange}
>
{events.map((event) => (
<SwiperSlide
key={`${event.year}-${event.description.slice(0, 20)}`}
>
<Card title={event.year} description={event.description} />
</SwiperSlide>
))}
</Swiper>
</div>
)
}
)
EventsCarousel.displayName = 'EventsCarousel'