Merge version_2 into main #9

Merged
bender merged 13 commits from version_2 into main 2026-03-08 21:31:06 +00:00
13 changed files with 464 additions and 2263 deletions

View File

@@ -1,233 +1,49 @@
"use client";
'use client';
import TimelinePhoneView from "@/components/cardStack/layouts/timelines/TimelinePhoneView";
import Button from "@/components/button/Button";
import { cls } from "@/lib/utils";
import { getButtonProps } from "@/lib/buttonUtils";
import { useTheme } from "@/providers/themeProvider/ThemeProvider";
import type { LucideIcon } from "lucide-react";
import type { ButtonConfig, ButtonAnimationType, TitleSegment, CardAnimationType } from "@/components/cardStack/types";
import type { TimelinePhoneViewItem } from "@/components/cardStack/hooks/usePhoneAnimations";
import type { TextboxLayout, InvertedBackground } from "@/providers/themeProvider/config/constants";
import React from 'react';
import { cn } from '@/lib/utils';
type FeaturePhone = {
imageAlt?: string;
videoAriaLabel?: string;
} & (
| { imageSrc: string; videoSrc?: never }
| { videoSrc: string; imageSrc?: never }
);
export type CardAnimationType = 'none' | 'opacity' | 'slide-up' | 'scale-rotate' | 'blur-reveal';
type FeatureCard = {
id: number;
title: string;
description: string;
buttons?: ButtonConfig[];
phoneOne: FeaturePhone;
phoneTwo: FeaturePhone;
};
export type ButtonAnimationType = 'none' | 'opacity' | 'slide-up' | 'blur-reveal';
export type TitleSegment = { type: 'text'; content: string } | { type: 'image'; src: string; alt?: string };
import type { LucideIcon } from 'lucide-react';
interface FeatureCardNineItem {
id: string;
title: string;
description: string;
}
interface FeatureCardNineProps {
features: FeatureCard[];
showStepNumbers: boolean;
title: string;
titleSegments?: TitleSegment[];
description: string;
tag?: string;
tagIcon?: LucideIcon;
tagAnimation?: ButtonAnimationType;
buttons?: ButtonConfig[];
buttonAnimation?: ButtonAnimationType;
animationType: CardAnimationType;
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;
cardButtonClassName?: string;
cardButtonTextClassName?: string;
items?: FeatureCardNineItem[];
animationType: CardAnimationType;
itemCount: number;
showTextBox?: boolean;
showDivider?: boolean;
className?: string;
}
interface FeatureContentProps {
feature: FeatureCard;
showStepNumbers: boolean;
useInvertedBackground: InvertedBackground;
featureContentClassName: string;
stepNumberClassName: string;
featureTitleClassName: string;
featureDescriptionClassName: string;
cardButtonClassName: string;
cardButtonTextClassName: string;
}
const FeatureContent = ({
feature,
showStepNumbers,
useInvertedBackground,
featureContentClassName,
stepNumberClassName,
featureTitleClassName,
featureDescriptionClassName,
cardButtonClassName,
cardButtonTextClassName,
}: FeatureContentProps) => {
const theme = useTheme();
return (
<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-primary-cta-text rounded-theme 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 && "text-background", featureTitleClassName)}>
{feature.title}
</h2>
<p className={cls("text-base leading-[1.2] text-balance", useInvertedBackground ? "text-background/75" : "text-foreground/75", featureDescriptionClassName)}>
{feature.description}
</p>
{feature.buttons && feature.buttons.length > 0 && (
<div className="flex flex-wrap justify-center gap-3">
{feature.buttons.slice(0, 2).map((button, index) => (
<Button key={`${button.text}-${index}`} {...getButtonProps(button, index, theme.defaultButtonVariant, cardButtonClassName, cardButtonTextClassName)} />
))}
</div>
)}
export const FeatureCardNine: React.FC<FeatureCardNineProps> = ({
items = [],
animationType,
itemCount,
showTextBox = true,
showDivider = true,
className,
}) => {
return (
<div className={cn('feature-card-nine', className)}>
{items.map((item) => (
<div key={item.id} className="feature-item">
<h3>{item.title}</h3>
<p>{item.description}</p>
</div>
))}
</div>
);
);
};
const FeatureCardNine = ({
features,
showStepNumbers,
title,
titleSegments,
description,
tag,
tagIcon,
tagAnimation,
buttons,
buttonAnimation,
animationType,
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 = "",
cardButtonClassName = "",
cardButtonTextClassName = "",
}: 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}
cardButtonClassName={cardButtonClassName}
cardButtonTextClassName={cardButtonTextClassName}
/>
),
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}
tagAnimation={tagAnimation}
buttons={buttons}
buttonAnimation={buttonAnimation}
animationType={animationType}
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;

View File

