"use client"; import cx from "classnames"; import type { ReactNode } from "react"; import React, { useId } from "react"; import { useStatic } from "@/utils/hooks"; import { Container } from "../Container"; import { Controls } from "../Controls"; import CarouselHeroStatic from "./CarouselHero.static"; import { CarouselHeroItem } from "./CarouselHeroItem"; import { CLASS_NAVIGATION, CLASS_NEXT, CLASS_PAGINATION, CLASS_PLAY_PAUSE, CLASS_PREV, CLASS_ROOT, CLASS_TAB, CLASS_TABS, CLASS_TRACK, CLASS_VIEWPORT, CLASS_VIEWPORT_WRAPPER, } from "./constants"; function getCarouselHeroSlides(children: ReactNode): React.ReactElement[] { const slides: React.ReactElement[] = []; React.Children.forEach(children, (child) => { if (!React.isValidElement(child)) return; if (child.type === React.Fragment) { slides.push( ...getCarouselHeroSlides( (child.props as { children?: ReactNode }).children, ), ); return; } slides.push(child); }); return slides; } interface TabItem { label: string; id?: string; [key: string]: any; } type CarouselHeroSlideElement = React.ReactElement>; interface CarouselHeroProps { className?: string; swiperOptions?: Record; children?: ReactNode; tabs?: TabItem[]; interval?: number; enableAutoplay?: boolean; [key: string]: any; } const CarouselHero: React.FC = ({ className, swiperOptions, children, tabs = [], interval, enableAutoplay = false, ...other }) => { const [carouselRef] = useStatic(CarouselHeroStatic); const classes = cx(CLASS_ROOT, className); const generatedId = `carousel-hero-${useId().replace(/:/g, "-")}`; const carouselId = typeof other.id === "string" && other.id ? other.id : generatedId; const slideItems = getCarouselHeroSlides( children, ) as CarouselHeroSlideElement[]; const hasTabs = tabs.length > 0 && slideItems.length > 0; const getTabId = (tab: TabItem | undefined, index: number) => typeof tab?.id === "string" && tab.id ? tab.id : `${carouselId}-tab-${index}`; const getPanelId = ( slide: CarouselHeroSlideElement | undefined, index: number, ) => slide && typeof slide.props.id === "string" && slide.props.id ? slide.props.id : `${carouselId}-panel-${index}`; const elementClasses = { prev: CLASS_PREV, next: CLASS_NEXT, playPause: CLASS_PLAY_PAUSE, tabs: CLASS_TABS, tab: CLASS_TAB, pagination: CLASS_PAGINATION, }; const forwardedSwiperOptions = (() => { if (!swiperOptions) return undefined; if (enableAutoplay || !("autoplay" in swiperOptions)) return swiperOptions; const { autoplay: _autoplay, ...rest } = swiperOptions; return Object.keys(rest).length > 0 ? rest : undefined; })(); const hasAutoplay = enableAutoplay && (((swiperOptions?.autoplay && typeof swiperOptions.autoplay === "object" && (swiperOptions.autoplay as any).delay >= 1000) as boolean) || !!(interval && interval >= 1000)); const playPauseIcon = "pause"; const tabSlides = slideItems.map((slide, index) => { if (!hasTabs) { return slide; } const tabId = getTabId(tabs[index], index); const panelId = getPanelId(slide, index); return React.cloneElement(slide as CarouselHeroSlideElement, { id: panelId, role: "tabpanel", "aria-labelledby": tabId, }); }); return (
= 1000 ? { "data-interval": interval } : {})} {...(forwardedSwiperOptions ? { "data-swiper-options": JSON.stringify(forwardedSwiperOptions) } : {})} {...other} >
{tabSlides}
{/* Controls container */} {/* Tab navigation */} {hasTabs && (
{tabs.map((tab, index) => ( ))}
)} {/* Pagination dots for mobile */}
{/* Navigation controls */}
{/* Only show play/pause button if autoplay is configured */} {hasAutoplay && ( )}
); }; CarouselHero.displayName = "CarouselHero"; export type { CarouselHeroProps, TabItem }; export { CarouselHero, CarouselHeroItem };