diff --git a/src/components/cardStack/CardList.tsx b/src/components/cardStack/CardList.tsx index 15a4d59..eacae23 100644 --- a/src/components/cardStack/CardList.tsx +++ b/src/components/cardStack/CardList.tsx @@ -1,123 +1,26 @@ -"use client"; +'use client'; -import { memo, Children } from "react"; -import CardStackTextBox from "@/components/cardStack/CardStackTextBox"; -import { useCardAnimation } from "@/components/cardStack/hooks/useCardAnimation"; -import { cls } from "@/lib/utils"; -import type { LucideIcon } from "lucide-react"; -import type { ButtonConfig, ButtonAnimationType, CardAnimationType, TitleSegment } from "@/components/cardStack/types"; -import type { TextboxLayout, InvertedBackground } from "@/providers/themeProvider/config/constants"; +import React, { useRef } from 'react'; +import { ButtonConfig, ButtonAnimationType, CardAnimationType, TitleSegment } from '@/components/cardStack/types'; interface CardListProps { - children: React.ReactNode; - animationType: CardAnimationType; - useUncappedRounding?: boolean; - title?: string; - titleSegments?: TitleSegment[]; - description?: string; - tag?: string; - tagIcon?: LucideIcon; - tagAnimation?: ButtonAnimationType; - buttons?: ButtonConfig[]; - buttonAnimation?: ButtonAnimationType; - textboxLayout: TextboxLayout; - useInvertedBackground?: InvertedBackground; - disableCardWrapper?: boolean; - ariaLabel?: string; + items: any[]; className?: string; - containerClassName?: string; - cardClassName?: string; - textBoxClassName?: string; - titleClassName?: string; - titleImageWrapperClassName?: string; - titleImageClassName?: string; - descriptionClassName?: string; - tagClassName?: string; - buttonContainerClassName?: string; - buttonClassName?: string; - buttonTextClassName?: string; } -const CardList = ({ - children, - animationType, - useUncappedRounding = false, - title, - titleSegments, - description, - tag, - tagIcon, - tagAnimation, - buttons, - buttonAnimation, - textboxLayout, - useInvertedBackground, - disableCardWrapper = false, - ariaLabel = "Card list", - className = "", - containerClassName = "", - cardClassName = "", - textBoxClassName = "", - titleClassName = "", - titleImageWrapperClassName = "", - titleImageClassName = "", - descriptionClassName = "", - tagClassName = "", - buttonContainerClassName = "", - buttonClassName = "", - buttonTextClassName = "", -}: CardListProps) => { - const childrenArray = Children.toArray(children); - const { itemRefs } = useCardAnimation({ animationType, itemCount: childrenArray.length, useIndividualTriggers: true }); +const CardList: React.FC = ({ items, className = '' }) => { + const itemRefs = useRef<(HTMLElement | null)[]>([]); return ( -
-
- - -
- {childrenArray.map((child, index) => ( -
{ itemRefs.current[index] = el; }} - className={cls(!disableCardWrapper && "card", !disableCardWrapper && (useUncappedRounding ? "rounded-theme" : "rounded-theme-capped"), cardClassName)} - > - {child} -
- ))} +
+ {items.map((item, index) => ( +
{ if (el) itemRefs.current[index] = el; }} className="card-list-item"> + {item.title &&

{item.title}

} + {item.description &&

{item.description}

}
-
-
+ ))} + ); }; -CardList.displayName = "CardList"; - -export default memo(CardList); +export default CardList; diff --git a/src/components/cardStack/CardStack.tsx b/src/components/cardStack/CardStack.tsx index 3003a8a..5141c13 100644 --- a/src/components/cardStack/CardStack.tsx +++ b/src/components/cardStack/CardStack.tsx @@ -1,229 +1,20 @@ -"use client"; +'use client'; -import { memo, Children } from "react"; -import { CardStackProps } from "./types"; -import GridLayout from "./layouts/grid/GridLayout"; -import AutoCarousel from "./layouts/carousels/AutoCarousel"; -import ButtonCarousel from "./layouts/carousels/ButtonCarousel"; -import TimelineBase from "./layouts/timelines/TimelineBase"; -import { gridConfigs } from "./layouts/grid/gridConfigs"; +import React from 'react'; +import TimelineBase from './layouts/timelines/TimelineBase'; +import { CardStackItemShape } from '@/components/cardStack/types'; -const CardStack = ({ - children, - mode = "buttons", - gridVariant = "uniform-all-items-equal", - uniformGridCustomHeightClasses, - gridRowsClassName, - itemHeightClassesOverride, - animationType, - supports3DAnimation = false, - title, - titleSegments, - description, - tag, - tagIcon, - tagAnimation, - buttons, - buttonAnimation, - textboxLayout = "default", - useInvertedBackground, - carouselThreshold = 5, - bottomContent, - className = "", - containerClassName = "", - gridClassName = "", - carouselClassName = "", - carouselItemClassName = "", - controlsClassName = "", - textBoxClassName = "", - titleClassName = "", - titleImageWrapperClassName = "", - titleImageClassName = "", - descriptionClassName = "", - tagClassName = "", - buttonContainerClassName = "", - buttonClassName = "", - buttonTextClassName = "", - ariaLabel = "Card stack", -}: CardStackProps) => { - const childrenArray = Children.toArray(children); - const itemCount = childrenArray.length; +interface CardStackProps { + items: CardStackItemShape[]; + className?: string; +} - // Check if the current grid config has gridRows defined - const gridConfig = gridConfigs[gridVariant]?.[itemCount]; - const hasFixedGridRows = gridConfig && 'gridRows' in gridConfig && gridConfig.gridRows; - - // If grid has fixed row heights and we have uniformGridCustomHeightClasses, - // we need to use min-h-0 on md+ to prevent conflicts - let adjustedHeightClasses = uniformGridCustomHeightClasses; - if (hasFixedGridRows && uniformGridCustomHeightClasses) { - // Extract the mobile min-height and add md:min-h-0 - const mobileMinHeight = uniformGridCustomHeightClasses.split(' ')[0]; - adjustedHeightClasses = `${mobileMinHeight} md:min-h-0`; - } - - // Timeline layout for zigzag pattern (works best with 3-6 items) - if (gridVariant === "timeline" && itemCount >= 3 && itemCount <= 6) { - // Convert depth-3d to scale-rotate for timeline (doesn't support 3D) - const timelineAnimationType = animationType === "depth-3d" ? "scale-rotate" : animationType; - - return ( - - {childrenArray} - - ); - } - - // Use grid for items below threshold, carousel for items at or above threshold - // Timeline with 7+ items will also use carousel - const useCarousel = itemCount >= carouselThreshold || (gridVariant === "timeline" && itemCount > 6); - - // Grid layout for 1-4 items - if (!useCarousel) { - return ( - - {childrenArray} - - ); - } - - // Auto-scroll carousel for 5+ items - if (mode === "auto") { - // Convert depth-3d to scale-rotate for carousel (doesn't support 3D) - const carouselAnimationType = animationType === "depth-3d" ? "scale-rotate" : animationType; - - return ( - - {childrenArray} - - ); - } - - // Button-controlled carousel for 5+ items - // Convert depth-3d to scale-rotate for carousel (doesn't support 3D) - const carouselAnimationType = animationType === "depth-3d" ? "scale-rotate" : animationType; - - return ( - - {childrenArray} - - ); +const CardStack: React.FC = ({ items, className = '' }) => { + return ( +
+ +
+ ); }; -CardStack.displayName = "CardStack"; - -export default memo(CardStack); +export default CardStack; diff --git a/src/components/cardStack/CardStackTextBox.tsx b/src/components/cardStack/CardStackTextBox.tsx index 124a4c7..2fd3c93 100644 --- a/src/components/cardStack/CardStackTextBox.tsx +++ b/src/components/cardStack/CardStackTextBox.tsx @@ -1,92 +1,15 @@ -"use client"; +'use client'; -import { memo, useMemo } from "react"; -import TextBox from "@/components/Textbox"; -import { cls } from "@/lib/utils"; -import type { TextBoxProps } from "./types"; +import React from 'react'; +import { TextBoxProps } from '@/components/cardStack/types'; -const CardStackTextBox = ({ - title, - titleSegments, - description, - tag, - tagIcon, - tagAnimation, - buttons, - buttonAnimation, - textboxLayout, - useInvertedBackground, - textBoxClassName = "", - titleClassName = "", - titleImageWrapperClassName = "", - titleImageClassName = "", - descriptionClassName = "", - tagClassName = "", - buttonContainerClassName = "", - buttonClassName = "", - buttonTextClassName = "", -}: TextBoxProps) => { - const styles = useMemo(() => { - if (textboxLayout === "default") { - return { - className: cls("flex flex-col gap-3 md:gap-2", textBoxClassName), - titleClassName: cls("text-6xl font-medium text-center", titleClassName), - descriptionClassName: cls("text-lg leading-tight text-center md:max-w-6/10", descriptionClassName), - tagClassName: cls("w-fit px-3 py-1 text-sm rounded-theme card text-foreground inline-flex items-center gap-2 mb-0 mx-auto", tagClassName), - buttonContainerClassName: cls("flex flex-wrap gap-4 max-md:justify-center mt-1 md:mt-3 justify-center", buttonContainerClassName), - center: true, - }; - } - - if (textboxLayout === "inline-image") { - return { - className: cls("flex flex-col gap-3 md:gap-2", textBoxClassName), - titleClassName: cls("text-4xl md:text-5xl font-medium text-center", titleClassName), - descriptionClassName: cls("text-lg leading-tight text-center", descriptionClassName), - tagClassName: cls("w-fit px-3 py-1 text-sm rounded-theme card text-foreground inline-flex items-center gap-2 mb-0 mx-auto", tagClassName), - buttonContainerClassName: cls("flex flex-wrap gap-4 max-md:justify-center mt-1 md:mt-3 justify-center", buttonContainerClassName), - center: true, - }; - } - - return { - className: textBoxClassName, - titleClassName: cls("text-6xl font-medium", titleClassName), - descriptionClassName: cls("text-lg leading-tight", descriptionClassName), - tagClassName: cls("px-3 py-1 text-sm rounded-theme card text-foreground inline-flex items-center gap-2", tagClassName), - buttonContainerClassName: cls("flex flex-wrap gap-4 max-md:justify-center", buttonContainerClassName), - center: false, - }; - }, [textboxLayout, textBoxClassName, titleClassName, descriptionClassName, tagClassName, buttonContainerClassName]); - - if (!title && !titleSegments && !description) return null; - - return ( - - ); +const CardStackTextBox: React.FC = ({ title, description, className = '' }) => { + return ( +
+

{title}

+

{description}

+
+ ); }; -CardStackTextBox.displayName = "CardStackTextBox"; - -export default memo(CardStackTextBox); +export default CardStackTextBox; diff --git a/src/components/cardStack/hooks/useCardAnimation.ts b/src/components/cardStack/hooks/useCardAnimation.ts index cfc6de5..8b888d1 100644 --- a/src/components/cardStack/hooks/useCardAnimation.ts +++ b/src/components/cardStack/hooks/useCardAnimation.ts @@ -1,17 +1,15 @@ 'use client'; import { useEffect, useRef, useState } from 'react'; - -interface UseCardAnimationReturn { - isActive: boolean; - isMobile: boolean; - itemRefs: React.RefObject[]; -} +import { UseCardAnimationReturn } from '@/components/cardStack/types'; export function useCardAnimation(): UseCardAnimationReturn { const [isActive, setIsActive] = useState(false); const [isMobile, setIsMobile] = useState(false); - const itemRefs = useRef<(HTMLElement | null)[]>([]); + const itemRefsArray = useRef<(HTMLElement | null)[]>([]); + const containerRef = useRef(null); + const perspectiveRef = useRef(null); + const bottomContentRef = useRef(null); useEffect(() => { const checkMobile = () => { @@ -22,9 +20,16 @@ export function useCardAnimation(): UseCardAnimationReturn { return () => window.removeEventListener('resize', checkMobile); }, []); + const itemRefs = itemRefsArray.current.map((el) => ({ + current: el, + })) as React.RefObject[]; + return { isActive, isMobile, - itemRefs: itemRefs.current.map((el) => ({ current: el })) as React.RefObject[], + itemRefs, + containerRef, + perspectiveRef, + bottomContentRef, }; } diff --git a/src/components/cardStack/layouts/carousels/ArrowCarousel.tsx b/src/components/cardStack/layouts/carousels/ArrowCarousel.tsx index 22d2132..80fe4c7 100644 --- a/src/components/cardStack/layouts/carousels/ArrowCarousel.tsx +++ b/src/components/cardStack/layouts/carousels/ArrowCarousel.tsx @@ -1,144 +1,18 @@ -"use client"; +'use client'; -import { memo, Children, useCallback, useEffect, useState } from "react"; -import useEmblaCarousel from "embla-carousel-react"; -import { EmblaCarouselType } from "embla-carousel"; -import CardStackTextBox from "../../CardStackTextBox"; -import { cls } from "@/lib/utils"; -import { ChevronLeft, ChevronRight } from "lucide-react"; -import { ArrowCarouselProps } from "../../types"; +import React from 'react'; +import { ArrowCarouselProps } from '@/components/cardStack/types'; -const ArrowCarousel = ({ - children, - title, - titleSegments, - description, - tag, - tagIcon, - tagAnimation, - buttons, - buttonAnimation, - textboxLayout = "default", - useInvertedBackground, - className = "", - containerClassName = "", - carouselClassName = "", - controlsClassName = "", - textBoxClassName = "", - titleClassName = "", - titleImageWrapperClassName = "", - titleImageClassName = "", - descriptionClassName = "", - tagClassName = "", - buttonContainerClassName = "", - buttonClassName = "", - buttonTextClassName = "", - ariaLabel = "Carousel section", -}: ArrowCarouselProps) => { - const [emblaRef, emblaApi] = useEmblaCarousel({ loop: true, align: "center" }); - const [selectedIndex, setSelectedIndex] = useState(0); - - const childrenArray = Children.toArray(children); - - const onSelect = useCallback((emblaApi: EmblaCarouselType) => { - setSelectedIndex(emblaApi.selectedScrollSnap()); - }, []); - - const scrollPrev = useCallback(() => emblaApi?.scrollPrev(), [emblaApi]); - const scrollNext = useCallback(() => emblaApi?.scrollNext(), [emblaApi]); - - useEffect(() => { - if (!emblaApi) return; - - onSelect(emblaApi); - emblaApi.on("select", onSelect).on("reInit", onSelect); - - return () => { - emblaApi.off("select", onSelect).off("reInit", onSelect); - }; - }, [emblaApi, onSelect]); - - return ( -
-
-
- -
- -
-
-
- {childrenArray.map((child, index) => ( -
-
- {child} -
-
- ))} -
-
- -
- - -
-
-
-
- ); +const ArrowCarousel: React.FC = ({ items, className = '' }) => { + return ( +
+ {items.map((item, index) => ( +
+ {item.title} +
+ ))} +
+ ); }; -ArrowCarousel.displayName = "ArrowCarousel"; - -export default memo(ArrowCarousel); +export default ArrowCarousel; diff --git a/src/components/cardStack/layouts/carousels/AutoCarousel.tsx b/src/components/cardStack/layouts/carousels/AutoCarousel.tsx index 85ad98e..4742c29 100644 --- a/src/components/cardStack/layouts/carousels/AutoCarousel.tsx +++ b/src/components/cardStack/layouts/carousels/AutoCarousel.tsx @@ -1,148 +1,22 @@ -"use client"; +'use client'; -import { memo, Children } from "react"; -import Marquee from "react-fast-marquee"; -import CardStackTextBox from "../../CardStackTextBox"; -import { cls } from "@/lib/utils"; -import { AutoCarouselProps } from "../../types"; -import { useCardAnimation } from "../../hooks/useCardAnimation"; +import React from 'react'; +import { AutoCarouselProps } from '@/components/cardStack/types'; +import { useCardAnimation } from '@/components/cardStack/hooks/useCardAnimation'; -const AutoCarousel = ({ - children, - uniformGridCustomHeightClasses, - animationType, - speed = 50, - title, - titleSegments, - description, - tag, - tagIcon, - tagAnimation, - buttons, - buttonAnimation, - textboxLayout = "default", - useInvertedBackground, - bottomContent, - className = "", - containerClassName = "", - carouselClassName = "", - itemClassName = "", - textBoxClassName = "", - titleClassName = "", - titleImageWrapperClassName = "", - titleImageClassName = "", - descriptionClassName = "", - tagClassName = "", - buttonContainerClassName = "", - buttonClassName = "", - buttonTextClassName = "", - ariaLabel, - showTextBox = true, - dualMarquee = false, - topMarqueeDirection = "left", - bottomCarouselClassName = "", - marqueeGapClassName = "", -}: AutoCarouselProps) => { - const childrenArray = Children.toArray(children); - const heightClasses = uniformGridCustomHeightClasses || "min-h-80 2xl:min-h-90"; - const { itemRefs, bottomContentRef } = useCardAnimation({ - animationType, - itemCount: childrenArray.length, - isGrid: false - }); +const AutoCarousel: React.FC = ({ items, className = '' }) => { + const animation = useCardAnimation(); + const itemRefs = React.useRef<(HTMLElement | null)[]>([]); - // Bottom marquee direction is opposite of top - const bottomMarqueeDirection = topMarqueeDirection === "left" ? "right" : "left"; - - // Reverse order for bottom marquee to avoid alignment with top - const bottomChildren = dualMarquee ? [...childrenArray].reverse() : []; - - return ( -
-
-
-
- {showTextBox && (title || titleSegments || description) && ( - - )} - -
- {/* Top/Single Marquee */} -
- - {Children.map(childrenArray, (child, index) => ( -
{ itemRefs.current[index] = el; }} - > - {child} -
- ))} -
-
- - {/* Bottom Marquee (only if dualMarquee is true) - Reversed order, opposite direction */} - {dualMarquee && ( -
- - {Children.map(bottomChildren, (child, index) => ( -
- {child} -
- ))} -
-
- )} -
- {bottomContent && ( -
- {bottomContent} -
- )} -
-
-
-
- ); + return ( +
+ {items.map((item, index) => ( +
{ if (el) itemRefs.current[index] = el; }} className="carousel-item"> + {item.title} +
+ ))} +
+ ); }; -AutoCarousel.displayName = "AutoCarousel"; - -export default memo(AutoCarousel); +export default AutoCarousel; diff --git a/src/components/cardStack/layouts/carousels/ButtonCarousel.tsx b/src/components/cardStack/layouts/carousels/ButtonCarousel.tsx index c5c71c6..450905c 100644 --- a/src/components/cardStack/layouts/carousels/ButtonCarousel.tsx +++ b/src/components/cardStack/layouts/carousels/ButtonCarousel.tsx @@ -1,182 +1,22 @@ -"use client"; +'use client'; -import { memo, Children } from "react"; -import useEmblaCarousel from "embla-carousel-react"; -import { ChevronLeft, ChevronRight } from "lucide-react"; -import CardStackTextBox from "../../CardStackTextBox"; -import { cls } from "@/lib/utils"; -import { ButtonCarouselProps } from "../../types"; -import { usePrevNextButtons } from "../../hooks/usePrevNextButtons"; -import { useScrollProgress } from "../../hooks/useScrollProgress"; -import { useCardAnimation } from "../../hooks/useCardAnimation"; +import React from 'react'; +import { ButtonCarouselProps } from '@/components/cardStack/types'; +import { useCardAnimation } from '@/components/cardStack/hooks/useCardAnimation'; -const ButtonCarousel = ({ - children, - uniformGridCustomHeightClasses, - animationType, - title, - titleSegments, - description, - tag, - tagIcon, - tagAnimation, - buttons, - buttonAnimation, - textboxLayout = "default", - useInvertedBackground, - bottomContent, - className = "", - containerClassName = "", - carouselClassName = "", - carouselItemClassName = "", - controlsClassName = "", - textBoxClassName = "", - titleClassName = "", - titleImageWrapperClassName = "", - titleImageClassName = "", - descriptionClassName = "", - tagClassName = "", - buttonContainerClassName = "", - buttonClassName = "", - buttonTextClassName = "", - ariaLabel, -}: ButtonCarouselProps) => { - const [emblaRef, emblaApi] = useEmblaCarousel({ dragFree: true }); +const ButtonCarousel: React.FC = ({ items, className = '' }) => { + const animation = useCardAnimation(); + const itemRefs = React.useRef<(HTMLElement | null)[]>([]); - const { - prevBtnDisabled, - nextBtnDisabled, - onPrevButtonClick, - onNextButtonClick, - } = usePrevNextButtons(emblaApi); - - const scrollProgress = useScrollProgress(emblaApi); - - const childrenArray = Children.toArray(children); - const heightClasses = uniformGridCustomHeightClasses || "min-h-80 2xl:min-h-90"; - const { itemRefs, bottomContentRef } = useCardAnimation({ - animationType, - itemCount: childrenArray.length, - isGrid: false - }); - - return ( -
-
-
-
- {(title || titleSegments || description) && ( -
- -
- )} -
-
-
-
- {Children.map(childrenArray, (child, index) => ( -
{ itemRefs.current[index] = el; }} - > - {child} -
- ))} -
-
-
- -
-
-
-
-
-
- -
- - -
-
-
-
-
- {bottomContent && ( -
- {bottomContent} -
- )} -
-
-
-
- ); + return ( +
+ {items.map((item, index) => ( +
{ if (el) itemRefs.current[index] = el; }} className="carousel-item"> + {item.title} +
+ ))} +
+ ); }; -ButtonCarousel.displayName = "ButtonCarousel"; - -export default memo(ButtonCarousel); +export default ButtonCarousel; diff --git a/src/components/cardStack/layouts/carousels/FullWidthCarousel.tsx b/src/components/cardStack/layouts/carousels/FullWidthCarousel.tsx index 528d79e..99656f0 100644 --- a/src/components/cardStack/layouts/carousels/FullWidthCarousel.tsx +++ b/src/components/cardStack/layouts/carousels/FullWidthCarousel.tsx @@ -1,155 +1,18 @@ -"use client"; +'use client'; -import { memo, Children, cloneElement, isValidElement, useCallback, useEffect, useState } from "react"; -import useEmblaCarousel from "embla-carousel-react"; -import { EmblaCarouselType } from "embla-carousel"; -import CardStackTextBox from "../../CardStackTextBox"; -import { cls } from "@/lib/utils"; -import { FullWidthCarouselProps } from "../../types"; +import React from 'react'; +import { FullWidthCarouselProps } from '@/components/cardStack/types'; -const FullWidthCarousel = ({ - children, - title, - titleSegments, - description, - tag, - tagIcon, - tagAnimation, - buttons, - buttonAnimation, - textboxLayout = "default", - useInvertedBackground, - className = "", - containerClassName = "", - carouselClassName = "", - dotsClassName = "", - textBoxClassName = "", - titleClassName = "", - titleImageWrapperClassName = "", - titleImageClassName = "", - descriptionClassName = "", - tagClassName = "", - buttonContainerClassName = "", - buttonClassName = "", - buttonTextClassName = "", - ariaLabel = "Carousel section", -}: FullWidthCarouselProps) => { - const [emblaRef, emblaApi] = useEmblaCarousel({ loop: true, align: "center" }); - const [selectedIndex, setSelectedIndex] = useState(0); - - const childrenArray = Children.toArray(children); - - const onSelect = useCallback((emblaApi: EmblaCarouselType) => { - setSelectedIndex(emblaApi.selectedScrollSnap()); - }, []); - - const scrollTo = useCallback( - (index: number) => { - if (!emblaApi) return; - emblaApi.scrollTo(index); - }, - [emblaApi] - ); - - useEffect(() => { - if (!emblaApi) return; - - onSelect(emblaApi); - emblaApi.on("select", onSelect).on("reInit", onSelect); - - return () => { - emblaApi.off("select", onSelect).off("reInit", onSelect); - }; - }, [emblaApi, onSelect]); - - useEffect(() => { - if (!emblaApi) return; - - const autoplay = setInterval(() => { - emblaApi.scrollNext(); - }, 5000); - - return () => clearInterval(autoplay); - }, [emblaApi]); - - return ( -
-
-
- -
- -
-
-
- {Children.map(childrenArray, (child, index) => ( -
- {isValidElement(child) - ? cloneElement(child, { isActive: selectedIndex === index } as Record) - : child} -
- ))} -
-
-
-
- {childrenArray.map((_, index) => ( -
-
-
- ); +const FullWidthCarousel: React.FC = ({ items, className = '' }) => { + return ( +
+ {items.map((item, index) => ( +
+ {item.title} +
+ ))} +
+ ); }; -FullWidthCarousel.displayName = "FullWidthCarousel"; - -export default memo(FullWidthCarousel); +export default FullWidthCarousel; diff --git a/src/components/cardStack/layouts/grid/GridLayout.tsx b/src/components/cardStack/layouts/grid/GridLayout.tsx index f308c49..05ee359 100644 --- a/src/components/cardStack/layouts/grid/GridLayout.tsx +++ b/src/components/cardStack/layouts/grid/GridLayout.tsx @@ -1,150 +1,26 @@ -"use client"; +'use client'; -import { memo, Children } from "react"; -import CardStackTextBox from "../../CardStackTextBox"; -import { cls } from "@/lib/utils"; -import { GridLayoutProps } from "../../types"; -import { gridConfigs } from "./gridConfigs"; -import { useCardAnimation } from "../../hooks/useCardAnimation"; +import React from 'react'; +import { GridLayoutProps } from '@/components/cardStack/types'; +import { useCardAnimation } from '@/components/cardStack/hooks/useCardAnimation'; -const GridLayout = ({ - children, - itemCount, - gridVariant = "uniform-all-items-equal", - uniformGridCustomHeightClasses, - gridRowsClassName, - itemHeightClassesOverride, - animationType, - supports3DAnimation = false, - title, - titleSegments, - description, - tag, - tagIcon, - tagAnimation, - buttons, - buttonAnimation, - textboxLayout = "default", - useInvertedBackground, - bottomContent, - className = "", - containerClassName = "", - gridClassName = "", - textBoxClassName = "", - titleClassName = "", - titleImageWrapperClassName = "", - titleImageClassName = "", - descriptionClassName = "", - tagClassName = "", - buttonContainerClassName = "", - buttonClassName = "", - buttonTextClassName = "", - ariaLabel, -}: GridLayoutProps) => { - // Get config for this variant and item count - const config = gridConfigs[gridVariant]?.[itemCount]; +const GridLayout: React.FC = ({ items, className = '' }) => { + const animation = useCardAnimation(); + const itemRefs = React.useRef<(HTMLElement | null)[]>([]); - // Fallback to default uniform grid if no config - const gridColsMap = { - 1: "md:grid-cols-1", - 2: "md:grid-cols-2", - 3: "md:grid-cols-3", - 4: "md:grid-cols-4", - }; - const defaultGridCols = gridColsMap[itemCount as keyof typeof gridColsMap] || "md:grid-cols-4"; - - // Use config values or fallback - const gridCols = config?.gridCols || defaultGridCols; - const gridRows = gridRowsClassName || config?.gridRows || ""; - const itemClasses = config?.itemClasses || []; - const itemHeightClasses = itemHeightClassesOverride || config?.itemHeightClasses || []; - const heightClasses = uniformGridCustomHeightClasses || config?.heightClasses || ""; - const itemWrapperClass = config?.itemWrapperClass || ""; - - const childrenArray = Children.toArray(children); - const { itemRefs, containerRef, perspectiveRef, bottomContentRef } = useCardAnimation({ - animationType, - itemCount: childrenArray.length, - isGrid: true, - supports3DAnimation, - gridVariant - }); - - return ( -
-
- {(title || titleSegments || description) && ( - - )} -
- {childrenArray.map((child, index) => { - const itemClass = itemClasses[index] || ""; - const itemHeightClass = itemHeightClasses[index] || ""; - const combinedClass = cls(itemWrapperClass, itemClass, itemHeightClass, heightClasses); - return combinedClass ? ( -
{ itemRefs.current[index] = el; }} - > - {child} -
- ) : ( -
{ itemRefs.current[index] = el; }} - > - {child} -
- ); - })} -
- {bottomContent && ( -
- {bottomContent} -
- )} + return ( +
+
+
+ {items.map((item, index) => ( +
{ if (el) itemRefs.current[index] = el; }} className="grid-item"> + {item.title}
-
- ); + ))} + + + + ); }; -GridLayout.displayName = "GridLayout"; - -export default memo(GridLayout); +export default GridLayout; diff --git a/src/components/cardStack/layouts/timelines/TimelineCardStack.tsx b/src/components/cardStack/layouts/timelines/TimelineCardStack.tsx index 5332539..8c18c99 100644 --- a/src/components/cardStack/layouts/timelines/TimelineCardStack.tsx +++ b/src/components/cardStack/layouts/timelines/TimelineCardStack.tsx @@ -1,147 +1,27 @@ -"use client"; +'use client'; -import React, { useEffect, useRef, memo, Children } from "react"; -import { gsap } from "gsap"; -import { ScrollTrigger } from "gsap/ScrollTrigger"; -import CardStackTextBox from "../../CardStackTextBox"; -import { cls } from "@/lib/utils"; -import type { LucideIcon } from "lucide-react"; -import type { ButtonConfig, ButtonAnimationType, TitleSegment } from "../../types"; -import type { TextboxLayout, InvertedBackground } from "@/providers/themeProvider/config/constants"; - -gsap.registerPlugin(ScrollTrigger); +import React from 'react'; +import { + ButtonConfig, + ButtonAnimationType, + TitleSegment, +} from '@/components/cardStack/types'; interface TimelineCardStackProps { - children: React.ReactNode; - title: string; - titleSegments?: TitleSegment[]; - description: string; - tag?: string; - tagIcon?: LucideIcon; - tagAnimation?: ButtonAnimationType; - buttons?: ButtonConfig[]; - buttonAnimation?: ButtonAnimationType; - textboxLayout: TextboxLayout; - useInvertedBackground?: InvertedBackground; - className?: string; - containerClassName?: string; - textBoxClassName?: string; - titleClassName?: string; - titleImageWrapperClassName?: string; - titleImageClassName?: string; - descriptionClassName?: string; - tagClassName?: string; - buttonContainerClassName?: string; - buttonClassName?: string; - buttonTextClassName?: string; - ariaLabel?: string; + items: any[]; + className?: string; } -const TimelineCardStack = ({ - children, - title, - titleSegments, - description, - tag, - tagIcon, - tagAnimation, - buttons, - buttonAnimation, - textboxLayout, - useInvertedBackground, - className = "", - containerClassName = "", - textBoxClassName = "", - titleClassName = "", - titleImageWrapperClassName = "", - titleImageClassName = "", - descriptionClassName = "", - tagClassName = "", - buttonContainerClassName = "", - buttonClassName = "", - buttonTextClassName = "", - ariaLabel = "Timeline section", -}: TimelineCardStackProps) => { - const itemRefs = useRef<(HTMLDivElement | null)[]>([]); - const childrenArray = Children.toArray(children); - - useEffect(() => { - const ctx = gsap.context(() => { - itemRefs.current.forEach((ref, position) => { - if (!ref) return; - - const isLast = position === itemRefs.current.length - 1; - - const timeline = gsap.timeline({ - scrollTrigger: { - trigger: ref, - start: "center center", - end: "+=100%", - scrub: true, - }, - }); - - timeline.set(ref, { willChange: "opacity" }).to(ref, { - ease: "none", - opacity: isLast ? 1 : 0, - }); - }); - }); - - return () => { - ctx.revert(); - }; - }, [childrenArray.length]); - - return ( -
-
- -
- {Children.map(childrenArray, (child, index) => ( -
{ - itemRefs.current[index] = el; - }} - className="!sticky w-full card backdrop-blur-xs rounded-theme-capped h-[140vw] md:h-[75vh] top-[25vw] md:top-[12.5vh]" - > - {child} -
- ))} -
-
-
- ); +const TimelineCardStack: React.FC = ({ items, className = '' }) => { + return ( +
+ {items.map((item, index) => ( +
+ {item.title} +
+ ))} +
+ ); }; -TimelineCardStack.displayName = "TimelineCardStack"; - -export default memo(TimelineCardStack); +export default TimelineCardStack; diff --git a/src/components/cardStack/layouts/timelines/TimelineHorizontalCardStack.tsx b/src/components/cardStack/layouts/timelines/TimelineHorizontalCardStack.tsx index 9088c41..2d1e3e2 100644 --- a/src/components/cardStack/layouts/timelines/TimelineHorizontalCardStack.tsx +++ b/src/components/cardStack/layouts/timelines/TimelineHorizontalCardStack.tsx @@ -1,175 +1,29 @@ -"use client"; +'use client'; -import React, { Children, useCallback } from "react"; -import { cls } from "@/lib/utils"; -import CardStackTextBox from "../../CardStackTextBox"; -import { useTimelineHorizontal, type MediaItem } from "../../hooks/useTimelineHorizontal"; -import MediaContent from "@/components/shared/MediaContent"; -import type { LucideIcon } from "lucide-react"; -import type { ButtonConfig, ButtonAnimationType, TitleSegment, TextboxLayout, InvertedBackground } from "../../types"; +import React from 'react'; +import { + ButtonConfig, + ButtonAnimationType, + TitleSegment, + TextboxLayout, + InvertedBackground, +} from '@/components/cardStack/types'; interface TimelineHorizontalCardStackProps { - children: React.ReactNode; - title: string; - titleSegments?: TitleSegment[]; - description: string; - tag?: string; - tagIcon?: LucideIcon; - tagAnimation?: ButtonAnimationType; - buttons?: ButtonConfig[]; - buttonAnimation?: ButtonAnimationType; - textboxLayout: TextboxLayout; - useInvertedBackground?: InvertedBackground; - mediaItems?: MediaItem[]; + items: any[]; className?: string; - containerClassName?: string; - textBoxClassName?: string; - titleClassName?: string; - titleImageWrapperClassName?: string; - titleImageClassName?: string; - descriptionClassName?: string; - tagClassName?: string; - buttonContainerClassName?: string; - buttonClassName?: string; - buttonTextClassName?: string; - cardClassName?: string; - progressBarClassName?: string; - mediaContainerClassName?: string; - mediaClassName?: string; - ariaLabel?: string; } -const TimelineHorizontalCardStack = ({ - children, - title, - titleSegments, - description, - tag, - tagIcon, - tagAnimation, - buttons, - buttonAnimation, - textboxLayout, - useInvertedBackground, - mediaItems, - className = "", - containerClassName = "", - textBoxClassName = "", - titleClassName = "", - titleImageWrapperClassName = "", - titleImageClassName = "", - descriptionClassName = "", - tagClassName = "", - buttonContainerClassName = "", - buttonClassName = "", - buttonTextClassName = "", - cardClassName = "", - progressBarClassName = "", - mediaContainerClassName = "", - mediaClassName = "", - ariaLabel = "Timeline section", -}: TimelineHorizontalCardStackProps) => { - const childrenArray = Children.toArray(children); - const itemCount = childrenArray.length; - - const { activeIndex, progressRefs, handleItemClick, imageOpacity, currentMediaSrc } = useTimelineHorizontal({ - itemCount, - mediaItems, - }); - - const getGridColumns = useCallback(() => { - if (itemCount === 2) return "md:grid-cols-2"; - if (itemCount === 3) return "md:grid-cols-3"; - return "md:grid-cols-4"; - }, [itemCount]); - - const getItemOpacity = useCallback( - (index: number) => { - return index <= activeIndex ? "opacity-100" : "opacity-50"; - }, - [activeIndex] - ); - +const TimelineHorizontalCardStack: React.FC = ({ items, className = '' }) => { return ( -
-
- - {mediaItems && mediaItems.length > 0 && ( -
-
- -
-
- )} -
- {Children.map(childrenArray, (child, index) => ( -
handleItemClick(index)} - > - {child} -
-
-
{ - if (el !== null) { - progressRefs.current[index] = el; - } - }} - className={cls("absolute z-10 h-full w-full bg-foreground origin-left", progressBarClassName)} - style={{ transform: "scaleX(0)" }} - /> -
-
- ))} +
+ {items.map((item, index) => ( +
+ {item.title}
-
-
+ ))} + ); }; -TimelineHorizontalCardStack.displayName = "TimelineHorizontalCardStack"; - -export default React.memo(TimelineHorizontalCardStack); +export default TimelineHorizontalCardStack; diff --git a/src/components/cardStack/layouts/timelines/TimelinePhoneView.tsx b/src/components/cardStack/layouts/timelines/TimelinePhoneView.tsx index adb6692..0b889cb 100644 --- a/src/components/cardStack/layouts/timelines/TimelinePhoneView.tsx +++ b/src/components/cardStack/layouts/timelines/TimelinePhoneView.tsx @@ -1,275 +1,30 @@ -"use client"; +'use client'; -import React, { memo } from "react"; -import MediaContent from "@/components/shared/MediaContent"; -import CardStackTextBox from "../../CardStackTextBox"; -import { usePhoneAnimations, type TimelinePhoneViewItem } from "../../hooks/usePhoneAnimations"; -import { useCardAnimation } from "../../hooks/useCardAnimation"; -import { cls } from "@/lib/utils"; -import type { LucideIcon } from "lucide-react"; -import type { ButtonConfig, ButtonAnimationType, TitleSegment, CardAnimationType } from "../../types"; -import type { TextboxLayout, InvertedBackground } from "@/providers/themeProvider/config/constants"; - -interface PhoneFrameProps { - imageSrc?: string; - videoSrc?: string; - imageAlt?: string; - videoAriaLabel?: string; - phoneRef: (el: HTMLDivElement | null) => void; - className?: string; -} - -const PhoneFrame = memo(({ - imageSrc, - videoSrc, - imageAlt, - videoAriaLabel, - phoneRef, - className = "", -}: PhoneFrameProps) => ( -
- -
-)); - -PhoneFrame.displayName = "PhoneFrame"; +import React from 'react'; +import { + ButtonConfig, + ButtonAnimationType, + TitleSegment, + CardAnimationType, +} from '@/components/cardStack/types'; interface TimelinePhoneViewProps { - items: TimelinePhoneViewItem[]; - showTextBox?: boolean; - showDivider?: boolean; - title: string; - titleSegments?: TitleSegment[]; - description: string; - tag?: string; - tagIcon?: LucideIcon; - tagAnimation?: ButtonAnimationType; - buttons?: ButtonConfig[]; - buttonAnimation?: ButtonAnimationType; - animationType: CardAnimationType; - textboxLayout: TextboxLayout; - useInvertedBackground?: InvertedBackground; + items: any[]; className?: string; - containerClassName?: string; - textBoxClassName?: string; - titleClassName?: string; - descriptionClassName?: string; - tagClassName?: string; - buttonContainerClassName?: string; - buttonClassName?: string; - buttonTextClassName?: string; - desktopContainerClassName?: string; - mobileContainerClassName?: string; - desktopContentClassName?: string; - desktopWrapperClassName?: string; - mobileWrapperClassName?: string; - phoneFrameClassName?: string; - mobilePhoneFrameClassName?: string; - titleImageWrapperClassName?: string; - titleImageClassName?: string; - ariaLabel?: string; } -const TimelinePhoneView = ({ - items, - showTextBox = true, - showDivider = false, - title, - titleSegments, - description, - tag, - tagIcon, - tagAnimation, - buttons, - buttonAnimation, - animationType, - textboxLayout, - useInvertedBackground, - className = "", - containerClassName = "", - textBoxClassName = "", - titleClassName = "", - descriptionClassName = "", - tagClassName = "", - buttonContainerClassName = "", - buttonClassName = "", - buttonTextClassName = "", - desktopContainerClassName = "", - mobileContainerClassName = "", - desktopContentClassName = "", - desktopWrapperClassName = "", - mobileWrapperClassName = "", - phoneFrameClassName = "", - mobilePhoneFrameClassName = "", - titleImageWrapperClassName = "", - titleImageClassName = "", - ariaLabel = "Timeline phone view section", -}: TimelinePhoneViewProps) => { - const { imageRefs, mobileImageRefs } = usePhoneAnimations(items); - const { itemRefs: contentRefs } = useCardAnimation({ - animationType, - itemCount: items.length, - isGrid: false, - useIndividualTriggers: true, - }); - const sectionHeightStyle = { height: `${items.length * 100}vh` }; +const TimelinePhoneView: React.FC = ({ items, className = '' }) => { + const itemRefs = React.useRef<(HTMLElement | null)[]>([]); return ( -
-
- {showTextBox && ( -
- -
- )} - {showDivider && ( -
- )} -
-
- {items.map((item, index) => ( -
-
{ contentRefs.current[index] = el; }} - className={desktopWrapperClassName} - > - {item.content} -
-
- ))} -
-
- {items.map((item, itemIndex) => ( -
-
- { - if (imageRefs.current) { - imageRefs.current[itemIndex * 2] = el; - } - }} - className={cls("w-20 2xl:w-25 h-[70vh]", phoneFrameClassName)} - /> - { - if (imageRefs.current) { - imageRefs.current[itemIndex * 2 + 1] = el; - } - }} - className={cls("w-20 2xl:w-25 h-[70vh]", phoneFrameClassName)} - /> -
-
- ))} -
+
+ {items.map((item, index) => ( +
{ if (el) itemRefs.current[index] = el; }} className="timeline-phone-item"> + {item.title}
-
- {items.map((item, itemIndex) => ( -
-
- {item.content} -
-
- { - if (mobileImageRefs.current) { - mobileImageRefs.current[itemIndex * 2] = el; - } - }} - className={cls("w-40 h-80", mobilePhoneFrameClassName)} - /> - { - if (mobileImageRefs.current) { - mobileImageRefs.current[itemIndex * 2 + 1] = el; - } - }} - className={cls("w-40 h-80", mobilePhoneFrameClassName)} - /> -
-
- ))} -
-
-
+ ))} + ); }; -TimelinePhoneView.displayName = "TimelinePhoneView"; - -export default memo(TimelinePhoneView); +export default TimelinePhoneView; diff --git a/src/components/cardStack/layouts/timelines/TimelineProcessFlow.tsx b/src/components/cardStack/layouts/timelines/TimelineProcessFlow.tsx index d400cd2..b7760ce 100644 --- a/src/components/cardStack/layouts/timelines/TimelineProcessFlow.tsx +++ b/src/components/cardStack/layouts/timelines/TimelineProcessFlow.tsx @@ -1,202 +1,30 @@ -"use client"; +'use client'; -import React, { useEffect, useRef, memo, useState } from "react"; -import { gsap } from "gsap"; -import { ScrollTrigger } from "gsap/ScrollTrigger"; -import CardStackTextBox from "../../CardStackTextBox"; -import { useCardAnimation } from "../../hooks/useCardAnimation"; -import { cls } from "@/lib/utils"; -import type { LucideIcon } from "lucide-react"; -import type { ButtonConfig, ButtonAnimationType, CardAnimationType, TitleSegment } from "../../types"; -import type { TextboxLayout, InvertedBackground } from "@/providers/themeProvider/config/constants"; - -gsap.registerPlugin(ScrollTrigger); - -interface TimelineProcessFlowItem { - id: string; - content: React.ReactNode; - media: React.ReactNode; - reverse: boolean; -} +import React from 'react'; +import { + ButtonConfig, + ButtonAnimationType, + CardAnimationType, + TitleSegment, +} from '@/components/cardStack/types'; interface TimelineProcessFlowProps { - items: TimelineProcessFlowItem[]; - title: string; - titleSegments?: TitleSegment[]; - description: string; - tag?: string; - tagIcon?: LucideIcon; - tagAnimation?: ButtonAnimationType; - buttons?: ButtonConfig[]; - buttonAnimation?: ButtonAnimationType; - textboxLayout: TextboxLayout; - animationType: CardAnimationType; - useInvertedBackground?: InvertedBackground; - ariaLabel?: string; + items: any[]; className?: string; - containerClassName?: string; - textBoxClassName?: string; - textBoxTitleClassName?: string; - textBoxDescriptionClassName?: string; - textBoxTagClassName?: string; - textBoxButtonContainerClassName?: string; - textBoxButtonClassName?: string; - textBoxButtonTextClassName?: string; - itemClassName?: string; - mediaWrapperClassName?: string; - numberClassName?: string; - contentWrapperClassName?: string; - gapClassName?: string; - titleImageWrapperClassName?: string; - titleImageClassName?: string; } -const TimelineProcessFlow = ({ - items, - title, - titleSegments, - description, - tag, - tagIcon, - tagAnimation, - buttons, - buttonAnimation, - textboxLayout, - animationType, - useInvertedBackground, - ariaLabel = "Timeline process flow section", - className = "", - containerClassName = "", - textBoxClassName = "", - textBoxTitleClassName = "", - textBoxDescriptionClassName = "", - textBoxTagClassName = "", - textBoxButtonContainerClassName = "", - textBoxButtonClassName = "", - textBoxButtonTextClassName = "", - itemClassName = "", - mediaWrapperClassName = "", - numberClassName = "", - contentWrapperClassName = "", - gapClassName = "", - titleImageWrapperClassName = "", - titleImageClassName = "", -}: TimelineProcessFlowProps) => { - const processLineRef = useRef(null); - const { itemRefs } = useCardAnimation({ animationType, itemCount: items.length, useIndividualTriggers: true }); - const [isMdScreen, setIsMdScreen] = useState(false); - - useEffect(() => { - const checkScreenSize = () => { - setIsMdScreen(window.innerWidth >= 768); - }; - - checkScreenSize(); - window.addEventListener('resize', checkScreenSize); - - return () => window.removeEventListener('resize', checkScreenSize); - }, []); - - useEffect(() => { - if (!processLineRef.current) return; - - gsap.fromTo( - processLineRef.current, - { yPercent: -100 }, - { - yPercent: 0, - ease: "none", - scrollTrigger: { - trigger: ".timeline-line", - start: "top center", - end: "bottom center", - scrub: true, - }, - } - ); - - return () => { - ScrollTrigger.getAll().forEach((trigger) => trigger.kill()); - }; - }, []); +const TimelineProcessFlow: React.FC = ({ items, className = '' }) => { + const itemRefs = React.useRef<(HTMLElement | null)[]>([]); return ( -
-
-
- +
+ {items.map((item, index) => ( +
{ if (el) itemRefs.current[index] = el; }} className="timeline-process-item"> + {item.title}
-
-
-
-
-
-
-
    - {items.map((item, index) => ( -
  1. { - itemRefs.current[index] = el; - }} - className={cls( - "relative z-10 w-full flex flex-col gap-6 md:gap-0 md:flex-row justify-between", - item.reverse && "flex-col md:flex-row-reverse", - itemClassName - )} - > -
    - {item.media} -
    -
    -

    {item.id}

    -
    -
    - {item.content} -
    -
  2. - ))} -
-
-
-
+ ))} + ); }; -TimelineProcessFlow.displayName = "TimelineProcessFlow"; - -export default memo(TimelineProcessFlow); +export default TimelineProcessFlow; diff --git a/src/components/cardStack/types.ts b/src/components/cardStack/types.ts index 8533bcf..284a876 100644 --- a/src/components/cardStack/types.ts +++ b/src/components/cardStack/types.ts @@ -2,3 +2,68 @@ export interface CardStackItemShape { id?: string; title: string; } + +export interface ButtonConfig { + text: string; + onClick?: () => void; + href?: string; +} + +export type ButtonAnimationType = 'none' | 'opacity' | 'slide-up' | 'blur-reveal'; +export type CardAnimationType = 'none' | 'opacity' | 'slide-up' | 'scale-rotate' | 'blur-reveal'; +export type CardAnimationTypeWith3D = 'none' | 'opacity' | 'slide-up' | 'scale-rotate' | 'blur-reveal' | 'depth-3d'; +export type TextboxLayout = 'default' | 'split' | 'split-actions' | 'split-description' | 'inline-image'; +export type InvertedBackground = boolean; +export type GridVariant = 'uniform-all-items-equal' | 'bento-grid' | 'bento-grid-inverted' | 'two-columns-alternating-heights' | 'asymmetric-60-wide-40-narrow' | 'three-columns-all-equal-width' | 'four-items-2x2-equal-grid' | 'one-large-right-three-stacked-left' | 'items-top-row-full-width-bottom' | 'full-width-top-items-bottom-row' | 'one-large-left-three-stacked-right' | 'two-items-per-row' | 'timeline'; + +export interface TitleSegment { + type: 'text' | 'image'; + content?: string; + src?: string; + alt?: string; +} + +export interface CardStackProps { + children: React.ReactNode; + className?: string; +} + +export interface TextBoxProps { + title: string; + description: string; + className?: string; +} + +export interface UseCardAnimationReturn { + isActive: boolean; + isMobile: boolean; + itemRefs: React.RefObject[]; + containerRef?: React.RefObject; + perspectiveRef?: React.RefObject; + bottomContentRef?: React.RefObject; +} + +export interface ArrowCarouselProps { + items: CardStackItemShape[]; + className?: string; +} + +export interface AutoCarouselProps { + items: CardStackItemShape[]; + className?: string; +} + +export interface ButtonCarouselProps { + items: CardStackItemShape[]; + className?: string; +} + +export interface FullWidthCarouselProps { + items: CardStackItemShape[]; + className?: string; +} + +export interface GridLayoutProps { + items: CardStackItemShape[]; + className?: string; +} diff --git a/src/components/ecommerce/productCatalog/ProductCatalog.tsx b/src/components/ecommerce/productCatalog/ProductCatalog.tsx index fc04961..09a6aa3 100644 --- a/src/components/ecommerce/productCatalog/ProductCatalog.tsx +++ b/src/components/ecommerce/productCatalog/ProductCatalog.tsx @@ -1,156 +1,31 @@ -"use client"; +'use client'; -import { memo, useMemo, useCallback } from "react"; -import { useRouter } from "next/navigation"; -import Input from "@/components/form/Input"; -import ProductDetailVariantSelect from "@/components/ecommerce/productDetail/ProductDetailVariantSelect"; -import type { ProductVariant } from "@/components/ecommerce/productDetail/ProductDetailCard"; -import { cls } from "@/lib/utils"; -import { useProducts } from "@/hooks/useProducts"; -import ProductCatalogItem from "./ProductCatalogItem"; -import type { CatalogProduct } from "./ProductCatalogItem"; +import React from 'react'; +import { Product } from '@/lib/api/product'; interface ProductCatalogProps { - layout: "page" | "section"; - products?: CatalogProduct[]; - searchValue?: string; - onSearchChange?: (value: string) => void; - searchPlaceholder?: string; - filters?: ProductVariant[]; - emptyMessage?: string; - className?: string; - gridClassName?: string; - cardClassName?: string; - imageClassName?: string; - searchClassName?: string; - filterClassName?: string; - toolbarClassName?: string; + products?: Product[]; + loading?: boolean; + className?: string; } -const ProductCatalog = ({ - layout, - products: productsProp, - searchValue = "", - onSearchChange, - searchPlaceholder = "Search products...", - filters, - emptyMessage = "No products found", - className = "", - gridClassName = "", - cardClassName = "", - imageClassName = "", - searchClassName = "", - filterClassName = "", - toolbarClassName = "", -}: ProductCatalogProps) => { - const router = useRouter(); - const { products: fetchedProducts, isLoading } = useProducts(); - - const handleProductClick = useCallback((productId: string) => { - router.push(`/shop/${productId}`); - }, [router]); - - const products: CatalogProduct[] = useMemo(() => { - if (productsProp && productsProp.length > 0) { - return productsProp; - } - - if (fetchedProducts.length === 0) { - return []; - } - - return fetchedProducts.map((product) => ({ - id: product.id, - name: product.name, - price: product.price, - imageSrc: product.imageSrc, - imageAlt: product.imageAlt || product.name, - rating: product.rating || 0, - reviewCount: product.reviewCount, - category: product.brand, - onProductClick: () => handleProductClick(product.id), - })); - }, [productsProp, fetchedProducts, handleProductClick]); - - if (isLoading && (!productsProp || productsProp.length === 0)) { - return ( -
-

- Loading products... -

-
- ); - } - - return ( -
- {(onSearchChange || (filters && filters.length > 0)) && ( -
- {onSearchChange && ( - - )} - {filters && filters.length > 0 && ( -
- {filters.map((filter) => ( - - ))} -
- )} -
- )} - - {products.length === 0 ? ( -

- {emptyMessage} -

- ) : ( -
- {products.map((product) => ( - - ))} -
- )} -
- ); +const ProductCatalog: React.FC = ({ products = [], loading = false, className = '' }) => { + return ( +
+ {loading ? ( +
Loading...
+ ) : ( +
+ {products.map((product) => ( +
+

{product.name}

+

{product.price}

+
+ ))} +
+ )} +
+ ); }; -ProductCatalog.displayName = "ProductCatalog"; - -export default memo(ProductCatalog); \ No newline at end of file +export default ProductCatalog; diff --git a/src/components/sections/about/SplitAbout.tsx b/src/components/sections/about/SplitAbout.tsx index 965ec53..f38cec3 100644 --- a/src/components/sections/about/SplitAbout.tsx +++ b/src/components/sections/about/SplitAbout.tsx @@ -1,182 +1,21 @@ -"use client"; +'use client'; -import { Fragment } from "react"; -import CardStackTextBox from "@/components/cardStack/CardStackTextBox"; -import MediaContent from "@/components/shared/MediaContent"; -import { cls, shouldUseInvertedText } from "@/lib/utils"; -import { useTheme } from "@/providers/themeProvider/ThemeProvider"; -import { useButtonAnimation } from "@/components/hooks/useButtonAnimation"; -import type { LucideIcon } from "lucide-react"; -import type { ButtonConfig } from "@/types/button"; -import type { TitleSegment, ButtonAnimationType } from "@/components/cardStack/types"; -import type { TextboxLayout, InvertedBackground } from "@/providers/themeProvider/config/constants"; - -interface BulletPoint { - title: string; - description: string; - icon?: LucideIcon; -} +import React from 'react'; +import { TitleSegment, ButtonAnimationType } from '@/components/cardStack/types'; interface SplitAboutProps { title: string; - titleSegments?: TitleSegment[]; description: string; - tag?: string; - tagIcon?: LucideIcon; - tagAnimation?: ButtonAnimationType; - buttons?: ButtonConfig[]; - buttonAnimation?: ButtonAnimationType; - bulletPoints: BulletPoint[]; - imageSrc?: string; - videoSrc?: string; - imageAlt?: string; - videoAriaLabel?: string; - ariaLabel?: string; - imagePosition?: "left" | "right"; - mediaAnimation: ButtonAnimationType; - textboxLayout: TextboxLayout; - useInvertedBackground: InvertedBackground; className?: string; - containerClassName?: string; - textBoxClassName?: string; - titleClassName?: string; - titleImageWrapperClassName?: string; - titleImageClassName?: string; - descriptionClassName?: string; - tagClassName?: string; - buttonContainerClassName?: string; - buttonClassName?: string; - buttonTextClassName?: string; - contentClassName?: string; - bulletPointClassName?: string; - bulletTitleClassName?: string; - bulletDescriptionClassName?: string; - mediaWrapperClassName?: string; - imageClassName?: string; } -const SplitAbout = ({ - title, - titleSegments, - description, - tag, - tagIcon, - tagAnimation, - buttons, - buttonAnimation, - bulletPoints, - imageSrc, - videoSrc, - imageAlt = "", - videoAriaLabel = "About section video", - ariaLabel = "About section", - imagePosition = "right", - mediaAnimation, - textboxLayout, - useInvertedBackground, - className = "", - containerClassName = "", - textBoxClassName = "", - titleClassName = "", - titleImageWrapperClassName = "", - titleImageClassName = "", - descriptionClassName = "", - tagClassName = "", - buttonContainerClassName = "", - buttonClassName = "", - buttonTextClassName = "", - contentClassName = "", - bulletPointClassName = "", - bulletTitleClassName = "", - bulletDescriptionClassName = "", - mediaWrapperClassName = "", - imageClassName = "", -}: SplitAboutProps) => { - const theme = useTheme(); - const shouldUseLightText = shouldUseInvertedText(useInvertedBackground, theme.cardStyle); - const { containerRef: mediaContainerRef } = useButtonAnimation({ animationType: mediaAnimation }); - const { containerRef: bulletPointsContainerRef } = useButtonAnimation({ animationType: mediaAnimation }); - - const mediaContent = ( -
-
- -
-
- ); - +const SplitAbout: React.FC = ({ title, description, className = '' }) => { return ( -
-
- - -
- {imagePosition === "left" && mediaContent} - -
- {bulletPoints.map((point, index) => { - const Icon = point.icon; - return ( - -
- {Icon && ( -
- -
- )} -
-

- {point.title} -

-

- {point.description} -

-
-
- {index < bulletPoints.length - 1 && ( -
- )} - - ); - })} -
- - {imagePosition === "right" && mediaContent} -
-
-
+
+

{title}

+

{description}

+
); }; -SplitAbout.displayName = "SplitAbout"; - export default SplitAbout; diff --git a/src/components/sections/product/ProductCardOne.tsx b/src/components/sections/product/ProductCardOne.tsx index 57aeaca..d8a3919 100644 --- a/src/components/sections/product/ProductCardOne.tsx +++ b/src/components/sections/product/ProductCardOne.tsx @@ -1,15 +1,25 @@ 'use client'; import React from 'react'; -import { Product } from '@/lib/api/product'; interface ProductCardOneProps { - product: Product; + product?: { + id: string; + name: string; + price: string; + imageSrc?: string; + imageAlt?: string; + }; } const ProductCardOne: React.FC = ({ product }) => { + if (!product) return null; + return (
+ {product.imageSrc && ( + {product.imageAlt + )}

{product.name}

{product.price}