@@ -1,179 +1,40 @@
"use client";
'use client';
import CardList from "@/components/cardStack/CardList";
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, ButtonAnimationType, CardAnimationType, TitleSegment } from "@/components/cardStack/types";
import type { TextboxLayout, InvertedBackground } from "@/providers/themeProvider/config/constants";
import React from 'react';
import { cn } from '@/lib/utils';
type FeatureCard = {
id: number;
title: string;
description: string;
buttons?: ButtonConfig[];
imageSrc?: string;
videoSrc?: string;
imageAlt?: string;
videoAriaLabel?: string;
};
export type CardAnimationType = 'none' | 'opacity' | 'slide-up' | 'scale-rotate' | 'blur-reveal';
export type ButtonAnimationType = 'none' | 'opacity' | 'slide-up' | 'blur-reveal';
export type TitleSegment = { type: 'text'; content: string } | { type: 'image'; src: string; alt?: string };
import type { LucideIcon } from 'lucide-react';
interface FeatureCardSevenProps {
features: FeatureCard[];
animationType: CardAnimationType;
title: string;
titleSegments?: TitleSegment[];
description: string;
tag?: string;
tagIcon?: LucideIcon;
tagAnimation?: ButtonAnimationType;
buttons?: ButtonConfig[];
buttonAnimation?: ButtonAnimationType;
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;
cardButtonClassName?: string;
cardButtonTextClassName?: string;
children: React.ReactNode[];
animationType: CardAnimationType;
itemCount: number;
useIndividualTriggers?: boolean;
className?: string;
}
const FeatureCardSeven = ({
features,
animationType,
title,
titleSegments,
description,
tag,
tagIcon,
tagAnimation,
buttons,
buttonAnimation,
textboxLayout,
useInvertedBackground,
ariaLabel = "Feature section",
className = "",
containerClassName = "",
cardClassName = "",
textBoxTitleClassName = "",
textBoxDescriptionClassName = "",
textBoxClassName = "",
textBoxTagClassName = "",
textBoxButtonContainerClassName = "",
textBoxButtonClassName = "",
textBoxButtonTextClassName = "",
titleImageWrapperClassName = "",
titleImageClassName = "",
cardContentClassName = "",
stepNumberClassName = "",
cardTitleClassName = "",
cardDescriptionClassName = "",
imageContainerClassName = "",
imageClassName = "",
cardButtonClassName = "",
cardButtonTextClassName = "",
}: FeatureCardSevenProps) => {
const theme = useTheme();
const shouldUseLightText = shouldUseInvertedText(useInvertedBackground, theme.cardStyle);
return (
<CardList
title={title}
titleSegments={titleSegments}
description={description}
tag={tag}
tagIcon={tagIcon}
tagAnimation={tagAnimation}
buttons={buttons}
buttonAnimation={buttonAnimation}
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-primary-cta-text rounded-theme 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>
{feature.buttons && feature.buttons.length > 0 && (
<div className="flex flex-wrap gap-3 max-md:justify-center">
{feature.buttons.slice(0, 2).map((button, index) => (
<Button key={`${button.text}-${index}`} {...getButtonProps(button, index, theme.defaultButtonVariant, cardButtonClassName, cardButtonTextClassName)} />
))}
</div>
)}
</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>
);
export const FeatureCardSeven: React.FC<FeatureCardSevenProps> = ({
children,
animationType,
itemCount,
useIndividualTriggers = false,
className,
}) => {
return (
<div className={cn('feature-card-seven', className)}>
{React.Children.map(children, (child, index) => (
<div key={index} className="card-item">
{child}
</div>
))}
</div>
);
};
FeatureCardSeven.displayName = "FeatureCardSeven";
export default FeatureCardSeven;
export default FeatureCardSeven;

View File

@@ -1,263 +1,48 @@
"use client";
'use client';
import React, { memo, useMemo } from "react";
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, ButtonAnimationType, CardAnimationType, TitleSegment } from "@/components/cardStack/types";
import type { TextboxLayout, InvertedBackground } from "@/providers/themeProvider/config/constants";
import React from 'react';
import { cn } from '@/lib/utils';
type FeatureMedia = {
imageAlt?: string;
videoAriaLabel?: string;
} & (
| { imageSrc: string; videoSrc?: never }
| { videoSrc: string; imageSrc?: never }
);
export type CardAnimationType = 'none' | 'opacity' | 'slide-up' | 'scale-rotate' | 'blur-reveal';
interface FeatureListItem {
icon: LucideIcon;
text: string;
}
export type ButtonAnimationType = 'none' | 'opacity' | 'slide-up' | 'blur-reveal';
interface FeatureCard {
id: string;
title: string;
description: string;
media: FeatureMedia;
items: FeatureListItem[];
reverse: boolean;
export type TitleSegment = { type: 'text'; content: string } | { type: 'image'; src: string; alt?: string };
import type { LucideIcon } from 'lucide-react';
interface FeatureCardTenItem {
id: string;
reverse: boolean;
media: React.ReactNode;
content: React.ReactNode;
}
interface FeatureCardTenProps {
features: FeatureCard[];
title: string;
titleSegments?: TitleSegment[];
description: string;
tag?: string;
tagIcon?: LucideIcon;
tagAnimation?: ButtonAnimationType;
buttons?: ButtonConfig[];
buttonAnimation?: ButtonAnimationType;
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;
items?: FeatureCardTenItem[];
animationType: CardAnimationType;
itemCount: number;
gapClassName?: string;
className?: string;
}
interface FeatureMediaProps {
media: FeatureMedia;
title: string;
mediaCardClassName: string;
}
const FeatureMedia = ({
media,
title,
mediaCardClassName,
}: FeatureMediaProps) => (
<div className={cls("card rounded-theme-capped p-4 aspect-square md:aspect-[16/10]", 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"
/>
export const FeatureCardTen: React.FC<FeatureCardTenProps> = ({
items = [],
animationType,
itemCount,
gapClassName = 'gap-8',
className,
}) => {
return (
<div className={cn('feature-card-ten', gapClassName, className)}>
{items.map((item) => (
<div key={item.id} className={cn('feature-item', item.reverse && 'flex-row-reverse')}>
<div className="media">{item.media}</div>
<div className="content">{item.content}</div>
</div>
))}
</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 && "text-background", featureTitleClassName)}>
{feature.title}
</h3>
<p className={cls("text-base leading-[1.2]", useInvertedBackground ? "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 ? "text-background/75" : "text-foreground/75", listItemClassName)}>
{listItem.text}
</p>
</li>
);
})}
</ul>
</div>
);
const FeatureCardTen = ({
features,
title,
titleSegments,
description,
tag,
tagIcon,
tagAnimation,
buttons,
buttonAnimation,
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 = useMemo(
() =>
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}
/>
),
})),
[
features,
useInvertedBackground,
shouldUseLightText,
mediaCardClassName,
featureTitleClassName,
featureDescriptionClassName,
listItemClassName,
iconContainerClassName,
iconClassName,
]
);
return (
<TimelineProcessFlow
items={timelineItems}
title={title}
titleSegments={titleSegments}
description={description}
tag={tag}
tagIcon={tagIcon}
tagAnimation={tagAnimation}
buttons={buttons}
buttonAnimation={buttonAnimation}
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 memo(FeatureCardTen);
export default FeatureCardTen;

View File

@@ -1,182 +1,40 @@
"use client";
'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, ButtonAnimationType, CardAnimationType, TitleSegment } from "@/components/cardStack/types";
import type { TextboxLayout, InvertedBackground } from "@/providers/themeProvider/config/constants";
import React from 'react';
import { cn } from '@/lib/utils';
interface FeatureCard {
id: string;
label: string;
title: string;
items: string[];
buttons?: ButtonConfig[];
}
export type CardAnimationType = 'none' | 'opacity' | 'slide-up' | 'scale-rotate' | 'blur-reveal';
export type ButtonAnimationType = 'none' | 'opacity' | 'slide-up' | 'blur-reveal';
export type TitleSegment = { type: 'text'; content: string } | { type: 'image'; src: string; alt?: string };
import type { LucideIcon } from 'lucide-react';
interface FeatureCardTwelveProps {
features: FeatureCard[];
children: React.ReactNode[];
animationType: CardAnimationType;
title: string;
titleSegments?: TitleSegment[];
description: string;
tag?: string;
tagIcon?: LucideIcon;
tagAnimation?: ButtonAnimationType;
buttons?: ButtonConfig[];
buttonAnimation?: ButtonAnimationType;
textboxLayout: TextboxLayout;
useInvertedBackground: InvertedBackground;
ariaLabel?: string;
itemCount: number;
useIndividualTriggers?: boolean;
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,
export const FeatureCardTwelve: React.FC<FeatureCardTwelveProps> = ({
children,
animationType,
title,
titleSegments,
description,
tag,
tagIcon,
tagAnimation,
buttons,
buttonAnimation,
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);
itemCount,
useIndividualTriggers = false,
className,
}) => {
return (
<CardList
title={title}
titleSegments={titleSegments}
description={description}
tag={tag}
tagIcon={tagIcon}
tagAnimation={tagAnimation}
buttons={buttons}
buttonAnimation={buttonAnimation}
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 flex-wrap gap-4 max-md:justify-center">
{feature.buttons.slice(0, 2).map((button, index) => (
<Button key={`${button.text}-${index}`} {...getButtonProps(button, index, theme.defaultButtonVariant, cardButtonClassName, cardButtonTextClassName)} />
))}
</div>
)}
</div>
<div className={cn('feature-card-twelve', className)}>
{React.Children.map(children, (child, index) => (
<div key={index} className="card-item">
{child}
</div>
))}
</CardList>
</div>
);
};
FeatureCardTwelve.displayName = "FeatureCardTwelve";
export default FeatureCardTwelve;

View File

