| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154 |
- 'use client';
- import { useEffect, useState } from 'react';
- import Autoplay from 'embla-carousel-autoplay';
- import {
- Carousel,
- CarouselApi,
- CarouselContent,
- CarouselItem,
- CarouselNext,
- CarouselPrevious,
- } from '@/components/ui/carousel';
- import type { BannerItem } from '@/types/response/banner/active-list';
- type Props = {
- items: BannerItem[];
- };
- export default function HomeBannerCarousel({ items }: Props)
- {
- const [api, setApi] = useState<CarouselApi|null>(null);
- const [selectedIndex, setSelectedIndex] = useState(0);
- const [slideCount, setSlideCount] = useState(0);
- useEffect(() => {
- if (!api) {
- return;
- }
- setSlideCount(api.scrollSnapList().length);
- setSelectedIndex(api.selectedScrollSnap());
- const handleSelect = () => {
- setSelectedIndex(api.selectedScrollSnap());
- };
- api.on('select', handleSelect);
- api.on('reInit', handleSelect);
- return () => {
- api.off('select', handleSelect);
- api.off('reInit', handleSelect);
- };
- }, [api]);
- if (items.length === 0) {
- return null;
- }
- return (
- <section
- className="home__banner"
- aria-label="프로모션 배너"
- >
- <Carousel
- setApi={setApi}
- opts={{ loop: items.length > 1, align: 'start' }}
- plugins={items.length > 1 ? [Autoplay({ delay: 5000, stopOnInteraction: false, stopOnMouseEnter: true })] : []}
- className="relative"
- >
- <CarouselContent className="-ml-0">
- {items.map((banner, idx) => (
- <CarouselItem key={banner.id} className="pl-0 basis-full">
- <BannerSlide banner={banner} index={idx} total={items.length} />
- </CarouselItem>
- ))}
- </CarouselContent>
- {items.length > 1 && (
- <>
- <CarouselPrevious
- className="left-3 top-1/2 -translate-y-1/2 size-10 bg-black/40 hover:bg-black/60 border-0 text-white"
- aria-label="이전 배너"
- />
- <CarouselNext
- className="right-3 top-1/2 -translate-y-1/2 size-10 bg-black/40 hover:bg-black/60 border-0 text-white"
- aria-label="다음 배너"
- />
- <div
- className="absolute bottom-3 left-1/2 -translate-x-1/2 flex gap-2 z-10"
- role="tablist"
- aria-label="배너 인디케이터"
- >
- {Array.from({ length: slideCount }).map((_, idx) => (
- <button
- key={idx}
- type="button"
- onClick={() => api?.scrollTo(idx)}
- aria-label={`${idx + 1}번째 배너로 이동`}
- aria-selected={idx === selectedIndex}
- role="tab"
- className={`h-2 rounded-full transition-all ${idx === selectedIndex ? 'w-6 bg-white' : 'w-2 bg-white/50 hover:bg-white/70'}`}
- />
- ))}
- </div>
- </>
- )}
- </Carousel>
- </section>
- );
- }
- type SlideProps = {
- banner: BannerItem;
- index: number;
- total: number;
- };
- function BannerSlide({ banner, index, total }: SlideProps)
- {
- const desktop = banner.desktopImage;
- const mobile = banner.mobileImage ?? banner.desktopImage;
- const content = (
- <picture className="home__banner-picture">
- {mobile && (
- <source media="(max-width: 768px)" srcSet={mobile} />
- )}
- {desktop && (
- // eslint-disable-next-line @next/next/no-img-element
- <img
- src={desktop}
- alt={banner.subject}
- loading={index === 0 ? 'eager' : 'lazy'}
- decoding="async"
- className="home__banner-image"
- />
- )}
- </picture>
- );
- const ariaLabel = `${banner.subject} (${index + 1}/${total})`;
- if (banner.link) {
- return (
- <a
- href={banner.link}
- target={banner.link.startsWith('http') ? '_blank' : undefined}
- rel={banner.link.startsWith('http') ? 'noopener noreferrer' : undefined}
- aria-label={ariaLabel}
- className="home__banner-slide"
- >
- {content}
- </a>
- );
- }
- return (
- <div aria-label={ariaLabel} className="home__banner-slide">
- {content}
- </div>
- );
- }
|