Initial commit
This commit is contained in:
206
src/components/sections/testimonial/TestimonialCardEight.tsx
Normal file
206
src/components/sections/testimonial/TestimonialCardEight.tsx
Normal file
@@ -0,0 +1,206 @@
|
||||
"use client";
|
||||
|
||||
import { memo } from "react";
|
||||
|
||||
|
||||
import { Star } from "lucide-react";
|
||||
import FullWidthCarousel from "@/components/cardStack/layouts/carousels/FullWidthCarousel";
|
||||
import MediaContent from "@/components/shared/MediaContent";
|
||||
import { cls, shouldUseInvertedText } from "@/lib/utils";
|
||||
import { useTheme } from "@/providers/themeProvider/ThemeProvider";
|
||||
import type { LucideIcon } from "lucide-react";
|
||||
import type { ButtonConfig, TitleSegment, TextboxLayout, InvertedBackground } from "@/components/cardStack/types";
|
||||
|
||||
type Testimonial = {
|
||||
id: string;
|
||||
name: string;
|
||||
role: string;
|
||||
company: string;
|
||||
rating: number;
|
||||
imageSrc?: string;
|
||||
videoSrc?: string;
|
||||
imageAlt?: string;
|
||||
videoAriaLabel?: string;
|
||||
};
|
||||
|
||||
interface TestimonialCardEightProps {
|
||||
testimonials: Testimonial[];
|
||||
title: string;
|
||||
titleSegments?: TitleSegment[];
|
||||
description: string;
|
||||
tag?: string;
|
||||
tagIcon?: LucideIcon;
|
||||
buttons?: ButtonConfig[];
|
||||
textboxLayout: TextboxLayout;
|
||||
useInvertedBackground: InvertedBackground;
|
||||
ariaLabel?: string;
|
||||
className?: string;
|
||||
containerClassName?: string;
|
||||
cardClassName?: string;
|
||||
textBoxTitleClassName?: string;
|
||||
textBoxTitleImageWrapperClassName?: string;
|
||||
textBoxTitleImageClassName?: string;
|
||||
textBoxDescriptionClassName?: string;
|
||||
cardImageClassName?: string;
|
||||
cardContentClassName?: string;
|
||||
ratingClassName?: string;
|
||||
nameClassName?: string;
|
||||
roleClassName?: string;
|
||||
companyClassName?: string;
|
||||
carouselClassName?: string;
|
||||
dotsClassName?: string;
|
||||
textBoxClassName?: string;
|
||||
textBoxTagClassName?: string;
|
||||
textBoxButtonContainerClassName?: string;
|
||||
textBoxButtonClassName?: string;
|
||||
textBoxButtonTextClassName?: string;
|
||||
}
|
||||
|
||||
interface TestimonialCardProps {
|
||||
testimonial: Testimonial;
|
||||
shouldUseLightText: boolean;
|
||||
cardClassName?: string;
|
||||
imageClassName?: string;
|
||||
cardContentClassName?: string;
|
||||
ratingClassName?: string;
|
||||
nameClassName?: string;
|
||||
roleClassName?: string;
|
||||
companyClassName?: string;
|
||||
}
|
||||
|
||||
const TestimonialCard = memo(({
|
||||
testimonial,
|
||||
shouldUseLightText,
|
||||
cardClassName = "",
|
||||
imageClassName = "",
|
||||
cardContentClassName = "",
|
||||
ratingClassName = "",
|
||||
nameClassName = "",
|
||||
roleClassName = "",
|
||||
companyClassName = "",
|
||||
}: TestimonialCardProps) => {
|
||||
return (
|
||||
<div className={cls("relative h-full w-full rounded-theme-capped overflow-hidden", cardClassName)}>
|
||||
<MediaContent
|
||||
imageSrc={testimonial.imageSrc}
|
||||
videoSrc={testimonial.videoSrc}
|
||||
imageAlt={testimonial.imageAlt}
|
||||
videoAriaLabel={testimonial.videoAriaLabel}
|
||||
imageClassName={cls("relative w-full h-full aspect-[10/16] md:aspect-video object-cover !rounded-none", imageClassName)}
|
||||
/>
|
||||
<div className={cls("absolute! md:w-20 card backdrop-blur-xs rounded-theme-capped bottom-8 left-8 right-8 md:right-auto p-6", cardContentClassName)}>
|
||||
<div className="w-full min-w-0 flex flex-col gap-1">
|
||||
<div className={cls("relative z-1 flex gap-1", ratingClassName)}>
|
||||
{Array.from({ length: 5 }).map((_, index) => (
|
||||
<Star
|
||||
key={index}
|
||||
className={cls(
|
||||
"h-5 w-auto text-accent",
|
||||
index < testimonial.rating ? "fill-accent" : "fill-transparent"
|
||||
)}
|
||||
strokeWidth={1.5}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<h3 className={cls("relative z-1 text-2xl font-medium leading-[1.1] mt-1", shouldUseLightText ? "text-background" : "text-foreground", nameClassName)}>
|
||||
{testimonial.name}
|
||||
</h3>
|
||||
|
||||
<div className="relative z-1 flex flex-col gap-1">
|
||||
<p className={cls("text-base leading-[1.1]", shouldUseLightText ? "text-background" : "text-foreground", roleClassName)}>
|
||||
{testimonial.role}
|
||||
</p>
|
||||
<p className={cls("text-base leading-[1.1]", shouldUseLightText ? "text-background" : "text-foreground", companyClassName)}>
|
||||
{testimonial.company}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
TestimonialCard.displayName = "TestimonialCard";
|
||||
|
||||
const TestimonialCardEight = ({
|
||||
testimonials,
|
||||
title,
|
||||
titleSegments,
|
||||
description,
|
||||
tag,
|
||||
tagIcon,
|
||||
buttons,
|
||||
textboxLayout,
|
||||
useInvertedBackground,
|
||||
ariaLabel = "Testimonials section",
|
||||
className = "",
|
||||
containerClassName = "",
|
||||
cardClassName = "",
|
||||
textBoxTitleClassName = "",
|
||||
textBoxTitleImageWrapperClassName = "",
|
||||
textBoxTitleImageClassName = "",
|
||||
textBoxDescriptionClassName = "",
|
||||
cardImageClassName = "",
|
||||
cardContentClassName = "",
|
||||
ratingClassName = "",
|
||||
nameClassName = "",
|
||||
roleClassName = "",
|
||||
companyClassName = "",
|
||||
carouselClassName = "",
|
||||
dotsClassName = "",
|
||||
textBoxClassName = "",
|
||||
textBoxTagClassName = "",
|
||||
textBoxButtonContainerClassName = "",
|
||||
textBoxButtonClassName = "",
|
||||
textBoxButtonTextClassName = "",
|
||||
}: TestimonialCardEightProps) => {
|
||||
const theme = useTheme();
|
||||
const shouldUseLightText = shouldUseInvertedText(useInvertedBackground, theme.cardStyle);
|
||||
|
||||
return (
|
||||
<FullWidthCarousel
|
||||
title={title}
|
||||
titleSegments={titleSegments}
|
||||
description={description}
|
||||
tag={tag}
|
||||
tagIcon={tagIcon}
|
||||
buttons={buttons}
|
||||
textboxLayout={textboxLayout}
|
||||
useInvertedBackground={useInvertedBackground}
|
||||
className={className}
|
||||
containerClassName={containerClassName}
|
||||
carouselClassName={carouselClassName}
|
||||
dotsClassName={dotsClassName}
|
||||
textBoxClassName={textBoxClassName}
|
||||
titleClassName={textBoxTitleClassName}
|
||||
titleImageWrapperClassName={textBoxTitleImageWrapperClassName}
|
||||
titleImageClassName={textBoxTitleImageClassName}
|
||||
descriptionClassName={textBoxDescriptionClassName}
|
||||
tagClassName={textBoxTagClassName}
|
||||
buttonContainerClassName={textBoxButtonContainerClassName}
|
||||
buttonClassName={textBoxButtonClassName}
|
||||
buttonTextClassName={textBoxButtonTextClassName}
|
||||
ariaLabel={ariaLabel}
|
||||
>
|
||||
{testimonials.map((testimonial, index) => (
|
||||
<TestimonialCard
|
||||
key={`${testimonial.id}-${index}`}
|
||||
testimonial={testimonial}
|
||||
shouldUseLightText={shouldUseLightText}
|
||||
cardClassName={cardClassName}
|
||||
imageClassName={cardImageClassName}
|
||||
cardContentClassName={cardContentClassName}
|
||||
ratingClassName={ratingClassName}
|
||||
nameClassName={nameClassName}
|
||||
roleClassName={roleClassName}
|
||||
companyClassName={companyClassName}
|
||||
/>
|
||||
))}
|
||||
</FullWidthCarousel>
|
||||
);
|
||||
};
|
||||
|
||||
TestimonialCardEight.displayName = "TestimonialCardEight";
|
||||
|
||||
export default TestimonialCardEight;
|
||||
173
src/components/sections/testimonial/TestimonialCardEleven.tsx
Normal file
173
src/components/sections/testimonial/TestimonialCardEleven.tsx
Normal file
@@ -0,0 +1,173 @@
|
||||
"use client";
|
||||
|
||||
import { memo } from "react";
|
||||
|
||||
|
||||
import FullWidthCarousel from "@/components/cardStack/layouts/carousels/FullWidthCarousel";
|
||||
import MediaContent from "@/components/shared/MediaContent";
|
||||
import { cls } from "@/lib/utils";
|
||||
import type { LucideIcon } from "lucide-react";
|
||||
import type { ButtonConfig, TitleSegment, TextboxLayout, InvertedBackground } from "@/components/cardStack/types";
|
||||
|
||||
const MASK_GRADIENT = "linear-gradient(to bottom, transparent, black 60%)";
|
||||
|
||||
type Testimonial = {
|
||||
id: string;
|
||||
nameTitle: string;
|
||||
quote: string;
|
||||
imageSrc?: string;
|
||||
videoSrc?: string;
|
||||
imageAlt?: string;
|
||||
videoAriaLabel?: string;
|
||||
};
|
||||
|
||||
interface TestimonialCardElevenProps {
|
||||
testimonials: Testimonial[];
|
||||
title: string;
|
||||
titleSegments?: TitleSegment[];
|
||||
description: string;
|
||||
tag?: string;
|
||||
tagIcon?: LucideIcon;
|
||||
buttons?: ButtonConfig[];
|
||||
textboxLayout: TextboxLayout;
|
||||
useInvertedBackground: InvertedBackground;
|
||||
ariaLabel?: string;
|
||||
className?: string;
|
||||
containerClassName?: string;
|
||||
cardClassName?: string;
|
||||
cardTitleClassName?: string;
|
||||
cardQuoteClassName?: string;
|
||||
cardImageClassName?: string;
|
||||
carouselClassName?: string;
|
||||
dotsClassName?: string;
|
||||
textBoxClassName?: string;
|
||||
textBoxTitleClassName?: string;
|
||||
textBoxTitleImageWrapperClassName?: string;
|
||||
textBoxTitleImageClassName?: string;
|
||||
textBoxDescriptionClassName?: string;
|
||||
textBoxTagClassName?: string;
|
||||
textBoxButtonContainerClassName?: string;
|
||||
textBoxButtonClassName?: string;
|
||||
textBoxButtonTextClassName?: string;
|
||||
}
|
||||
|
||||
interface TestimonialCardProps {
|
||||
testimonial: Testimonial;
|
||||
useInvertedBackground: "noInvert" | "invertDefault" | "invertCard";
|
||||
isActive?: boolean;
|
||||
cardClassName?: string;
|
||||
titleClassName?: string;
|
||||
quoteClassName?: string;
|
||||
imageClassName?: string;
|
||||
}
|
||||
|
||||
const TestimonialCard = memo(({
|
||||
testimonial,
|
||||
useInvertedBackground,
|
||||
isActive = false,
|
||||
cardClassName = "",
|
||||
titleClassName = "",
|
||||
quoteClassName = "",
|
||||
imageClassName = "",
|
||||
}: TestimonialCardProps) => {
|
||||
return (
|
||||
<div className="flex flex-col">
|
||||
<div className={cls("card aspect-square md:aspect-video rounded-theme-capped overflow-hidden relative flex items-end p-6 md:p-8", cardClassName)}>
|
||||
<div className={cls("absolute inset-0 z-10 backdrop-blur-xs bg-background/10 transition-opacity duration-500", isActive ? "opacity-0" : "opacity-100")} />
|
||||
<MediaContent
|
||||
imageSrc={testimonial.imageSrc}
|
||||
videoSrc={testimonial.videoSrc}
|
||||
imageAlt={testimonial.imageAlt}
|
||||
videoAriaLabel={testimonial.videoAriaLabel}
|
||||
imageClassName={cls("!absolute inset-0 w-full h-full object-cover !rounded-none", imageClassName)}
|
||||
/>
|
||||
<div
|
||||
className="absolute left-0 right-0 bottom-0 h-1/2 backdrop-blur-xl opacity-100"
|
||||
style={{ maskImage: MASK_GRADIENT }}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<blockquote className={cls("relative z-0 text-lg md:text-2xl text-balance font-medium leading-tight line-clamp-2 text-background", quoteClassName)}>
|
||||
{testimonial.quote}
|
||||
</blockquote>
|
||||
</div>
|
||||
<h3 className={cls("text-base md:text-xl text-balance font-medium leading-tight mt-4", (useInvertedBackground === "invertDefault" || useInvertedBackground === "invertCard") && "text-background", titleClassName)}>
|
||||
{testimonial.nameTitle}
|
||||
</h3>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
TestimonialCard.displayName = "TestimonialCard";
|
||||
|
||||
const TestimonialCardEleven = ({
|
||||
testimonials,
|
||||
title,
|
||||
titleSegments,
|
||||
description,
|
||||
tag,
|
||||
tagIcon,
|
||||
buttons,
|
||||
textboxLayout,
|
||||
useInvertedBackground,
|
||||
ariaLabel = "Testimonials section",
|
||||
className = "",
|
||||
containerClassName = "",
|
||||
cardClassName = "",
|
||||
cardTitleClassName = "",
|
||||
cardQuoteClassName = "",
|
||||
cardImageClassName = "",
|
||||
carouselClassName = "",
|
||||
dotsClassName = "",
|
||||
textBoxClassName = "",
|
||||
textBoxTitleClassName = "",
|
||||
textBoxTitleImageWrapperClassName = "",
|
||||
textBoxTitleImageClassName = "",
|
||||
textBoxDescriptionClassName = "",
|
||||
textBoxTagClassName = "",
|
||||
textBoxButtonContainerClassName = "",
|
||||
textBoxButtonClassName = "",
|
||||
textBoxButtonTextClassName = "",
|
||||
}: TestimonialCardElevenProps) => {
|
||||
return (
|
||||
<FullWidthCarousel
|
||||
title={title}
|
||||
titleSegments={titleSegments}
|
||||
description={description}
|
||||
tag={tag}
|
||||
tagIcon={tagIcon}
|
||||
buttons={buttons}
|
||||
textboxLayout={textboxLayout}
|
||||
useInvertedBackground={useInvertedBackground}
|
||||
className={className}
|
||||
containerClassName={containerClassName}
|
||||
carouselClassName={carouselClassName}
|
||||
dotsClassName={dotsClassName}
|
||||
textBoxClassName={textBoxClassName}
|
||||
titleClassName={textBoxTitleClassName}
|
||||
titleImageWrapperClassName={textBoxTitleImageWrapperClassName}
|
||||
titleImageClassName={textBoxTitleImageClassName}
|
||||
descriptionClassName={textBoxDescriptionClassName}
|
||||
tagClassName={textBoxTagClassName}
|
||||
buttonContainerClassName={textBoxButtonContainerClassName}
|
||||
buttonClassName={textBoxButtonClassName}
|
||||
buttonTextClassName={textBoxButtonTextClassName}
|
||||
ariaLabel={ariaLabel}
|
||||
>
|
||||
{testimonials.map((testimonial, index) => (
|
||||
<TestimonialCard
|
||||
key={`${testimonial.id}-${index}`}
|
||||
testimonial={testimonial}
|
||||
useInvertedBackground={useInvertedBackground}
|
||||
cardClassName={cardClassName}
|
||||
titleClassName={cardTitleClassName}
|
||||
quoteClassName={cardQuoteClassName}
|
||||
imageClassName={cardImageClassName}
|
||||
/>
|
||||
))}
|
||||
</FullWidthCarousel>
|
||||
);
|
||||
};
|
||||
|
||||
TestimonialCardEleven.displayName = "TestimonialCardEleven";
|
||||
|
||||
export default TestimonialCardEleven;
|
||||
116
src/components/sections/testimonial/TestimonialCardFifteen.tsx
Normal file
116
src/components/sections/testimonial/TestimonialCardFifteen.tsx
Normal file
@@ -0,0 +1,116 @@
|
||||
"use client";
|
||||
|
||||
import { useState, useEffect } from "react";
|
||||
import { Star } from "lucide-react";
|
||||
import TextAnimation from "@/components/text/TextAnimation";
|
||||
import AvatarGroup from "@/components/shared/AvatarGroup";
|
||||
import type { Avatar } from "@/components/shared/AvatarGroup";
|
||||
import { cls } from "@/lib/utils";
|
||||
import { useTheme } from "@/providers/themeProvider/ThemeProvider";
|
||||
import type { InvertedBackground } from "@/providers/themeProvider/config/constants";
|
||||
|
||||
interface TestimonialCardFifteenProps {
|
||||
testimonial: string;
|
||||
rating: number;
|
||||
author: string;
|
||||
avatars: Avatar[];
|
||||
useInvertedBackground: InvertedBackground;
|
||||
ariaLabel?: string;
|
||||
className?: string;
|
||||
containerClassName?: string;
|
||||
ratingClassName?: string;
|
||||
starClassName?: string;
|
||||
testimonialClassName?: string;
|
||||
avatarGroupClassName?: string;
|
||||
avatarClassName?: string;
|
||||
avatarImageClassName?: string;
|
||||
}
|
||||
|
||||
const TestimonialCardFifteen = ({
|
||||
testimonial,
|
||||
rating,
|
||||
author,
|
||||
avatars,
|
||||
useInvertedBackground,
|
||||
ariaLabel = "Testimonial section",
|
||||
className = "",
|
||||
containerClassName = "",
|
||||
ratingClassName = "",
|
||||
starClassName = "",
|
||||
testimonialClassName = "",
|
||||
avatarGroupClassName = "",
|
||||
avatarClassName = "",
|
||||
avatarImageClassName = "",
|
||||
}: TestimonialCardFifteenProps) => {
|
||||
const theme = useTheme();
|
||||
const [isMobile, setIsMobile] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const checkMobile = () => setIsMobile(window.innerWidth < 768);
|
||||
checkMobile();
|
||||
window.addEventListener("resize", checkMobile);
|
||||
return () => window.removeEventListener("resize", checkMobile);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<section
|
||||
aria-label={ariaLabel}
|
||||
className={cls(
|
||||
"relative py-20",
|
||||
useInvertedBackground === "invertCard"
|
||||
? "w-content-width-expanded mx-auto rounded-theme-capped bg-foreground"
|
||||
: "w-full",
|
||||
useInvertedBackground === "invertDefault" && "bg-foreground",
|
||||
className
|
||||
)}
|
||||
>
|
||||
<div className={cls("w-content-width mx-auto flex flex-col items-center gap-6", containerClassName)}>
|
||||
<div className={cls("relative z-1 flex gap-1 -mb-1", ratingClassName)}>
|
||||
{Array.from({ length: 5 }).map((_, index) => (
|
||||
<Star
|
||||
key={index}
|
||||
className={cls(
|
||||
"h-6 w-auto text-accent",
|
||||
index < rating ? "fill-accent" : "fill-transparent",
|
||||
starClassName
|
||||
)}
|
||||
strokeWidth={1.5}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<TextAnimation
|
||||
type={theme.defaultTextAnimation}
|
||||
text={testimonial}
|
||||
variant="words-trigger"
|
||||
as="p"
|
||||
className={cls(
|
||||
"text-3xl md:text-5xl font-medium text-balance text-center leading-tight",
|
||||
(useInvertedBackground === "invertDefault" || useInvertedBackground === "invertCard") && "text-background",
|
||||
testimonialClassName
|
||||
)}
|
||||
/>
|
||||
|
||||
<p className={cls(
|
||||
"text-xl text-center mb-1",
|
||||
(useInvertedBackground === "invertDefault" || useInvertedBackground === "invertCard") && "text-background"
|
||||
)}>
|
||||
{author}
|
||||
</p>
|
||||
|
||||
<AvatarGroup
|
||||
avatars={avatars}
|
||||
maxVisible={isMobile ? 3 : 6}
|
||||
className={cls("justify-center", avatarGroupClassName)}
|
||||
avatarClassName={avatarClassName}
|
||||
avatarImageClassName={cls("h-[var(--width-17_5)] md:h-[var(--width-5)]", avatarImageClassName)}
|
||||
avatarOverlapClassName="-ml-8"
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
TestimonialCardFifteen.displayName = "TestimonialCardFifteen";
|
||||
|
||||
export default TestimonialCardFifteen;
|
||||
230
src/components/sections/testimonial/TestimonialCardFive.tsx
Normal file
230
src/components/sections/testimonial/TestimonialCardFive.tsx
Normal file
@@ -0,0 +1,230 @@
|
||||
"use client";
|
||||
|
||||
import { memo } from "react";
|
||||
|
||||
|
||||
import FullWidthCarousel from "@/components/cardStack/layouts/carousels/FullWidthCarousel";
|
||||
import MediaContent from "@/components/shared/MediaContent";
|
||||
import Tag from "@/components/shared/Tag";
|
||||
import TestimonialAuthor from "@/components/shared/TestimonialAuthor";
|
||||
import { cls, shouldUseInvertedText } from "@/lib/utils";
|
||||
import { useTheme } from "@/providers/themeProvider/ThemeProvider";
|
||||
import type { LucideIcon } from "lucide-react";
|
||||
import type { ButtonConfig, TitleSegment, TextboxLayout, InvertedBackground } from "@/components/cardStack/types";
|
||||
|
||||
type Testimonial = {
|
||||
id: string;
|
||||
name: string;
|
||||
date: string;
|
||||
title: string;
|
||||
quote: string;
|
||||
tag: string;
|
||||
avatarSrc: string;
|
||||
avatarAlt?: string;
|
||||
imageSrc?: string;
|
||||
videoSrc?: string;
|
||||
imageAlt?: string;
|
||||
videoAriaLabel?: string;
|
||||
};
|
||||
|
||||
interface TestimonialCardFiveProps {
|
||||
testimonials: Testimonial[];
|
||||
title: string;
|
||||
titleSegments?: TitleSegment[];
|
||||
description: string;
|
||||
tag?: string;
|
||||
tagIcon?: LucideIcon;
|
||||
buttons?: ButtonConfig[];
|
||||
textboxLayout: TextboxLayout;
|
||||
useInvertedBackground: InvertedBackground;
|
||||
ariaLabel?: string;
|
||||
className?: string;
|
||||
containerClassName?: string;
|
||||
cardClassName?: string;
|
||||
textBoxTitleClassName?: string;
|
||||
textBoxTitleImageWrapperClassName?: string;
|
||||
textBoxTitleImageClassName?: string;
|
||||
textBoxDescriptionClassName?: string;
|
||||
cardTagClassName?: string;
|
||||
cardTitleClassName?: string;
|
||||
cardQuoteClassName?: string;
|
||||
cardAuthorClassName?: string;
|
||||
cardAvatarWrapperClassName?: string;
|
||||
cardAvatarClassName?: string;
|
||||
cardNameClassName?: string;
|
||||
cardDateClassName?: string;
|
||||
cardImageClassName?: string;
|
||||
carouselClassName?: string;
|
||||
dotsClassName?: string;
|
||||
textBoxClassName?: string;
|
||||
textBoxTagClassName?: string;
|
||||
textBoxButtonContainerClassName?: string;
|
||||
textBoxButtonClassName?: string;
|
||||
textBoxButtonTextClassName?: string;
|
||||
}
|
||||
|
||||
interface TestimonialCardProps {
|
||||
testimonial: Testimonial;
|
||||
useInvertedBackground: "noInvert" | "invertDefault" | "invertCard";
|
||||
cardClassName?: string;
|
||||
tagClassName?: string;
|
||||
titleClassName?: string;
|
||||
quoteClassName?: string;
|
||||
authorClassName?: string;
|
||||
avatarWrapperClassName?: string;
|
||||
avatarClassName?: string;
|
||||
nameClassName?: string;
|
||||
dateClassName?: string;
|
||||
imageClassName?: string;
|
||||
}
|
||||
|
||||
const TestimonialCard = memo(({
|
||||
testimonial,
|
||||
useInvertedBackground,
|
||||
cardClassName = "",
|
||||
tagClassName = "",
|
||||
titleClassName = "",
|
||||
quoteClassName = "",
|
||||
authorClassName = "",
|
||||
avatarWrapperClassName = "",
|
||||
avatarClassName = "",
|
||||
nameClassName = "",
|
||||
dateClassName = "",
|
||||
imageClassName = "",
|
||||
}: TestimonialCardProps) => {
|
||||
const theme = useTheme();
|
||||
const shouldUseLightText = shouldUseInvertedText(useInvertedBackground, theme.cardStyle);
|
||||
|
||||
return (
|
||||
<div className={cls("card h-fit md:h-full rounded-theme-capped overflow-hidden flex flex-col-reverse md:grid md:grid-cols-2 gap-0", cardClassName)}>
|
||||
<div className="relative z-1 p-6 md:p-10 flex flex-col gap-10 justify-between">
|
||||
<div className="flex flex-col gap-4 md:gap-6">
|
||||
<Tag
|
||||
text={testimonial.tag}
|
||||
useInvertedBackground={useInvertedBackground}
|
||||
className={tagClassName}
|
||||
/>
|
||||
|
||||
<div className="flex flex-col gap-4 overflow-hidden">
|
||||
<h3 className={cls("text-4xl font-medium leading-tight line-clamp-3", shouldUseLightText ? "text-background" : "text-foreground", titleClassName)}>
|
||||
{testimonial.title}
|
||||
</h3>
|
||||
<blockquote className={cls("text-base md:text-lg leading-tight line-clamp-10", shouldUseLightText ? "text-background/75" : "text-foreground/75", quoteClassName)}>
|
||||
{testimonial.quote}
|
||||
</blockquote>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<TestimonialAuthor
|
||||
name={testimonial.name}
|
||||
subtitle={testimonial.date}
|
||||
imageSrc={testimonial.avatarSrc}
|
||||
imageAlt={testimonial.avatarAlt}
|
||||
useInvertedBackground={useInvertedBackground}
|
||||
className={authorClassName}
|
||||
imageWrapperClassName={avatarWrapperClassName}
|
||||
imageClassName={avatarClassName}
|
||||
nameClassName={nameClassName}
|
||||
subtitleClassName={dateClassName}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="relative z-1 min-h-0 aspect-square">
|
||||
<MediaContent
|
||||
imageSrc={testimonial.imageSrc}
|
||||
videoSrc={testimonial.videoSrc}
|
||||
imageAlt={testimonial.imageAlt}
|
||||
videoAriaLabel={testimonial.videoAriaLabel}
|
||||
imageClassName={cls("!absolute inset-0 w-full h-full object-cover !rounded-none", imageClassName)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
TestimonialCard.displayName = "TestimonialCard";
|
||||
|
||||
const TestimonialCardFive = ({
|
||||
testimonials,
|
||||
title,
|
||||
titleSegments,
|
||||
description,
|
||||
tag,
|
||||
tagIcon,
|
||||
buttons,
|
||||
textboxLayout,
|
||||
useInvertedBackground,
|
||||
ariaLabel = "Testimonials section",
|
||||
className = "",
|
||||
containerClassName = "",
|
||||
cardClassName = "",
|
||||
textBoxTitleClassName = "",
|
||||
textBoxTitleImageWrapperClassName = "",
|
||||
textBoxTitleImageClassName = "",
|
||||
textBoxDescriptionClassName = "",
|
||||
cardTagClassName = "",
|
||||
cardTitleClassName = "",
|
||||
cardQuoteClassName = "",
|
||||
cardAuthorClassName = "",
|
||||
cardAvatarWrapperClassName = "",
|
||||
cardAvatarClassName = "",
|
||||
cardNameClassName = "",
|
||||
cardDateClassName = "",
|
||||
cardImageClassName = "",
|
||||
carouselClassName = "",
|
||||
dotsClassName = "",
|
||||
textBoxClassName = "",
|
||||
textBoxTagClassName = "",
|
||||
textBoxButtonContainerClassName = "",
|
||||
textBoxButtonClassName = "",
|
||||
textBoxButtonTextClassName = "",
|
||||
}: TestimonialCardFiveProps) => {
|
||||
return (
|
||||
<FullWidthCarousel
|
||||
title={title}
|
||||
titleSegments={titleSegments}
|
||||
description={description}
|
||||
tag={tag}
|
||||
tagIcon={tagIcon}
|
||||
buttons={buttons}
|
||||
textboxLayout={textboxLayout}
|
||||
useInvertedBackground={useInvertedBackground}
|
||||
className={className}
|
||||
containerClassName={containerClassName}
|
||||
carouselClassName={carouselClassName}
|
||||
dotsClassName={dotsClassName}
|
||||
textBoxClassName={textBoxClassName}
|
||||
titleClassName={textBoxTitleClassName}
|
||||
titleImageWrapperClassName={textBoxTitleImageWrapperClassName}
|
||||
titleImageClassName={textBoxTitleImageClassName}
|
||||
descriptionClassName={textBoxDescriptionClassName}
|
||||
tagClassName={textBoxTagClassName}
|
||||
buttonContainerClassName={textBoxButtonContainerClassName}
|
||||
buttonClassName={textBoxButtonClassName}
|
||||
buttonTextClassName={textBoxButtonTextClassName}
|
||||
ariaLabel={ariaLabel}
|
||||
>
|
||||
{testimonials.map((testimonial, index) => (
|
||||
<TestimonialCard
|
||||
key={`${testimonial.id}-${index}`}
|
||||
testimonial={testimonial}
|
||||
useInvertedBackground={useInvertedBackground}
|
||||
cardClassName={cardClassName}
|
||||
tagClassName={cardTagClassName}
|
||||
titleClassName={cardTitleClassName}
|
||||
quoteClassName={cardQuoteClassName}
|
||||
authorClassName={cardAuthorClassName}
|
||||
avatarWrapperClassName={cardAvatarWrapperClassName}
|
||||
avatarClassName={cardAvatarClassName}
|
||||
nameClassName={cardNameClassName}
|
||||
dateClassName={cardDateClassName}
|
||||
imageClassName={cardImageClassName}
|
||||
/>
|
||||
))}
|
||||
</FullWidthCarousel>
|
||||
);
|
||||
};
|
||||
|
||||
TestimonialCardFive.displayName = "TestimonialCardFive";
|
||||
|
||||
export default TestimonialCardFive;
|
||||
163
src/components/sections/testimonial/TestimonialCardFour.tsx
Normal file
163
src/components/sections/testimonial/TestimonialCardFour.tsx
Normal file
@@ -0,0 +1,163 @@
|
||||
"use client";
|
||||
|
||||
import TimelineHorizontalCardStack from "@/components/cardStack/layouts/timelines/TimelineHorizontalCardStack";
|
||||
import { cls, shouldUseInvertedText } from "@/lib/utils";
|
||||
import { useTheme } from "@/providers/themeProvider/ThemeProvider";
|
||||
import { Star } from "lucide-react";
|
||||
import type { LucideIcon } from "lucide-react";
|
||||
import type { ButtonConfig, TitleSegment, TextboxLayout, InvertedBackground } from "@/components/cardStack/types";
|
||||
|
||||
type Testimonial = {
|
||||
id: string;
|
||||
name: string;
|
||||
role: string;
|
||||
company: string;
|
||||
rating: number;
|
||||
imageAlt?: string;
|
||||
videoAriaLabel?: string;
|
||||
} & (
|
||||
| { imageSrc: string; videoSrc?: never }
|
||||
| { videoSrc: string; imageSrc?: never }
|
||||
);
|
||||
|
||||
interface TestimonialCardFourProps {
|
||||
testimonials: Testimonial[];
|
||||
title: string;
|
||||
titleSegments?: TitleSegment[];
|
||||
description: string;
|
||||
tag?: string;
|
||||
tagIcon?: LucideIcon;
|
||||
buttons?: ButtonConfig[];
|
||||
textboxLayout: TextboxLayout;
|
||||
useInvertedBackground: InvertedBackground;
|
||||
ariaLabel?: string;
|
||||
className?: string;
|
||||
containerClassName?: string;
|
||||
textBoxTitleClassName?: string;
|
||||
textBoxTitleImageWrapperClassName?: string;
|
||||
textBoxTitleImageClassName?: string;
|
||||
textBoxDescriptionClassName?: string;
|
||||
textBoxClassName?: string;
|
||||
textBoxTagClassName?: string;
|
||||
textBoxButtonContainerClassName?: string;
|
||||
textBoxButtonClassName?: string;
|
||||
textBoxButtonTextClassName?: string;
|
||||
cardClassName?: string;
|
||||
progressBarClassName?: string;
|
||||
cardContentClassName?: string;
|
||||
ratingClassName?: string;
|
||||
nameClassName?: string;
|
||||
roleClassName?: string;
|
||||
companyClassName?: string;
|
||||
mediaContainerClassName?: string;
|
||||
mediaClassName?: string;
|
||||
}
|
||||
|
||||
const TestimonialCardFour = ({
|
||||
testimonials,
|
||||
title,
|
||||
titleSegments,
|
||||
description,
|
||||
tag,
|
||||
tagIcon,
|
||||
buttons,
|
||||
textboxLayout,
|
||||
useInvertedBackground,
|
||||
ariaLabel = "Testimonials section",
|
||||
className = "",
|
||||
containerClassName = "",
|
||||
textBoxTitleClassName = "",
|
||||
textBoxTitleImageWrapperClassName = "",
|
||||
textBoxTitleImageClassName = "",
|
||||
textBoxDescriptionClassName = "",
|
||||
textBoxClassName = "",
|
||||
textBoxTagClassName = "",
|
||||
textBoxButtonContainerClassName = "",
|
||||
textBoxButtonClassName = "",
|
||||
textBoxButtonTextClassName = "",
|
||||
cardClassName = "",
|
||||
progressBarClassName = "",
|
||||
cardContentClassName = "",
|
||||
ratingClassName = "",
|
||||
nameClassName = "",
|
||||
roleClassName = "",
|
||||
companyClassName = "",
|
||||
mediaContainerClassName = "",
|
||||
mediaClassName = "",
|
||||
}: TestimonialCardFourProps) => {
|
||||
const theme = useTheme();
|
||||
const shouldUseLightText = shouldUseInvertedText(useInvertedBackground, theme.cardStyle);
|
||||
const mediaItems = testimonials.map((testimonial) => ({
|
||||
imageSrc: testimonial.imageSrc,
|
||||
videoSrc: testimonial.videoSrc,
|
||||
imageAlt: testimonial.imageAlt || testimonial.name,
|
||||
videoAriaLabel: testimonial.videoAriaLabel || testimonial.name,
|
||||
}));
|
||||
|
||||
return (
|
||||
<TimelineHorizontalCardStack
|
||||
title={title}
|
||||
titleSegments={titleSegments}
|
||||
description={description}
|
||||
tag={tag}
|
||||
tagIcon={tagIcon}
|
||||
buttons={buttons}
|
||||
textboxLayout={textboxLayout}
|
||||
useInvertedBackground={useInvertedBackground}
|
||||
mediaItems={mediaItems}
|
||||
className={className}
|
||||
containerClassName={containerClassName}
|
||||
titleClassName={textBoxTitleClassName}
|
||||
titleImageWrapperClassName={textBoxTitleImageWrapperClassName}
|
||||
titleImageClassName={textBoxTitleImageClassName}
|
||||
descriptionClassName={textBoxDescriptionClassName}
|
||||
textBoxClassName={textBoxClassName}
|
||||
tagClassName={textBoxTagClassName}
|
||||
buttonContainerClassName={textBoxButtonContainerClassName}
|
||||
buttonClassName={textBoxButtonClassName}
|
||||
buttonTextClassName={textBoxButtonTextClassName}
|
||||
cardClassName={cardClassName}
|
||||
progressBarClassName={progressBarClassName}
|
||||
mediaContainerClassName={mediaContainerClassName}
|
||||
mediaClassName={mediaClassName}
|
||||
ariaLabel={ariaLabel}
|
||||
>
|
||||
{testimonials.map((testimonial, index) => (
|
||||
<div
|
||||
key={`${testimonial.id}-${index}`}
|
||||
className={cls("w-full min-h-0 h-fit flex flex-col gap-3", cardContentClassName)}
|
||||
>
|
||||
<div className={cls("flex gap-1", ratingClassName)}>
|
||||
{Array.from({ length: 5 }).map((_, index) => (
|
||||
<Star
|
||||
key={index}
|
||||
className={cls(
|
||||
"h-5 w-auto text-accent",
|
||||
index < testimonial.rating ? "fill-accent" : "fill-transparent"
|
||||
)}
|
||||
strokeWidth={1.5}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<h3 className={cls("text-2xl font-medium leading-[1.1] mt-1", shouldUseLightText ? "text-background" : "text-foreground", nameClassName)}>
|
||||
{testimonial.name}
|
||||
</h3>
|
||||
|
||||
<div className="flex flex-col gap-1">
|
||||
<p className={cls("text-base leading-[1.1]", shouldUseLightText ? "text-background" : "text-foreground", roleClassName)}>
|
||||
{testimonial.role}
|
||||
</p>
|
||||
<p className={cls("text-base leading-[1.1]", shouldUseLightText ? "text-background" : "text-foreground", companyClassName)}>
|
||||
{testimonial.company}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</TimelineHorizontalCardStack>
|
||||
);
|
||||
};
|
||||
|
||||
TestimonialCardFour.displayName = "TestimonialCardFour";
|
||||
|
||||
export default TestimonialCardFour;
|
||||
218
src/components/sections/testimonial/TestimonialCardNine.tsx
Normal file
218
src/components/sections/testimonial/TestimonialCardNine.tsx
Normal file
@@ -0,0 +1,218 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { ArrowLeft, ArrowRight } from "lucide-react";
|
||||
import CardStackTextBox from "@/components/cardStack/CardStackTextBox";
|
||||
import MediaContent from "@/components/shared/MediaContent";
|
||||
import AnimationContainer from "@/components/sections/AnimationContainer";
|
||||
import { cls, shouldUseInvertedText } from "@/lib/utils";
|
||||
import { useTheme } from "@/providers/themeProvider/ThemeProvider";
|
||||
import type { LucideIcon } from "lucide-react";
|
||||
import type { ButtonConfig, TitleSegment } from "@/components/cardStack/types";
|
||||
import type { TextboxLayout, InvertedBackground } from "@/providers/themeProvider/config/constants";
|
||||
|
||||
interface Testimonial {
|
||||
id: string;
|
||||
quote: string;
|
||||
name: string;
|
||||
role: string;
|
||||
imageSrc?: string;
|
||||
videoSrc?: string;
|
||||
imageAlt?: string;
|
||||
videoAriaLabel?: string;
|
||||
}
|
||||
|
||||
interface TestimonialCardNineProps {
|
||||
testimonials: Testimonial[];
|
||||
title: string;
|
||||
titleSegments?: TitleSegment[];
|
||||
description: string;
|
||||
tag?: string;
|
||||
tagIcon?: LucideIcon;
|
||||
buttons?: ButtonConfig[];
|
||||
textboxLayout: TextboxLayout;
|
||||
useInvertedBackground: InvertedBackground;
|
||||
ariaLabel?: string;
|
||||
className?: string;
|
||||
containerClassName?: string;
|
||||
textBoxTitleClassName?: string;
|
||||
textBoxDescriptionClassName?: string;
|
||||
textBoxClassName?: string;
|
||||
textBoxTagClassName?: string;
|
||||
textBoxButtonContainerClassName?: string;
|
||||
textBoxButtonClassName?: string;
|
||||
textBoxButtonTextClassName?: string;
|
||||
titleImageWrapperClassName?: string;
|
||||
titleImageClassName?: string;
|
||||
contentClassName?: string;
|
||||
quoteCardClassName?: string;
|
||||
quoteClassName?: string;
|
||||
nameClassName?: string;
|
||||
roleClassName?: string;
|
||||
navigationClassName?: string;
|
||||
navigationButtonClassName?: string;
|
||||
mediaCardClassName?: string;
|
||||
mediaClassName?: string;
|
||||
}
|
||||
|
||||
const TestimonialCardNine = ({
|
||||
testimonials,
|
||||
title,
|
||||
titleSegments,
|
||||
description,
|
||||
tag,
|
||||
tagIcon,
|
||||
buttons,
|
||||
textboxLayout,
|
||||
useInvertedBackground,
|
||||
ariaLabel = "Testimonials section",
|
||||
className = "",
|
||||
containerClassName = "",
|
||||
textBoxTitleClassName = "",
|
||||
textBoxDescriptionClassName = "",
|
||||
textBoxClassName = "",
|
||||
textBoxTagClassName = "",
|
||||
textBoxButtonContainerClassName = "",
|
||||
textBoxButtonClassName = "",
|
||||
textBoxButtonTextClassName = "",
|
||||
titleImageWrapperClassName = "",
|
||||
titleImageClassName = "",
|
||||
contentClassName = "",
|
||||
quoteCardClassName = "",
|
||||
quoteClassName = "",
|
||||
nameClassName = "",
|
||||
roleClassName = "",
|
||||
navigationClassName = "",
|
||||
navigationButtonClassName = "",
|
||||
mediaCardClassName = "",
|
||||
mediaClassName = "",
|
||||
}: TestimonialCardNineProps) => {
|
||||
const [activeIndex, setActiveIndex] = useState(0);
|
||||
const theme = useTheme();
|
||||
const shouldUseLightText = shouldUseInvertedText(useInvertedBackground, theme.cardStyle);
|
||||
|
||||
const handlePrev = () => {
|
||||
setActiveIndex((prev) => (prev === 0 ? testimonials.length - 1 : prev - 1));
|
||||
};
|
||||
|
||||
const handleNext = () => {
|
||||
setActiveIndex((prev) => (prev === testimonials.length - 1 ? 0 : prev + 1));
|
||||
};
|
||||
|
||||
const activeTestimonial = testimonials[activeIndex];
|
||||
|
||||
return (
|
||||
<section
|
||||
aria-label={ariaLabel}
|
||||
className={cls(
|
||||
"relative py-20",
|
||||
useInvertedBackground === "invertCard"
|
||||
? "w-content-width-expanded mx-auto rounded-theme-capped bg-foreground"
|
||||
: "w-full",
|
||||
useInvertedBackground === "invertDefault" && "bg-foreground",
|
||||
className
|
||||
)}
|
||||
>
|
||||
<div className={cls("w-content-width mx-auto flex flex-col gap-10", containerClassName)}>
|
||||
<CardStackTextBox
|
||||
title={title}
|
||||
titleSegments={titleSegments}
|
||||
description={description}
|
||||
tag={tag}
|
||||
tagIcon={tagIcon}
|
||||
buttons={buttons}
|
||||
textboxLayout={textboxLayout}
|
||||
useInvertedBackground={useInvertedBackground}
|
||||
textBoxClassName={textBoxClassName}
|
||||
titleClassName={textBoxTitleClassName}
|
||||
titleImageWrapperClassName={titleImageWrapperClassName}
|
||||
titleImageClassName={titleImageClassName}
|
||||
descriptionClassName={textBoxDescriptionClassName}
|
||||
tagClassName={textBoxTagClassName}
|
||||
buttonContainerClassName={textBoxButtonContainerClassName}
|
||||
buttonClassName={textBoxButtonClassName}
|
||||
buttonTextClassName={textBoxButtonTextClassName}
|
||||
/>
|
||||
|
||||
<div className={cls(
|
||||
"grid grid-cols-1 gap-6 md:grid-cols-2",
|
||||
contentClassName
|
||||
)}>
|
||||
<div className={cls(
|
||||
"flex flex-col justify-between gap-6 card rounded-theme-capped p-6 md:p-10",
|
||||
quoteCardClassName
|
||||
)}>
|
||||
<AnimationContainer key={activeIndex} animationType="fade" className="relative z-1 flex flex-col gap-6">
|
||||
<blockquote className={cls(
|
||||
"text-2xl md:text-3xl font-medium leading-tight",
|
||||
shouldUseLightText ? "text-background" : "text-foreground",
|
||||
quoteClassName
|
||||
)}>
|
||||
{activeTestimonial.quote}
|
||||
</blockquote>
|
||||
|
||||
<div className="flex flex-col">
|
||||
<p className={cls(
|
||||
"text-lg leading-tight font-medium",
|
||||
shouldUseLightText ? "text-background" : "text-foreground",
|
||||
nameClassName
|
||||
)}>
|
||||
{activeTestimonial.name}
|
||||
</p>
|
||||
<p className={cls(
|
||||
"text-base leading-tight",
|
||||
shouldUseLightText ? "text-background/75" : "text-foreground/75",
|
||||
roleClassName
|
||||
)}>
|
||||
{activeTestimonial.role}
|
||||
</p>
|
||||
</div>
|
||||
</AnimationContainer>
|
||||
|
||||
<div className={cls("relative z-1 flex gap-4 mt-1 md:mt-0", navigationClassName)}>
|
||||
<button
|
||||
onClick={handlePrev}
|
||||
aria-label="Previous testimonial"
|
||||
className={cls(
|
||||
"relative cursor-pointer h-9 w-auto aspect-square rounded-theme flex items-center justify-center primary-button",
|
||||
navigationButtonClassName
|
||||
)}
|
||||
>
|
||||
<ArrowLeft className="h-4/10 w-4/10 text-background" strokeWidth={1.5} />
|
||||
</button>
|
||||
<button
|
||||
onClick={handleNext}
|
||||
aria-label="Next testimonial"
|
||||
className={cls(
|
||||
"relative cursor-pointer h-9 w-auto aspect-square rounded-theme flex items-center justify-center primary-button",
|
||||
navigationButtonClassName
|
||||
)}
|
||||
>
|
||||
<ArrowRight className="h-4/10 w-4/10 text-background" strokeWidth={1.5} />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={cls(
|
||||
"rounded-theme-capped overflow-hidden",
|
||||
mediaCardClassName
|
||||
)}>
|
||||
<AnimationContainer key={activeIndex} animationType="fade">
|
||||
<MediaContent
|
||||
imageSrc={activeTestimonial.imageSrc}
|
||||
videoSrc={activeTestimonial.videoSrc}
|
||||
imageAlt={activeTestimonial.imageAlt || activeTestimonial.name}
|
||||
videoAriaLabel={activeTestimonial.videoAriaLabel || activeTestimonial.name}
|
||||
imageClassName={cls("w-full h-full aspect-square object-cover", mediaClassName)}
|
||||
/>
|
||||
</AnimationContainer>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
TestimonialCardNine.displayName = "TestimonialCardNine";
|
||||
|
||||
export default TestimonialCardNine;
|
||||
212
src/components/sections/testimonial/TestimonialCardOne.tsx
Normal file
212
src/components/sections/testimonial/TestimonialCardOne.tsx
Normal file
@@ -0,0 +1,212 @@
|
||||
"use client";
|
||||
|
||||
import { memo } from "react";
|
||||
import Image from "next/image";
|
||||
import CardStack from "@/components/cardStack/CardStack";
|
||||
import { cls } from "@/lib/utils";
|
||||
import { Star } from "lucide-react";
|
||||
import type { LucideIcon } from "lucide-react";
|
||||
import type { ButtonConfig, CardAnimationType, GridVariant, TitleSegment, TextboxLayout, InvertedBackground } from "@/components/cardStack/types";
|
||||
|
||||
type Testimonial = {
|
||||
id: string;
|
||||
name: string;
|
||||
role: string;
|
||||
company: string;
|
||||
rating: number;
|
||||
imageSrc: string;
|
||||
imageAlt?: string;
|
||||
};
|
||||
|
||||
interface TestimonialCardOneProps {
|
||||
testimonials: Testimonial[];
|
||||
carouselMode?: "auto" | "buttons";
|
||||
uniformGridCustomHeightClasses?: string;
|
||||
gridVariant: GridVariant;
|
||||
animationType: CardAnimationType;
|
||||
title: string;
|
||||
titleSegments?: TitleSegment[];
|
||||
description: string;
|
||||
tag?: string;
|
||||
tagIcon?: LucideIcon;
|
||||
buttons?: ButtonConfig[];
|
||||
textboxLayout: TextboxLayout;
|
||||
useInvertedBackground: InvertedBackground;
|
||||
ariaLabel?: string;
|
||||
className?: string;
|
||||
containerClassName?: string;
|
||||
cardClassName?: string;
|
||||
textBoxTitleClassName?: string;
|
||||
textBoxTitleImageWrapperClassName?: string;
|
||||
textBoxTitleImageClassName?: string;
|
||||
textBoxDescriptionClassName?: string;
|
||||
imageClassName?: string;
|
||||
overlayClassName?: string;
|
||||
ratingClassName?: string;
|
||||
nameClassName?: string;
|
||||
roleClassName?: string;
|
||||
companyClassName?: string;
|
||||
gridClassName?: string;
|
||||
carouselClassName?: string;
|
||||
controlsClassName?: string;
|
||||
textBoxClassName?: string;
|
||||
textBoxTagClassName?: string;
|
||||
textBoxButtonContainerClassName?: string;
|
||||
textBoxButtonClassName?: string;
|
||||
textBoxButtonTextClassName?: string;
|
||||
}
|
||||
|
||||
interface TestimonialCardProps {
|
||||
testimonial: Testimonial;
|
||||
cardClassName?: string;
|
||||
imageClassName?: string;
|
||||
overlayClassName?: string;
|
||||
ratingClassName?: string;
|
||||
nameClassName?: string;
|
||||
roleClassName?: string;
|
||||
companyClassName?: string;
|
||||
}
|
||||
|
||||
const TestimonialCard = memo(({
|
||||
testimonial,
|
||||
cardClassName = "",
|
||||
imageClassName = "",
|
||||
overlayClassName = "",
|
||||
ratingClassName = "",
|
||||
nameClassName = "",
|
||||
roleClassName = "",
|
||||
companyClassName = "",
|
||||
}: TestimonialCardProps) => {
|
||||
return (
|
||||
<div className={cls("relative h-full rounded-theme-capped overflow-hidden group", cardClassName)}>
|
||||
<Image
|
||||
src={testimonial.imageSrc}
|
||||
alt={testimonial.imageAlt || testimonial.name}
|
||||
width={800}
|
||||
height={800}
|
||||
className={cls("relative z-1 w-full h-full object-cover!", imageClassName)}
|
||||
unoptimized={testimonial.imageSrc.startsWith('http') || testimonial.imageSrc.startsWith('//')}
|
||||
aria-hidden={testimonial.imageAlt === ""}
|
||||
/>
|
||||
|
||||
<div className={cls("!absolute z-1 bottom-6 left-6 right-6 card backdrop-blur-xs p-6 flex flex-col gap-3 rounded-theme-capped", overlayClassName)}>
|
||||
<div className={cls("relative z-1 flex gap-1", ratingClassName)}>
|
||||
{Array.from({ length: 5 }).map((_, index) => (
|
||||
<Star
|
||||
key={index}
|
||||
className={cls(
|
||||
"h-5 w-auto text-accent",
|
||||
index < testimonial.rating ? "fill-accent" : "fill-transparent"
|
||||
)}
|
||||
strokeWidth={1.5}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<h3 className={cls("relative z-1 text-2xl font-medium text-foreground leading-[1.1] mt-1", nameClassName)}>
|
||||
{testimonial.name}
|
||||
</h3>
|
||||
|
||||
<div className="relative z-1 flex flex-col gap-1">
|
||||
<p className={cls("text-base text-foreground leading-[1.1]", roleClassName)}>
|
||||
{testimonial.role}
|
||||
</p>
|
||||
<p className={cls("text-base text-foreground leading-[1.1]", companyClassName)}>
|
||||
{testimonial.company}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
TestimonialCard.displayName = "TestimonialCard";
|
||||
|
||||
const TestimonialCardOne = ({
|
||||
testimonials,
|
||||
carouselMode = "buttons",
|
||||
uniformGridCustomHeightClasses = "min-h-95 2xl:min-h-105",
|
||||
gridVariant,
|
||||
animationType,
|
||||
title,
|
||||
titleSegments,
|
||||
description,
|
||||
tag,
|
||||
tagIcon,
|
||||
buttons,
|
||||
textboxLayout,
|
||||
useInvertedBackground,
|
||||
ariaLabel = "Testimonials section",
|
||||
className = "",
|
||||
containerClassName = "",
|
||||
cardClassName = "",
|
||||
textBoxTitleClassName = "",
|
||||
textBoxTitleImageWrapperClassName = "",
|
||||
textBoxTitleImageClassName = "",
|
||||
textBoxDescriptionClassName = "",
|
||||
imageClassName = "",
|
||||
overlayClassName = "",
|
||||
ratingClassName = "",
|
||||
nameClassName = "",
|
||||
roleClassName = "",
|
||||
companyClassName = "",
|
||||
gridClassName = "",
|
||||
carouselClassName = "",
|
||||
controlsClassName = "",
|
||||
textBoxClassName = "",
|
||||
textBoxTagClassName = "",
|
||||
textBoxButtonContainerClassName = "",
|
||||
textBoxButtonClassName = "",
|
||||
textBoxButtonTextClassName = "",
|
||||
}: TestimonialCardOneProps) => {
|
||||
return (
|
||||
<CardStack
|
||||
mode={carouselMode}
|
||||
gridVariant={gridVariant}
|
||||
uniformGridCustomHeightClasses={uniformGridCustomHeightClasses}
|
||||
animationType={animationType}
|
||||
|
||||
title={title}
|
||||
titleSegments={titleSegments}
|
||||
description={description}
|
||||
tag={tag}
|
||||
tagIcon={tagIcon}
|
||||
buttons={buttons}
|
||||
textboxLayout={textboxLayout}
|
||||
useInvertedBackground={useInvertedBackground}
|
||||
className={className}
|
||||
containerClassName={containerClassName}
|
||||
gridClassName={gridClassName}
|
||||
carouselClassName={carouselClassName}
|
||||
controlsClassName={controlsClassName}
|
||||
textBoxClassName={textBoxClassName}
|
||||
titleClassName={textBoxTitleClassName}
|
||||
titleImageWrapperClassName={textBoxTitleImageWrapperClassName}
|
||||
titleImageClassName={textBoxTitleImageClassName}
|
||||
descriptionClassName={textBoxDescriptionClassName}
|
||||
tagClassName={textBoxTagClassName}
|
||||
buttonContainerClassName={textBoxButtonContainerClassName}
|
||||
buttonClassName={textBoxButtonClassName}
|
||||
buttonTextClassName={textBoxButtonTextClassName}
|
||||
ariaLabel={ariaLabel}
|
||||
>
|
||||
{testimonials.map((testimonial, index) => (
|
||||
<TestimonialCard
|
||||
key={`${testimonial.id}-${index}`}
|
||||
testimonial={testimonial}
|
||||
cardClassName={cardClassName}
|
||||
imageClassName={imageClassName}
|
||||
overlayClassName={overlayClassName}
|
||||
ratingClassName={ratingClassName}
|
||||
nameClassName={nameClassName}
|
||||
roleClassName={roleClassName}
|
||||
companyClassName={companyClassName}
|
||||
/>
|
||||
))}
|
||||
</CardStack>
|
||||
);
|
||||
};
|
||||
|
||||
TestimonialCardOne.displayName = "TestimonialCardOne";
|
||||
|
||||
export default TestimonialCardOne;
|
||||
181
src/components/sections/testimonial/TestimonialCardSeven.tsx
Normal file
181
src/components/sections/testimonial/TestimonialCardSeven.tsx
Normal file
@@ -0,0 +1,181 @@
|
||||
"use client";
|
||||
|
||||
import Image from "next/image";
|
||||
import CardList from "@/components/cardStack/CardList";
|
||||
import Button from "@/components/button/Button";
|
||||
import { cls, shouldUseInvertedText } from "@/lib/utils";
|
||||
import { getButtonProps } from "@/lib/buttonUtils";
|
||||
import { useTheme } from "@/providers/themeProvider/ThemeProvider";
|
||||
import type { LucideIcon } from "lucide-react";
|
||||
import type { ButtonConfig, CardAnimationType, TitleSegment } from "@/components/cardStack/types";
|
||||
import type { TextboxLayout, InvertedBackground } from "@/providers/themeProvider/config/constants";
|
||||
|
||||
interface TestimonialCard {
|
||||
id: string;
|
||||
companyName: string;
|
||||
companyLogo?: string;
|
||||
companyLogoAlt?: string;
|
||||
quote: string;
|
||||
author: string;
|
||||
buttons?: ButtonConfig[];
|
||||
}
|
||||
|
||||
interface TestimonialCardSevenProps {
|
||||
testimonials: TestimonialCard[];
|
||||
animationType: CardAnimationType;
|
||||
title: string;
|
||||
titleSegments?: TitleSegment[];
|
||||
description: string;
|
||||
tag?: string;
|
||||
tagIcon?: LucideIcon;
|
||||
buttons?: ButtonConfig[];
|
||||
textboxLayout: TextboxLayout;
|
||||
useInvertedBackground: InvertedBackground;
|
||||
ariaLabel?: string;
|
||||
className?: string;
|
||||
containerClassName?: string;
|
||||
cardClassName?: string;
|
||||
textBoxTitleClassName?: string;
|
||||
textBoxDescriptionClassName?: string;
|
||||
textBoxClassName?: string;
|
||||
textBoxTagClassName?: string;
|
||||
textBoxButtonContainerClassName?: string;
|
||||
textBoxButtonClassName?: string;
|
||||
textBoxButtonTextClassName?: string;
|
||||
titleImageWrapperClassName?: string;
|
||||
titleImageClassName?: string;
|
||||
cardContentClassName?: string;
|
||||
companyLogoClassName?: string;
|
||||
companyNameClassName?: string;
|
||||
quoteClassName?: string;
|
||||
authorClassName?: string;
|
||||
testimonialButtonClassName?: string;
|
||||
testimonialButtonTextClassName?: string;
|
||||
}
|
||||
|
||||
const TestimonialCardSeven = ({
|
||||
testimonials,
|
||||
animationType,
|
||||
title,
|
||||
titleSegments,
|
||||
description,
|
||||
tag,
|
||||
tagIcon,
|
||||
buttons,
|
||||
textboxLayout,
|
||||
useInvertedBackground,
|
||||
ariaLabel = "Testimonial section",
|
||||
className = "",
|
||||
containerClassName = "",
|
||||
cardClassName = "",
|
||||
textBoxTitleClassName = "",
|
||||
textBoxDescriptionClassName = "",
|
||||
textBoxClassName = "",
|
||||
textBoxTagClassName = "",
|
||||
textBoxButtonContainerClassName = "",
|
||||
textBoxButtonClassName = "",
|
||||
textBoxButtonTextClassName = "",
|
||||
titleImageWrapperClassName = "",
|
||||
titleImageClassName = "",
|
||||
cardContentClassName = "",
|
||||
companyLogoClassName = "",
|
||||
companyNameClassName = "",
|
||||
quoteClassName = "",
|
||||
authorClassName = "",
|
||||
testimonialButtonClassName = "",
|
||||
testimonialButtonTextClassName = "",
|
||||
}: TestimonialCardSevenProps) => {
|
||||
const theme = useTheme();
|
||||
const shouldUseLightText = shouldUseInvertedText(useInvertedBackground, theme.cardStyle);
|
||||
|
||||
return (
|
||||
<CardList
|
||||
title={title}
|
||||
titleSegments={titleSegments}
|
||||
description={description}
|
||||
tag={tag}
|
||||
tagIcon={tagIcon}
|
||||
buttons={buttons}
|
||||
textboxLayout={textboxLayout}
|
||||
animationType={animationType}
|
||||
|
||||
useInvertedBackground={useInvertedBackground}
|
||||
className={className}
|
||||
containerClassName={containerClassName}
|
||||
cardClassName={cardClassName}
|
||||
titleClassName={textBoxTitleClassName}
|
||||
descriptionClassName={textBoxDescriptionClassName}
|
||||
textBoxClassName={textBoxClassName}
|
||||
tagClassName={textBoxTagClassName}
|
||||
buttonContainerClassName={textBoxButtonContainerClassName}
|
||||
buttonClassName={textBoxButtonClassName}
|
||||
buttonTextClassName={textBoxButtonTextClassName}
|
||||
titleImageWrapperClassName={titleImageWrapperClassName}
|
||||
titleImageClassName={titleImageClassName}
|
||||
ariaLabel={ariaLabel}
|
||||
>
|
||||
{testimonials.map((testimonial) => (
|
||||
<div
|
||||
key={testimonial.id}
|
||||
className={cls(
|
||||
"relative z-1 w-full min-h-0 h-full flex flex-col md:flex-row gap-6 p-6 md:p-15",
|
||||
cardContentClassName
|
||||
)}
|
||||
>
|
||||
<div className="relative z-1 w-full md:w-1/2">
|
||||
{testimonial.companyLogo ? (
|
||||
<Image
|
||||
src={testimonial.companyLogo}
|
||||
alt={testimonial.companyLogoAlt || testimonial.companyName}
|
||||
width={120}
|
||||
height={40}
|
||||
className={cls("h-15 w-auto object-contain", companyLogoClassName)}
|
||||
unoptimized={testimonial.companyLogo.startsWith('http') || testimonial.companyLogo.startsWith('//')}
|
||||
/>
|
||||
) : (
|
||||
<h2 className={cls(
|
||||
"text-6xl font-medium leading-[1.1]",
|
||||
shouldUseLightText && "text-background",
|
||||
companyNameClassName
|
||||
)}>
|
||||
{testimonial.companyName}
|
||||
</h2>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="relative z-1 w-full h-px bg-foreground/20 md:hidden" />
|
||||
|
||||
<div className="relative z-1 w-full md:w-1/2 flex flex-col gap-4">
|
||||
<blockquote className={cls(
|
||||
"text-lg md:text-xl leading-[1.4]",
|
||||
shouldUseLightText ? "text-background" : "text-foreground",
|
||||
quoteClassName
|
||||
)}>
|
||||
{testimonial.quote}
|
||||
</blockquote>
|
||||
|
||||
<p className={cls(
|
||||
"text-sm",
|
||||
shouldUseLightText ? "text-background/75" : "text-foreground/75",
|
||||
authorClassName
|
||||
)}>
|
||||
{testimonial.author}
|
||||
</p>
|
||||
|
||||
{testimonial.buttons && testimonial.buttons.length > 0 && (
|
||||
<div className="mt-3 flex gap-4">
|
||||
{testimonial.buttons.slice(0, 2).map((button, index) => (
|
||||
<Button key={`${button.text}-${index}`} {...getButtonProps(button, index, theme.defaultButtonVariant, testimonialButtonClassName, testimonialButtonTextClassName)} />
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</CardList>
|
||||
);
|
||||
};
|
||||
|
||||
TestimonialCardSeven.displayName = "TestimonialCardSeven";
|
||||
|
||||
export default TestimonialCardSeven;
|
||||
197
src/components/sections/testimonial/TestimonialCardSix.tsx
Normal file
197
src/components/sections/testimonial/TestimonialCardSix.tsx
Normal file
@@ -0,0 +1,197 @@
|
||||
"use client";
|
||||
|
||||
import { memo } from "react";
|
||||
import AutoCarousel from "@/components/cardStack/layouts/carousels/AutoCarousel";
|
||||
import TestimonialAuthor from "@/components/shared/TestimonialAuthor";
|
||||
import { cls, shouldUseInvertedText } from "@/lib/utils";
|
||||
import { useTheme } from "@/providers/themeProvider/ThemeProvider";
|
||||
import { Quote } from "lucide-react";
|
||||
import type { LucideIcon } from "lucide-react";
|
||||
import type { CardAnimationType, ButtonConfig, TitleSegment, TextboxLayout, InvertedBackground } from "@/components/cardStack/types";
|
||||
|
||||
type Testimonial = {
|
||||
id: string;
|
||||
name: string;
|
||||
handle: string;
|
||||
testimonial: string;
|
||||
imageSrc?: string;
|
||||
imageAlt?: string;
|
||||
icon?: LucideIcon;
|
||||
};
|
||||
|
||||
interface TestimonialCardSixProps {
|
||||
testimonials: Testimonial[];
|
||||
animationType: CardAnimationType;
|
||||
title: string;
|
||||
titleSegments?: TitleSegment[];
|
||||
description: string;
|
||||
textboxLayout: TextboxLayout;
|
||||
useInvertedBackground: InvertedBackground;
|
||||
tag?: string;
|
||||
tagIcon?: LucideIcon;
|
||||
buttons?: ButtonConfig[];
|
||||
speed?: number;
|
||||
topMarqueeDirection?: "left" | "right";
|
||||
ariaLabel?: string;
|
||||
className?: string;
|
||||
containerClassName?: string;
|
||||
carouselClassName?: string;
|
||||
bottomCarouselClassName?: string;
|
||||
cardClassName?: string;
|
||||
testimonialClassName?: string;
|
||||
imageWrapperClassName?: string;
|
||||
imageClassName?: string;
|
||||
iconClassName?: string;
|
||||
nameClassName?: string;
|
||||
handleClassName?: string;
|
||||
textBoxClassName?: string;
|
||||
textBoxTitleClassName?: string;
|
||||
textBoxTitleImageWrapperClassName?: string;
|
||||
textBoxTitleImageClassName?: string;
|
||||
textBoxDescriptionClassName?: string;
|
||||
textBoxTagClassName?: string;
|
||||
textBoxButtonContainerClassName?: string;
|
||||
textBoxButtonClassName?: string;
|
||||
textBoxButtonTextClassName?: string;
|
||||
}
|
||||
|
||||
interface TestimonialCardProps {
|
||||
testimonial: Testimonial;
|
||||
useInvertedBackground: "noInvert" | "invertDefault" | "invertCard";
|
||||
cardClassName?: string;
|
||||
testimonialClassName?: string;
|
||||
imageWrapperClassName?: string;
|
||||
imageClassName?: string;
|
||||
iconClassName?: string;
|
||||
nameClassName?: string;
|
||||
handleClassName?: string;
|
||||
}
|
||||
|
||||
const TestimonialCard = memo(({
|
||||
testimonial,
|
||||
useInvertedBackground,
|
||||
cardClassName = "",
|
||||
testimonialClassName = "",
|
||||
imageWrapperClassName = "",
|
||||
imageClassName = "",
|
||||
iconClassName = "",
|
||||
nameClassName = "",
|
||||
handleClassName = "",
|
||||
}: TestimonialCardProps) => {
|
||||
const Icon = testimonial.icon || Quote;
|
||||
const theme = useTheme();
|
||||
const shouldUseLightText = shouldUseInvertedText(useInvertedBackground, theme.cardStyle);
|
||||
|
||||
return (
|
||||
<div className={cls("relative h-full card rounded-theme-capped p-6 min-h-0 flex flex-col gap-10", cardClassName)}>
|
||||
<p className={cls("relative z-1 text-lg leading-tight line-clamp-2", shouldUseLightText ? "text-background" : "text-foreground", testimonialClassName)}>
|
||||
{testimonial.testimonial}
|
||||
</p>
|
||||
|
||||
<TestimonialAuthor
|
||||
name={testimonial.name}
|
||||
subtitle={testimonial.handle}
|
||||
imageSrc={testimonial.imageSrc}
|
||||
imageAlt={testimonial.imageAlt}
|
||||
icon={Icon}
|
||||
useInvertedBackground={useInvertedBackground}
|
||||
imageWrapperClassName={imageWrapperClassName}
|
||||
imageClassName={imageClassName}
|
||||
iconClassName={iconClassName}
|
||||
nameClassName={nameClassName}
|
||||
subtitleClassName={handleClassName}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
TestimonialCard.displayName = "TestimonialCard";
|
||||
|
||||
const TestimonialCardSix = ({
|
||||
testimonials,
|
||||
animationType,
|
||||
title,
|
||||
titleSegments,
|
||||
description,
|
||||
textboxLayout,
|
||||
useInvertedBackground,
|
||||
tag,
|
||||
tagIcon,
|
||||
buttons,
|
||||
speed = 40,
|
||||
topMarqueeDirection = "left",
|
||||
ariaLabel = "Testimonials section",
|
||||
className = "",
|
||||
containerClassName = "",
|
||||
carouselClassName = "",
|
||||
bottomCarouselClassName = "",
|
||||
cardClassName = "",
|
||||
testimonialClassName = "",
|
||||
imageWrapperClassName = "",
|
||||
imageClassName = "",
|
||||
iconClassName = "",
|
||||
nameClassName = "",
|
||||
handleClassName = "",
|
||||
textBoxClassName = "",
|
||||
textBoxTitleClassName = "",
|
||||
textBoxTitleImageWrapperClassName = "",
|
||||
textBoxTitleImageClassName = "",
|
||||
textBoxDescriptionClassName = "",
|
||||
textBoxTagClassName = "",
|
||||
textBoxButtonContainerClassName = "",
|
||||
textBoxButtonClassName = "",
|
||||
textBoxButtonTextClassName = "",
|
||||
}: TestimonialCardSixProps) => {
|
||||
return (
|
||||
<AutoCarousel
|
||||
speed={speed}
|
||||
uniformGridCustomHeightClasses="min-h-none"
|
||||
animationType={animationType}
|
||||
title={title}
|
||||
titleSegments={titleSegments}
|
||||
description={description}
|
||||
tag={tag}
|
||||
tagIcon={tagIcon}
|
||||
buttons={buttons}
|
||||
textboxLayout={textboxLayout}
|
||||
useInvertedBackground={useInvertedBackground}
|
||||
showTextBox={true}
|
||||
dualMarquee={true}
|
||||
topMarqueeDirection={topMarqueeDirection}
|
||||
carouselClassName={carouselClassName}
|
||||
bottomCarouselClassName={bottomCarouselClassName}
|
||||
containerClassName={containerClassName}
|
||||
className={className}
|
||||
textBoxClassName={textBoxClassName}
|
||||
titleClassName={textBoxTitleClassName}
|
||||
titleImageWrapperClassName={textBoxTitleImageWrapperClassName}
|
||||
titleImageClassName={textBoxTitleImageClassName}
|
||||
descriptionClassName={textBoxDescriptionClassName}
|
||||
tagClassName={textBoxTagClassName}
|
||||
buttonContainerClassName={textBoxButtonContainerClassName}
|
||||
buttonClassName={textBoxButtonClassName}
|
||||
buttonTextClassName={textBoxButtonTextClassName}
|
||||
ariaLabel={ariaLabel}
|
||||
itemClassName="w-60! md:w-carousel-item-3! xl:w-carousel-item-4!"
|
||||
>
|
||||
{testimonials.map((testimonial, index) => (
|
||||
<TestimonialCard
|
||||
key={`${testimonial.id}-${index}`}
|
||||
testimonial={testimonial}
|
||||
useInvertedBackground={useInvertedBackground}
|
||||
cardClassName={cardClassName}
|
||||
testimonialClassName={testimonialClassName}
|
||||
imageWrapperClassName={imageWrapperClassName}
|
||||
imageClassName={imageClassName}
|
||||
iconClassName={iconClassName}
|
||||
nameClassName={nameClassName}
|
||||
handleClassName={handleClassName}
|
||||
/>
|
||||
))}
|
||||
</AutoCarousel>
|
||||
);
|
||||
};
|
||||
|
||||
TestimonialCardSix.displayName = "TestimonialCardSix";
|
||||
|
||||
export default TestimonialCardSix;
|
||||
235
src/components/sections/testimonial/TestimonialCardTen.tsx
Normal file
235
src/components/sections/testimonial/TestimonialCardTen.tsx
Normal file
@@ -0,0 +1,235 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { ArrowLeft, ArrowRight } from "lucide-react";
|
||||
import CardStackTextBox from "@/components/cardStack/CardStackTextBox";
|
||||
import MediaContent from "@/components/shared/MediaContent";
|
||||
import AnimationContainer from "@/components/sections/AnimationContainer";
|
||||
import { cls, shouldUseInvertedText } from "@/lib/utils";
|
||||
import { useTheme } from "@/providers/themeProvider/ThemeProvider";
|
||||
import type { LucideIcon } from "lucide-react";
|
||||
import type { ButtonConfig, TitleSegment } from "@/components/cardStack/types";
|
||||
import type { TextboxLayout, InvertedBackground } from "@/providers/themeProvider/config/constants";
|
||||
|
||||
interface Testimonial {
|
||||
id: string;
|
||||
title: string;
|
||||
quote: string;
|
||||
name: string;
|
||||
role: string;
|
||||
imageSrc?: string;
|
||||
videoSrc?: string;
|
||||
imageAlt?: string;
|
||||
videoAriaLabel?: string;
|
||||
}
|
||||
|
||||
interface TestimonialCardTenProps {
|
||||
testimonials: Testimonial[];
|
||||
title: string;
|
||||
titleSegments?: TitleSegment[];
|
||||
description: string;
|
||||
tag?: string;
|
||||
tagIcon?: LucideIcon;
|
||||
buttons?: ButtonConfig[];
|
||||
textboxLayout: TextboxLayout;
|
||||
useInvertedBackground: InvertedBackground;
|
||||
ariaLabel?: string;
|
||||
className?: string;
|
||||
containerClassName?: string;
|
||||
textBoxTitleClassName?: string;
|
||||
textBoxDescriptionClassName?: string;
|
||||
textBoxClassName?: string;
|
||||
textBoxTagClassName?: string;
|
||||
textBoxButtonContainerClassName?: string;
|
||||
textBoxButtonClassName?: string;
|
||||
textBoxButtonTextClassName?: string;
|
||||
titleImageWrapperClassName?: string;
|
||||
titleImageClassName?: string;
|
||||
contentClassName?: string;
|
||||
quoteCardClassName?: string;
|
||||
testimonialTitleClassName?: string;
|
||||
quoteClassName?: string;
|
||||
nameClassName?: string;
|
||||
roleClassName?: string;
|
||||
navigationClassName?: string;
|
||||
navigationButtonClassName?: string;
|
||||
mediaCardClassName?: string;
|
||||
mediaClassName?: string;
|
||||
}
|
||||
|
||||
const TestimonialCardTen = ({
|
||||
testimonials,
|
||||
title,
|
||||
titleSegments,
|
||||
description,
|
||||
tag,
|
||||
tagIcon,
|
||||
buttons,
|
||||
textboxLayout,
|
||||
useInvertedBackground,
|
||||
ariaLabel = "Testimonials section",
|
||||
className = "",
|
||||
containerClassName = "",
|
||||
textBoxTitleClassName = "",
|
||||
textBoxDescriptionClassName = "",
|
||||
textBoxClassName = "",
|
||||
textBoxTagClassName = "",
|
||||
textBoxButtonContainerClassName = "",
|
||||
textBoxButtonClassName = "",
|
||||
textBoxButtonTextClassName = "",
|
||||
titleImageWrapperClassName = "",
|
||||
titleImageClassName = "",
|
||||
contentClassName = "",
|
||||
quoteCardClassName = "",
|
||||
testimonialTitleClassName = "",
|
||||
quoteClassName = "",
|
||||
nameClassName = "",
|
||||
roleClassName = "",
|
||||
navigationClassName = "",
|
||||
navigationButtonClassName = "",
|
||||
mediaCardClassName = "",
|
||||
mediaClassName = "",
|
||||
}: TestimonialCardTenProps) => {
|
||||
const [activeIndex, setActiveIndex] = useState(0);
|
||||
const theme = useTheme();
|
||||
const shouldUseLightText = shouldUseInvertedText(useInvertedBackground, theme.cardStyle);
|
||||
|
||||
const handlePrev = () => {
|
||||
setActiveIndex((prev) => (prev === 0 ? testimonials.length - 1 : prev - 1));
|
||||
};
|
||||
|
||||
const handleNext = () => {
|
||||
setActiveIndex((prev) => (prev === testimonials.length - 1 ? 0 : prev + 1));
|
||||
};
|
||||
|
||||
const activeTestimonial = testimonials[activeIndex];
|
||||
|
||||
return (
|
||||
<section
|
||||
aria-label={ariaLabel}
|
||||
className={cls(
|
||||
"relative py-20",
|
||||
useInvertedBackground === "invertCard"
|
||||
? "w-content-width-expanded mx-auto rounded-theme-capped bg-foreground"
|
||||
: "w-full",
|
||||
useInvertedBackground === "invertDefault" && "bg-foreground",
|
||||
className
|
||||
)}
|
||||
>
|
||||
<div className={cls("w-content-width mx-auto flex flex-col gap-10", containerClassName)}>
|
||||
<CardStackTextBox
|
||||
title={title}
|
||||
titleSegments={titleSegments}
|
||||
description={description}
|
||||
tag={tag}
|
||||
tagIcon={tagIcon}
|
||||
buttons={buttons}
|
||||
textboxLayout={textboxLayout}
|
||||
useInvertedBackground={useInvertedBackground}
|
||||
textBoxClassName={textBoxClassName}
|
||||
titleClassName={textBoxTitleClassName}
|
||||
titleImageWrapperClassName={titleImageWrapperClassName}
|
||||
titleImageClassName={titleImageClassName}
|
||||
descriptionClassName={textBoxDescriptionClassName}
|
||||
tagClassName={textBoxTagClassName}
|
||||
buttonContainerClassName={textBoxButtonContainerClassName}
|
||||
buttonClassName={textBoxButtonClassName}
|
||||
buttonTextClassName={textBoxButtonTextClassName}
|
||||
/>
|
||||
|
||||
<div className={cls(
|
||||
"grid grid-cols-1 gap-6 md:grid-cols-2",
|
||||
contentClassName
|
||||
)}>
|
||||
<div className={cls(
|
||||
"flex flex-col justify-between gap-6 card rounded-theme-capped p-6 md:p-10",
|
||||
quoteCardClassName
|
||||
)}>
|
||||
<AnimationContainer key={activeIndex} animationType="fade" className="relative z-1 flex flex-col justify-between gap-6 flex-1">
|
||||
<div className="flex flex-col gap-3">
|
||||
<h3 className={cls(
|
||||
"text-2xl md:text-3xl font-medium leading-tight",
|
||||
shouldUseLightText ? "text-background" : "text-foreground",
|
||||
testimonialTitleClassName
|
||||
)}>
|
||||
{activeTestimonial.title}
|
||||
</h3>
|
||||
|
||||
<blockquote className={cls(
|
||||
"text-base md:text-lg leading-tight",
|
||||
shouldUseLightText ? "text-background/75" : "text-foreground/75",
|
||||
quoteClassName
|
||||
)}>
|
||||
“{activeTestimonial.quote}”
|
||||
</blockquote>
|
||||
</div>
|
||||
|
||||
<div className={cls(
|
||||
"w-full h-px md:hidden",
|
||||
shouldUseLightText ? "bg-background/20" : "bg-foreground/20"
|
||||
)} />
|
||||
|
||||
<div className={cls("flex items-center justify-between gap-4", navigationClassName)}>
|
||||
<div className="flex flex-col">
|
||||
<p className={cls(
|
||||
"text-base leading-tight font-medium",
|
||||
shouldUseLightText ? "text-background" : "text-foreground",
|
||||
nameClassName
|
||||
)}>
|
||||
{activeTestimonial.name}
|
||||
</p>
|
||||
<p className={cls(
|
||||
"text-sm leading-tight",
|
||||
shouldUseLightText ? "text-background/75" : "text-foreground/75",
|
||||
roleClassName
|
||||
)}>
|
||||
{activeTestimonial.role}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="flex gap-4">
|
||||
<button
|
||||
onClick={handlePrev}
|
||||
aria-label="Previous testimonial"
|
||||
className={cls(
|
||||
"relative cursor-pointer h-9 w-auto aspect-square rounded-theme flex items-center justify-center primary-button",
|
||||
navigationButtonClassName
|
||||
)}
|
||||
>
|
||||
<ArrowLeft className="h-4/10 w-4/10 text-background" strokeWidth={1.5} />
|
||||
</button>
|
||||
<button
|
||||
onClick={handleNext}
|
||||
aria-label="Next testimonial"
|
||||
className={cls(
|
||||
"relative cursor-pointer h-9 w-auto aspect-square rounded-theme flex items-center justify-center primary-button",
|
||||
navigationButtonClassName
|
||||
)}
|
||||
>
|
||||
<ArrowRight className="h-4/10 w-4/10 text-background" strokeWidth={1.5} />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</AnimationContainer>
|
||||
</div>
|
||||
|
||||
<div className={cls("rounded-theme-capped overflow-hidden", mediaCardClassName)}>
|
||||
<AnimationContainer key={activeIndex} animationType="fade">
|
||||
<MediaContent
|
||||
imageSrc={activeTestimonial.imageSrc}
|
||||
videoSrc={activeTestimonial.videoSrc}
|
||||
imageAlt={activeTestimonial.imageAlt || activeTestimonial.name}
|
||||
videoAriaLabel={activeTestimonial.videoAriaLabel || activeTestimonial.name}
|
||||
imageClassName={cls("w-full h-full aspect-square object-cover", mediaClassName)}
|
||||
/>
|
||||
</AnimationContainer>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
TestimonialCardTen.displayName = "TestimonialCardTen";
|
||||
|
||||
export default TestimonialCardTen;
|
||||
233
src/components/sections/testimonial/TestimonialCardThirteen.tsx
Normal file
233
src/components/sections/testimonial/TestimonialCardThirteen.tsx
Normal file
@@ -0,0 +1,233 @@
|
||||
"use client";
|
||||
|
||||
import { memo } from "react";
|
||||
import CardStack from "@/components/cardStack/CardStack";
|
||||
import TestimonialAuthor from "@/components/shared/TestimonialAuthor";
|
||||
import { cls, shouldUseInvertedText } from "@/lib/utils";
|
||||
import { useTheme } from "@/providers/themeProvider/ThemeProvider";
|
||||
import { Quote, Star } from "lucide-react";
|
||||
import type { LucideIcon } from "lucide-react";
|
||||
import type { ButtonConfig, CardAnimationType, TitleSegment, TextboxLayout, InvertedBackground } from "@/components/cardStack/types";
|
||||
|
||||
type Testimonial = {
|
||||
id: string;
|
||||
name: string;
|
||||
handle: string;
|
||||
testimonial: string;
|
||||
rating: number;
|
||||
imageSrc?: string;
|
||||
imageAlt?: string;
|
||||
icon?: LucideIcon;
|
||||
};
|
||||
|
||||
interface TestimonialCardThirteenProps {
|
||||
testimonials: Testimonial[];
|
||||
showRating: boolean;
|
||||
carouselMode?: "auto" | "buttons";
|
||||
uniformGridCustomHeightClasses?: string;
|
||||
animationType: CardAnimationType;
|
||||
title: string;
|
||||
titleSegments?: TitleSegment[];
|
||||
description: string;
|
||||
tag?: string;
|
||||
tagIcon?: LucideIcon;
|
||||
buttons?: ButtonConfig[];
|
||||
textboxLayout: TextboxLayout;
|
||||
useInvertedBackground: InvertedBackground;
|
||||
ariaLabel?: string;
|
||||
className?: string;
|
||||
containerClassName?: string;
|
||||
cardClassName?: string;
|
||||
textBoxTitleClassName?: string;
|
||||
textBoxTitleImageWrapperClassName?: string;
|
||||
textBoxTitleImageClassName?: string;
|
||||
textBoxDescriptionClassName?: string;
|
||||
imageWrapperClassName?: string;
|
||||
imageClassName?: string;
|
||||
iconClassName?: string;
|
||||
nameClassName?: string;
|
||||
handleClassName?: string;
|
||||
testimonialClassName?: string;
|
||||
ratingClassName?: string;
|
||||
contentWrapperClassName?: string;
|
||||
gridClassName?: string;
|
||||
carouselClassName?: string;
|
||||
controlsClassName?: string;
|
||||
textBoxClassName?: string;
|
||||
textBoxTagClassName?: string;
|
||||
textBoxButtonContainerClassName?: string;
|
||||
textBoxButtonClassName?: string;
|
||||
textBoxButtonTextClassName?: string;
|
||||
}
|
||||
|
||||
interface TestimonialCardProps {
|
||||
testimonial: Testimonial;
|
||||
showRating: boolean;
|
||||
useInvertedBackground: "noInvert" | "invertDefault" | "invertCard";
|
||||
cardClassName?: string;
|
||||
imageWrapperClassName?: string;
|
||||
imageClassName?: string;
|
||||
iconClassName?: string;
|
||||
nameClassName?: string;
|
||||
handleClassName?: string;
|
||||
testimonialClassName?: string;
|
||||
ratingClassName?: string;
|
||||
contentWrapperClassName?: string;
|
||||
}
|
||||
|
||||
const TestimonialCard = memo(({
|
||||
testimonial,
|
||||
showRating,
|
||||
useInvertedBackground,
|
||||
cardClassName = "",
|
||||
imageWrapperClassName = "",
|
||||
imageClassName = "",
|
||||
iconClassName = "",
|
||||
nameClassName = "",
|
||||
handleClassName = "",
|
||||
testimonialClassName = "",
|
||||
ratingClassName = "",
|
||||
contentWrapperClassName = "",
|
||||
}: TestimonialCardProps) => {
|
||||
const Icon = testimonial.icon || Quote;
|
||||
const theme = useTheme();
|
||||
const shouldUseLightText = shouldUseInvertedText(useInvertedBackground, theme.cardStyle);
|
||||
|
||||
return (
|
||||
<div className={cls("relative h-full card rounded-theme-capped p-6 flex flex-col justify-between", showRating ? "gap-5" : "gap-16", cardClassName)}>
|
||||
<div className={cls("flex flex-col gap-5 items-start", contentWrapperClassName)}>
|
||||
{showRating ? (
|
||||
<div className={cls("relative z-1 flex gap-1", ratingClassName)}>
|
||||
{Array.from({ length: 5 }).map((_, index) => (
|
||||
<Star
|
||||
key={index}
|
||||
className={cls(
|
||||
"h-5 w-auto text-accent",
|
||||
index < testimonial.rating ? "fill-accent" : "fill-transparent"
|
||||
)}
|
||||
strokeWidth={1.5}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<Quote className="h-6 w-auto text-accent fill-accent" strokeWidth={1.5} />
|
||||
)}
|
||||
|
||||
<p className={cls("relative z-1 text-lg leading-[1.2]", shouldUseLightText ? "text-background" : "text-foreground", testimonialClassName)}>
|
||||
{testimonial.testimonial}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<TestimonialAuthor
|
||||
name={testimonial.name}
|
||||
subtitle={testimonial.handle}
|
||||
imageSrc={testimonial.imageSrc}
|
||||
imageAlt={testimonial.imageAlt}
|
||||
icon={Icon}
|
||||
useInvertedBackground={useInvertedBackground}
|
||||
imageWrapperClassName={imageWrapperClassName}
|
||||
imageClassName={imageClassName}
|
||||
iconClassName={iconClassName}
|
||||
nameClassName={nameClassName}
|
||||
subtitleClassName={handleClassName}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
TestimonialCard.displayName = "TestimonialCard";
|
||||
|
||||
const TestimonialCardThirteen = ({
|
||||
testimonials,
|
||||
showRating,
|
||||
carouselMode = "buttons",
|
||||
uniformGridCustomHeightClasses = "min-h-none",
|
||||
animationType,
|
||||
title,
|
||||
titleSegments,
|
||||
description,
|
||||
tag,
|
||||
tagIcon,
|
||||
buttons,
|
||||
textboxLayout,
|
||||
useInvertedBackground,
|
||||
ariaLabel = "Testimonials section",
|
||||
className = "",
|
||||
containerClassName = "",
|
||||
cardClassName = "",
|
||||
textBoxTitleClassName = "",
|
||||
textBoxTitleImageWrapperClassName = "",
|
||||
textBoxTitleImageClassName = "",
|
||||
textBoxDescriptionClassName = "",
|
||||
imageWrapperClassName = "",
|
||||
imageClassName = "",
|
||||
iconClassName = "",
|
||||
nameClassName = "",
|
||||
handleClassName = "",
|
||||
testimonialClassName = "",
|
||||
ratingClassName = "",
|
||||
contentWrapperClassName = "",
|
||||
gridClassName = "",
|
||||
carouselClassName = "",
|
||||
controlsClassName = "",
|
||||
textBoxClassName = "",
|
||||
textBoxTagClassName = "",
|
||||
textBoxButtonContainerClassName = "",
|
||||
textBoxButtonClassName = "",
|
||||
textBoxButtonTextClassName = "",
|
||||
}: TestimonialCardThirteenProps) => {
|
||||
return (
|
||||
<CardStack
|
||||
mode={carouselMode}
|
||||
gridVariant="uniform-all-items-equal"
|
||||
uniformGridCustomHeightClasses={uniformGridCustomHeightClasses}
|
||||
animationType={animationType}
|
||||
|
||||
title={title}
|
||||
titleSegments={titleSegments}
|
||||
description={description}
|
||||
tag={tag}
|
||||
tagIcon={tagIcon}
|
||||
buttons={buttons}
|
||||
textboxLayout={textboxLayout}
|
||||
useInvertedBackground={useInvertedBackground}
|
||||
className={className}
|
||||
containerClassName={containerClassName}
|
||||
gridClassName={gridClassName}
|
||||
carouselClassName={carouselClassName}
|
||||
controlsClassName={controlsClassName}
|
||||
textBoxClassName={textBoxClassName}
|
||||
titleClassName={textBoxTitleClassName}
|
||||
titleImageWrapperClassName={textBoxTitleImageWrapperClassName}
|
||||
titleImageClassName={textBoxTitleImageClassName}
|
||||
descriptionClassName={textBoxDescriptionClassName}
|
||||
tagClassName={textBoxTagClassName}
|
||||
buttonContainerClassName={textBoxButtonContainerClassName}
|
||||
buttonClassName={textBoxButtonClassName}
|
||||
buttonTextClassName={textBoxButtonTextClassName}
|
||||
ariaLabel={ariaLabel}
|
||||
>
|
||||
{testimonials.map((testimonial, index) => (
|
||||
<TestimonialCard
|
||||
key={`${testimonial.id}-${index}`}
|
||||
testimonial={testimonial}
|
||||
showRating={showRating}
|
||||
useInvertedBackground={useInvertedBackground}
|
||||
cardClassName={cardClassName}
|
||||
imageWrapperClassName={imageWrapperClassName}
|
||||
imageClassName={imageClassName}
|
||||
iconClassName={iconClassName}
|
||||
nameClassName={nameClassName}
|
||||
handleClassName={handleClassName}
|
||||
testimonialClassName={testimonialClassName}
|
||||
ratingClassName={ratingClassName}
|
||||
contentWrapperClassName={contentWrapperClassName}
|
||||
/>
|
||||
))}
|
||||
</CardStack>
|
||||
);
|
||||
};
|
||||
|
||||
TestimonialCardThirteen.displayName = "TestimonialCardThirteen";
|
||||
|
||||
export default TestimonialCardThirteen;
|
||||
101
src/components/sections/testimonial/TestimonialCardTwelve.tsx
Normal file
101
src/components/sections/testimonial/TestimonialCardTwelve.tsx
Normal file
@@ -0,0 +1,101 @@
|
||||
"use client";
|
||||
|
||||
import { useState, useEffect } from "react";
|
||||
import AvatarGroup from "@/components/shared/AvatarGroup";
|
||||
import Tag from "@/components/shared/Tag";
|
||||
import { cls, shouldUseInvertedText } from "@/lib/utils";
|
||||
import { useTheme } from "@/providers/themeProvider/ThemeProvider";
|
||||
import type { LucideIcon } from "lucide-react";
|
||||
import type { InvertedBackground } from "@/providers/themeProvider/config/constants";
|
||||
|
||||
type Testimonial = {
|
||||
id: string;
|
||||
name: string;
|
||||
imageSrc: string;
|
||||
imageAlt?: string;
|
||||
};
|
||||
|
||||
interface TestimonialCardTwelveProps {
|
||||
testimonials: Testimonial[];
|
||||
cardTitle: string;
|
||||
cardTag: string;
|
||||
cardTagIcon?: LucideIcon;
|
||||
useInvertedBackground: InvertedBackground;
|
||||
ariaLabel?: string;
|
||||
className?: string;
|
||||
containerClassName?: string;
|
||||
cardClassName?: string;
|
||||
avatarGroupClassName?: string;
|
||||
avatarClassName?: string;
|
||||
cardTitleClassName?: string;
|
||||
cardTagClassName?: string;
|
||||
}
|
||||
|
||||
const TestimonialCardTwelve = ({
|
||||
testimonials,
|
||||
cardTitle,
|
||||
cardTag,
|
||||
cardTagIcon,
|
||||
useInvertedBackground,
|
||||
ariaLabel = "Testimonials section",
|
||||
className = "",
|
||||
containerClassName = "",
|
||||
cardClassName = "",
|
||||
avatarGroupClassName = "",
|
||||
avatarClassName = "",
|
||||
cardTitleClassName = "",
|
||||
cardTagClassName = "",
|
||||
}: TestimonialCardTwelveProps) => {
|
||||
const theme = useTheme();
|
||||
const shouldUseLightText = shouldUseInvertedText(useInvertedBackground, theme.cardStyle);
|
||||
|
||||
const [isMobile, setIsMobile] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const checkMobile = () => setIsMobile(window.innerWidth < 768);
|
||||
checkMobile();
|
||||
window.addEventListener("resize", checkMobile);
|
||||
return () => window.removeEventListener("resize", checkMobile);
|
||||
}, []);
|
||||
|
||||
const avatars = testimonials.map((testimonial) => ({
|
||||
src: testimonial.imageSrc,
|
||||
alt: testimonial.imageAlt || testimonial.name,
|
||||
}));
|
||||
|
||||
return (
|
||||
<section
|
||||
aria-label={ariaLabel}
|
||||
className={cls("relative py-20", useInvertedBackground === "invertCard" ? "w-content-width-expanded mx-auto rounded-theme-capped bg-foreground" : "w-full", useInvertedBackground === "invertDefault" && "bg-foreground", className)}
|
||||
>
|
||||
<div className={cls("w-content-width mx-auto", containerClassName)}>
|
||||
<div className={cls("w-full card rounded-theme-capped p-8 flex flex-col items-center justify-between gap-8", cardClassName)}>
|
||||
<div className="flex flex-col gap-3 items-center">
|
||||
<Tag
|
||||
text={cardTag}
|
||||
icon={cardTagIcon}
|
||||
useInvertedBackground={useInvertedBackground}
|
||||
className={cardTagClassName}
|
||||
/>
|
||||
<h3 className={cls("relative md:max-w-7/10 text-3xl md:text-5xl font-medium leading-tight text-center text-balance", shouldUseLightText ? "text-background" : "text-foreground", cardTitleClassName)}>
|
||||
{cardTitle}
|
||||
</h3>
|
||||
</div>
|
||||
<AvatarGroup
|
||||
avatars={avatars}
|
||||
className={avatarGroupClassName}
|
||||
avatarClassName={avatarClassName}
|
||||
maxVisible={isMobile ? 3 : 4}
|
||||
ariaLabel="Customer testimonials"
|
||||
avatarImageClassName="h-[var(--width-17_5)] md:h-[var(--width-12_5)]"
|
||||
avatarOverlapClassName="-ml-8"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
TestimonialCardTwelve.displayName = "TestimonialCardTwelve";
|
||||
|
||||
export default TestimonialCardTwelve;
|
||||
209
src/components/sections/testimonial/TestimonialCardTwo.tsx
Normal file
209
src/components/sections/testimonial/TestimonialCardTwo.tsx
Normal file
@@ -0,0 +1,209 @@
|
||||
"use client";
|
||||
|
||||
import { memo } from "react";
|
||||
import Image from "next/image";
|
||||
import CardStack from "@/components/cardStack/CardStack";
|
||||
import { cls, shouldUseInvertedText } from "@/lib/utils";
|
||||
import { useTheme } from "@/providers/themeProvider/ThemeProvider";
|
||||
import { Quote } from "lucide-react";
|
||||
import type { LucideIcon } from "lucide-react";
|
||||
import type { ButtonConfig, CardAnimationType, TitleSegment, TextboxLayout, InvertedBackground } from "@/components/cardStack/types";
|
||||
|
||||
type Testimonial = {
|
||||
id: string;
|
||||
name: string;
|
||||
role: string;
|
||||
testimonial: string;
|
||||
imageSrc?: string;
|
||||
imageAlt?: string;
|
||||
icon?: LucideIcon;
|
||||
};
|
||||
|
||||
interface TestimonialCardTwoProps {
|
||||
testimonials: Testimonial[];
|
||||
carouselMode?: "auto" | "buttons";
|
||||
uniformGridCustomHeightClasses?: string;
|
||||
animationType: CardAnimationType;
|
||||
title: string;
|
||||
titleSegments?: TitleSegment[];
|
||||
description: string;
|
||||
tag?: string;
|
||||
tagIcon?: LucideIcon;
|
||||
buttons?: ButtonConfig[];
|
||||
textboxLayout: TextboxLayout;
|
||||
useInvertedBackground: InvertedBackground;
|
||||
ariaLabel?: string;
|
||||
className?: string;
|
||||
containerClassName?: string;
|
||||
cardClassName?: string;
|
||||
textBoxTitleClassName?: string;
|
||||
textBoxTitleImageWrapperClassName?: string;
|
||||
textBoxTitleImageClassName?: string;
|
||||
textBoxDescriptionClassName?: string;
|
||||
imageWrapperClassName?: string;
|
||||
imageClassName?: string;
|
||||
iconClassName?: string;
|
||||
nameClassName?: string;
|
||||
roleClassName?: string;
|
||||
testimonialClassName?: string;
|
||||
gridClassName?: string;
|
||||
carouselClassName?: string;
|
||||
controlsClassName?: string;
|
||||
textBoxClassName?: string;
|
||||
textBoxTagClassName?: string;
|
||||
textBoxButtonContainerClassName?: string;
|
||||
textBoxButtonClassName?: string;
|
||||
textBoxButtonTextClassName?: string;
|
||||
}
|
||||
|
||||
interface TestimonialCardProps {
|
||||
testimonial: Testimonial;
|
||||
shouldUseLightText: boolean;
|
||||
cardClassName?: string;
|
||||
imageWrapperClassName?: string;
|
||||
imageClassName?: string;
|
||||
iconClassName?: string;
|
||||
nameClassName?: string;
|
||||
roleClassName?: string;
|
||||
testimonialClassName?: string;
|
||||
}
|
||||
|
||||
const TestimonialCard = memo(({
|
||||
testimonial,
|
||||
shouldUseLightText,
|
||||
cardClassName = "",
|
||||
imageWrapperClassName = "",
|
||||
imageClassName = "",
|
||||
iconClassName = "",
|
||||
nameClassName = "",
|
||||
roleClassName = "",
|
||||
testimonialClassName = "",
|
||||
}: TestimonialCardProps) => {
|
||||
const Icon = testimonial.icon || Quote;
|
||||
|
||||
return (
|
||||
<div className={cls("relative h-full card rounded-theme-capped p-6 flex flex-col gap-6", cardClassName)}>
|
||||
<div className={cls("relative z-1 h-30 w-fit aspect-square rounded-theme flex items-center justify-center primary-button overflow-hidden", imageWrapperClassName)}>
|
||||
{testimonial.imageSrc ? (
|
||||
<Image
|
||||
src={testimonial.imageSrc}
|
||||
alt={testimonial.imageAlt || testimonial.name}
|
||||
width={800}
|
||||
height={800}
|
||||
className={cls("absolute inset-0 h-full w-full object-cover", imageClassName)}
|
||||
unoptimized={testimonial.imageSrc.startsWith('http') || testimonial.imageSrc.startsWith('//')}
|
||||
aria-hidden={testimonial.imageAlt === ""}
|
||||
/>
|
||||
) : (
|
||||
<Icon className={cls("h-1/2 w-1/2 text-background", iconClassName)} strokeWidth={1} />
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="relative z-1 flex flex-col gap-1 mt-1">
|
||||
<h3 className={cls("text-2xl font-medium leading-[1.1]", shouldUseLightText ? "text-background" : "text-foreground", nameClassName)}>
|
||||
{testimonial.name}
|
||||
</h3>
|
||||
<p className={cls("text-base leading-[1.1]", shouldUseLightText ? "text-background" : "text-foreground", roleClassName)}>
|
||||
{testimonial.role}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<p className={cls("relative z-1 text-lg leading-[1.25]", shouldUseLightText ? "text-background" : "text-foreground", testimonialClassName)}>
|
||||
{testimonial.testimonial}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
TestimonialCard.displayName = "TestimonialCard";
|
||||
|
||||
const TestimonialCardTwo = ({
|
||||
testimonials,
|
||||
carouselMode = "buttons",
|
||||
uniformGridCustomHeightClasses = "min-h-none",
|
||||
animationType,
|
||||
title,
|
||||
titleSegments,
|
||||
description,
|
||||
tag,
|
||||
tagIcon,
|
||||
buttons,
|
||||
textboxLayout,
|
||||
useInvertedBackground,
|
||||
ariaLabel = "Testimonials section",
|
||||
className = "",
|
||||
containerClassName = "",
|
||||
cardClassName = "",
|
||||
textBoxTitleClassName = "",
|
||||
textBoxTitleImageWrapperClassName = "",
|
||||
textBoxTitleImageClassName = "",
|
||||
textBoxDescriptionClassName = "",
|
||||
imageWrapperClassName = "",
|
||||
imageClassName = "",
|
||||
iconClassName = "",
|
||||
nameClassName = "",
|
||||
roleClassName = "",
|
||||
testimonialClassName = "",
|
||||
gridClassName = "",
|
||||
carouselClassName = "",
|
||||
controlsClassName = "",
|
||||
textBoxClassName = "",
|
||||
textBoxTagClassName = "",
|
||||
textBoxButtonContainerClassName = "",
|
||||
textBoxButtonClassName = "",
|
||||
textBoxButtonTextClassName = "",
|
||||
}: TestimonialCardTwoProps) => {
|
||||
const theme = useTheme();
|
||||
const shouldUseLightText = shouldUseInvertedText(useInvertedBackground, theme.cardStyle);
|
||||
return (
|
||||
<CardStack
|
||||
mode={carouselMode}
|
||||
gridVariant="uniform-all-items-equal"
|
||||
uniformGridCustomHeightClasses={uniformGridCustomHeightClasses}
|
||||
animationType={animationType}
|
||||
|
||||
title={title}
|
||||
titleSegments={titleSegments}
|
||||
description={description}
|
||||
tag={tag}
|
||||
tagIcon={tagIcon}
|
||||
buttons={buttons}
|
||||
textboxLayout={textboxLayout}
|
||||
useInvertedBackground={useInvertedBackground}
|
||||
className={className}
|
||||
containerClassName={containerClassName}
|
||||
gridClassName={gridClassName}
|
||||
carouselClassName={carouselClassName}
|
||||
controlsClassName={controlsClassName}
|
||||
textBoxClassName={textBoxClassName}
|
||||
titleClassName={textBoxTitleClassName}
|
||||
titleImageWrapperClassName={textBoxTitleImageWrapperClassName}
|
||||
titleImageClassName={textBoxTitleImageClassName}
|
||||
descriptionClassName={textBoxDescriptionClassName}
|
||||
tagClassName={textBoxTagClassName}
|
||||
buttonContainerClassName={textBoxButtonContainerClassName}
|
||||
buttonClassName={textBoxButtonClassName}
|
||||
buttonTextClassName={textBoxButtonTextClassName}
|
||||
ariaLabel={ariaLabel}
|
||||
>
|
||||
{testimonials.map((testimonial, index) => (
|
||||
<TestimonialCard
|
||||
key={`${testimonial.id}-${index}`}
|
||||
testimonial={testimonial}
|
||||
shouldUseLightText={shouldUseLightText}
|
||||
cardClassName={cardClassName}
|
||||
imageWrapperClassName={imageWrapperClassName}
|
||||
imageClassName={imageClassName}
|
||||
iconClassName={iconClassName}
|
||||
nameClassName={nameClassName}
|
||||
roleClassName={roleClassName}
|
||||
testimonialClassName={testimonialClassName}
|
||||
/>
|
||||
))}
|
||||
</CardStack>
|
||||
);
|
||||
};
|
||||
|
||||
TestimonialCardTwo.displayName = "TestimonialCardTwo";
|
||||
|
||||
export default TestimonialCardTwo;
|
||||
Reference in New Issue
Block a user