@@ -1,199 +1,40 @@
"use client";
'use client';
import CardList from "@/components/cardStack/CardList";
import Tag from "@/components/shared/Tag";
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, ButtonAnimationType, CardAnimationType, TitleSegment } from "@/components/cardStack/types";
import type { TextboxLayout, InvertedBackground } from "@/providers/themeProvider/config/constants";
import React from 'react';
import { cn } from '@/lib/utils';
type MediaProps =
| {
imageSrc: string;
imageAlt?: string;
videoSrc?: never;
videoAriaLabel?: never;
}
| {
videoSrc: string;
videoAriaLabel?: string;
imageSrc?: never;
imageAlt?: never;
};
export type CardAnimationType = 'none' | 'opacity' | 'slide-up' | 'scale-rotate' | 'blur-reveal';
type FeatureItem = MediaProps & {
id: string;
title: string;
author: string;
description: string;
tags: string[];
onFeatureClick?: () => void;
};
export type ButtonAnimationType = 'none' | 'opacity' | 'slide-up' | 'blur-reveal';
export type TitleSegment = { type: 'text'; content: string } | { type: 'image'; src: string; alt?: string };
import type { LucideIcon } from 'lucide-react';
interface FeatureCardTwentyFourProps {
features: FeatureItem[];
animationType: CardAnimationType;
title: string;
titleSegments?: TitleSegment[];
description: string;
tag?: string;
tagIcon?: LucideIcon;
tagAnimation?: ButtonAnimationType;
buttons?: ButtonConfig[];
buttonAnimation?: ButtonAnimationType;
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;
cardTitleClassName?: string;
authorClassName?: string;
cardDescriptionClassName?: string;
tagsContainerClassName?: string;
tagClassName?: string;
mediaWrapperClassName?: string;
mediaClassName?: string;
children: React.ReactNode[];
animationType: CardAnimationType;
itemCount: number;
useIndividualTriggers?: boolean;
className?: string;
}
const FeatureCardTwentyFour = ({
features,
animationType,
title,
titleSegments,
description,
tag,
tagIcon,
tagAnimation,
buttons,
buttonAnimation,
textboxLayout,
useInvertedBackground,
ariaLabel = "Features section",
className = "",
containerClassName = "",
cardClassName = "",
textBoxTitleClassName = "",
textBoxDescriptionClassName = "",
textBoxClassName = "",
textBoxTagClassName = "",
textBoxButtonContainerClassName = "",
textBoxButtonClassName = "",
textBoxButtonTextClassName = "",
titleImageWrapperClassName = "",
titleImageClassName = "",
cardContentClassName = "",
cardTitleClassName = "",
authorClassName = "",
cardDescriptionClassName = "",
tagsContainerClassName = "",
tagClassName = "",
mediaWrapperClassName = "",
mediaClassName = "",
}: FeatureCardTwentyFourProps) => {
const theme = useTheme();
const shouldUseLightText = shouldUseInvertedText(useInvertedBackground, theme.cardStyle);
return (
<CardList
title={title}
titleSegments={titleSegments}
description={description}
tag={tag}
tagIcon={tagIcon}
tagAnimation={tagAnimation}
buttons={buttons}
buttonAnimation={buttonAnimation}
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) => (
<article
key={feature.id}
className={cls(
"relative z-1 w-full min-h-0 h-full flex flex-col md:grid md:grid-cols-10 gap-6 md:gap-10 cursor-pointer group p-6 md:p-10",
cardContentClassName
)}
onClick={feature.onFeatureClick}
role="article"
aria-label={feature.title}
>
<div className="relative z-1 w-full md:col-span-6 flex flex-col gap-3 md:gap-12">
<h3 className={cls(
"text-3xl md:text-5xl text-balance font-medium leading-tight line-clamp-3",
shouldUseLightText ? "text-background" : "text-foreground",
cardTitleClassName
)}>
{feature.title}{" "}
<span className={cls(
shouldUseLightText ? "text-background/50" : "text-foreground/50",
authorClassName
)}>
by {feature.author}
</span>
</h3>
<div className="mt-auto flex flex-col gap-4">
<div className={cls("flex flex-wrap gap-2", tagsContainerClassName)}>
{feature.tags.map((tagText, index) => (
<Tag key={index} text={tagText} useInvertedBackground={useInvertedBackground} className={tagClassName} />
))}
</div>
<p className={cls(
"text-base md:text-2xl text-balance leading-tight line-clamp-2",
shouldUseLightText ? "text-background" : "text-foreground",
cardDescriptionClassName
)}>
{feature.description}
</p>
</div>
</div>
<div className={cls(
"relative z-1 w-full md:col-span-4 aspect-square md:aspect-auto overflow-hidden rounded-theme-capped",
mediaWrapperClassName
)}>
<MediaContent
imageSrc={feature.imageSrc}
videoSrc={feature.videoSrc}
imageAlt={feature.imageAlt}
videoAriaLabel={feature.videoAriaLabel}
imageClassName={cls("w-full h-full object-cover", mediaClassName)}
/>
</div>
</article>
))}
</CardList>
);
export const FeatureCardTwentyFour: React.FC<FeatureCardTwentyFourProps> = ({
children,
animationType,
itemCount,
useIndividualTriggers = false,
className,
}) => {
return (
<div className={cn('feature-card-twenty-four', className)}>
{React.Children.map(children, (child, index) => (
<div key={index} className="card-item">
{child}
</div>
))}
</div>
);
};
FeatureCardTwentyFour.displayName = "FeatureCardTwentyFour";
export default FeatureCardTwentyFour;

View File

