Initial commit
This commit is contained in:
151
src/components/sections/feature/FeatureCardEight.tsx
Normal file
151
src/components/sections/feature/FeatureCardEight.tsx
Normal file
@@ -0,0 +1,151 @@
|
||||
"use client";
|
||||
|
||||
import TimelineHorizontalCardStack from "@/components/cardStack/layouts/timelines/TimelineHorizontalCardStack";
|
||||
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";
|
||||
|
||||
type FeatureCard = {
|
||||
id: number;
|
||||
title: string;
|
||||
description: string;
|
||||
imageAlt?: string;
|
||||
videoAriaLabel?: string;
|
||||
} & (
|
||||
| { imageSrc: string; videoSrc?: never }
|
||||
| { videoSrc: string; imageSrc?: never }
|
||||
);
|
||||
|
||||
interface FeatureCardEightProps {
|
||||
features: FeatureCard[];
|
||||
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;
|
||||
cardClassName?: string;
|
||||
progressBarClassName?: string;
|
||||
cardContentClassName?: string;
|
||||
stepNumberClassName?: string;
|
||||
cardTitleClassName?: string;
|
||||
cardDescriptionClassName?: string;
|
||||
mediaContainerClassName?: string;
|
||||
mediaClassName?: string;
|
||||
}
|
||||
|
||||
const FeatureCardEight = ({
|
||||
features,
|
||||
title,
|
||||
titleSegments,
|
||||
description,
|
||||
tag,
|
||||
tagIcon,
|
||||
buttons,
|
||||
textboxLayout,
|
||||
useInvertedBackground,
|
||||
ariaLabel = "Feature section",
|
||||
className = "",
|
||||
containerClassName = "",
|
||||
textBoxTitleClassName = "",
|
||||
textBoxDescriptionClassName = "",
|
||||
textBoxClassName = "",
|
||||
textBoxTagClassName = "",
|
||||
textBoxButtonContainerClassName = "",
|
||||
textBoxButtonClassName = "",
|
||||
textBoxButtonTextClassName = "",
|
||||
titleImageWrapperClassName = "",
|
||||
titleImageClassName = "",
|
||||
cardClassName = "",
|
||||
progressBarClassName = "",
|
||||
cardContentClassName = "",
|
||||
stepNumberClassName = "",
|
||||
cardTitleClassName = "",
|
||||
cardDescriptionClassName = "",
|
||||
mediaContainerClassName = "",
|
||||
mediaClassName = "",
|
||||
}: FeatureCardEightProps) => {
|
||||
const theme = useTheme();
|
||||
const shouldUseLightText = shouldUseInvertedText(useInvertedBackground, theme.cardStyle);
|
||||
|
||||
const mediaItems = features.map((feature) => ({
|
||||
imageSrc: feature.imageSrc,
|
||||
videoSrc: feature.videoSrc,
|
||||
imageAlt: feature.imageAlt || feature.title,
|
||||
videoAriaLabel: feature.videoAriaLabel || feature.title,
|
||||
}));
|
||||
|
||||
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}
|
||||
descriptionClassName={textBoxDescriptionClassName}
|
||||
textBoxClassName={textBoxClassName}
|
||||
tagClassName={textBoxTagClassName}
|
||||
buttonContainerClassName={textBoxButtonContainerClassName}
|
||||
buttonClassName={textBoxButtonClassName}
|
||||
buttonTextClassName={textBoxButtonTextClassName}
|
||||
titleImageWrapperClassName={titleImageWrapperClassName}
|
||||
titleImageClassName={titleImageClassName}
|
||||
cardClassName={cardClassName}
|
||||
progressBarClassName={progressBarClassName}
|
||||
mediaContainerClassName={mediaContainerClassName}
|
||||
mediaClassName={mediaClassName}
|
||||
ariaLabel={ariaLabel}
|
||||
>
|
||||
{features.map((feature) => (
|
||||
<div
|
||||
key={feature.id}
|
||||
className={cls("relative z-1 w-full min-h-0 h-fit flex flex-col gap-3", cardContentClassName)}
|
||||
>
|
||||
<div
|
||||
className={cls(
|
||||
"h-8 w-[var(--height-8)] primary-button text-background rounded-full flex items-center justify-center",
|
||||
stepNumberClassName
|
||||
)}
|
||||
>
|
||||
<p className="text-sm truncate">
|
||||
{feature.id}
|
||||
</p>
|
||||
</div>
|
||||
<h2 className={cls("mt-1 text-3xl font-medium leading-[1.15] text-balance", shouldUseLightText && "text-background", cardTitleClassName)}>
|
||||
{feature.title}
|
||||
</h2>
|
||||
<p className={cls("text-base leading-[1.15] text-balance", shouldUseLightText ? "text-background" : "text-foreground", cardDescriptionClassName)}>
|
||||
{feature.description}
|
||||
</p>
|
||||
</div>
|
||||
))}
|
||||
</TimelineHorizontalCardStack>
|
||||
);
|
||||
};
|
||||
|
||||
FeatureCardEight.displayName = "FeatureCardEight";
|
||||
|
||||
export default FeatureCardEight;
|
||||
186
src/components/sections/feature/FeatureCardEighteen.tsx
Normal file
186
src/components/sections/feature/FeatureCardEighteen.tsx
Normal file
@@ -0,0 +1,186 @@
|
||||
"use client";
|
||||
|
||||
import CardStackTextBox from "@/components/cardStack/CardStackTextBox";
|
||||
import PricingFeatureList from "@/components/shared/PricingFeatureList";
|
||||
import MediaContent from "@/components/shared/MediaContent";
|
||||
import { useCardAnimation } from "@/components/cardStack/hooks/useCardAnimation";
|
||||
import { Check, X } from "lucide-react";
|
||||
import { cls, shouldUseInvertedText } from "@/lib/utils";
|
||||
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";
|
||||
|
||||
type ComparisonItem = {
|
||||
title: string;
|
||||
items: string[];
|
||||
imageSrc?: string;
|
||||
videoSrc?: string;
|
||||
imageAlt?: string;
|
||||
videoAriaLabel?: string;
|
||||
};
|
||||
|
||||
interface FeatureCardEighteenProps {
|
||||
negativeCard: ComparisonItem;
|
||||
positiveCard: ComparisonItem;
|
||||
animationType: CardAnimationType;
|
||||
title: string;
|
||||
titleSegments?: TitleSegment[];
|
||||
description: string;
|
||||
textboxLayout: TextboxLayout;
|
||||
useInvertedBackground: InvertedBackground;
|
||||
tag?: string;
|
||||
tagIcon?: LucideIcon;
|
||||
buttons?: ButtonConfig[];
|
||||
ariaLabel?: string;
|
||||
className?: string;
|
||||
containerClassName?: string;
|
||||
textBoxTitleClassName?: string;
|
||||
titleImageWrapperClassName?: string;
|
||||
titleImageClassName?: string;
|
||||
textBoxDescriptionClassName?: string;
|
||||
textBoxClassName?: string;
|
||||
textBoxTagClassName?: string;
|
||||
textBoxButtonContainerClassName?: string;
|
||||
textBoxButtonClassName?: string;
|
||||
textBoxButtonTextClassName?: string;
|
||||
gridClassName?: string;
|
||||
cardClassName?: string;
|
||||
cardMediaWrapperClassName?: string;
|
||||
cardMediaClassName?: string;
|
||||
cardTitleClassName?: string;
|
||||
itemsListClassName?: string;
|
||||
itemClassName?: string;
|
||||
itemIconClassName?: string;
|
||||
itemTextClassName?: string;
|
||||
}
|
||||
|
||||
const FeatureCardEighteen = ({
|
||||
negativeCard,
|
||||
positiveCard,
|
||||
animationType,
|
||||
title,
|
||||
titleSegments,
|
||||
description,
|
||||
textboxLayout,
|
||||
useInvertedBackground,
|
||||
tag,
|
||||
tagIcon,
|
||||
buttons,
|
||||
ariaLabel = "Feature comparison section",
|
||||
className = "",
|
||||
containerClassName = "",
|
||||
textBoxTitleClassName = "",
|
||||
titleImageWrapperClassName = "",
|
||||
titleImageClassName = "",
|
||||
textBoxDescriptionClassName = "",
|
||||
textBoxClassName = "",
|
||||
textBoxTagClassName = "",
|
||||
textBoxButtonContainerClassName = "",
|
||||
textBoxButtonClassName = "",
|
||||
textBoxButtonTextClassName = "",
|
||||
gridClassName = "",
|
||||
cardClassName = "",
|
||||
cardMediaWrapperClassName = "",
|
||||
cardMediaClassName = "",
|
||||
cardTitleClassName = "",
|
||||
itemsListClassName = "",
|
||||
itemClassName = "",
|
||||
itemIconClassName = "",
|
||||
itemTextClassName = "",
|
||||
}: FeatureCardEighteenProps) => {
|
||||
const theme = useTheme();
|
||||
const shouldCardUseLightText = shouldUseInvertedText("invertDefault", theme.cardStyle) || shouldUseInvertedText("invertCard", theme.cardStyle);
|
||||
const { itemRefs } = useCardAnimation({ animationType, itemCount: 2 });
|
||||
|
||||
const cards = [
|
||||
{ ...negativeCard, variant: "negative" as const },
|
||||
{ ...positiveCard, variant: "positive" as const },
|
||||
];
|
||||
|
||||
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-8", 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(
|
||||
"relative mx-auto w-full md:w-60 grid grid-cols-1 gap-6",
|
||||
cards.length >= 2 ? "md:grid-cols-2" : "md:grid-cols-1",
|
||||
gridClassName
|
||||
)}>
|
||||
{cards.map((card, index) => (
|
||||
<div
|
||||
key={card.variant}
|
||||
ref={(el) => { itemRefs.current[index] = el; }}
|
||||
className={cls(
|
||||
"relative h-full card rounded-theme-capped p-6",
|
||||
cardClassName
|
||||
)}
|
||||
>
|
||||
<div className={cls("flex flex-col gap-6", card.variant === "negative" && "opacity-50")}>
|
||||
<div className={cls("relative w-full aspect-[4/3] rounded-theme-capped overflow-hidden", cardMediaWrapperClassName)}>
|
||||
<MediaContent
|
||||
imageSrc={card.imageSrc}
|
||||
videoSrc={card.videoSrc}
|
||||
imageAlt={card.imageAlt || card.title}
|
||||
videoAriaLabel={card.videoAriaLabel || card.title}
|
||||
imageClassName={cls("w-full h-full object-cover", cardMediaClassName)}
|
||||
/>
|
||||
</div>
|
||||
<h3 className={cls(
|
||||
"text-3xl font-medium",
|
||||
shouldCardUseLightText ? "text-background" : "text-foreground",
|
||||
cardTitleClassName
|
||||
)}>
|
||||
{card.title}
|
||||
</h3>
|
||||
<PricingFeatureList
|
||||
features={card.items}
|
||||
icon={card.variant === "positive" ? Check : X}
|
||||
shouldUseLightText={shouldCardUseLightText}
|
||||
className={itemsListClassName}
|
||||
featureItemClassName={itemClassName}
|
||||
featureIconWrapperClassName=""
|
||||
featureIconClassName={itemIconClassName}
|
||||
featureTextClassName={cls("truncate", itemTextClassName)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
FeatureCardEighteen.displayName = "FeatureCardEighteen";
|
||||
|
||||
export default FeatureCardEighteen;
|
||||
165
src/components/sections/feature/FeatureCardEleven.tsx
Normal file
165
src/components/sections/feature/FeatureCardEleven.tsx
Normal file
@@ -0,0 +1,165 @@
|
||||
"use client";
|
||||
|
||||
import CardList from "@/components/cardStack/CardList";
|
||||
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, CardAnimationType, TitleSegment } from "@/components/cardStack/types";
|
||||
import type { TextboxLayout, InvertedBackground } from "@/providers/themeProvider/config/constants";
|
||||
|
||||
type FeatureCard = {
|
||||
id: number;
|
||||
title: string;
|
||||
description: string;
|
||||
imageSrc?: string;
|
||||
videoSrc?: string;
|
||||
imageAlt?: string;
|
||||
videoAriaLabel?: string;
|
||||
};
|
||||
|
||||
interface FeatureCardElevenProps {
|
||||
features: FeatureCard[];
|
||||
animationType: CardAnimationType;
|
||||
title: string;
|
||||
titleSegments?: TitleSegment[];
|
||||
description: string;
|
||||
textboxLayout: TextboxLayout;
|
||||
useInvertedBackground: InvertedBackground;
|
||||
usePrimaryButtonImage: boolean;
|
||||
tag?: string;
|
||||
tagIcon?: LucideIcon;
|
||||
buttons?: ButtonConfig[];
|
||||
ariaLabel?: string;
|
||||
className?: string;
|
||||
containerClassName?: string;
|
||||
textBoxTitleClassName?: string;
|
||||
textBoxDescriptionClassName?: string;
|
||||
textBoxClassName?: string;
|
||||
textBoxTagClassName?: string;
|
||||
textBoxButtonContainerClassName?: string;
|
||||
textBoxButtonClassName?: string;
|
||||
textBoxButtonTextClassName?: string;
|
||||
titleImageWrapperClassName?: string;
|
||||
titleImageClassName?: string;
|
||||
cardContentClassName?: string;
|
||||
textCardClassName?: string;
|
||||
imageCardClassName?: string;
|
||||
stepNumberClassName?: string;
|
||||
cardTitleClassName?: string;
|
||||
cardDescriptionClassName?: string;
|
||||
imageClassName?: string;
|
||||
}
|
||||
|
||||
const FeatureCardEleven = ({
|
||||
features,
|
||||
animationType,
|
||||
title,
|
||||
titleSegments,
|
||||
description,
|
||||
textboxLayout,
|
||||
useInvertedBackground,
|
||||
usePrimaryButtonImage,
|
||||
tag,
|
||||
tagIcon,
|
||||
buttons,
|
||||
ariaLabel = "Feature section",
|
||||
className = "",
|
||||
containerClassName = "",
|
||||
textBoxTitleClassName = "",
|
||||
textBoxDescriptionClassName = "",
|
||||
textBoxClassName = "",
|
||||
textBoxTagClassName = "",
|
||||
textBoxButtonContainerClassName = "",
|
||||
textBoxButtonClassName = "",
|
||||
textBoxButtonTextClassName = "",
|
||||
titleImageWrapperClassName = "",
|
||||
titleImageClassName = "",
|
||||
cardContentClassName = "",
|
||||
textCardClassName = "",
|
||||
imageCardClassName = "",
|
||||
stepNumberClassName = "",
|
||||
cardTitleClassName = "",
|
||||
cardDescriptionClassName = "",
|
||||
imageClassName = "",
|
||||
}: FeatureCardElevenProps) => {
|
||||
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}
|
||||
disableCardWrapper={true}
|
||||
className={className}
|
||||
containerClassName={containerClassName}
|
||||
cardClassName=""
|
||||
titleClassName={textBoxTitleClassName}
|
||||
descriptionClassName={textBoxDescriptionClassName}
|
||||
textBoxClassName={textBoxClassName}
|
||||
tagClassName={textBoxTagClassName}
|
||||
buttonContainerClassName={textBoxButtonContainerClassName}
|
||||
buttonClassName={textBoxButtonClassName}
|
||||
buttonTextClassName={textBoxButtonTextClassName}
|
||||
titleImageWrapperClassName={titleImageWrapperClassName}
|
||||
titleImageClassName={titleImageClassName}
|
||||
ariaLabel={ariaLabel}
|
||||
>
|
||||
{features.map((feature, index) => (
|
||||
<div
|
||||
key={feature.id}
|
||||
className={cls("w-full min-h-0 h-full flex flex-col justify-between items-center gap-6", index % 2 === 0 ? "md:flex-row" : "md:flex-row-reverse", cardContentClassName)}
|
||||
>
|
||||
<div className={cls("w-full md:w-1/2 min-w-0 h-fit md:h-full md:aspect-square card rounded-theme-capped p-6 md:p-15 flex flex-col justify-center", textCardClassName)}>
|
||||
<div className="relative z-1 w-full min-w-0 flex flex-col gap-3 md:gap-5">
|
||||
<div
|
||||
className={cls(
|
||||
"h-8 w-[var(--height-8)] primary-button text-background rounded-full flex items-center justify-center",
|
||||
stepNumberClassName
|
||||
)}
|
||||
>
|
||||
<p className="text-sm truncate">
|
||||
{feature.id}
|
||||
</p>
|
||||
</div>
|
||||
<h2 className={cls("mt-1 text-4xl md:text-5xl font-medium leading-[1.15] text-balance", shouldUseLightText && "text-background", cardTitleClassName)}>
|
||||
{feature.title}
|
||||
</h2>
|
||||
<p className={cls("text-base leading-[1.15] text-balance", shouldUseLightText ? "text-background" : "text-foreground", cardDescriptionClassName)}>
|
||||
{feature.description}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className={cls(
|
||||
"relative w-full md:w-1/2 aspect-square overflow-hidden rounded-theme-capped",
|
||||
usePrimaryButtonImage ? "primary-button p-10 md:p-20" : "card",
|
||||
imageCardClassName
|
||||
)}
|
||||
>
|
||||
<div className="absolute inset-10 md:inset-20 z-1" >
|
||||
<MediaContent
|
||||
imageSrc={feature.imageSrc}
|
||||
videoSrc={feature.videoSrc}
|
||||
imageAlt={feature.imageAlt || feature.title}
|
||||
videoAriaLabel={feature.videoAriaLabel || feature.title}
|
||||
imageClassName={cls("relative z-1 w-full h-full object-cover", imageClassName)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</CardList>
|
||||
);
|
||||
};
|
||||
|
||||
FeatureCardEleven.displayName = "FeatureCardEleven";
|
||||
|
||||
export default FeatureCardEleven;
|
||||
179
src/components/sections/feature/FeatureCardFifteen.tsx
Normal file
179
src/components/sections/feature/FeatureCardFifteen.tsx
Normal file
@@ -0,0 +1,179 @@
|
||||
"use client";
|
||||
|
||||
import { memo } from "react";
|
||||
import AutoCarousel from "@/components/cardStack/layouts/carousels/AutoCarousel";
|
||||
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 { CardAnimationType, ButtonConfig, TitleSegment, TextboxLayout, InvertedBackground } from "@/components/cardStack/types";
|
||||
|
||||
type Feature = {
|
||||
id: string;
|
||||
title: string;
|
||||
description: string;
|
||||
imageSrc?: string;
|
||||
videoSrc?: string;
|
||||
imageAlt?: string;
|
||||
videoAriaLabel?: string;
|
||||
};
|
||||
|
||||
interface FeatureCardFifteenProps {
|
||||
features: Feature[];
|
||||
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;
|
||||
mediaClassName?: string;
|
||||
featureTitleClassName?: string;
|
||||
featureDescriptionClassName?: string;
|
||||
textBoxClassName?: string;
|
||||
textBoxTitleClassName?: string;
|
||||
textBoxTitleImageWrapperClassName?: string;
|
||||
textBoxTitleImageClassName?: string;
|
||||
textBoxDescriptionClassName?: string;
|
||||
textBoxTagClassName?: string;
|
||||
textBoxButtonContainerClassName?: string;
|
||||
textBoxButtonClassName?: string;
|
||||
textBoxButtonTextClassName?: string;
|
||||
}
|
||||
|
||||
interface FeatureCardProps {
|
||||
feature: Feature;
|
||||
useInvertedBackground: "noInvert" | "invertDefault" | "invertCard";
|
||||
cardClassName?: string;
|
||||
mediaClassName?: string;
|
||||
featureTitleClassName?: string;
|
||||
featureDescriptionClassName?: string;
|
||||
}
|
||||
|
||||
const FeatureCard = memo(({
|
||||
feature,
|
||||
useInvertedBackground,
|
||||
cardClassName = "",
|
||||
mediaClassName = "",
|
||||
featureTitleClassName = "",
|
||||
featureDescriptionClassName = "",
|
||||
}: FeatureCardProps) => {
|
||||
const theme = useTheme();
|
||||
const shouldUseLightText = shouldUseInvertedText(useInvertedBackground, theme.cardStyle);
|
||||
|
||||
return (
|
||||
<div className={cls("relative h-full min-h-0 flex flex-col gap-4", cardClassName)}>
|
||||
<MediaContent
|
||||
imageSrc={feature.imageSrc}
|
||||
videoSrc={feature.videoSrc}
|
||||
imageAlt={feature.imageAlt || feature.title}
|
||||
videoAriaLabel={feature.videoAriaLabel || feature.title}
|
||||
imageClassName={cls("w-full aspect-square object-cover rounded-theme", mediaClassName)}
|
||||
/>
|
||||
<div className="flex flex-col gap-1">
|
||||
<h3 className={cls("relative z-1 text-lg font-medium leading-tight", shouldUseLightText ? "text-background" : "text-foreground", featureTitleClassName)}>
|
||||
{feature.title}
|
||||
</h3>
|
||||
<p className={cls("relative z-1 text-base leading-tight truncate", shouldUseLightText ? "text-background/75" : "text-foreground/75", featureDescriptionClassName)}>
|
||||
{feature.description}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
FeatureCard.displayName = "FeatureCard";
|
||||
|
||||
const FeatureCardFifteen = ({
|
||||
features,
|
||||
animationType,
|
||||
title,
|
||||
titleSegments,
|
||||
description,
|
||||
textboxLayout,
|
||||
useInvertedBackground,
|
||||
tag,
|
||||
tagIcon,
|
||||
buttons,
|
||||
speed = 40,
|
||||
topMarqueeDirection = "left",
|
||||
ariaLabel = "Features section",
|
||||
className = "",
|
||||
containerClassName = "",
|
||||
carouselClassName = "",
|
||||
bottomCarouselClassName = "",
|
||||
cardClassName = "",
|
||||
mediaClassName = "",
|
||||
featureTitleClassName = "",
|
||||
featureDescriptionClassName = "",
|
||||
textBoxClassName = "",
|
||||
textBoxTitleClassName = "",
|
||||
textBoxTitleImageWrapperClassName = "",
|
||||
textBoxTitleImageClassName = "",
|
||||
textBoxDescriptionClassName = "",
|
||||
textBoxTagClassName = "",
|
||||
textBoxButtonContainerClassName = "",
|
||||
textBoxButtonClassName = "",
|
||||
textBoxButtonTextClassName = "",
|
||||
}: FeatureCardFifteenProps) => {
|
||||
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}
|
||||
marqueeGapClassName="gap-8"
|
||||
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!"
|
||||
>
|
||||
{features.map((feature, index) => (
|
||||
<FeatureCard
|
||||
key={`${feature.id}-${index}`}
|
||||
feature={feature}
|
||||
useInvertedBackground={useInvertedBackground}
|
||||
cardClassName={cardClassName}
|
||||
mediaClassName={mediaClassName}
|
||||
featureTitleClassName={featureTitleClassName}
|
||||
featureDescriptionClassName={featureDescriptionClassName}
|
||||
/>
|
||||
))}
|
||||
</AutoCarousel>
|
||||
);
|
||||
};
|
||||
|
||||
FeatureCardFifteen.displayName = "FeatureCardFifteen";
|
||||
|
||||
export default FeatureCardFifteen;
|
||||
174
src/components/sections/feature/FeatureCardFourteen.tsx
Normal file
174
src/components/sections/feature/FeatureCardFourteen.tsx
Normal file
@@ -0,0 +1,174 @@
|
||||
"use client";
|
||||
|
||||
import { memo } from "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 Feature = {
|
||||
id: string;
|
||||
title: string;
|
||||
description: string;
|
||||
imageSrc?: string;
|
||||
videoSrc?: string;
|
||||
imageAlt?: string;
|
||||
videoAriaLabel?: string;
|
||||
};
|
||||
|
||||
interface FeatureCardFourteenProps {
|
||||
features: Feature[];
|
||||
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;
|
||||
cardTitleClassName?: string;
|
||||
cardDescriptionClassName?: string;
|
||||
cardContentClassName?: string;
|
||||
carouselClassName?: string;
|
||||
dotsClassName?: string;
|
||||
textBoxClassName?: string;
|
||||
textBoxTagClassName?: string;
|
||||
textBoxButtonContainerClassName?: string;
|
||||
textBoxButtonClassName?: string;
|
||||
textBoxButtonTextClassName?: string;
|
||||
}
|
||||
|
||||
interface FeatureCardProps {
|
||||
feature: Feature;
|
||||
shouldUseLightText: boolean;
|
||||
cardClassName?: string;
|
||||
imageClassName?: string;
|
||||
cardTitleClassName?: string;
|
||||
cardDescriptionClassName?: string;
|
||||
cardContentClassName?: string;
|
||||
}
|
||||
|
||||
const FeatureCard = memo(({
|
||||
feature,
|
||||
shouldUseLightText,
|
||||
cardClassName = "",
|
||||
imageClassName = "",
|
||||
cardTitleClassName = "",
|
||||
cardDescriptionClassName = "",
|
||||
cardContentClassName = "",
|
||||
}: FeatureCardProps) => {
|
||||
return (
|
||||
<div className={cls("relative h-full w-full rounded-theme-capped overflow-hidden", cardClassName)}>
|
||||
<MediaContent
|
||||
imageSrc={feature.imageSrc}
|
||||
videoSrc={feature.videoSrc}
|
||||
imageAlt={feature.imageAlt}
|
||||
videoAriaLabel={feature.videoAriaLabel}
|
||||
imageClassName={cls("relative w-full h-full aspect-[10/16] md:aspect-video object-cover !rounded-none", imageClassName)}
|
||||
/>
|
||||
<div className={cls("absolute! md:max-w-[var(--width-30)] 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">
|
||||
<h2 className={cls("text-3xl md:text-4xl font-medium leading-[1.15] text-balance truncate", shouldUseLightText && "text-background", cardTitleClassName)}>
|
||||
{feature.title}
|
||||
</h2>
|
||||
<p className={cls("text-base leading-[1.15] text-balance truncate", shouldUseLightText ? "text-background" : "text-foreground", cardDescriptionClassName)}>
|
||||
{feature.description}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
FeatureCard.displayName = "FeatureCard";
|
||||
|
||||
const FeatureCardFourteen = ({
|
||||
features,
|
||||
title,
|
||||
titleSegments,
|
||||
description,
|
||||
tag,
|
||||
tagIcon,
|
||||
buttons,
|
||||
textboxLayout,
|
||||
useInvertedBackground,
|
||||
ariaLabel = "Features section",
|
||||
className = "",
|
||||
containerClassName = "",
|
||||
cardClassName = "",
|
||||
textBoxTitleClassName = "",
|
||||
textBoxTitleImageWrapperClassName = "",
|
||||
textBoxTitleImageClassName = "",
|
||||
textBoxDescriptionClassName = "",
|
||||
cardImageClassName = "",
|
||||
cardTitleClassName = "",
|
||||
cardDescriptionClassName = "",
|
||||
cardContentClassName = "",
|
||||
carouselClassName = "",
|
||||
dotsClassName = "",
|
||||
textBoxClassName = "",
|
||||
textBoxTagClassName = "",
|
||||
textBoxButtonContainerClassName = "",
|
||||
textBoxButtonClassName = "",
|
||||
textBoxButtonTextClassName = "",
|
||||
}: FeatureCardFourteenProps) => {
|
||||
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}
|
||||
>
|
||||
{features.map((feature, index) => (
|
||||
<FeatureCard
|
||||
key={`${feature.id}-${index}`}
|
||||
feature={feature}
|
||||
shouldUseLightText={shouldUseLightText}
|
||||
cardClassName={cardClassName}
|
||||
imageClassName={cardImageClassName}
|
||||
cardTitleClassName={cardTitleClassName}
|
||||
cardDescriptionClassName={cardDescriptionClassName}
|
||||
cardContentClassName={cardContentClassName}
|
||||
/>
|
||||
))}
|
||||
</FullWidthCarousel>
|
||||
);
|
||||
};
|
||||
|
||||
FeatureCardFourteen.displayName = "FeatureCardFourteen";
|
||||
|
||||
export default FeatureCardFourteen;
|
||||
199
src/components/sections/feature/FeatureCardNine.tsx
Normal file
199
src/components/sections/feature/FeatureCardNine.tsx
Normal file
@@ -0,0 +1,199 @@
|
||||
"use client";
|
||||
|
||||
import TimelinePhoneView from "@/components/cardStack/layouts/timelines/TimelinePhoneView";
|
||||
import { cls } from "@/lib/utils";
|
||||
import type { LucideIcon } from "lucide-react";
|
||||
import type { ButtonConfig, TitleSegment } from "@/components/cardStack/types";
|
||||
import type { TimelinePhoneViewItem } from "@/components/cardStack/hooks/usePhoneAnimations";
|
||||
import type { TextboxLayout, InvertedBackground } from "@/providers/themeProvider/config/constants";
|
||||
|
||||
type FeaturePhone = {
|
||||
imageAlt?: string;
|
||||
videoAriaLabel?: string;
|
||||
} & (
|
||||
| { imageSrc: string; videoSrc?: never }
|
||||
| { videoSrc: string; imageSrc?: never }
|
||||
);
|
||||
|
||||
type FeatureCard = {
|
||||
id: number;
|
||||
title: string;
|
||||
description: string;
|
||||
phoneOne: FeaturePhone;
|
||||
phoneTwo: FeaturePhone;
|
||||
};
|
||||
|
||||
interface FeatureCardNineProps {
|
||||
features: FeatureCard[];
|
||||
showStepNumbers: boolean;
|
||||
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;
|
||||
desktopContainerClassName?: string;
|
||||
mobileContainerClassName?: string;
|
||||
desktopContentClassName?: string;
|
||||
desktopWrapperClassName?: string;
|
||||
mobileWrapperClassName?: string;
|
||||
phoneFrameClassName?: string;
|
||||
mobilePhoneFrameClassName?: string;
|
||||
featureContentClassName?: string;
|
||||
stepNumberClassName?: string;
|
||||
featureTitleClassName?: string;
|
||||
featureDescriptionClassName?: string;
|
||||
}
|
||||
|
||||
interface FeatureContentProps {
|
||||
feature: FeatureCard;
|
||||
showStepNumbers: boolean;
|
||||
useInvertedBackground: InvertedBackground;
|
||||
featureContentClassName: string;
|
||||
stepNumberClassName: string;
|
||||
featureTitleClassName: string;
|
||||
featureDescriptionClassName: string;
|
||||
}
|
||||
|
||||
const FeatureContent = ({
|
||||
feature,
|
||||
showStepNumbers,
|
||||
useInvertedBackground,
|
||||
featureContentClassName,
|
||||
stepNumberClassName,
|
||||
featureTitleClassName,
|
||||
featureDescriptionClassName,
|
||||
}: FeatureContentProps) => (
|
||||
<div className={cls("relative z-1 h-full w-content-width mx-auto md:w-full flex flex-col items-center text-center gap-3 md:px-5", featureContentClassName)}>
|
||||
{showStepNumbers && (
|
||||
<div
|
||||
className={cls(
|
||||
"h-8 w-[var(--height-8)] primary-button text-background rounded-full flex items-center justify-center",
|
||||
stepNumberClassName
|
||||
)}
|
||||
>
|
||||
<p className="text-sm truncate">
|
||||
{feature.id}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
<h2 className={cls("text-5xl font-medium leading-[1.15] text-balance", (useInvertedBackground === "invertDefault" || useInvertedBackground === "invertCard") && "text-background", featureTitleClassName)}>
|
||||
{feature.title}
|
||||
</h2>
|
||||
<p className={cls("text-base leading-[1.2] text-balance", (useInvertedBackground === "invertDefault" || useInvertedBackground === "invertCard") ? "text-background/75" : "text-foreground/75", featureDescriptionClassName)}>
|
||||
{feature.description}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
|
||||
const FeatureCardNine = ({
|
||||
features,
|
||||
showStepNumbers,
|
||||
title,
|
||||
titleSegments,
|
||||
description,
|
||||
tag,
|
||||
tagIcon,
|
||||
buttons,
|
||||
textboxLayout,
|
||||
useInvertedBackground,
|
||||
ariaLabel = "Feature section",
|
||||
className = "",
|
||||
containerClassName = "",
|
||||
textBoxTitleClassName = "",
|
||||
textBoxDescriptionClassName = "",
|
||||
textBoxClassName = "",
|
||||
textBoxTagClassName = "",
|
||||
textBoxButtonContainerClassName = "",
|
||||
textBoxButtonClassName = "",
|
||||
textBoxButtonTextClassName = "",
|
||||
titleImageWrapperClassName = "",
|
||||
titleImageClassName = "",
|
||||
desktopContainerClassName = "",
|
||||
mobileContainerClassName = "",
|
||||
desktopContentClassName = "",
|
||||
desktopWrapperClassName = "",
|
||||
mobileWrapperClassName = "",
|
||||
phoneFrameClassName = "",
|
||||
mobilePhoneFrameClassName = "",
|
||||
featureContentClassName = "",
|
||||
stepNumberClassName = "",
|
||||
featureTitleClassName = "",
|
||||
featureDescriptionClassName = "",
|
||||
}: FeatureCardNineProps) => {
|
||||
const items: TimelinePhoneViewItem[] = features.map((feature, index) => ({
|
||||
trigger: `trigger-${index}`,
|
||||
content: (
|
||||
<FeatureContent
|
||||
feature={feature}
|
||||
showStepNumbers={showStepNumbers}
|
||||
useInvertedBackground={useInvertedBackground}
|
||||
featureContentClassName={featureContentClassName}
|
||||
stepNumberClassName={stepNumberClassName}
|
||||
featureTitleClassName={featureTitleClassName}
|
||||
featureDescriptionClassName={featureDescriptionClassName}
|
||||
/>
|
||||
),
|
||||
imageOne: feature.phoneOne.imageSrc,
|
||||
videoOne: feature.phoneOne.videoSrc,
|
||||
imageAltOne: feature.phoneOne.imageAlt || `${feature.title} - Phone 1`,
|
||||
videoAriaLabelOne: feature.phoneOne.videoAriaLabel || `${feature.title} - Phone 1 video`,
|
||||
imageTwo: feature.phoneTwo.imageSrc,
|
||||
videoTwo: feature.phoneTwo.videoSrc,
|
||||
imageAltTwo: feature.phoneTwo.imageAlt || `${feature.title} - Phone 2`,
|
||||
videoAriaLabelTwo: feature.phoneTwo.videoAriaLabel || `${feature.title} - Phone 2 video`,
|
||||
}));
|
||||
|
||||
return (
|
||||
<TimelinePhoneView
|
||||
items={items}
|
||||
showTextBox={true}
|
||||
showDivider={true}
|
||||
title={title}
|
||||
titleSegments={titleSegments}
|
||||
description={description}
|
||||
tag={tag}
|
||||
tagIcon={tagIcon}
|
||||
buttons={buttons}
|
||||
textboxLayout={textboxLayout}
|
||||
useInvertedBackground={useInvertedBackground}
|
||||
className={className}
|
||||
containerClassName={containerClassName}
|
||||
textBoxClassName={textBoxClassName}
|
||||
titleClassName={textBoxTitleClassName}
|
||||
descriptionClassName={textBoxDescriptionClassName}
|
||||
tagClassName={textBoxTagClassName}
|
||||
buttonContainerClassName={textBoxButtonContainerClassName}
|
||||
buttonClassName={textBoxButtonClassName}
|
||||
buttonTextClassName={textBoxButtonTextClassName}
|
||||
titleImageWrapperClassName={titleImageWrapperClassName}
|
||||
titleImageClassName={titleImageClassName}
|
||||
desktopContainerClassName={desktopContainerClassName}
|
||||
mobileContainerClassName={mobileContainerClassName}
|
||||
desktopContentClassName={desktopContentClassName}
|
||||
desktopWrapperClassName={desktopWrapperClassName}
|
||||
mobileWrapperClassName={mobileWrapperClassName}
|
||||
phoneFrameClassName={phoneFrameClassName}
|
||||
mobilePhoneFrameClassName={mobilePhoneFrameClassName}
|
||||
ariaLabel={ariaLabel}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
FeatureCardNine.displayName = "FeatureCardNine";
|
||||
|
||||
export default FeatureCardNine;
|
||||
193
src/components/sections/feature/FeatureCardNineteen.tsx
Normal file
193
src/components/sections/feature/FeatureCardNineteen.tsx
Normal file
@@ -0,0 +1,193 @@
|
||||
"use client";
|
||||
|
||||
import TimelineCardStack from "@/components/cardStack/layouts/timelines/TimelineCardStack";
|
||||
import MediaContent from "@/components/shared/MediaContent";
|
||||
import Tag from "@/components/shared/Tag";
|
||||
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, TitleSegment } from "@/components/cardStack/types";
|
||||
import type { TextboxLayout, InvertedBackground } from "@/providers/themeProvider/config/constants";
|
||||
|
||||
type FeatureCard = {
|
||||
id: number;
|
||||
tag: string;
|
||||
title: string;
|
||||
subtitle: string;
|
||||
description: string;
|
||||
buttons?: ButtonConfig[];
|
||||
imageSrc?: string;
|
||||
videoSrc?: string;
|
||||
imageAlt?: string;
|
||||
videoAriaLabel?: string;
|
||||
};
|
||||
|
||||
interface FeatureCardNineteenProps {
|
||||
features: FeatureCard[];
|
||||
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;
|
||||
cardContentClassName?: string;
|
||||
cardTagClassName?: string;
|
||||
cardTitleClassName?: string;
|
||||
cardDescriptionClassName?: string;
|
||||
cardButtonClassName?: string;
|
||||
cardButtonTextClassName?: string;
|
||||
imageContainerClassName?: string;
|
||||
imageClassName?: string;
|
||||
}
|
||||
|
||||
const FeatureCardNineteen = ({
|
||||
features,
|
||||
title,
|
||||
titleSegments,
|
||||
description,
|
||||
tag,
|
||||
tagIcon,
|
||||
buttons,
|
||||
textboxLayout,
|
||||
useInvertedBackground,
|
||||
ariaLabel = "Feature section",
|
||||
className = "",
|
||||
containerClassName = "",
|
||||
textBoxTitleClassName = "",
|
||||
textBoxDescriptionClassName = "",
|
||||
textBoxClassName = "",
|
||||
textBoxTagClassName = "",
|
||||
textBoxButtonContainerClassName = "",
|
||||
textBoxButtonClassName = "",
|
||||
textBoxButtonTextClassName = "",
|
||||
titleImageWrapperClassName = "",
|
||||
titleImageClassName = "",
|
||||
cardContentClassName = "",
|
||||
cardTagClassName = "",
|
||||
cardTitleClassName = "",
|
||||
cardDescriptionClassName = "",
|
||||
cardButtonClassName = "",
|
||||
cardButtonTextClassName = "",
|
||||
imageContainerClassName = "",
|
||||
imageClassName = "",
|
||||
}: FeatureCardNineteenProps) => {
|
||||
const theme = useTheme();
|
||||
const shouldUseLightText = shouldUseInvertedText(useInvertedBackground, theme.cardStyle);
|
||||
|
||||
return (
|
||||
<TimelineCardStack
|
||||
title={title}
|
||||
titleSegments={titleSegments}
|
||||
description={description}
|
||||
tag={tag}
|
||||
tagIcon={tagIcon}
|
||||
buttons={buttons}
|
||||
textboxLayout={textboxLayout}
|
||||
useInvertedBackground={useInvertedBackground}
|
||||
className={className}
|
||||
containerClassName={containerClassName}
|
||||
titleClassName={textBoxTitleClassName}
|
||||
descriptionClassName={textBoxDescriptionClassName}
|
||||
textBoxClassName={textBoxClassName}
|
||||
tagClassName={textBoxTagClassName}
|
||||
buttonContainerClassName={textBoxButtonContainerClassName}
|
||||
buttonClassName={textBoxButtonClassName}
|
||||
buttonTextClassName={textBoxButtonTextClassName}
|
||||
titleImageWrapperClassName={titleImageWrapperClassName}
|
||||
titleImageClassName={titleImageClassName}
|
||||
ariaLabel={ariaLabel}
|
||||
>
|
||||
{features.map((feature) => {
|
||||
const stepNumber = String(feature.id).padStart(2, "0");
|
||||
return (
|
||||
<div
|
||||
key={feature.id}
|
||||
className={cls("relative z-1 w-full min-h-0 h-full flex flex-col md:flex-row justify-between overflow-hidden", cardContentClassName)}
|
||||
>
|
||||
<div className="relative w-full md:w-1/2 md:h-full flex flex-col justify-between p-8 md:p-12">
|
||||
<div className="flex flex-col gap-4 md:gap-6">
|
||||
<Tag
|
||||
text={feature.tag}
|
||||
useInvertedBackground={useInvertedBackground}
|
||||
className={cardTagClassName}
|
||||
/>
|
||||
<h2 className={cls(
|
||||
"text-5xl md:text-7xl font-medium leading-none",
|
||||
shouldUseLightText ? "text-background" : "text-foreground",
|
||||
cardTitleClassName
|
||||
)}>
|
||||
{feature.title}
|
||||
</h2>
|
||||
</div>
|
||||
<div className="block md:hidden w-full h-px my-6 bg-accent/20" />
|
||||
<div className="flex flex-col gap-2 md:gap-4">
|
||||
<h3 className={cls(
|
||||
"text-xl md:text-2xl font-medium",
|
||||
shouldUseLightText ? "text-background" : "text-foreground"
|
||||
)}>
|
||||
{feature.subtitle}
|
||||
</h3>
|
||||
<p className={cls(
|
||||
"text-base md:text-lg leading-tight",
|
||||
shouldUseLightText ? "text-background/80" : "text-foreground/80",
|
||||
cardDescriptionClassName
|
||||
)}>
|
||||
{feature.description}
|
||||
</p>
|
||||
{feature.buttons && feature.buttons.length > 0 && (
|
||||
<div className="flex gap-4 mt-2">
|
||||
{feature.buttons.slice(0, 2).map((button, buttonIndex) => (
|
||||
<Button
|
||||
key={`${button.text}-${buttonIndex}`}
|
||||
{...getButtonProps(button, buttonIndex, theme.defaultButtonVariant, cardButtonClassName, cardButtonTextClassName)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="relative w-full md:w-4/10 min-h-0 h-full flex flex-col gap-10 p-8 md:p-12">
|
||||
<h3 className="hidden md:block text-8xl font-medium text-accent/20 self-end">
|
||||
{stepNumber}
|
||||
</h3>
|
||||
<div
|
||||
className={cls(
|
||||
"relative max-h-full min-h-0 w-full h-full min-w-0 max-w-full md:aspect-[4/5] rounded-theme-capped overflow-hidden rotate-3",
|
||||
imageContainerClassName
|
||||
)}
|
||||
>
|
||||
<MediaContent
|
||||
imageSrc={feature.imageSrc}
|
||||
videoSrc={feature.videoSrc}
|
||||
imageAlt={feature.imageAlt || feature.title}
|
||||
videoAriaLabel={feature.videoAriaLabel || feature.title}
|
||||
imageClassName={cls("w-full h-full object-cover", imageClassName)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</TimelineCardStack>
|
||||
);
|
||||
};
|
||||
|
||||
FeatureCardNineteen.displayName = "FeatureCardNineteen";
|
||||
|
||||
export default FeatureCardNineteen;
|
||||
172
src/components/sections/feature/FeatureCardOne.tsx
Normal file
172
src/components/sections/feature/FeatureCardOne.tsx
Normal file
@@ -0,0 +1,172 @@
|
||||
"use client";
|
||||
|
||||
import CardStack from "@/components/cardStack/CardStack";
|
||||
import MediaContent from "@/components/shared/MediaContent";
|
||||
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, GridVariant, CardAnimationType, TitleSegment } from "@/components/cardStack/types";
|
||||
|
||||
type FeatureCardOneGridVariant = Exclude<GridVariant, "uniform-alternating-heights" | "uniform-alternating-heights-inverted">;
|
||||
import type { TextboxLayout, InvertedBackground } from "@/providers/themeProvider/config/constants";
|
||||
|
||||
type FeatureCard = {
|
||||
title: string;
|
||||
description: string;
|
||||
button?: ButtonConfig;
|
||||
} & (
|
||||
| {
|
||||
imageSrc: string;
|
||||
imageAlt?: string;
|
||||
videoSrc?: never;
|
||||
videoAriaLabel?: never;
|
||||
}
|
||||
| {
|
||||
videoSrc: string;
|
||||
videoAriaLabel?: string;
|
||||
imageSrc?: never;
|
||||
imageAlt?: never;
|
||||
}
|
||||
);
|
||||
|
||||
interface FeatureCardOneProps {
|
||||
features: FeatureCard[];
|
||||
carouselMode?: "auto" | "buttons";
|
||||
gridVariant: FeatureCardOneGridVariant;
|
||||
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;
|
||||
mediaClassName?: string;
|
||||
textBoxTitleClassName?: string;
|
||||
textBoxTitleImageWrapperClassName?: string;
|
||||
textBoxTitleImageClassName?: string;
|
||||
textBoxDescriptionClassName?: string;
|
||||
cardTitleClassName?: string;
|
||||
cardDescriptionClassName?: string;
|
||||
cardButtonClassName?: string;
|
||||
cardButtonTextClassName?: string;
|
||||
gridClassName?: string;
|
||||
carouselClassName?: string;
|
||||
controlsClassName?: string;
|
||||
textBoxClassName?: string;
|
||||
textBoxTagClassName?: string;
|
||||
textBoxButtonContainerClassName?: string;
|
||||
textBoxButtonClassName?: string;
|
||||
textBoxButtonTextClassName?: string;
|
||||
}
|
||||
|
||||
const FeatureCardOne = ({
|
||||
features,
|
||||
carouselMode = "buttons",
|
||||
gridVariant,
|
||||
uniformGridCustomHeightClasses,
|
||||
animationType,
|
||||
title,
|
||||
titleSegments,
|
||||
description,
|
||||
tag,
|
||||
tagIcon,
|
||||
buttons,
|
||||
textboxLayout,
|
||||
useInvertedBackground,
|
||||
ariaLabel = "Feature section",
|
||||
className = "",
|
||||
containerClassName = "",
|
||||
cardClassName = "",
|
||||
mediaClassName = "",
|
||||
textBoxTitleClassName = "",
|
||||
textBoxTitleImageWrapperClassName = "",
|
||||
textBoxTitleImageClassName = "",
|
||||
textBoxDescriptionClassName = "",
|
||||
cardTitleClassName = "",
|
||||
cardDescriptionClassName = "",
|
||||
cardButtonClassName = "",
|
||||
cardButtonTextClassName = "",
|
||||
gridClassName = "",
|
||||
carouselClassName = "",
|
||||
controlsClassName = "",
|
||||
textBoxClassName = "",
|
||||
textBoxTagClassName = "",
|
||||
textBoxButtonContainerClassName = "",
|
||||
textBoxButtonClassName = "",
|
||||
textBoxButtonTextClassName = "",
|
||||
}: FeatureCardOneProps) => {
|
||||
const theme = useTheme();
|
||||
const shouldUseLightText = shouldUseInvertedText(useInvertedBackground, theme.cardStyle);
|
||||
|
||||
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}
|
||||
>
|
||||
{features.map((feature, index) => (
|
||||
<div
|
||||
key={`${feature.title}-${index}`}
|
||||
className={cls("card flex flex-col gap-4 p-4 rounded-theme-capped min-h-0 h-full", cardClassName)}
|
||||
>
|
||||
<MediaContent
|
||||
imageSrc={feature.imageSrc}
|
||||
videoSrc={feature.videoSrc}
|
||||
imageAlt={feature.imageAlt || "Feature image"}
|
||||
videoAriaLabel={feature.videoAriaLabel || "Feature video"}
|
||||
imageClassName={cls("relative z-1 min-h-0 h-full", mediaClassName)}
|
||||
/>
|
||||
<div className="relative z-1 flex flex-col gap-1">
|
||||
<h3 className={cls("text-2xl font-medium", shouldUseLightText && "text-background", cardTitleClassName)}>
|
||||
{feature.title}
|
||||
</h3>
|
||||
<p className={cls("text-sm leading-[1.1]", shouldUseLightText ? "text-background" : "text-foreground", cardDescriptionClassName)}>
|
||||
{feature.description}
|
||||
</p>
|
||||
</div>
|
||||
{feature.button && (
|
||||
<Button {...getButtonProps(feature.button, 0, theme.defaultButtonVariant, cls("w-full", cardButtonClassName), cardButtonTextClassName)} />
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</CardStack>
|
||||
);
|
||||
};
|
||||
|
||||
FeatureCardOne.displayName = "FeatureCardOne";
|
||||
|
||||
export default FeatureCardOne;
|
||||
159
src/components/sections/feature/FeatureCardSeven.tsx
Normal file
159
src/components/sections/feature/FeatureCardSeven.tsx
Normal file
@@ -0,0 +1,159 @@
|
||||
"use client";
|
||||
|
||||
import CardList from "@/components/cardStack/CardList";
|
||||
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, CardAnimationType, TitleSegment } from "@/components/cardStack/types";
|
||||
import type { TextboxLayout, InvertedBackground } from "@/providers/themeProvider/config/constants";
|
||||
|
||||
type FeatureCard = {
|
||||
id: number;
|
||||
title: string;
|
||||
description: string;
|
||||
imageSrc?: string;
|
||||
videoSrc?: string;
|
||||
imageAlt?: string;
|
||||
videoAriaLabel?: string;
|
||||
};
|
||||
|
||||
interface FeatureCardSevenProps {
|
||||
features: FeatureCard[];
|
||||
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;
|
||||
stepNumberClassName?: string;
|
||||
cardTitleClassName?: string;
|
||||
cardDescriptionClassName?: string;
|
||||
imageContainerClassName?: string;
|
||||
imageClassName?: string;
|
||||
}
|
||||
|
||||
const FeatureCardSeven = ({
|
||||
features,
|
||||
animationType,
|
||||
title,
|
||||
titleSegments,
|
||||
description,
|
||||
tag,
|
||||
tagIcon,
|
||||
buttons,
|
||||
textboxLayout,
|
||||
useInvertedBackground,
|
||||
ariaLabel = "Feature section",
|
||||
className = "",
|
||||
containerClassName = "",
|
||||
cardClassName = "",
|
||||
textBoxTitleClassName = "",
|
||||
textBoxDescriptionClassName = "",
|
||||
textBoxClassName = "",
|
||||
textBoxTagClassName = "",
|
||||
textBoxButtonContainerClassName = "",
|
||||
textBoxButtonClassName = "",
|
||||
textBoxButtonTextClassName = "",
|
||||
titleImageWrapperClassName = "",
|
||||
titleImageClassName = "",
|
||||
cardContentClassName = "",
|
||||
stepNumberClassName = "",
|
||||
cardTitleClassName = "",
|
||||
cardDescriptionClassName = "",
|
||||
imageContainerClassName = "",
|
||||
imageClassName = "",
|
||||
}: FeatureCardSevenProps) => {
|
||||
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}
|
||||
>
|
||||
{features.map((feature, index) => (
|
||||
<div
|
||||
key={feature.id}
|
||||
className={cls("relative z-1 w-full min-h-0 h-full flex flex-col justify-between items-center p-6 gap-6 md:p-15 md:gap-15", index % 2 === 0 ? "md:flex-row" : "md:flex-row-reverse", cardContentClassName)}
|
||||
>
|
||||
<div className="w-full md:w-1/2 min-w-0 h-fit md:h-full flex flex-col justify-center">
|
||||
<div className="w-full min-w-0 flex flex-col gap-3 md:gap-5">
|
||||
<div
|
||||
className={cls(
|
||||
"h-8 w-[var(--height-8)] primary-button text-background rounded-full flex items-center justify-center",
|
||||
stepNumberClassName
|
||||
)}
|
||||
>
|
||||
<p className="text-sm truncate">
|
||||
{feature.id}
|
||||
</p>
|
||||
</div>
|
||||
<h2 className={cls("mt-1 text-4xl md:text-5xl font-medium leading-[1.15] text-balance", shouldUseLightText && "text-background", cardTitleClassName)}>
|
||||
{feature.title}
|
||||
</h2>
|
||||
<p className={cls("text-base leading-[1.15] text-balance", shouldUseLightText ? "text-background" : "text-foreground", cardDescriptionClassName)}>
|
||||
{feature.description}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className={cls(
|
||||
"relative w-full md:w-1/2 aspect-square overflow-hidden rounded-theme-capped",
|
||||
imageContainerClassName
|
||||
)}
|
||||
>
|
||||
<MediaContent
|
||||
imageSrc={feature.imageSrc}
|
||||
videoSrc={feature.videoSrc}
|
||||
imageAlt={feature.imageAlt || feature.title}
|
||||
videoAriaLabel={feature.videoAriaLabel || feature.title}
|
||||
imageClassName={cls("w-full h-full object-cover", imageClassName)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</CardList>
|
||||
);
|
||||
};
|
||||
|
||||
FeatureCardSeven.displayName = "FeatureCardSeven";
|
||||
|
||||
export default FeatureCardSeven;
|
||||
153
src/components/sections/feature/FeatureCardSix.tsx
Normal file
153
src/components/sections/feature/FeatureCardSix.tsx
Normal file
@@ -0,0 +1,153 @@
|
||||
"use client";
|
||||
|
||||
import TimelineCardStack from "@/components/cardStack/layouts/timelines/TimelineCardStack";
|
||||
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 } from "@/components/cardStack/types";
|
||||
import type { TextboxLayout, InvertedBackground } from "@/providers/themeProvider/config/constants";
|
||||
|
||||
type FeatureCard = {
|
||||
id: number;
|
||||
title: string;
|
||||
description: string;
|
||||
imageSrc?: string;
|
||||
videoSrc?: string;
|
||||
imageAlt?: string;
|
||||
videoAriaLabel?: string;
|
||||
};
|
||||
|
||||
interface FeatureCardSixProps {
|
||||
features: FeatureCard[];
|
||||
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;
|
||||
cardContentClassName?: string;
|
||||
stepNumberClassName?: string;
|
||||
cardTitleClassName?: string;
|
||||
cardDescriptionClassName?: string;
|
||||
imageContainerClassName?: string;
|
||||
imageClassName?: string;
|
||||
}
|
||||
|
||||
const FeatureCardSix = ({
|
||||
features,
|
||||
title,
|
||||
titleSegments,
|
||||
description,
|
||||
tag,
|
||||
tagIcon,
|
||||
buttons,
|
||||
textboxLayout,
|
||||
useInvertedBackground,
|
||||
ariaLabel = "Feature section",
|
||||
className = "",
|
||||
containerClassName = "",
|
||||
textBoxTitleClassName = "",
|
||||
textBoxDescriptionClassName = "",
|
||||
textBoxClassName = "",
|
||||
textBoxTagClassName = "",
|
||||
textBoxButtonContainerClassName = "",
|
||||
textBoxButtonClassName = "",
|
||||
textBoxButtonTextClassName = "",
|
||||
titleImageWrapperClassName = "",
|
||||
titleImageClassName = "",
|
||||
cardContentClassName = "",
|
||||
stepNumberClassName = "",
|
||||
cardTitleClassName = "",
|
||||
cardDescriptionClassName = "",
|
||||
imageContainerClassName = "",
|
||||
imageClassName = "",
|
||||
}: FeatureCardSixProps) => {
|
||||
const theme = useTheme();
|
||||
const shouldUseLightText = shouldUseInvertedText(useInvertedBackground, theme.cardStyle);
|
||||
|
||||
return (
|
||||
<TimelineCardStack
|
||||
title={title}
|
||||
titleSegments={titleSegments}
|
||||
description={description}
|
||||
tag={tag}
|
||||
tagIcon={tagIcon}
|
||||
buttons={buttons}
|
||||
textboxLayout={textboxLayout}
|
||||
useInvertedBackground={useInvertedBackground}
|
||||
className={className}
|
||||
containerClassName={containerClassName}
|
||||
titleClassName={textBoxTitleClassName}
|
||||
descriptionClassName={textBoxDescriptionClassName}
|
||||
textBoxClassName={textBoxClassName}
|
||||
tagClassName={textBoxTagClassName}
|
||||
buttonContainerClassName={textBoxButtonContainerClassName}
|
||||
buttonClassName={textBoxButtonClassName}
|
||||
buttonTextClassName={textBoxButtonTextClassName}
|
||||
titleImageWrapperClassName={titleImageWrapperClassName}
|
||||
titleImageClassName={titleImageClassName}
|
||||
ariaLabel={ariaLabel}
|
||||
>
|
||||
{features.map((feature) => (
|
||||
<div
|
||||
key={feature.id}
|
||||
className={cls("relative z-1 w-full min-h-0 h-full flex flex-col md:flex-row justify-between items-center p-10 gap-10 md:p-15 md:gap-15", cardContentClassName)}
|
||||
>
|
||||
<div className="w-full md:w-1/2 min-w-0 h-fit md:h-full flex flex-col justify-center">
|
||||
<div className="w-full min-w-0 flex flex-col gap-3 md:gap-5">
|
||||
<div
|
||||
className={cls(
|
||||
"h-8 w-[var(--height-8)] primary-button text-background rounded-full flex items-center justify-center",
|
||||
stepNumberClassName
|
||||
)}
|
||||
>
|
||||
<p className="text-sm truncate">
|
||||
{feature.id}
|
||||
</p>
|
||||
</div>
|
||||
<h2 className={cls("mt-1 text-4xl md:text-5xl font-medium leading-[1.15] text-balance truncate", shouldUseLightText && "text-background", cardTitleClassName)}>
|
||||
{feature.title}
|
||||
</h2>
|
||||
<p className={cls("text-base leading-[1.15] text-balance truncate", shouldUseLightText ? "text-background" : "text-foreground", cardDescriptionClassName)}>
|
||||
{feature.description}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className={cls(
|
||||
"relative z-1 w-full md:w-1/2 min-h-0 h-full overflow-hidden rounded-theme-capped",
|
||||
imageContainerClassName
|
||||
)}
|
||||
>
|
||||
<MediaContent
|
||||
imageSrc={feature.imageSrc}
|
||||
videoSrc={feature.videoSrc}
|
||||
imageAlt={feature.imageAlt || feature.title}
|
||||
videoAriaLabel={feature.videoAriaLabel || feature.title}
|
||||
imageClassName={cls("w-full min-h-0 h-full object-cover", imageClassName)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</TimelineCardStack>
|
||||
);
|
||||
};
|
||||
|
||||
FeatureCardSix.displayName = "FeatureCardSix";
|
||||
|
||||
export default FeatureCardSix;
|
||||
168
src/components/sections/feature/FeatureCardSixteen.tsx
Normal file
168
src/components/sections/feature/FeatureCardSixteen.tsx
Normal file
@@ -0,0 +1,168 @@
|
||||
"use client";
|
||||
|
||||
import CardStackTextBox from "@/components/cardStack/CardStackTextBox";
|
||||
import PricingFeatureList from "@/components/shared/PricingFeatureList";
|
||||
import { useCardAnimation } from "@/components/cardStack/hooks/useCardAnimation";
|
||||
import { Check, X } from "lucide-react";
|
||||
import { cls, shouldUseInvertedText } from "@/lib/utils";
|
||||
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";
|
||||
|
||||
type ComparisonItem = {
|
||||
title: string;
|
||||
items: string[];
|
||||
};
|
||||
|
||||
interface FeatureCardSixteenProps {
|
||||
negativeCard: ComparisonItem;
|
||||
positiveCard: ComparisonItem;
|
||||
animationType: CardAnimationType;
|
||||
title: string;
|
||||
titleSegments?: TitleSegment[];
|
||||
description: string;
|
||||
textboxLayout: TextboxLayout;
|
||||
useInvertedBackground: InvertedBackground;
|
||||
tag?: string;
|
||||
tagIcon?: LucideIcon;
|
||||
buttons?: ButtonConfig[];
|
||||
ariaLabel?: string;
|
||||
className?: string;
|
||||
containerClassName?: string;
|
||||
textBoxTitleClassName?: string;
|
||||
titleImageWrapperClassName?: string;
|
||||
titleImageClassName?: string;
|
||||
textBoxDescriptionClassName?: string;
|
||||
textBoxClassName?: string;
|
||||
textBoxTagClassName?: string;
|
||||
textBoxButtonContainerClassName?: string;
|
||||
textBoxButtonClassName?: string;
|
||||
textBoxButtonTextClassName?: string;
|
||||
gridClassName?: string;
|
||||
cardClassName?: string;
|
||||
cardTitleClassName?: string;
|
||||
itemsListClassName?: string;
|
||||
itemClassName?: string;
|
||||
itemIconClassName?: string;
|
||||
itemTextClassName?: string;
|
||||
}
|
||||
|
||||
const FeatureCardSixteen = ({
|
||||
negativeCard,
|
||||
positiveCard,
|
||||
animationType,
|
||||
title,
|
||||
titleSegments,
|
||||
description,
|
||||
textboxLayout,
|
||||
useInvertedBackground,
|
||||
tag,
|
||||
tagIcon,
|
||||
buttons,
|
||||
ariaLabel = "Feature comparison section",
|
||||
className = "",
|
||||
containerClassName = "",
|
||||
textBoxTitleClassName = "",
|
||||
titleImageWrapperClassName = "",
|
||||
titleImageClassName = "",
|
||||
textBoxDescriptionClassName = "",
|
||||
textBoxClassName = "",
|
||||
textBoxTagClassName = "",
|
||||
textBoxButtonContainerClassName = "",
|
||||
textBoxButtonClassName = "",
|
||||
textBoxButtonTextClassName = "",
|
||||
gridClassName = "",
|
||||
cardClassName = "",
|
||||
cardTitleClassName = "",
|
||||
itemsListClassName = "",
|
||||
itemClassName = "",
|
||||
itemIconClassName = "",
|
||||
itemTextClassName = "",
|
||||
}: FeatureCardSixteenProps) => {
|
||||
const theme = useTheme();
|
||||
const shouldCardUseLightText = shouldUseInvertedText("invertDefault", theme.cardStyle) || shouldUseInvertedText("invertCard", theme.cardStyle);
|
||||
const { itemRefs } = useCardAnimation({ animationType, itemCount: 2 });
|
||||
|
||||
const cards = [
|
||||
{ ...negativeCard, variant: "negative" as const },
|
||||
{ ...positiveCard, variant: "positive" as const },
|
||||
];
|
||||
|
||||
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-8", 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(
|
||||
"relative mx-auto w-full md:w-60 grid grid-cols-1 gap-6",
|
||||
cards.length >= 2 ? "md:grid-cols-2" : "md:grid-cols-1",
|
||||
gridClassName
|
||||
)}>
|
||||
{cards.map((card, index) => (
|
||||
<div
|
||||
key={card.variant}
|
||||
ref={(el) => { itemRefs.current[index] = el; }}
|
||||
className={cls(
|
||||
"relative h-full card rounded-theme-capped p-6",
|
||||
cardClassName
|
||||
)}
|
||||
>
|
||||
<div className={cls("flex flex-col gap-6", card.variant === "negative" && "opacity-50")}>
|
||||
<h3 className={cls(
|
||||
"text-3xl text-center font-medium",
|
||||
shouldCardUseLightText ? "text-background" : "text-foreground",
|
||||
cardTitleClassName
|
||||
)}>
|
||||
{card.title}
|
||||
</h3>
|
||||
<PricingFeatureList
|
||||
features={card.items}
|
||||
icon={card.variant === "positive" ? Check : X}
|
||||
shouldUseLightText={shouldCardUseLightText}
|
||||
className={itemsListClassName}
|
||||
featureItemClassName={itemClassName}
|
||||
featureIconWrapperClassName=""
|
||||
featureIconClassName={itemIconClassName}
|
||||
featureTextClassName={cls("truncate", itemTextClassName)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
FeatureCardSixteen.displayName = "FeatureCardSixteen";
|
||||
|
||||
export default FeatureCardSixteen;
|
||||
242
src/components/sections/feature/FeatureCardTen.tsx
Normal file
242
src/components/sections/feature/FeatureCardTen.tsx
Normal file
@@ -0,0 +1,242 @@
|
||||
"use client";
|
||||
|
||||
import TimelineProcessFlow from "@/components/cardStack/layouts/timelines/TimelineProcessFlow";
|
||||
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, CardAnimationType, TitleSegment } from "@/components/cardStack/types";
|
||||
import type { TextboxLayout, InvertedBackground } from "@/providers/themeProvider/config/constants";
|
||||
|
||||
type FeatureMedia = {
|
||||
imageAlt?: string;
|
||||
videoAriaLabel?: string;
|
||||
} & (
|
||||
| { imageSrc: string; videoSrc?: never }
|
||||
| { videoSrc: string; imageSrc?: never }
|
||||
);
|
||||
|
||||
interface FeatureListItem {
|
||||
icon: LucideIcon;
|
||||
text: string;
|
||||
}
|
||||
|
||||
interface FeatureCard {
|
||||
id: string;
|
||||
title: string;
|
||||
description: string;
|
||||
media: FeatureMedia;
|
||||
items: FeatureListItem[];
|
||||
reverse: boolean;
|
||||
}
|
||||
|
||||
interface FeatureCardTenProps {
|
||||
features: FeatureCard[];
|
||||
title: string;
|
||||
titleSegments?: TitleSegment[];
|
||||
description: string;
|
||||
tag?: string;
|
||||
tagIcon?: LucideIcon;
|
||||
buttons?: ButtonConfig[];
|
||||
textboxLayout: TextboxLayout;
|
||||
animationType: CardAnimationType;
|
||||
useInvertedBackground: InvertedBackground;
|
||||
ariaLabel?: string;
|
||||
className?: string;
|
||||
containerClassName?: string;
|
||||
textBoxClassName?: string;
|
||||
textBoxTitleClassName?: string;
|
||||
textBoxDescriptionClassName?: string;
|
||||
textBoxTagClassName?: string;
|
||||
textBoxButtonContainerClassName?: string;
|
||||
textBoxButtonClassName?: string;
|
||||
textBoxButtonTextClassName?: string;
|
||||
titleImageWrapperClassName?: string;
|
||||
titleImageClassName?: string;
|
||||
itemClassName?: string;
|
||||
mediaWrapperClassName?: string;
|
||||
mediaCardClassName?: string;
|
||||
numberClassName?: string;
|
||||
contentWrapperClassName?: string;
|
||||
featureTitleClassName?: string;
|
||||
featureDescriptionClassName?: string;
|
||||
listItemClassName?: string;
|
||||
iconContainerClassName?: string;
|
||||
iconClassName?: string;
|
||||
gapClassName?: string;
|
||||
}
|
||||
|
||||
interface FeatureMediaProps {
|
||||
media: FeatureMedia;
|
||||
title: string;
|
||||
mediaCardClassName: string;
|
||||
}
|
||||
|
||||
const FeatureMedia = ({
|
||||
media,
|
||||
title,
|
||||
mediaCardClassName,
|
||||
}: FeatureMediaProps) => (
|
||||
<div className={cls("card rounded-theme-capped p-4", mediaCardClassName)}>
|
||||
<MediaContent
|
||||
imageSrc={media.imageSrc}
|
||||
videoSrc={media.videoSrc}
|
||||
imageAlt={media.imageAlt || title}
|
||||
videoAriaLabel={media.videoAriaLabel || `${title} video`}
|
||||
imageClassName="relative z-1 w-full h-full object-cover rounded-theme-capped"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
interface FeatureContentProps {
|
||||
feature: FeatureCard;
|
||||
useInvertedBackground: InvertedBackground;
|
||||
shouldUseLightText: boolean;
|
||||
featureTitleClassName: string;
|
||||
featureDescriptionClassName: string;
|
||||
listItemClassName: string;
|
||||
iconContainerClassName: string;
|
||||
iconClassName: string;
|
||||
}
|
||||
|
||||
const FeatureContent = ({
|
||||
feature,
|
||||
useInvertedBackground,
|
||||
shouldUseLightText,
|
||||
featureTitleClassName,
|
||||
featureDescriptionClassName,
|
||||
listItemClassName,
|
||||
iconContainerClassName,
|
||||
iconClassName,
|
||||
}: FeatureContentProps) => (
|
||||
<div className="flex flex-col gap-3" >
|
||||
<h3 className={cls("text-xl md:text-4xl font-medium leading-[1.15]", (useInvertedBackground === "invertDefault" || useInvertedBackground === "invertCard") && "text-background", featureTitleClassName)}>
|
||||
{feature.title}
|
||||
</h3>
|
||||
<p className={cls("text-base leading-[1.2]", (useInvertedBackground === "invertDefault" || useInvertedBackground === "invertCard") ? "text-background/75" : "text-foreground/75", featureDescriptionClassName)}>
|
||||
{feature.description}
|
||||
</p>
|
||||
<ul className="flex flex-col m-0 mt-1 p-0 list-none gap-3">
|
||||
{feature.items.map((listItem, listIndex) => {
|
||||
const Icon = listItem.icon;
|
||||
return (
|
||||
<li key={listIndex} className="flex items-center gap-3">
|
||||
<div
|
||||
className={cls(
|
||||
"shrink-0 h-9 aspect-square flex items-center justify-center rounded bg-background card",
|
||||
iconContainerClassName
|
||||
)}
|
||||
>
|
||||
<Icon
|
||||
className={cls("h-4/10 w-4/10", shouldUseLightText ? "text-background" : "text-foreground", iconClassName)}
|
||||
strokeWidth={1.25}
|
||||
/>
|
||||
</div>
|
||||
<p className={cls("text-base", (useInvertedBackground === "invertDefault" || useInvertedBackground === "invertCard") ? "text-background/75" : "text-foreground/75", listItemClassName)}>
|
||||
{listItem.text}
|
||||
</p>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
|
||||
const FeatureCardTen = ({
|
||||
features,
|
||||
title,
|
||||
titleSegments,
|
||||
description,
|
||||
tag,
|
||||
tagIcon,
|
||||
buttons,
|
||||
textboxLayout,
|
||||
animationType,
|
||||
useInvertedBackground,
|
||||
ariaLabel = "Feature section",
|
||||
className = "",
|
||||
containerClassName = "",
|
||||
textBoxClassName = "",
|
||||
textBoxTitleClassName = "",
|
||||
textBoxDescriptionClassName = "",
|
||||
textBoxTagClassName = "",
|
||||
textBoxButtonContainerClassName = "",
|
||||
textBoxButtonClassName = "",
|
||||
textBoxButtonTextClassName = "",
|
||||
titleImageWrapperClassName = "",
|
||||
titleImageClassName = "",
|
||||
itemClassName = "",
|
||||
mediaWrapperClassName = "",
|
||||
mediaCardClassName = "",
|
||||
numberClassName = "",
|
||||
contentWrapperClassName = "",
|
||||
featureTitleClassName = "",
|
||||
featureDescriptionClassName = "",
|
||||
listItemClassName = "",
|
||||
iconContainerClassName = "",
|
||||
iconClassName = "",
|
||||
gapClassName = "",
|
||||
}: FeatureCardTenProps) => {
|
||||
const theme = useTheme();
|
||||
const shouldUseLightText = shouldUseInvertedText(useInvertedBackground, theme.cardStyle);
|
||||
|
||||
const timelineItems = features.map((feature) => ({
|
||||
id: feature.id,
|
||||
reverse: feature.reverse,
|
||||
media: (
|
||||
<FeatureMedia
|
||||
media={feature.media}
|
||||
title={feature.title}
|
||||
mediaCardClassName={mediaCardClassName}
|
||||
/>
|
||||
),
|
||||
content: (
|
||||
<FeatureContent
|
||||
feature={feature}
|
||||
useInvertedBackground={useInvertedBackground}
|
||||
shouldUseLightText={shouldUseLightText}
|
||||
featureTitleClassName={featureTitleClassName}
|
||||
featureDescriptionClassName={featureDescriptionClassName}
|
||||
listItemClassName={listItemClassName}
|
||||
iconContainerClassName={iconContainerClassName}
|
||||
iconClassName={iconClassName}
|
||||
/>
|
||||
),
|
||||
}));
|
||||
|
||||
return (
|
||||
<TimelineProcessFlow
|
||||
items={timelineItems}
|
||||
title={title}
|
||||
titleSegments={titleSegments}
|
||||
description={description}
|
||||
tag={tag}
|
||||
tagIcon={tagIcon}
|
||||
buttons={buttons}
|
||||
textboxLayout={textboxLayout}
|
||||
animationType={animationType}
|
||||
useInvertedBackground={useInvertedBackground}
|
||||
ariaLabel={ariaLabel}
|
||||
className={className}
|
||||
containerClassName={containerClassName}
|
||||
textBoxClassName={textBoxClassName}
|
||||
textBoxTitleClassName={textBoxTitleClassName}
|
||||
textBoxDescriptionClassName={textBoxDescriptionClassName}
|
||||
textBoxTagClassName={textBoxTagClassName}
|
||||
textBoxButtonContainerClassName={textBoxButtonContainerClassName}
|
||||
textBoxButtonClassName={textBoxButtonClassName}
|
||||
textBoxButtonTextClassName={textBoxButtonTextClassName}
|
||||
titleImageWrapperClassName={titleImageWrapperClassName}
|
||||
titleImageClassName={titleImageClassName}
|
||||
itemClassName={itemClassName}
|
||||
mediaWrapperClassName={mediaWrapperClassName}
|
||||
numberClassName={numberClassName}
|
||||
contentWrapperClassName={contentWrapperClassName}
|
||||
gapClassName={gapClassName}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
FeatureCardTen.displayName = "FeatureCardTen";
|
||||
|
||||
export default FeatureCardTen;
|
||||
231
src/components/sections/feature/FeatureCardThirteen.tsx
Normal file
231
src/components/sections/feature/FeatureCardThirteen.tsx
Normal file
@@ -0,0 +1,231 @@
|
||||
"use client";
|
||||
|
||||
import CardStack from "@/components/cardStack/CardStack";
|
||||
import { cls, shouldUseInvertedText } from "@/lib/utils";
|
||||
import { useTheme } from "@/providers/themeProvider/ThemeProvider";
|
||||
import type { LucideIcon } from "lucide-react";
|
||||
import type { ButtonConfig, GridVariant, CardAnimationType, TitleSegment } from "@/components/cardStack/types";
|
||||
import type { TextboxLayout, InvertedBackground } from "@/providers/themeProvider/config/constants";
|
||||
|
||||
type FeatureCardThirteenGridVariant = Exclude<GridVariant, "one-large-right-three-stacked-left" | "one-large-left-three-stacked-right" | "timeline" | "timeline-three-columns">;
|
||||
|
||||
type FeatureCard = {
|
||||
id: string;
|
||||
title: string;
|
||||
description: string;
|
||||
};
|
||||
|
||||
interface FeatureCardThirteenProps {
|
||||
features: FeatureCard[];
|
||||
carouselMode?: "auto" | "buttons";
|
||||
gridVariant: FeatureCardThirteenGridVariant;
|
||||
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;
|
||||
cardIdClassName?: string;
|
||||
cardTitleClassName?: string;
|
||||
cardDescriptionClassName?: string;
|
||||
cardDotsClassName?: string;
|
||||
gridClassName?: string;
|
||||
carouselClassName?: string;
|
||||
controlsClassName?: string;
|
||||
textBoxClassName?: string;
|
||||
textBoxTagClassName?: string;
|
||||
textBoxButtonContainerClassName?: string;
|
||||
textBoxButtonClassName?: string;
|
||||
textBoxButtonTextClassName?: string;
|
||||
}
|
||||
|
||||
const FeatureCardThirteen = ({
|
||||
features,
|
||||
carouselMode = "buttons",
|
||||
gridVariant,
|
||||
uniformGridCustomHeightClasses,
|
||||
animationType,
|
||||
title,
|
||||
titleSegments,
|
||||
description,
|
||||
tag,
|
||||
tagIcon,
|
||||
buttons,
|
||||
textboxLayout,
|
||||
useInvertedBackground,
|
||||
ariaLabel = "Feature section",
|
||||
className = "",
|
||||
containerClassName = "",
|
||||
cardClassName = "",
|
||||
textBoxTitleClassName = "",
|
||||
textBoxTitleImageWrapperClassName = "",
|
||||
textBoxTitleImageClassName = "",
|
||||
textBoxDescriptionClassName = "",
|
||||
cardIdClassName = "",
|
||||
cardTitleClassName = "",
|
||||
cardDescriptionClassName = "",
|
||||
cardDotsClassName = "",
|
||||
gridClassName = "",
|
||||
carouselClassName = "",
|
||||
controlsClassName = "",
|
||||
textBoxClassName = "",
|
||||
textBoxTagClassName = "",
|
||||
textBoxButtonContainerClassName = "",
|
||||
textBoxButtonClassName = "",
|
||||
textBoxButtonTextClassName = "",
|
||||
}: FeatureCardThirteenProps) => {
|
||||
const theme = useTheme();
|
||||
const shouldUseLightText = shouldUseInvertedText(useInvertedBackground, theme.cardStyle);
|
||||
|
||||
// Override gridRows for certain variants to use smaller heights
|
||||
const getGridRowsOverride = () => {
|
||||
if (gridVariant === "two-columns-alternating-heights") {
|
||||
return "md:grid-rows-[9rem_9rem_0.5rem_0.5rem_9rem_9rem] 2xl:grid-rows-[12rem_12rem_0.5rem_0.5rem_12rem_12rem]";
|
||||
}
|
||||
if (gridVariant === "asymmetric-60-wide-40-narrow") {
|
||||
return "md:grid-rows-[18rem_18rem] 2xl:grid-rows-[21rem_21rem]";
|
||||
}
|
||||
if (gridVariant === "three-columns-all-equal-width") {
|
||||
return "md:grid-rows-[15rem_15rem] 2xl:grid-rows-[18rem_18rem]";
|
||||
}
|
||||
if (gridVariant === "four-items-2x2-equal-grid") {
|
||||
return "md:grid-rows-[20rem_20rem] 2xl:grid-rows-[26rem_26rem]";
|
||||
}
|
||||
if (gridVariant === "items-top-row-full-width-bottom" || gridVariant === "full-width-top-items-bottom-row") {
|
||||
return "md:grid-rows-[18rem_18rem] 2xl:grid-rows-[21rem_21rem]";
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
const gridRowsOverride = getGridRowsOverride();
|
||||
|
||||
// Override heightClasses for certain variants to use smaller heights
|
||||
const getHeightClassesOverride = () => {
|
||||
if (gridVariant === "four-items-2x2-staggered-grid" || gridVariant === "four-items-2x2-staggered-grid-inverted") {
|
||||
return "min-h-60 2xl:min-h-70";
|
||||
}
|
||||
return uniformGridCustomHeightClasses;
|
||||
};
|
||||
const heightClassesOverride = getHeightClassesOverride();
|
||||
|
||||
// Override itemHeightClasses for alternating height variants to use smaller heights
|
||||
const getItemHeightClassesOverride = () => {
|
||||
if (gridVariant === "four-items-2x2-alternating-heights") {
|
||||
return [
|
||||
"min-h-80 md:min-h-80 2xl:min-h-100",
|
||||
"min-h-80 md:min-h-50 2xl:min-h-60",
|
||||
"min-h-80 md:min-h-80 2xl:min-h-100",
|
||||
"min-h-80 md:min-h-50 2xl:min-h-60",
|
||||
];
|
||||
}
|
||||
if (gridVariant === "four-items-2x2-alternating-heights-inverted") {
|
||||
return [
|
||||
"min-h-80 md:min-h-50 2xl:min-h-60",
|
||||
"min-h-80 md:min-h-80 2xl:min-h-100",
|
||||
"min-h-80 md:min-h-50 2xl:min-h-60",
|
||||
"min-h-80 md:min-h-80 2xl:min-h-100",
|
||||
];
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
const itemHeightClassesOverride = getItemHeightClassesOverride();
|
||||
|
||||
return (
|
||||
<CardStack
|
||||
mode={carouselMode}
|
||||
gridVariant={gridVariant}
|
||||
uniformGridCustomHeightClasses={heightClassesOverride}
|
||||
gridRowsClassName={gridRowsOverride}
|
||||
itemHeightClassesOverride={itemHeightClassesOverride}
|
||||
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}
|
||||
>
|
||||
{features.map((feature, index) => (
|
||||
<div
|
||||
key={`${feature.id}-${index}`}
|
||||
className={cls(
|
||||
"card relative flex flex-col justify-between p-6 rounded-theme-capped min-h-0 h-full overflow-hidden",
|
||||
cardClassName
|
||||
)}
|
||||
>
|
||||
<div className="relative z-10 flex flex-col gap-2" >
|
||||
<div className="relative z-10 flex justify-between items-center">
|
||||
<span
|
||||
className={cls(
|
||||
"text-sm font-medium text-foreground",
|
||||
cardIdClassName
|
||||
)}
|
||||
>
|
||||
/ {feature.id}
|
||||
</span>
|
||||
<div className={cls("flex gap-1", cardDotsClassName)}>
|
||||
<span className="h-2 w-auto aspect-square rounded-theme bg-accent" />
|
||||
<span className="h-2 w-auto aspect-square rounded-theme bg-accent" />
|
||||
<span className="h-2 w-auto aspect-square rounded-theme bg-accent" />
|
||||
</div>
|
||||
</div>
|
||||
<h3
|
||||
className={cls(
|
||||
"text-4xl font-medium leading-tight",
|
||||
shouldUseLightText ? "text-background" : "text-foreground",
|
||||
cardTitleClassName
|
||||
)}
|
||||
>
|
||||
{feature.title}
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
<div className="relative z-10 flex flex-col gap-1">
|
||||
<p
|
||||
className={cls(
|
||||
"text-sm leading-tight text-balance line-clamp-3",
|
||||
shouldUseLightText ? "text-background" : "text-foreground",
|
||||
cardDescriptionClassName
|
||||
)}
|
||||
>
|
||||
{feature.description}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</CardStack>
|
||||
);
|
||||
};
|
||||
|
||||
FeatureCardThirteen.displayName = "FeatureCardThirteen";
|
||||
|
||||
export default FeatureCardThirteen;
|
||||
177
src/components/sections/feature/FeatureCardTwelve.tsx
Normal file
177
src/components/sections/feature/FeatureCardTwelve.tsx
Normal file
@@ -0,0 +1,177 @@
|
||||
"use client";
|
||||
|
||||
import { Fragment } from "react";
|
||||
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 FeatureCard {
|
||||
id: string;
|
||||
label: string;
|
||||
title: string;
|
||||
items: string[];
|
||||
buttons?: ButtonConfig[];
|
||||
}
|
||||
|
||||
interface FeatureCardTwelveProps {
|
||||
features: FeatureCard[];
|
||||
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;
|
||||
labelClassName?: string;
|
||||
cardTitleClassName?: string;
|
||||
itemsContainerClassName?: string;
|
||||
itemTextClassName?: string;
|
||||
cardButtonClassName?: string;
|
||||
cardButtonTextClassName?: string;
|
||||
}
|
||||
|
||||
const FeatureCardTwelve = ({
|
||||
features,
|
||||
animationType,
|
||||
title,
|
||||
titleSegments,
|
||||
description,
|
||||
tag,
|
||||
tagIcon,
|
||||
buttons,
|
||||
textboxLayout,
|
||||
useInvertedBackground,
|
||||
ariaLabel = "Feature section",
|
||||
className = "",
|
||||
containerClassName = "",
|
||||
cardClassName = "",
|
||||
textBoxTitleClassName = "",
|
||||
textBoxDescriptionClassName = "",
|
||||
textBoxClassName = "",
|
||||
textBoxTagClassName = "",
|
||||
textBoxButtonContainerClassName = "",
|
||||
textBoxButtonClassName = "",
|
||||
textBoxButtonTextClassName = "",
|
||||
titleImageWrapperClassName = "",
|
||||
titleImageClassName = "",
|
||||
cardContentClassName = "",
|
||||
labelClassName = "",
|
||||
cardTitleClassName = "",
|
||||
itemsContainerClassName = "",
|
||||
itemTextClassName = "",
|
||||
cardButtonClassName = "",
|
||||
cardButtonTextClassName = "",
|
||||
}: FeatureCardTwelveProps) => {
|
||||
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}
|
||||
>
|
||||
{features.map((feature) => (
|
||||
<div
|
||||
key={feature.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 flex md:justify-start">
|
||||
<h2 className={cls(
|
||||
"text-5xl md:text-6xl font-medium leading-[1.1]",
|
||||
shouldUseLightText && "text-background",
|
||||
labelClassName
|
||||
)}>
|
||||
{feature.label}
|
||||
</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">
|
||||
<h3 className={cls(
|
||||
"text-xl md:text-3xl font-medium leading-tight",
|
||||
shouldUseLightText ? "text-background" : "text-foreground",
|
||||
cardTitleClassName
|
||||
)}>
|
||||
{feature.title}
|
||||
</h3>
|
||||
|
||||
<div className={cls("flex flex-wrap items-center gap-2", itemsContainerClassName)}>
|
||||
{feature.items.map((item, index) => (
|
||||
<Fragment key={index}>
|
||||
<span className={cls(
|
||||
"text-base",
|
||||
shouldUseLightText ? "text-background" : "text-foreground",
|
||||
itemTextClassName
|
||||
)}>
|
||||
{item}
|
||||
</span>
|
||||
{index < feature.items.length - 1 && (
|
||||
<span className="text-base text-accent">•</span>
|
||||
)}
|
||||
</Fragment>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{feature.buttons && feature.buttons.length > 0 && (
|
||||
<div className="mt-3 flex gap-4">
|
||||
{feature.buttons.slice(0, 2).map((button, index) => (
|
||||
<Button key={`${button.text}-${index}`} {...getButtonProps(button, index, theme.defaultButtonVariant, cardButtonClassName, cardButtonTextClassName)} />
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</CardList>
|
||||
);
|
||||
};
|
||||
|
||||
FeatureCardTwelve.displayName = "FeatureCardTwelve";
|
||||
|
||||
export default FeatureCardTwelve;
|
||||
188
src/components/sections/feature/FeatureCardTwenty.tsx
Normal file
188
src/components/sections/feature/FeatureCardTwenty.tsx
Normal file
@@ -0,0 +1,188 @@
|
||||
"use client";
|
||||
|
||||
import TextBox from "@/components/Textbox";
|
||||
import MediaContent from "@/components/shared/MediaContent";
|
||||
import AutoCarousel from "@/components/cardStack/layouts/carousels/AutoCarousel";
|
||||
import { cls } from "@/lib/utils";
|
||||
import type { LucideIcon } from "lucide-react";
|
||||
import type { ButtonConfig, TitleSegment } from "@/components/cardStack/types";
|
||||
import type { TextboxLayout, InvertedBackground } from "@/providers/themeProvider/config/constants";
|
||||
|
||||
type FeatureImage = {
|
||||
id: number;
|
||||
imageSrc?: string;
|
||||
videoSrc?: string;
|
||||
imageAlt?: string;
|
||||
videoAriaLabel?: string;
|
||||
};
|
||||
|
||||
interface FeatureCardTwentyProps {
|
||||
images: FeatureImage[];
|
||||
title: string;
|
||||
titleSegments?: TitleSegment[];
|
||||
description: string;
|
||||
tag?: string;
|
||||
tagIcon?: LucideIcon;
|
||||
buttons?: ButtonConfig[];
|
||||
textboxLayout: TextboxLayout;
|
||||
useInvertedBackground: InvertedBackground;
|
||||
ariaLabel?: string;
|
||||
className?: string;
|
||||
containerClassName?: string;
|
||||
textBoxClassName?: string;
|
||||
titleClassName?: string;
|
||||
descriptionClassName?: string;
|
||||
tagClassName?: string;
|
||||
buttonContainerClassName?: string;
|
||||
buttonClassName?: string;
|
||||
buttonTextClassName?: string;
|
||||
titleImageWrapperClassName?: string;
|
||||
titleImageClassName?: string;
|
||||
imagesContainerClassName?: string;
|
||||
imageWrapperClassName?: string;
|
||||
imageClassName?: string;
|
||||
}
|
||||
|
||||
const FeatureCardTwenty = ({
|
||||
images,
|
||||
title,
|
||||
titleSegments,
|
||||
description,
|
||||
tag,
|
||||
tagIcon,
|
||||
buttons,
|
||||
textboxLayout,
|
||||
useInvertedBackground,
|
||||
ariaLabel = "Feature section",
|
||||
className = "",
|
||||
containerClassName = "",
|
||||
textBoxClassName = "",
|
||||
titleClassName = "",
|
||||
descriptionClassName = "",
|
||||
tagClassName = "",
|
||||
buttonContainerClassName = "",
|
||||
buttonClassName = "",
|
||||
buttonTextClassName = "",
|
||||
titleImageWrapperClassName = "",
|
||||
titleImageClassName = "",
|
||||
imagesContainerClassName = "",
|
||||
imageWrapperClassName = "",
|
||||
imageClassName = "",
|
||||
}: FeatureCardTwentyProps) => {
|
||||
const renderImageCard = (image: FeatureImage, heightClass: string) => (
|
||||
<div
|
||||
key={image.id}
|
||||
className={cls(
|
||||
"w-full overflow-hidden rounded-theme-capped",
|
||||
heightClass,
|
||||
imageWrapperClassName
|
||||
)}
|
||||
>
|
||||
<MediaContent
|
||||
imageSrc={image.imageSrc}
|
||||
videoSrc={image.videoSrc}
|
||||
imageAlt={image.imageAlt || `Feature image ${image.id}`}
|
||||
videoAriaLabel={image.videoAriaLabel || `Feature video ${image.id}`}
|
||||
imageClassName={cls("w-full h-full object-cover", imageClassName)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
const textBoxElementMobile = (
|
||||
<TextBox
|
||||
title={title}
|
||||
titleSegments={titleSegments}
|
||||
description={description}
|
||||
tag={tag}
|
||||
tagIcon={tagIcon}
|
||||
buttons={buttons}
|
||||
textboxLayout={textboxLayout}
|
||||
useInvertedBackground={useInvertedBackground}
|
||||
center={true}
|
||||
className={cls("flex flex-col gap-3 md:gap-3", textBoxClassName)}
|
||||
titleClassName={cls("text-7xl 2xl:text-8xl font-medium text-center md:text-left text-balance", titleClassName)}
|
||||
titleImageWrapperClassName={titleImageWrapperClassName}
|
||||
titleImageClassName={titleImageClassName}
|
||||
descriptionClassName={cls("max-w-8/10 text-lg md:text-xl leading-[1.2] text-center md:text-left", descriptionClassName)}
|
||||
tagClassName={cls("w-fit px-3 py-1 text-sm rounded-theme card text-foreground inline-flex items-center gap-2 mb-3", tagClassName)}
|
||||
buttonContainerClassName={cls("flex gap-4 mt-4", buttonContainerClassName)}
|
||||
buttonClassName={cls("", buttonClassName)}
|
||||
buttonTextClassName={cls("text-base", buttonTextClassName)}
|
||||
/>
|
||||
);
|
||||
|
||||
const textBoxElementDesktop = (
|
||||
<TextBox
|
||||
title={title}
|
||||
titleSegments={titleSegments}
|
||||
description={description}
|
||||
tag={tag}
|
||||
tagIcon={tagIcon}
|
||||
buttons={buttons}
|
||||
textboxLayout={textboxLayout}
|
||||
useInvertedBackground={useInvertedBackground}
|
||||
center={false}
|
||||
className={cls("flex flex-col gap-3 md:gap-3", textBoxClassName)}
|
||||
titleClassName={cls("text-7xl 2xl:text-8xl font-medium text-center md:text-left text-balance", titleClassName)}
|
||||
titleImageWrapperClassName={titleImageWrapperClassName}
|
||||
titleImageClassName={titleImageClassName}
|
||||
descriptionClassName={cls("max-w-8/10 text-lg md:text-xl leading-[1.2] text-center md:text-left", descriptionClassName)}
|
||||
tagClassName={cls("w-fit px-3 py-1 text-sm rounded-theme card text-foreground inline-flex items-center gap-2 mb-3", tagClassName)}
|
||||
buttonContainerClassName={cls("flex gap-4 mt-4", buttonContainerClassName)}
|
||||
buttonClassName={cls("", buttonClassName)}
|
||||
buttonTextClassName={cls("text-base", buttonTextClassName)}
|
||||
/>
|
||||
);
|
||||
|
||||
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("hidden md:flex w-content-width mx-auto gap-10", containerClassName)}>
|
||||
<div className={cls("w-1/2 flex flex-col gap-10", imagesContainerClassName)}>
|
||||
{images.map((image) => renderImageCard(image, "h-[90svh]"))}
|
||||
</div>
|
||||
<div className="w-1/2">
|
||||
<div className={cls("sticky top-0 flex flex-col justify-center gap-6 h-svh", textBoxClassName)}>
|
||||
{textBoxElementDesktop}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="md:hidden">
|
||||
<AutoCarousel
|
||||
title=""
|
||||
description=""
|
||||
textboxLayout="default"
|
||||
animationType="none"
|
||||
className="py-0"
|
||||
carouselClassName="py-0"
|
||||
containerClassName="!w-full"
|
||||
itemClassName="!w-55"
|
||||
ariaLabel="Feature images carousel"
|
||||
showTextBox={false}
|
||||
>
|
||||
{images.map((image) => renderImageCard(image, "aspect-[4/5]"))}
|
||||
</AutoCarousel>
|
||||
</div>
|
||||
|
||||
<div className="md:hidden w-content-width mx-auto">
|
||||
<div className={cls("flex flex-col gap-6 mt-8", textBoxClassName)}>
|
||||
{textBoxElementMobile}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
FeatureCardTwenty.displayName = "FeatureCardTwenty";
|
||||
|
||||
export default FeatureCardTwenty;
|
||||
225
src/components/sections/feature/FeatureCardTwentyOne.tsx
Normal file
225
src/components/sections/feature/FeatureCardTwentyOne.tsx
Normal file
@@ -0,0 +1,225 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import TextBox from "@/components/Textbox";
|
||||
import Accordion from "@/components/Accordion";
|
||||
import MediaContent from "@/components/shared/MediaContent";
|
||||
import { cls } from "@/lib/utils";
|
||||
import type { LucideIcon } from "lucide-react";
|
||||
import type { ButtonConfig, TitleSegment } from "@/components/cardStack/types";
|
||||
import type { InvertedBackground } from "@/providers/themeProvider/config/constants";
|
||||
|
||||
type MediaProps =
|
||||
| {
|
||||
imageSrc: string;
|
||||
imageAlt?: string;
|
||||
videoSrc?: never;
|
||||
videoAriaLabel?: never;
|
||||
}
|
||||
| {
|
||||
videoSrc: string;
|
||||
videoAriaLabel?: string;
|
||||
imageSrc?: never;
|
||||
imageAlt?: never;
|
||||
};
|
||||
|
||||
type AccordionItem = {
|
||||
id: string;
|
||||
title: string;
|
||||
content: string;
|
||||
};
|
||||
|
||||
type FeatureCardTwentyOneProps = MediaProps & {
|
||||
title: string;
|
||||
titleSegments?: TitleSegment[];
|
||||
description: string;
|
||||
tag?: string;
|
||||
tagIcon?: LucideIcon;
|
||||
buttons?: ButtonConfig[];
|
||||
accordionItems: AccordionItem[];
|
||||
useInvertedBackground: InvertedBackground;
|
||||
mediaPosition?: "left" | "right";
|
||||
ariaLabel?: string;
|
||||
className?: string;
|
||||
containerClassName?: string;
|
||||
mediaWrapperClassName?: string;
|
||||
mediaClassName?: string;
|
||||
contentClassName?: string;
|
||||
textBoxClassName?: string;
|
||||
titleClassName?: string;
|
||||
descriptionClassName?: string;
|
||||
tagClassName?: string;
|
||||
buttonContainerClassName?: string;
|
||||
buttonClassName?: string;
|
||||
buttonTextClassName?: string;
|
||||
titleImageWrapperClassName?: string;
|
||||
titleImageClassName?: string;
|
||||
accordionContainerClassName?: string;
|
||||
accordionClassName?: string;
|
||||
accordionTitleClassName?: string;
|
||||
accordionContentClassName?: string;
|
||||
accordionIconContainerClassName?: string;
|
||||
accordionIconClassName?: string;
|
||||
};
|
||||
|
||||
const FeatureCardTwentyOne = ({
|
||||
title,
|
||||
titleSegments,
|
||||
description,
|
||||
tag,
|
||||
tagIcon,
|
||||
buttons,
|
||||
accordionItems,
|
||||
imageSrc,
|
||||
imageAlt,
|
||||
videoSrc,
|
||||
videoAriaLabel,
|
||||
useInvertedBackground,
|
||||
mediaPosition = "left",
|
||||
ariaLabel = "Feature section",
|
||||
className = "",
|
||||
containerClassName = "",
|
||||
mediaWrapperClassName = "",
|
||||
mediaClassName = "",
|
||||
contentClassName = "",
|
||||
textBoxClassName = "",
|
||||
titleClassName = "",
|
||||
descriptionClassName = "",
|
||||
tagClassName = "",
|
||||
buttonContainerClassName = "",
|
||||
buttonClassName = "",
|
||||
buttonTextClassName = "",
|
||||
titleImageWrapperClassName = "",
|
||||
titleImageClassName = "",
|
||||
accordionContainerClassName = "",
|
||||
accordionClassName = "",
|
||||
accordionTitleClassName = "",
|
||||
accordionContentClassName = "",
|
||||
accordionIconContainerClassName = "",
|
||||
accordionIconClassName = "",
|
||||
}: FeatureCardTwentyOneProps) => {
|
||||
const [activeAccordion, setActiveAccordion] = useState<number>(0);
|
||||
|
||||
const handleAccordionToggle = (index: number) => {
|
||||
setActiveAccordion(activeAccordion === index ? -1 : index);
|
||||
};
|
||||
|
||||
const mediaElement = (
|
||||
<div className={cls(
|
||||
"w-full md:w-1/2 h-[50svh] md:h-auto rounded-theme-capped overflow-hidden",
|
||||
mediaWrapperClassName
|
||||
)}>
|
||||
<MediaContent
|
||||
imageSrc={imageSrc}
|
||||
videoSrc={videoSrc}
|
||||
imageAlt={imageAlt}
|
||||
videoAriaLabel={videoAriaLabel}
|
||||
imageClassName={cls("w-full h-full object-cover", mediaClassName)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
const contentElement = (
|
||||
<div className={cls(
|
||||
"w-full md:w-1/2 flex flex-col",
|
||||
contentClassName
|
||||
)}>
|
||||
{/* Mobile */}
|
||||
<TextBox
|
||||
title={title}
|
||||
titleSegments={titleSegments}
|
||||
description={description}
|
||||
tag={tag}
|
||||
tagIcon={tagIcon}
|
||||
buttons={buttons}
|
||||
useInvertedBackground={useInvertedBackground}
|
||||
className={cls("flex flex-col gap-1 md:hidden", textBoxClassName)}
|
||||
titleClassName={cls("text-4xl md:text-5xl font-medium text-center md:text-left text-balance", titleClassName)}
|
||||
descriptionClassName={cls("text-base md:text-lg leading-[1.2] text-center md:text-left", descriptionClassName)}
|
||||
tagClassName={cls("w-fit px-3 py-1 text-sm rounded-theme card text-foreground inline-flex items-center gap-2 mb-1 md:mb-2", tagClassName)}
|
||||
buttonContainerClassName={cls("flex gap-4 mt-4", buttonContainerClassName)}
|
||||
buttonClassName={buttonClassName}
|
||||
buttonTextClassName={cls("text-base", buttonTextClassName)}
|
||||
titleImageWrapperClassName={titleImageWrapperClassName}
|
||||
titleImageClassName={titleImageClassName}
|
||||
center={true}
|
||||
/>
|
||||
{/* Desktop */}
|
||||
<TextBox
|
||||
title={title}
|
||||
titleSegments={titleSegments}
|
||||
description={description}
|
||||
tag={tag}
|
||||
tagIcon={tagIcon}
|
||||
buttons={buttons}
|
||||
useInvertedBackground={useInvertedBackground}
|
||||
className={cls("hidden md:flex flex-col gap-1", textBoxClassName)}
|
||||
titleClassName={cls("text-4xl md:text-5xl font-medium text-center md:text-left text-balance", titleClassName)}
|
||||
descriptionClassName={cls("text-base md:text-lg leading-[1.2] text-center md:text-left", descriptionClassName)}
|
||||
tagClassName={cls("w-fit px-3 py-1 text-sm rounded-theme card text-foreground inline-flex items-center gap-2 mb-1 md:mb-2", tagClassName)}
|
||||
buttonContainerClassName={cls("flex gap-4 mt-4", buttonContainerClassName)}
|
||||
buttonClassName={buttonClassName}
|
||||
buttonTextClassName={cls("text-base", buttonTextClassName)}
|
||||
titleImageWrapperClassName={titleImageWrapperClassName}
|
||||
titleImageClassName={titleImageClassName}
|
||||
center={false}
|
||||
/>
|
||||
|
||||
<div className={cls(
|
||||
"flex flex-col mt-8 divide-y divide-accent/20 border-y border-accent/20",
|
||||
accordionContainerClassName
|
||||
)}>
|
||||
{accordionItems.map((item, index) => (
|
||||
<Accordion
|
||||
key={item.id}
|
||||
index={index}
|
||||
isActive={activeAccordion === index}
|
||||
onToggle={handleAccordionToggle}
|
||||
title={item.title}
|
||||
content={item.content}
|
||||
showCard={false}
|
||||
useInvertedBackground={useInvertedBackground === "noInvert" ? undefined : useInvertedBackground}
|
||||
className={cls("py-4 md:py-6", accordionClassName)}
|
||||
titleClassName={cls("text-xl md:text-2xl", accordionTitleClassName)}
|
||||
contentClassName={accordionContentClassName}
|
||||
iconContainerClassName={accordionIconContainerClassName}
|
||||
iconClassName={accordionIconClassName}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<section
|
||||
aria-label={ariaLabel}
|
||||
className={cls(
|
||||
"relative w-full py-20",
|
||||
useInvertedBackground === "invertCard" && "w-content-width-expanded mx-auto rounded-theme-capped bg-foreground",
|
||||
useInvertedBackground === "invertDefault" && "bg-foreground",
|
||||
className
|
||||
)}
|
||||
>
|
||||
<div className={cls(
|
||||
"w-content-width mx-auto flex flex-col md:flex-row gap-8 md:gap-15",
|
||||
containerClassName
|
||||
)}>
|
||||
{mediaPosition === "left" ? (
|
||||
<>
|
||||
{mediaElement}
|
||||
{contentElement}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
{contentElement}
|
||||
{mediaElement}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
FeatureCardTwentyOne.displayName = "FeatureCardTwentyOne";
|
||||
|
||||
export default FeatureCardTwentyOne;
|
||||
@@ -0,0 +1,32 @@
|
||||
.feature-card-three-title {
|
||||
font-size: var(--text-2xl);
|
||||
}
|
||||
|
||||
.feature-card-three-description {
|
||||
font-size: var(--text-sm);
|
||||
}
|
||||
|
||||
/* Mobile touch support - duplicate hover states for is-active class */
|
||||
.feature-card-three-item.is-active .feature-card-three-item-box > div {
|
||||
transform: rotateY(180deg);
|
||||
}
|
||||
|
||||
.feature-card-three-item.is-active .feature-card-three-content-wrapper {
|
||||
transform: translateY(var(--hover-translate-y));
|
||||
}
|
||||
|
||||
.feature-card-three-item.is-active .feature-card-three-title {
|
||||
color: var(--color-foreground);
|
||||
}
|
||||
|
||||
.feature-card-three-item.is-active .feature-card-three-description-wrapper {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.feature-card-three-item.is-active .feature-card-three-reveal-bg {
|
||||
--tw-translate-y: 0px !important;
|
||||
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)) !important;
|
||||
left: calc(var(--vw-1_5) * 0.75);
|
||||
bottom: calc(var(--vw-1_5) * 0.75);
|
||||
right: calc(var(--vw-1_5) * 0.75);
|
||||
}
|
||||
@@ -0,0 +1,181 @@
|
||||
"use client";
|
||||
|
||||
import "./FeatureCardThree.css";
|
||||
import { useRef, useCallback, useState } from "react";
|
||||
import CardStack from "@/components/cardStack/CardStack";
|
||||
import FeatureCardThreeItem from "./FeatureCardThreeItem";
|
||||
import { useDynamicDimensions } from "./useDynamicDimensions";
|
||||
import { useClickOutside } from "@/hooks/useClickOutside";
|
||||
import type { LucideIcon } from "lucide-react";
|
||||
import type { ButtonConfig, GridVariant, CardAnimationType, TitleSegment } from "@/components/cardStack/types";
|
||||
import type { TextboxLayout, InvertedBackground } from "@/providers/themeProvider/config/constants";
|
||||
|
||||
type FeatureCard = {
|
||||
id: string;
|
||||
title: string;
|
||||
description: string;
|
||||
imageSrc: string;
|
||||
imageAlt?: string;
|
||||
};
|
||||
|
||||
interface FeatureCardThreeProps {
|
||||
features: FeatureCard[];
|
||||
carouselMode?: "auto" | "buttons";
|
||||
gridVariant: GridVariant;
|
||||
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;
|
||||
cardTitleClassName?: string;
|
||||
cardDescriptionClassName?: string;
|
||||
gridClassName?: string;
|
||||
carouselClassName?: string;
|
||||
controlsClassName?: string;
|
||||
textBoxClassName?: string;
|
||||
textBoxTagClassName?: string;
|
||||
textBoxButtonContainerClassName?: string;
|
||||
textBoxButtonClassName?: string;
|
||||
textBoxButtonTextClassName?: string;
|
||||
itemContentClassName?: string;
|
||||
}
|
||||
|
||||
const FeatureCardThree = ({
|
||||
features,
|
||||
carouselMode = "buttons",
|
||||
gridVariant,
|
||||
uniformGridCustomHeightClasses,
|
||||
animationType,
|
||||
title,
|
||||
titleSegments,
|
||||
description,
|
||||
tag,
|
||||
tagIcon,
|
||||
buttons,
|
||||
textboxLayout,
|
||||
useInvertedBackground,
|
||||
ariaLabel = "Feature section",
|
||||
className = "",
|
||||
containerClassName = "",
|
||||
cardClassName = "",
|
||||
textBoxTitleClassName = "",
|
||||
textBoxTitleImageWrapperClassName = "",
|
||||
textBoxTitleImageClassName = "",
|
||||
textBoxDescriptionClassName = "",
|
||||
cardTitleClassName = "",
|
||||
cardDescriptionClassName = "",
|
||||
gridClassName = "",
|
||||
carouselClassName = "",
|
||||
controlsClassName = "",
|
||||
textBoxClassName = "",
|
||||
textBoxTagClassName = "",
|
||||
textBoxButtonContainerClassName = "",
|
||||
textBoxButtonClassName = "",
|
||||
textBoxButtonTextClassName = "",
|
||||
itemContentClassName = "",
|
||||
}: FeatureCardThreeProps) => {
|
||||
const featureCardThreeRefs = useRef<(HTMLDivElement | null)[]>([]);
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const [activeIndex, setActiveIndex] = useState<number | null>(null);
|
||||
|
||||
// Override heightClasses for staggered variants to use smaller heights
|
||||
const heightClassesOverride =
|
||||
gridVariant === "four-items-2x2-staggered-grid" || gridVariant === "four-items-2x2-staggered-grid-inverted"
|
||||
? "h-110 2xl:h-120"
|
||||
: uniformGridCustomHeightClasses;
|
||||
|
||||
const setRef = useCallback(
|
||||
(index: number) => (el: HTMLDivElement | null) => {
|
||||
if (featureCardThreeRefs.current) {
|
||||
featureCardThreeRefs.current[index] = el;
|
||||
}
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
// Check if device supports hover (desktop) or not (mobile/touch)
|
||||
const isTouchDevice = typeof window !== "undefined" && window.matchMedia("(hover: none)").matches;
|
||||
|
||||
// Handle click outside to deactivate on mobile
|
||||
useClickOutside(
|
||||
containerRef,
|
||||
() => setActiveIndex(null),
|
||||
activeIndex !== null && isTouchDevice
|
||||
);
|
||||
|
||||
const handleItemClick = useCallback((index: number) => {
|
||||
if (typeof window !== "undefined" && !window.matchMedia("(hover: none)").matches) return;
|
||||
setActiveIndex((prev) => (prev === index ? null : index));
|
||||
}, []);
|
||||
|
||||
useDynamicDimensions([featureCardThreeRefs], {
|
||||
titleSelector: ".feature-card-three-title-row .feature-card-three-title",
|
||||
descriptionSelector: ".feature-card-three-description-wrapper .feature-card-three-description",
|
||||
});
|
||||
|
||||
return (
|
||||
<div ref={containerRef}>
|
||||
<CardStack
|
||||
mode={carouselMode}
|
||||
gridVariant={gridVariant}
|
||||
uniformGridCustomHeightClasses={heightClassesOverride}
|
||||
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}
|
||||
>
|
||||
{features.map((feature, index) => (
|
||||
<FeatureCardThreeItem
|
||||
key={`${feature.id}-${index}`}
|
||||
ref={setRef(index)}
|
||||
item={feature}
|
||||
isActive={activeIndex === index}
|
||||
onItemClick={() => handleItemClick(index)}
|
||||
className={cardClassName}
|
||||
itemContentClassName={itemContentClassName}
|
||||
itemTitleClassName={cardTitleClassName}
|
||||
itemDescriptionClassName={cardDescriptionClassName}
|
||||
/>
|
||||
))}
|
||||
</CardStack>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
FeatureCardThree.displayName = "FeatureCardThree";
|
||||
|
||||
export default FeatureCardThree;
|
||||
@@ -0,0 +1,144 @@
|
||||
"use client";
|
||||
|
||||
import { forwardRef, memo } from "react";
|
||||
import Image from "next/image";
|
||||
import { Info } from "lucide-react";
|
||||
import { cls } from "@/lib/utils";
|
||||
|
||||
interface FeatureCardThreeItemData {
|
||||
id: string;
|
||||
title: string;
|
||||
description: string;
|
||||
imageSrc: string;
|
||||
imageAlt?: string;
|
||||
}
|
||||
|
||||
interface FeatureCardThreeItemProps {
|
||||
item: FeatureCardThreeItemData;
|
||||
isActive?: boolean;
|
||||
onItemClick?: () => void;
|
||||
className?: string;
|
||||
itemContentClassName?: string;
|
||||
itemTitleClassName?: string;
|
||||
itemDescriptionClassName?: string;
|
||||
}
|
||||
|
||||
const MASK_GRADIENT = "linear-gradient(to bottom, transparent, black 60%)";
|
||||
|
||||
const FeatureCardThreeItem = memo(
|
||||
forwardRef<HTMLDivElement, FeatureCardThreeItemProps>(
|
||||
(
|
||||
{
|
||||
item,
|
||||
isActive = false,
|
||||
onItemClick,
|
||||
className = "",
|
||||
itemContentClassName = "",
|
||||
itemTitleClassName = "",
|
||||
itemDescriptionClassName = "",
|
||||
},
|
||||
ref
|
||||
) => {
|
||||
return (
|
||||
<div
|
||||
ref={ref}
|
||||
className={cls(
|
||||
"feature-card-three-item relative overflow-hidden h-full rounded-theme-capped group",
|
||||
isActive && "is-active",
|
||||
className
|
||||
)}
|
||||
role="article"
|
||||
aria-label={`${item.title} - Feature ${item.id}`}
|
||||
tabIndex={0}
|
||||
onClick={onItemClick}
|
||||
>
|
||||
<div
|
||||
className="feature-card-three-item-box absolute top-6 left-6 z-10 flex items-center justify-center [perspective:1000px] [transform-style:preserve-3d]"
|
||||
>
|
||||
<div
|
||||
className="relative h-8 aspect-square rounded-theme bg-background transition-transform duration-400 ease-[cubic-bezier(0.4,0,0.2,1)] [transform-style:preserve-3d] group-hover:[transform:rotateY(180deg)]"
|
||||
>
|
||||
<div
|
||||
className="feature-card-three-item-box-front absolute w-full h-full rounded-theme bg-background flex items-center justify-center [backface-visibility:hidden]"
|
||||
>
|
||||
<p
|
||||
className="feature-card-three-description text-foreground truncate"
|
||||
>
|
||||
{item.id}
|
||||
</p>
|
||||
</div>
|
||||
<div
|
||||
className="feature-card-three-item-box-back absolute w-full h-full rounded-theme bg-background flex items-center justify-center [backface-visibility:hidden] [transform:rotateY(180deg)]"
|
||||
>
|
||||
<Info
|
||||
className="w-1/2 h-1/2 text-foreground"
|
||||
strokeWidth={1.5}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Image
|
||||
src={item.imageSrc}
|
||||
alt={item.imageAlt || item.title}
|
||||
width={1920}
|
||||
height={1080}
|
||||
className="relative z-1 object-cover rounded-theme-capped h-full w-full "
|
||||
unoptimized={item.imageSrc.startsWith('http') || item.imageSrc.startsWith('//')}
|
||||
aria-hidden={item.imageAlt === ""}
|
||||
/>
|
||||
|
||||
<div
|
||||
className="absolute z-10 bottom-0 left-0 right-0 h-30 backdrop-blur-xl opacity-100"
|
||||
style={{ maskImage: MASK_GRADIENT }}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
|
||||
<div
|
||||
className="feature-card-three-content-wrapper absolute z-20 transition-all duration-400 ease-out flex flex-col gap-2 group-hover:[transform:translateY(var(--hover-translate-y))]"
|
||||
style={{
|
||||
top: "var(--content-top-position)",
|
||||
left: "calc((var(--vw-1_5) * 1.5))",
|
||||
width: "calc(100% - (var(--vw-1_5) * 3))",
|
||||
}}
|
||||
>
|
||||
<div className="feature-card-three-title-row">
|
||||
<h2
|
||||
className={cls(
|
||||
"feature-card-three-title font-semibold leading-[110%] transition-colors text-background group-hover:text-foreground",
|
||||
itemTitleClassName
|
||||
)}
|
||||
>
|
||||
{item.title}
|
||||
</h2>
|
||||
</div>
|
||||
<div
|
||||
className="feature-card-three-description-wrapper transition-all duration-400 ease-out opacity-0 group-hover:opacity-100"
|
||||
>
|
||||
<p
|
||||
className={cls("feature-card-three-description leading-[120%] w-full text-foreground", itemDescriptionClassName)}
|
||||
>
|
||||
{item.description}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
className={cls(
|
||||
"feature-card-three-reveal-bg !absolute left-0 bottom-0 card z-10 rounded-theme-capped transition-all duration-400 ease-out translate-y-full right-0 group-hover:translate-y-0 group-hover:left-[calc(var(--vw-1_5)*0.75)] group-hover:bottom-[calc(var(--vw-1_5)*0.75)] group-hover:right-[calc(var(--vw-1_5)*0.75)]",
|
||||
itemContentClassName
|
||||
)}
|
||||
style={{
|
||||
height: "var(--reveal-bg-height)",
|
||||
}}
|
||||
></div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
FeatureCardThreeItem.displayName = "FeatureCardThreeItem";
|
||||
|
||||
export default FeatureCardThreeItem;
|
||||
@@ -0,0 +1,122 @@
|
||||
import { useEffect, useCallback, useMemo } from 'react'
|
||||
|
||||
let cachedVw15: number | null = null
|
||||
let lastWindowWidth = 0
|
||||
|
||||
const getVw15InPixels = (): number => {
|
||||
const currentWidth = window.innerWidth
|
||||
if (cachedVw15 !== null && lastWindowWidth === currentWidth) {
|
||||
return cachedVw15
|
||||
}
|
||||
|
||||
const temp = document.createElement('div')
|
||||
temp.style.position = 'absolute'
|
||||
temp.style.width = 'var(--vw-1_5)'
|
||||
document.body.appendChild(temp)
|
||||
const width = temp.getBoundingClientRect().width
|
||||
document.body.removeChild(temp)
|
||||
|
||||
cachedVw15 = width || 0
|
||||
lastWindowWidth = currentWidth
|
||||
return cachedVw15
|
||||
}
|
||||
|
||||
|
||||
const debounce = <T extends (...args: unknown[]) => void>(func: T, wait: number): ((...args: Parameters<T>) => void) & { cancel: () => void } => {
|
||||
let timeout: NodeJS.Timeout | null = null
|
||||
|
||||
const debouncedFunction = function executedFunction(...args: Parameters<T>) {
|
||||
const later = () => {
|
||||
timeout = null
|
||||
func(...args)
|
||||
}
|
||||
if (timeout !== null) {
|
||||
clearTimeout(timeout)
|
||||
}
|
||||
timeout = setTimeout(later, wait)
|
||||
}
|
||||
|
||||
debouncedFunction.cancel = () => {
|
||||
if (timeout !== null) {
|
||||
clearTimeout(timeout)
|
||||
timeout = null
|
||||
}
|
||||
}
|
||||
|
||||
return debouncedFunction
|
||||
}
|
||||
|
||||
interface DynamicDimensionsOptions {
|
||||
titleSelector?: string
|
||||
descriptionSelector?: string
|
||||
containerSelector?: string | null
|
||||
}
|
||||
|
||||
type RefArray = React.RefObject<(HTMLDivElement | null)[]> | React.RefObject<(HTMLDivElement | null)[]>[]
|
||||
|
||||
export const useDynamicDimensions = (refs: RefArray, options: DynamicDimensionsOptions = {}) => {
|
||||
const {
|
||||
titleSelector = '.feature-card-three-title',
|
||||
descriptionSelector = '.feature-card-three-description',
|
||||
containerSelector = null
|
||||
} = options
|
||||
|
||||
const calculateDimensions = useCallback(() => {
|
||||
const processRef = (ref: HTMLElement | null) => {
|
||||
if (!ref) return
|
||||
|
||||
const container = containerSelector ? ref.querySelector(containerSelector) as HTMLElement : ref
|
||||
if (!container) return
|
||||
|
||||
const titleElement = container.querySelector(titleSelector) as HTMLElement
|
||||
const descriptionElement = container.querySelector(descriptionSelector) as HTMLElement
|
||||
|
||||
if (titleElement && descriptionElement) {
|
||||
const titleHeight = titleElement.offsetHeight
|
||||
const descriptionHeight = descriptionElement.offsetHeight
|
||||
|
||||
const contentTop = `calc(100% - ${titleHeight}px - calc(var(--vw-1_5) * 1.5))`
|
||||
|
||||
const vw15 = getVw15InPixels()
|
||||
|
||||
const contentWrapperHeight = titleHeight + descriptionHeight
|
||||
|
||||
const revealBgHeight = contentWrapperHeight + (vw15 * 2)
|
||||
|
||||
const moveUp = descriptionHeight + (vw15 * 0.55)
|
||||
|
||||
ref.style.setProperty('--reveal-bg-height', `${revealBgHeight}px`)
|
||||
ref.style.setProperty('--content-top-position', contentTop)
|
||||
ref.style.setProperty('--hover-translate-y', `-${moveUp}px`)
|
||||
}
|
||||
}
|
||||
|
||||
if (Array.isArray(refs)) {
|
||||
refs.forEach((refArray) => {
|
||||
if (refArray?.current && Array.isArray(refArray.current)) {
|
||||
refArray.current.forEach(processRef)
|
||||
}
|
||||
})
|
||||
} else if ('current' in refs && refs.current) {
|
||||
if (Array.isArray(refs.current)) {
|
||||
refs.current.forEach(processRef)
|
||||
} else {
|
||||
processRef(refs.current)
|
||||
}
|
||||
}
|
||||
}, [titleSelector, descriptionSelector, containerSelector, refs])
|
||||
|
||||
const debouncedCalculate = useMemo(
|
||||
() => debounce(calculateDimensions, 250),
|
||||
[calculateDimensions]
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
calculateDimensions()
|
||||
window.addEventListener('resize', debouncedCalculate)
|
||||
return () => {
|
||||
window.removeEventListener('resize', debouncedCalculate)
|
||||
debouncedCalculate.cancel()
|
||||
}
|
||||
}, [calculateDimensions, debouncedCalculate])
|
||||
}
|
||||
Reference in New Issue
Block a user