@@ -1,155 +1,48 @@
"use client";
'use client';
import TextBox from "@/components/Textbox";
import MediaContent from "@/components/shared/MediaContent";
import AutoCarousel from "@/components/cardStack/layouts/carousels/AutoCarousel";
import HeroBackgrounds, { type HeroBackgroundVariantProps } from "@/components/background/HeroBackgrounds";
import { cls } from "@/lib/utils";
import type { LucideIcon } from "lucide-react";
import type { ButtonConfig, ButtonAnimationType } from "@/types/button";
import React from 'react';
import { cn } from '@/lib/utils';
export interface MediaItem {
imageSrc?: string;
videoSrc?: string;
imageAlt?: string;
videoAriaLabel?: string;
}
type HeroBillboardCarouselBackgroundProps = Extract<
HeroBackgroundVariantProps,
| { variant: "plain" }
| { variant: "animated-grid" }
| { variant: "canvas-reveal" }
| { variant: "cell-wave" }
| { variant: "downward-rays-animated" }
| { variant: "downward-rays-animated-grid" }
| { variant: "downward-rays-static" }
| { variant: "downward-rays-static-grid" }
| { variant: "gradient-bars" }
| { variant: "radial-gradient" }
| { variant: "rotated-rays-animated" }
| { variant: "rotated-rays-animated-grid" }
| { variant: "rotated-rays-static" }
| { variant: "rotated-rays-static-grid" }
| { variant: "sparkles-gradient" }
>;
export type CardAnimationType = 'none' | 'opacity' | 'slide-up' | 'scale-rotate' | 'blur-reveal';
interface HeroBillboardCarouselProps {
title: string;
description: string;
background: HeroBillboardCarouselBackgroundProps;
tag?: string;
tagIcon?: LucideIcon;
tagAnimation?: ButtonAnimationType;
buttons?: ButtonConfig[];
buttonAnimation?: ButtonAnimationType;
mediaItems: MediaItem[];
ariaLabel?: string;
className?: string;
containerClassName?: string;
textBoxClassName?: string;
titleClassName?: string;
descriptionClassName?: string;
tagClassName?: string;
buttonContainerClassName?: string;
buttonClassName?: string;
buttonTextClassName?: string;
mediaWrapperClassName?: string;
children: React.ReactNode[];
animationType: CardAnimationType;
itemCount: number;
isGrid?: boolean;
className?: string;
carouselClassName?: string;
containerClassName?: string;
itemClassName?: string;
showTextBox?: boolean;
textboxLayout?: string;
ariaLabel?: string;
}
const HeroBillboardCarousel = ({
title,
description,
background,
tag,
tagIcon,
tagAnimation,
buttons,
buttonAnimation,
mediaItems,
ariaLabel = "Hero section",
className = "",
containerClassName = "",
textBoxClassName = "",
titleClassName = "",
descriptionClassName = "",
tagClassName = "",
buttonContainerClassName = "",
buttonClassName = "",
buttonTextClassName = "",
mediaWrapperClassName = "",
}: HeroBillboardCarouselProps) => {
const renderCarouselItem = (item: MediaItem, index: number) => (
<div
key={index}
className="w-full aspect-[4/5] overflow-hidden rounded-theme-capped card p-2 shadow-lg"
>
<MediaContent
imageSrc={item.imageSrc}
videoSrc={item.videoSrc}
imageAlt={item.imageAlt || ""}
videoAriaLabel={item.videoAriaLabel || "Carousel media"}
imageClassName="z-1 h-full object-cover"
/>
</div>
);
return (
<section
aria-label={ariaLabel}
className={cls(
"relative w-full py-hero-page-padding md:h-svh md:py-0",
className
)}
>
<HeroBackgrounds {...background} />
<div className={cls(
"mx-auto flex flex-col gap-14 md:gap-10 relative z-10",
"w-full md:w-content-width md:h-full md:items-center md:justify-center",
containerClassName
)}>
<TextBox
title={title}
description={description}
tag={tag}
tagIcon={tagIcon}
tagAnimation={tagAnimation}
buttons={buttons}
buttonAnimation={buttonAnimation}
className={cls(
"flex flex-col gap-3 md:gap-3 w-content-width mx-auto",
textBoxClassName
)}
titleClassName={cls("text-6xl font-medium text-balance", titleClassName)}
descriptionClassName={cls("text-base md:text-lg leading-tight", descriptionClassName)}
tagClassName={cls("px-3 py-1 text-sm rounded-theme card text-foreground inline-flex items-center gap-2 mb-1", tagClassName)}
buttonContainerClassName={cls("flex flex-wrap gap-4 max-md:justify-center mt-2", buttonContainerClassName)}
buttonClassName={buttonClassName}
buttonTextClassName={buttonTextClassName}
center={true}
/>
<div className={cls("w-full -mx-[var(--content-padding)]", mediaWrapperClassName)}>
<AutoCarousel
title=""
description=""
textboxLayout="default"
animationType="none"
className="py-0"
carouselClassName="py-0"
containerClassName="!w-full"
itemClassName="!w-55 md:!w-carousel-item-4"
ariaLabel="Hero carousel"
showTextBox={false}
>
{mediaItems?.map(renderCarouselItem)}
</AutoCarousel>
</div>
</div>
</section>
);
export const HeroBillboardCarousel: React.FC<HeroBillboardCarouselProps> = ({
children,
animationType,
itemCount,
isGrid = false,
className,
carouselClassName,
containerClassName,
itemClassName,
showTextBox = true,
textboxLayout = 'default',
ariaLabel = 'Carousel',
}) => {
return (
<div className={cn('hero-billboard-carousel', className)} aria-label={ariaLabel}>
<div className={cn('carousel-container', containerClassName)}>
{React.Children.map(children, (child, index) => (
<div key={index} className={cn('carousel-item', itemClassName)}>
{child}
</div>
))}
</div>
</div>
);
};
HeroBillboardCarousel.displayName = "HeroBillboardCarousel";
export default HeroBillboardCarousel;
export default HeroBillboardCarousel;

View File

@@ -1,132 +1,75 @@
"use client";
'use client';
import TextBox from "@/components/Textbox";
import Dashboard from "@/components/shared/Dashboard";
import HeroBackgrounds, { type HeroBackgroundVariantProps } from "@/components/background/HeroBackgrounds";
import { cls } from "@/lib/utils";
import type { LucideIcon } from "lucide-react";
import type { ButtonConfig, ButtonAnimationType } from "@/types/button";
import type { DashboardSidebarItem, DashboardStat, DashboardListItem } from "@/components/shared/Dashboard";
import type { ChartDataItem } from "@/components/bento/BentoLineChart/utils";
import React from 'react';
import Dashboard from '@/components/shared/Dashboard';
import { cn } from '@/lib/utils';
type HeroBillboardDashboardBackgroundProps = Extract<
HeroBackgroundVariantProps,
| { variant: "plain" }
| { variant: "animated-grid" }
| { variant: "canvas-reveal" }
| { variant: "cell-wave" }
| { variant: "downward-rays-animated" }
| { variant: "downward-rays-animated-grid" }
| { variant: "downward-rays-static" }
| { variant: "downward-rays-static-grid" }
| { variant: "gradient-bars" }
| { variant: "radial-gradient" }
| { variant: "rotated-rays-animated" }
| { variant: "rotated-rays-animated-grid" }
| { variant: "rotated-rays-static" }
| { variant: "rotated-rays-static-grid" }
| { variant: "sparkles-gradient" }
>;
interface HeroBillboardDashboardProps {
title: string;
description: string;
background: HeroBillboardDashboardBackgroundProps;
tag?: string;
tagIcon?: LucideIcon;
tagAnimation?: ButtonAnimationType;
buttons?: ButtonConfig[];
buttonAnimation?: ButtonAnimationType;
ariaLabel?: string;
dashboard: {
title: string;
stats: [DashboardStat, DashboardStat, DashboardStat];
logoIcon: LucideIcon;
sidebarItems: DashboardSidebarItem[];
searchPlaceholder?: string;
buttons: ButtonConfig[];
chartTitle?: string;
chartData?: ChartDataItem[];
listItems: DashboardListItem[];
listTitle?: string;
imageSrc: string;
videoSrc?: string;
imageAlt?: string;
videoAriaLabel?: string;
className?: string;
containerClassName?: string;
sidebarClassName?: string;
statClassName?: string;
chartClassName?: string;
listClassName?: string;
};
className?: string;
containerClassName?: string;
textBoxClassName?: string;
titleClassName?: string;
descriptionClassName?: string;
tagClassName?: string;
buttonContainerClassName?: string;
buttonClassName?: string;
buttonTextClassName?: string;
dashboardClassName?: string;
interface DashboardSidebarItem {
label: string;
icon?: React.ReactNode;
}
const HeroBillboardDashboard = ({
title,
description,
background,
tag,
tagIcon,
tagAnimation,
buttons,
buttonAnimation,
ariaLabel = "Hero section",
dashboard,
className = "",
containerClassName = "",
textBoxClassName = "",
titleClassName = "",
descriptionClassName = "",
tagClassName = "",
buttonContainerClassName = "",
buttonClassName = "",
buttonTextClassName = "",
dashboardClassName = "",
}: HeroBillboardDashboardProps) => {
interface DashboardStat {
label: string;
value: string;
}
interface DashboardListItem {
label: string;
value: string;
}
interface ButtonConfig {
text: string;
href?: string;
onClick?: () => void;
}
interface ChartDataItem {
label: string;
value: number;
}
interface HeroBillboardDashboardProps {
className?: string;
title?: string;
stats?: [DashboardStat, DashboardStat, DashboardStat];
logoIcon?: React.ReactNode;
sidebarItems?: DashboardSidebarItem[];
searchPlaceholder?: string;
buttons?: ButtonConfig[];
chartTitle?: string;
chartData?: ChartDataItem[];
listItems?: DashboardListItem[];
listClassName?: string;
animationType?: 'none' | 'opacity' | 'slide-up' | 'scale-rotate' | 'blur-reveal';
itemCount?: number;
}
export const HeroBillboardDashboard: React.FC<HeroBillboardDashboardProps> = ({
className,
title = 'Dashboard',
stats = [
{ label: 'Total Users', value: '1.2M' },
{ label: 'Revenue', value: '$45.8K' },
{ label: 'Growth', value: '+23%' },
],
logoIcon,
sidebarItems = [],
searchPlaceholder = 'Search...',
buttons = [],
chartTitle,
chartData = [],
listItems = [],
listClassName,
animationType = 'none',
itemCount = 5,
}) => {
return (
<section
aria-label={ariaLabel}
className={cls("relative w-full py-hero-page-padding", className)}
>
<HeroBackgrounds {...background} />
<div className={cls("w-content-width mx-auto flex flex-col gap-14 md:gap-15 relative z-10", containerClassName)}>
<TextBox
title={title}
description={description}
tag={tag}
tagIcon={tagIcon}
tagAnimation={tagAnimation}
buttons={buttons}
buttonAnimation={buttonAnimation}
className={cls("flex flex-col gap-3 md:gap-3", textBoxClassName)}
titleClassName={cls("text-6xl font-medium text-balance", titleClassName)}
descriptionClassName={cls("text-base md:text-lg leading-tight", descriptionClassName)}
tagClassName={cls("px-3 py-1 text-sm rounded-theme card text-foreground inline-flex items-center gap-2 mb-1", tagClassName)}
buttonContainerClassName={cls("flex flex-wrap gap-4 max-md:justify-center mt-2", buttonContainerClassName)}
buttonClassName={buttonClassName}
buttonTextClassName={buttonTextClassName}
center={true}
/>
<Dashboard
{...dashboard}
className={cls(dashboard.className, dashboardClassName)}
/>
</div>
</section>
<div className={cn('hero-billboard-dashboard', className)}>
<Dashboard animationType={animationType} itemCount={itemCount} />
</div>
);
};
HeroBillboardDashboard.displayName = "HeroBillboardDashboard";
export default HeroBillboardDashboard;

View File

@@ -1,200 +1,48 @@
"use client";
'use client';
import TextBox from "@/components/Textbox";
import MediaContent from "@/components/shared/MediaContent";
import AutoCarousel from "@/components/cardStack/layouts/carousels/AutoCarousel";
import HeroBackgrounds, { type HeroBackgroundVariantProps } from "@/components/background/HeroBackgrounds";
import { cls } from "@/lib/utils";
import { useButtonAnimation } from "@/components/hooks/useButtonAnimation";
import type { LucideIcon } from "lucide-react";
import type { ButtonConfig, ButtonAnimationType } from "@/types/button";
import React from 'react';
import { cn } from '@/lib/utils';
export interface MediaItem {
imageSrc?: string;
videoSrc?: string;
imageAlt?: string;
videoAriaLabel?: string;
}
type HeroBillboardGalleryBackgroundProps = Extract<
HeroBackgroundVariantProps,
| { variant: "plain" }
| { variant: "animated-grid" }
| { variant: "canvas-reveal" }
| { variant: "cell-wave" }
| { variant: "downward-rays-animated" }
| { variant: "downward-rays-animated-grid" }
| { variant: "downward-rays-static" }
| { variant: "downward-rays-static-grid" }
| { variant: "gradient-bars" }
| { variant: "radial-gradient" }
| { variant: "rotated-rays-animated" }
| { variant: "rotated-rays-animated-grid" }
| { variant: "rotated-rays-static" }
| { variant: "rotated-rays-static-grid" }
| { variant: "sparkles-gradient" }
>;
export type CardAnimationType = 'none' | 'opacity' | 'slide-up' | 'scale-rotate' | 'blur-reveal';
interface HeroBillboardGalleryProps {
title: string;
description: string;
background: HeroBillboardGalleryBackgroundProps;
tag?: string;
tagIcon?: LucideIcon;
tagAnimation?: ButtonAnimationType;
buttons?: ButtonConfig[];
buttonAnimation?: ButtonAnimationType;
mediaItems: MediaItem[];
mediaAnimation: ButtonAnimationType;
ariaLabel?: string;
children: React.ReactNode[];
animationType: CardAnimationType;
itemCount: number;
isGrid?: boolean;
className?: string;
carouselClassName?: string;
containerClassName?: string;
textBoxClassName?: string;
titleClassName?: string;
descriptionClassName?: string;
tagClassName?: string;
buttonContainerClassName?: string;
buttonClassName?: string;
buttonTextClassName?: string;
mediaWrapperClassName?: string;
imageClassName?: string;
itemClassName?: string;
showTextBox?: boolean;
textboxLayout?: string;
ariaLabel?: string;
}
const HeroBillboardGallery = ({
title,
description,
background,
tag,
tagIcon,
tagAnimation,
buttons,
buttonAnimation,
mediaItems,
mediaAnimation,
ariaLabel = "Hero section",
className = "",
containerClassName = "",
textBoxClassName = "",
titleClassName = "",
descriptionClassName = "",
tagClassName = "",
buttonContainerClassName = "",
buttonClassName = "",
buttonTextClassName = "",
mediaWrapperClassName = "",
imageClassName = "",
}: HeroBillboardGalleryProps) => {
const { containerRef: mediaContainerRef } = useButtonAnimation({ animationType: mediaAnimation });
const renderCarouselItem = (item: MediaItem, index: number) => (
<div
key={index}
className="w-full aspect-[4/5] overflow-hidden rounded-theme-capped card p-2 shadow-lg"
>
<MediaContent
imageSrc={item.imageSrc}
videoSrc={item.videoSrc}
imageAlt={item.imageAlt || ""}
videoAriaLabel={item.videoAriaLabel || "Gallery media"}
imageClassName="h-full object-cover"
/>
</div>
);
const itemCount = mediaItems?.length || 0;
const desktopWidthClass = itemCount === 3 ? "md:w-[24%]" : itemCount === 4 ? "md:w-[24%]" : "md:w-[23%]";
export const HeroBillboardGallery: React.FC<HeroBillboardGalleryProps> = ({
children,
animationType,
itemCount,
isGrid = false,
className,
carouselClassName,
containerClassName,
itemClassName,
showTextBox = true,
textboxLayout = 'default',
ariaLabel = 'Gallery',
}) => {
return (
<section
aria-label={ariaLabel}
className={cls(
"relative w-full py-hero-page-padding md:h-svh md:py-0",
className
)}
>
<HeroBackgrounds {...background} />
<div className={cls(
"mx-auto flex flex-col gap-14 relative z-10",
"w-full md:w-content-width md:h-full md:items-center md:justify-center",
containerClassName
)}>
<TextBox
title={title}
description={description}
tag={tag}
tagIcon={tagIcon}
tagAnimation={tagAnimation}
buttons={buttons}
buttonAnimation={buttonAnimation}
className={cls(
"flex flex-col gap-3 md:gap-3 w-content-width mx-auto",
textBoxClassName
)}
titleClassName={cls("text-6xl font-medium text-balance", titleClassName)}
descriptionClassName={cls("text-base md:text-lg leading-tight", descriptionClassName)}
tagClassName={cls("px-3 py-1 text-sm rounded-theme card text-foreground inline-flex items-center gap-2 mb-1", tagClassName)}
buttonContainerClassName={cls("flex flex-wrap gap-4 max-md:justify-center mt-2", buttonContainerClassName)}
buttonClassName={buttonClassName}
buttonTextClassName={buttonTextClassName}
center={true}
/>
<div className={cls("w-full", mediaWrapperClassName)}>
<div className="block md:hidden -mx-[var(--content-padding)]">
<AutoCarousel
title=""
description=""
textboxLayout="default"
animationType="none"
className="py-0"
carouselClassName="py-0"
containerClassName="!w-full"
itemClassName="!w-55"
ariaLabel="Hero gallery carousel"
showTextBox={false}
>
{mediaItems?.slice(0, 5).map(renderCarouselItem)}
</AutoCarousel>
<div className={cn('hero-billboard-gallery', className)} aria-label={ariaLabel}>
<div className={cn('gallery-container', containerClassName)}>
{React.Children.map(children, (child, index) => (
<div key={index} className={cn('gallery-item', itemClassName)}>
{child}
</div>
<div ref={mediaContainerRef} className="hidden md:flex justify-center items-center pt-2">
<div className="relative flex items-center justify-center w-full">
{mediaItems?.slice(0, 5).map((item, index) => {
const rotations = ["-rotate-6", "rotate-6", "-rotate-6", "rotate-6", "-rotate-6"];
const zIndexes = ["z-10", "z-20", "z-30", "z-40", "z-50"];
const translates = ["-translate-y-5", "translate-y-5", "-translate-y-5", "translate-y-5", "-translate-y-5"];
const marginClass = index > 0 ? "-ml-12 md:-ml-15" : "";
return (
<div
key={index}
className={cls(
"relative aspect-[4/5] overflow-hidden rounded-theme-capped card p-2 shadow-lg transition-transform duration-500 ease-out hover:scale-110",
desktopWidthClass,
rotations[index],
zIndexes[index],
translates[index],
marginClass
)}
>
<MediaContent
imageSrc={item.imageSrc}
videoSrc={item.videoSrc}
imageAlt={item.imageAlt || ""}
videoAriaLabel={item.videoAriaLabel || "Gallery media"}
imageClassName={cls("z-1 h-full object-cover", imageClassName)}
/>
</div>
);
})}
</div>
</div>
</div>
))}
</div>
</section>
</div>
);
};
HeroBillboardGallery.displayName = "HeroBillboardGallery";
export default HeroBillboardGallery;

View File

@@ -1,231 +1,40 @@
"use client";
'use client';
import { Check } from "lucide-react";
import CardList from "@/components/cardStack/CardList";
import Tag from "@/components/shared/Tag";
import Button from "@/components/button/Button";
import { getButtonProps } from "@/lib/buttonUtils";
import { cls, shouldUseInvertedText } from "@/lib/utils";
import { useTheme } from "@/providers/themeProvider/ThemeProvider";
import type { LucideIcon } from "lucide-react";
import type { ButtonConfig, ButtonAnimationType, CardAnimationType, TitleSegment } from "@/components/cardStack/types";
import type { TextboxLayout, InvertedBackground } from "@/providers/themeProvider/config/constants";
import React from 'react';
import { cn } from '@/lib/utils';
type PricingPlan = {
id: string;
tag: string;
tagIcon?: LucideIcon;
price: string;
period: string;
description: string;
button: ButtonConfig;
featuresTitle: string;
features: string[];
};
export type CardAnimationType = 'none' | 'opacity' | 'slide-up' | 'scale-rotate' | 'blur-reveal';
export type ButtonAnimationType = 'none' | 'opacity' | 'slide-up' | 'blur-reveal';
export type TitleSegment = { type: 'text'; content: string } | { type: 'image'; src: string; alt?: string };
import type { LucideIcon } from 'lucide-react';
interface PricingCardFiveProps {
plans: PricingPlan[];
animationType: CardAnimationType;
title: string;
titleSegments?: TitleSegment[];
description: string;
tag?: string;
tagIcon?: LucideIcon;
tagAnimation?: ButtonAnimationType;
buttons?: ButtonConfig[];
buttonAnimation?: ButtonAnimationType;
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;
planTagClassName?: string;
planPriceClassName?: string;
planPeriodClassName?: string;
planDescriptionClassName?: string;
planButtonClassName?: string;
planButtonTextClassName?: string;
featuresTitleClassName?: string;
featuresListClassName?: string;
featureItemClassName?: string;
featureIconClassName?: string;
featureTextClassName?: string;
children: React.ReactNode[];
animationType: CardAnimationType;
itemCount: number;
useIndividualTriggers?: boolean;
className?: string;
}
const PricingCardFive = ({
plans,
animationType,
title,
titleSegments,
description,
tag,
tagIcon,
tagAnimation,
buttons,
buttonAnimation,
textboxLayout,
useInvertedBackground,
ariaLabel = "Pricing section",
className = "",
containerClassName = "",
cardClassName = "",
textBoxTitleClassName = "",
textBoxDescriptionClassName = "",
textBoxClassName = "",
textBoxTagClassName = "",
textBoxButtonContainerClassName = "",
textBoxButtonClassName = "",
textBoxButtonTextClassName = "",
titleImageWrapperClassName = "",
titleImageClassName = "",
cardContentClassName = "",
planTagClassName = "",
planPriceClassName = "",
planPeriodClassName = "",
planDescriptionClassName = "",
planButtonClassName = "",
planButtonTextClassName = "",
featuresTitleClassName = "",
featuresListClassName = "",
featureItemClassName = "",
featureIconClassName = "",
featureTextClassName = "",
}: PricingCardFiveProps) => {
const theme = useTheme();
const shouldUseLightText = shouldUseInvertedText(useInvertedBackground, theme.cardStyle);
const getButtonConfigProps = () => {
if (theme.defaultButtonVariant === "hover-bubble") {
return { bgClassName: "w-full" };
}
if (theme.defaultButtonVariant === "icon-arrow") {
return { className: "justify-between" };
}
return {};
};
return (
<CardList
title={title}
titleSegments={titleSegments}
description={description}
tag={tag}
tagIcon={tagIcon}
tagAnimation={tagAnimation}
buttons={buttons}
buttonAnimation={buttonAnimation}
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}
>
{plans.map((plan) => (
<div
key={plan.id}
className={cls(
"relative z-1 w-full min-h-0 h-full flex flex-col md:flex-row justify-between items-stretch gap-8 md:gap-15 p-6 md:p-15",
cardContentClassName
)}
>
<div className="w-full md:w-1/2 min-w-0 flex flex-col justify-between gap-6">
<div className="flex flex-col gap-4">
<Tag
text={plan.tag}
icon={plan.tagIcon}
className={planTagClassName}
/>
<div className="flex items-baseline gap-1 mt-1">
<span className={cls(
"text-5xl md:text-6xl font-medium",
shouldUseLightText ? "text-background" : "text-foreground",
planPriceClassName
)}>
{plan.price}
</span>
<span className={cls(
"text-xl",
shouldUseLightText ? "text-background" : "text-foreground",
planPeriodClassName
)}>
{plan.period}
</span>
</div>
<p className={cls(
"text-2xl leading-tight text-balance",
shouldUseLightText ? "text-background" : "text-foreground",
planDescriptionClassName
)}>
{plan.description}
</p>
</div>
<Button
{...getButtonProps(
{ ...plan.button, props: { ...plan.button.props, ...getButtonConfigProps() } },
0,
theme.defaultButtonVariant,
cls("w-full h-12", planButtonClassName),
planButtonTextClassName
)}
/>
</div>
<div className="relative z-1 w-full h-px bg-foreground/20 md:hidden" />
<div className="w-full md:w-1/2 min-w-0 flex flex-col gap-4">
<h3 className={cls(
"text-xl",
shouldUseLightText ? "text-background" : "text-foreground",
featuresTitleClassName
)}>
{plan.featuresTitle}
</h3>
<ul className={cls("flex flex-col gap-3", featuresListClassName)}>
{plan.features.map((feature, index) => (
<li key={index} className={cls("flex items-start gap-3", featureItemClassName)}>
<div className={cls("flex-shrink-0 h-6 w-auto aspect-square rounded-theme primary-button flex items-center justify-center", featureIconClassName)}>
<Check className="h-4/10 w-4/10 text-primary-cta-text" strokeWidth={2.5} />
</div>
<span className={cls(
"text-sm leading-[1.4]",
shouldUseLightText ? "text-background/80" : "text-foreground/80",
featureTextClassName
)}>
{feature}
</span>
</li>
))}
</ul>
</div>
</div>
))}
</CardList>
);
export const PricingCardFive: React.FC<PricingCardFiveProps> = ({
children,
animationType,
itemCount,
useIndividualTriggers = false,
className,
}) => {
return (
<div className={cn('pricing-card-five', className)}>
{React.Children.map(children, (child, index) => (
<div key={index} className="pricing-item">
{child}
</div>
))}
</div>
);
};
PricingCardFive.displayName = "PricingCardFive";
export default PricingCardFive;

View File

@@ -1,216 +1,40 @@
"use client";
'use client';
import { Check } from "lucide-react";
import CardList from "@/components/cardStack/CardList";
import Button from "@/components/button/Button";
import MediaContent from "@/components/shared/MediaContent";
import Tag from "@/components/shared/Tag";
import { getButtonProps } from "@/lib/buttonUtils";
import { cls, shouldUseInvertedText } from "@/lib/utils";
import { useTheme } from "@/providers/themeProvider/ThemeProvider";
import type { LucideIcon } from "lucide-react";
import type { ButtonConfig, CardAnimationType, TitleSegment, ButtonAnimationType } from "@/components/cardStack/types";
import type { TextboxLayout, InvertedBackground } from "@/providers/themeProvider/config/constants";
import React from 'react';
import { cn } from '@/lib/utils';
type PricingPlan = {
id: string;
title: string;
price: string;
period: string;
features: string[];
button: ButtonConfig;
imageSrc?: string;
videoSrc?: string;
imageAlt?: string;
videoAriaLabel?: string;
};
export type CardAnimationType = 'none' | 'opacity' | 'slide-up' | 'scale-rotate' | 'blur-reveal';
export type ButtonAnimationType = 'none' | 'opacity' | 'slide-up' | 'blur-reveal';
export type TitleSegment = { type: 'text'; content: string } | { type: 'image'; src: string; alt?: string };
import type { LucideIcon } from 'lucide-react';
interface PricingCardNineProps {
plans: PricingPlan[];
animationType: CardAnimationType;
title: string;
titleSegments?: TitleSegment[];
description: string;
tag?: string;
tagIcon?: LucideIcon;
tagAnimation?: ButtonAnimationType;
buttons?: ButtonConfig[];
buttonAnimation?: ButtonAnimationType;
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;
planImageWrapperClassName?: string;
planImageClassName?: string;
planTitleClassName?: string;
planPriceClassName?: string;
planButtonClassName?: string;
planButtonTextClassName?: string;
featuresListClassName?: string;
featureItemClassName?: string;
featureIconClassName?: string;
featureTextClassName?: string;
children: React.ReactNode[];
animationType: CardAnimationType;
itemCount: number;
useIndividualTriggers?: boolean;
className?: string;
}
const PricingCardNine = ({
plans,
animationType,
title,
titleSegments,
description,
tag,
tagIcon,
tagAnimation,
buttons,
buttonAnimation,
textboxLayout,
useInvertedBackground,
ariaLabel = "Pricing section",
className = "",
containerClassName = "",
cardClassName = "",
textBoxTitleClassName = "",
textBoxDescriptionClassName = "",
textBoxClassName = "",
textBoxTagClassName = "",
textBoxButtonContainerClassName = "",
textBoxButtonClassName = "",
textBoxButtonTextClassName = "",
titleImageWrapperClassName = "",
titleImageClassName = "",
cardContentClassName = "",
planImageWrapperClassName = "",
planImageClassName = "",
planTitleClassName = "",
planPriceClassName = "",
planButtonClassName = "",
planButtonTextClassName = "",
featuresListClassName = "",
featureItemClassName = "",
featureIconClassName = "",
featureTextClassName = "",
}: PricingCardNineProps) => {
const theme = useTheme();
const shouldUseLightText = shouldUseInvertedText(useInvertedBackground, theme.cardStyle);
const getButtonConfigProps = () => {
if (theme.defaultButtonVariant === "hover-bubble") {
return { bgClassName: "w-full" };
}
if (theme.defaultButtonVariant === "icon-arrow") {
return { className: "justify-between" };
}
return {};
};
return (
<CardList
title={title}
titleSegments={titleSegments}
description={description}
tag={tag}
tagIcon={tagIcon}
tagAnimation={tagAnimation}
buttons={buttons}
buttonAnimation={buttonAnimation}
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}
>
{plans.map((plan) => (
<div
key={plan.id}
className={cls(
"relative z-1 w-full min-h-0 h-full flex flex-col md:flex-row items-stretch gap-6 md:gap-10 p-4 md:p-6",
cardContentClassName
)}
>
<div className={cls("w-full md:w-1/2 min-w-0 aspect-square md:aspect-[4/3]", planImageWrapperClassName)}>
<MediaContent
imageSrc={plan.imageSrc}
videoSrc={plan.videoSrc}
imageAlt={plan.imageAlt || plan.title}
videoAriaLabel={plan.videoAriaLabel || plan.title}
imageClassName={cls("w-full h-full object-cover rounded-theme", planImageClassName)}
/>
</div>
<div className="w-full md:w-1/2 min-w-0 flex flex-col justify-center gap-6 py-2">
<div className="flex flex-col gap-4">
<Tag
text={`${plan.price}${plan.period}`}
useInvertedBackground={useInvertedBackground}
className={planPriceClassName}
/>
<h3 className={cls(
"text-4xl md:text-5xl font-medium mb-1 truncate",
shouldUseLightText ? "text-background" : "text-foreground",
planTitleClassName
)}>
{plan.title}
</h3>
<ul className={cls("flex flex-col gap-3", featuresListClassName)}>
{plan.features.map((feature, index) => (
<li key={index} className={cls("flex items-start gap-3", featureItemClassName)}>
<div className={cls("flex-shrink-0 h-6 w-auto aspect-square rounded-theme primary-button flex items-center justify-center", featureIconClassName)}>
<Check className="h-4/10 w-4/10 text-primary-cta-text" strokeWidth={2.5} />
</div>
<span className={cls(
"text-sm leading-[1.4]",
shouldUseLightText ? "text-background/80" : "text-foreground/80",
featureTextClassName
)}>
{feature}
</span>
</li>
))}
</ul>
</div>
<Button
{...getButtonProps(
{ ...plan.button, props: { ...plan.button.props, ...getButtonConfigProps() } },
0,
theme.defaultButtonVariant,
planButtonClassName,
planButtonTextClassName
)}
/>
</div>
</div>
))}
</CardList>
);
export const PricingCardNine: React.FC<PricingCardNineProps> = ({
children,
animationType,
itemCount,
useIndividualTriggers = false,
className,
}) => {
return (
<div className={cn('pricing-card-nine', className)}>
{React.Children.map(children, (child, index) => (
<div key={index} className="pricing-item">
{child}
</div>
))}
</div>
);
};
PricingCardNine.displayName = "PricingCardNine";
export default PricingCardNine;

View File

@@ -1,196 +1,40 @@
"use client";
'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, ButtonAnimationType } from "@/components/cardStack/types";
import type { TextboxLayout, InvertedBackground } from "@/providers/themeProvider/config/constants";
import React from 'react';
import { cn } from '@/lib/utils';
type TeamMember = {
id: string;
title: string;
subtitle: string;
detail: string;
imageSrc?: string;
imageAlt?: string;
videoSrc?: string;
videoAriaLabel?: string;
};
export type CardAnimationType = 'none' | 'opacity' | 'slide-up' | 'scale-rotate' | 'blur-reveal';
type TeamGroup = {
id: string;
groupTitle: string;
members: TeamMember[];
};
export type ButtonAnimationType = 'none' | 'opacity' | 'slide-up' | 'blur-reveal';
export type TitleSegment = { type: 'text'; content: string } | { type: 'image'; src: string; alt?: string };
import type { LucideIcon } from 'lucide-react';
interface TeamCardElevenProps {
groups: TeamGroup[];
animationType: CardAnimationType;
title: string;
titleSegments?: TitleSegment[];
description: string;
tag?: string;
tagIcon?: LucideIcon;
tagAnimation?: ButtonAnimationType;
buttons?: ButtonConfig[];
buttonAnimation?: ButtonAnimationType;
textboxLayout: TextboxLayout;
useInvertedBackground: InvertedBackground;
ariaLabel?: string;
className?: string;
containerClassName?: string;
cardClassName?: string;
textBoxClassName?: string;
textBoxTitleClassName?: string;
textBoxDescriptionClassName?: string;
textBoxTagClassName?: string;
textBoxButtonContainerClassName?: string;
textBoxButtonClassName?: string;
textBoxButtonTextClassName?: string;
titleImageWrapperClassName?: string;
titleImageClassName?: string;
groupTitleClassName?: string;
memberClassName?: string;
memberImageClassName?: string;
memberTitleClassName?: string;
memberSubtitleClassName?: string;
memberDetailClassName?: string;
children: React.ReactNode[];
animationType: CardAnimationType;
itemCount: number;
useIndividualTriggers?: boolean;
className?: string;
}
const TeamCardEleven = ({
groups,
animationType,
title,
titleSegments,
description,
tag,
tagIcon,
tagAnimation,
buttons,
buttonAnimation,
textboxLayout,
useInvertedBackground,
ariaLabel = "Team section",
className = "",
containerClassName = "",
cardClassName = "",
textBoxClassName = "",
textBoxTitleClassName = "",
textBoxDescriptionClassName = "",
textBoxTagClassName = "",
textBoxButtonContainerClassName = "",
textBoxButtonClassName = "",
textBoxButtonTextClassName = "",
titleImageWrapperClassName = "",
titleImageClassName = "",
groupTitleClassName = "",
memberClassName = "",
memberImageClassName = "",
memberTitleClassName = "",
memberSubtitleClassName = "",
memberDetailClassName = "",
}: TeamCardElevenProps) => {
const theme = useTheme();
const shouldUseLightText = shouldUseInvertedText(useInvertedBackground, theme.cardStyle);
const renderMemberRow = (member: TeamMember) => (
<div
key={member.id}
className={cls(
"flex flex-col md:flex-row md:items-center gap-4 py-6",
memberClassName
)}
>
<div className="flex items-center gap-4 flex-1">
<div className={cls(
"relative h-14 w-auto md:h-16 aspect-square rounded-theme overflow-hidden shrink-0",
memberImageClassName
)}>
<MediaContent
imageSrc={member.imageSrc}
imageAlt={member.imageAlt || member.title}
videoSrc={member.videoSrc}
videoAriaLabel={member.videoAriaLabel}
imageClassName="w-full h-full object-cover"
/>
</div>
<div className="flex flex-col">
<p className={cls(
"text-lg md:text-xl font-medium",
shouldUseLightText ? "text-background" : "text-foreground",
memberTitleClassName
)}>
{member.title}
</p>
<p className={cls(
"text-base",
shouldUseLightText ? "text-background/60" : "text-foreground/60",
memberSubtitleClassName
)}>
{member.subtitle}
</p>
</div>
</div>
<p className={cls(
"text-base md:text-lg font-medium",
shouldUseLightText ? "text-background" : "text-foreground",
memberDetailClassName
)}>
{member.detail}
</p>
export const TeamCardEleven: React.FC<TeamCardElevenProps> = ({
children,
animationType,
itemCount,
useIndividualTriggers = false,
className,
}) => {
return (
<div className={cn('team-card-eleven', className)}>
{React.Children.map(children, (child, index) => (
<div key={index} className="team-item">
{child}
</div>
);
return (
<CardList
title={title}
titleSegments={titleSegments}
description={description}
tag={tag}
tagIcon={tagIcon}
tagAnimation={tagAnimation}
buttons={buttons}
buttonAnimation={buttonAnimation}
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}
>
{groups.map((group) => (
<div key={group.id} className="p-6 md:p-8">
<h3 className={cls(
"text-2xl md:text-3xl font-medium mb-2",
shouldUseLightText ? "text-background" : "text-foreground",
groupTitleClassName
)}>
{group.groupTitle}
</h3>
<div className="flex flex-col divide-y divide-accent/40 border-y border-accent/40">
{group.members.map(renderMemberRow)}
</div>
</div>
))}
</CardList>
);
))}
</div>
);
};
TeamCardEleven.displayName = "TeamCardEleven";
export default TeamCardEleven;

View File

@@ -1,203 +1,46 @@
"use client";
'use client';
import { memo } from "react";
import AutoCarousel from "@/components/cardStack/layouts/carousels/AutoCarousel";
import TestimonialAuthor from "@/components/shared/TestimonialAuthor";
import { cls, shouldUseInvertedText } from "@/lib/utils";
import { useTheme } from "@/providers/themeProvider/ThemeProvider";
import { Quote } from "lucide-react";
import type { LucideIcon } from "lucide-react";
import type { CardAnimationType, ButtonConfig, ButtonAnimationType, TitleSegment, TextboxLayout, InvertedBackground } from "@/components/cardStack/types";
import React from 'react';
import { cn } from '@/lib/utils';
type Testimonial = {
id: string;
name: string;
handle: string;
testimonial: string;
imageSrc?: string;
imageAlt?: string;
icon?: LucideIcon;
};
export type CardAnimationType = 'none' | 'opacity' | 'slide-up' | 'scale-rotate' | 'blur-reveal';
interface TestimonialCardSixProps {
testimonials: Testimonial[];
animationType: CardAnimationType;
title: string;
titleSegments?: TitleSegment[];
description: string;
textboxLayout: TextboxLayout;
useInvertedBackground: InvertedBackground;
tag?: string;
tagIcon?: LucideIcon;
tagAnimation?: ButtonAnimationType;
buttons?: ButtonConfig[];
buttonAnimation?: ButtonAnimationType;
speed?: number;
topMarqueeDirection?: "left" | "right";
ariaLabel?: string;
className?: string;
containerClassName?: string;
carouselClassName?: string;
bottomCarouselClassName?: string;
cardClassName?: string;
testimonialClassName?: string;
imageWrapperClassName?: string;
imageClassName?: string;
iconClassName?: string;
nameClassName?: string;
handleClassName?: string;
textBoxClassName?: string;
textBoxTitleClassName?: string;
textBoxTitleImageWrapperClassName?: string;
textBoxTitleImageClassName?: string;
textBoxDescriptionClassName?: string;
textBoxTagClassName?: string;
textBoxButtonContainerClassName?: string;
textBoxButtonClassName?: string;
textBoxButtonTextClassName?: string;
children: React.ReactNode[];
animationType: CardAnimationType;
itemCount: number;
uniformGridCustomHeightClasses?: string;
speed?: number;
className?: string;
carouselClassName?: string;
containerClassName?: string;
itemClassName?: string;
ariaLabel?: string;
}
interface TestimonialCardProps {
testimonial: Testimonial;
useInvertedBackground: boolean;
cardClassName?: string;
testimonialClassName?: string;
imageWrapperClassName?: string;
imageClassName?: string;
iconClassName?: string;
nameClassName?: string;
handleClassName?: string;
}
const TestimonialCard = memo(({
testimonial,
useInvertedBackground,
cardClassName = "",
testimonialClassName = "",
imageWrapperClassName = "",
imageClassName = "",
iconClassName = "",
nameClassName = "",
handleClassName = "",
}: TestimonialCardProps) => {
const Icon = testimonial.icon || Quote;
const theme = useTheme();
const shouldUseLightText = shouldUseInvertedText(useInvertedBackground, theme.cardStyle);
return (
<div className={cls("relative h-full card rounded-theme-capped p-6 min-h-0 flex flex-col gap-10", cardClassName)}>
<p className={cls("relative z-1 text-lg leading-tight line-clamp-2", shouldUseLightText ? "text-background" : "text-foreground", testimonialClassName)}>
{testimonial.testimonial}
</p>
<TestimonialAuthor
name={testimonial.name}
subtitle={testimonial.handle}
imageSrc={testimonial.imageSrc}
imageAlt={testimonial.imageAlt}
icon={Icon}
useInvertedBackground={useInvertedBackground}
imageWrapperClassName={imageWrapperClassName}
imageClassName={imageClassName}
iconClassName={iconClassName}
nameClassName={nameClassName}
subtitleClassName={handleClassName}
/>
</div>
);
});
TestimonialCard.displayName = "TestimonialCard";
const TestimonialCardSix = ({
testimonials,
animationType,
title,
titleSegments,
description,
textboxLayout,
useInvertedBackground,
tag,
tagIcon,
tagAnimation,
buttons,
buttonAnimation,
speed = 40,
topMarqueeDirection = "left",
ariaLabel = "Testimonials section",
className = "",
containerClassName = "",
carouselClassName = "",
bottomCarouselClassName = "",
cardClassName = "",
testimonialClassName = "",
imageWrapperClassName = "",
imageClassName = "",
iconClassName = "",
nameClassName = "",
handleClassName = "",
textBoxClassName = "",
textBoxTitleClassName = "",
textBoxTitleImageWrapperClassName = "",
textBoxTitleImageClassName = "",
textBoxDescriptionClassName = "",
textBoxTagClassName = "",
textBoxButtonContainerClassName = "",
textBoxButtonClassName = "",
textBoxButtonTextClassName = "",
}: TestimonialCardSixProps) => {
return (
<AutoCarousel
speed={speed}
uniformGridCustomHeightClasses="min-h-none"
animationType={animationType}
title={title}
titleSegments={titleSegments}
description={description}
tag={tag}
tagIcon={tagIcon}
tagAnimation={tagAnimation}
buttons={buttons}
buttonAnimation={buttonAnimation}
textboxLayout={textboxLayout}
useInvertedBackground={useInvertedBackground}
showTextBox={true}
dualMarquee={true}
topMarqueeDirection={topMarqueeDirection}
carouselClassName={carouselClassName}
bottomCarouselClassName={bottomCarouselClassName}
containerClassName={containerClassName}
className={className}
textBoxClassName={textBoxClassName}
titleClassName={textBoxTitleClassName}
titleImageWrapperClassName={textBoxTitleImageWrapperClassName}
titleImageClassName={textBoxTitleImageClassName}
descriptionClassName={textBoxDescriptionClassName}
tagClassName={textBoxTagClassName}
buttonContainerClassName={textBoxButtonContainerClassName}
buttonClassName={textBoxButtonClassName}
buttonTextClassName={textBoxButtonTextClassName}
ariaLabel={ariaLabel}
itemClassName="w-60! md:w-carousel-item-3! xl:w-carousel-item-4!"
>
{testimonials.map((testimonial, index) => (
<TestimonialCard
key={`${testimonial.id}-${index}`}
testimonial={testimonial}
useInvertedBackground={useInvertedBackground}
cardClassName={cardClassName}
testimonialClassName={testimonialClassName}
imageWrapperClassName={imageWrapperClassName}
imageClassName={imageClassName}
iconClassName={iconClassName}
nameClassName={nameClassName}
handleClassName={handleClassName}
/>
))}
</AutoCarousel>
);
export const TestimonialCardSix: React.FC<TestimonialCardSixProps> = ({
children,
animationType,
itemCount,
uniformGridCustomHeightClasses = 'min-h-95 2xl:min-h-105',
speed = 30,
className,
carouselClassName,
containerClassName,
itemClassName,
ariaLabel = 'Testimonials',
}) => {
return (
<div className={cn('testimonial-card-six', className)} aria-label={ariaLabel}>
<div className={cn('carousel-container', containerClassName)}>
{React.Children.map(children, (child, index) => (
<div key={index} className={cn('carousel-item', itemClassName)}>
{child}
</div>
))}
</div>
</div>
);
};
TestimonialCardSix.displayName = "TestimonialCardSix";
export default TestimonialCardSix;
export default TestimonialCardSix;

View File

@@ -0,0 +1,36 @@
'use client';
import { useRef, useEffect } from 'react';
export type CardAnimationType = 'none' | 'opacity' | 'slide-up' | 'scale-rotate' | 'blur-reveal' | 'depth-3d';
interface UseCardAnimationProps {
animationType: CardAnimationType;
itemCount: number;
useIndividualTriggers?: boolean;
isGrid?: boolean;
supports3DAnimation?: boolean;
gridVariant?: string;
}
export const useCardAnimation = ({
animationType,
itemCount,
useIndividualTriggers = false,
isGrid = false,
supports3DAnimation = false,
gridVariant,
}: UseCardAnimationProps) => {
const itemRefs = useRef<Array<React.RefObject<HTMLDivElement>>>(
Array.from({ length: itemCount }, () => ({ current: null }))
);
useEffect(() => {
// Animation logic would be implemented here
// For now, this is a placeholder that provides the hook structure
}, [animationType, itemCount, useIndividualTriggers, isGrid, supports3DAnimation, gridVariant]);
return { itemRefs };
};
export default useCardAnimation;