Initial commit

This commit is contained in:
dk
2025-12-21 22:41:18 +02:00
commit b681287d10
308 changed files with 64791 additions and 0 deletions

View File

@@ -0,0 +1,144 @@
"use client";
import { memo, useState, Fragment } from "react";
import CardStackTextBox from "@/components/cardStack/CardStackTextBox";
import Accordion from "@/components/Accordion";
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";
interface FaqItem {
id: string;
title: string;
content: string;
}
interface FaqBaseProps {
faqs: FaqItem[];
title: string;
titleSegments?: TitleSegment[];
description: string;
tag?: string;
tagIcon?: LucideIcon;
buttons?: ButtonConfig[];
textboxLayout: TextboxLayout;
useInvertedBackground: InvertedBackground;
animationType?: "smooth" | "instant";
showCard?: boolean;
ariaLabel?: string;
className?: string;
containerClassName?: string;
textBoxTitleClassName?: string;
titleImageWrapperClassName?: string;
titleImageClassName?: string;
textBoxDescriptionClassName?: string;
textBoxClassName?: string;
textBoxTagClassName?: string;
textBoxButtonContainerClassName?: string;
textBoxButtonClassName?: string;
textBoxButtonTextClassName?: string;
faqsContainerClassName?: string;
accordionClassName?: string;
accordionTitleClassName?: string;
accordionIconContainerClassName?: string;
accordionIconClassName?: string;
accordionContentClassName?: string;
separatorClassName?: string;
}
const FaqBase = ({
faqs,
title,
titleSegments,
description,
tag,
tagIcon,
buttons,
textboxLayout,
useInvertedBackground,
animationType = "smooth",
showCard = true,
ariaLabel = "FAQ section",
className = "",
containerClassName = "",
textBoxTitleClassName = "",
titleImageWrapperClassName = "",
titleImageClassName = "",
textBoxDescriptionClassName = "",
textBoxClassName = "",
textBoxTagClassName = "",
textBoxButtonContainerClassName = "",
textBoxButtonClassName = "",
textBoxButtonTextClassName = "",
faqsContainerClassName = "",
accordionClassName = "",
accordionTitleClassName = "",
accordionIconContainerClassName = "",
accordionIconClassName = "",
accordionContentClassName = "",
separatorClassName = "",
}: FaqBaseProps) => {
const [activeIndex, setActiveIndex] = useState<number | null>(null);
const handleToggle = (index: number) => {
setActiveIndex(activeIndex === index ? null : index);
};
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)}>
{(title || description) && (
<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("flex flex-col gap-4", faqsContainerClassName)}>
{faqs.map((faq, index) => (
<Fragment key={faq.id}>
<Accordion
index={index}
isActive={activeIndex === index}
onToggle={handleToggle}
title={faq.title}
content={faq.content}
animationType={animationType}
showCard={showCard}
useInvertedBackground={useInvertedBackground}
className={accordionClassName}
titleClassName={accordionTitleClassName}
iconContainerClassName={accordionIconContainerClassName}
iconClassName={accordionIconClassName}
contentClassName={accordionContentClassName}
/>
{!showCard && index < faqs.length - 1 && (
<div className={cls("w-full border-b border-foreground/10", separatorClassName)} />
)}
</Fragment>
))}
</div>
</div>
</section>
);
};
FaqBase.displayName = "FaqBase";
export default memo(FaqBase);

View File

@@ -0,0 +1,169 @@
"use client";
import React, { memo, useState, useCallback } from "react";
import CardStackTextBox from "@/components/cardStack/CardStackTextBox";
import Accordion from "@/components/Accordion";
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";
interface FaqItem {
id: string;
title: string;
content: string;
}
interface FaqDoubleProps {
faqs: FaqItem[];
title: string;
titleSegments?: TitleSegment[];
description: string;
tag?: string;
tagIcon?: LucideIcon;
buttons?: ButtonConfig[];
textboxLayout: TextboxLayout;
useInvertedBackground: InvertedBackground;
animationType?: "smooth" | "instant";
ariaLabel?: string;
className?: string;
containerClassName?: string;
textBoxTitleClassName?: string;
titleImageWrapperClassName?: string;
titleImageClassName?: string;
textBoxDescriptionClassName?: string;
textBoxClassName?: string;
textBoxTagClassName?: string;
textBoxButtonContainerClassName?: string;
textBoxButtonClassName?: string;
textBoxButtonTextClassName?: string;
faqsContainerClassName?: string;
columnClassName?: string;
accordionClassName?: string;
accordionTitleClassName?: string;
accordionIconContainerClassName?: string;
accordionIconClassName?: string;
accordionContentClassName?: string;
}
const FaqDouble = ({
faqs,
title,
titleSegments,
description,
tag,
tagIcon,
buttons,
textboxLayout,
useInvertedBackground,
animationType = "smooth",
ariaLabel = "FAQ section",
className = "",
containerClassName = "",
textBoxTitleClassName = "",
titleImageWrapperClassName = "",
titleImageClassName = "",
textBoxDescriptionClassName = "",
textBoxClassName = "",
textBoxTagClassName = "",
textBoxButtonContainerClassName = "",
textBoxButtonClassName = "",
textBoxButtonTextClassName = "",
faqsContainerClassName = "",
columnClassName = "",
accordionClassName = "",
accordionTitleClassName = "",
accordionIconContainerClassName = "",
accordionIconClassName = "",
accordionContentClassName = "",
}: FaqDoubleProps) => {
const [activeIndex, setActiveIndex] = useState<number | null>(null);
const handleToggle = useCallback((index: number) => {
setActiveIndex((prevActiveIndex) =>
prevActiveIndex === index ? null : index
);
}, []);
const halfLength = Math.ceil(faqs.length / 2);
const firstHalf = faqs.slice(0, halfLength);
const secondHalf = faqs.slice(halfLength);
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)}>
{(title || description) && (
<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("card p-4 rounded-theme-capped flex flex-col md:flex-row gap-4", faqsContainerClassName)}>
<div className={cls("relative z-1 flex-1 flex flex-col gap-4", columnClassName)}>
{firstHalf.map((faq, index) => (
<Accordion
key={faq.id}
index={index}
isActive={activeIndex === index}
onToggle={handleToggle}
title={faq.title}
content={faq.content}
animationType={animationType}
useInvertedBackground={useInvertedBackground}
className={accordionClassName}
titleClassName={accordionTitleClassName}
iconContainerClassName={accordionIconContainerClassName}
iconClassName={accordionIconClassName}
contentClassName={accordionContentClassName}
/>
))}
</div>
{secondHalf.length > 0 && (
<div className={cls("relative z-1 flex-1 flex flex-col gap-4", columnClassName)}>
{secondHalf.map((faq, index) => {
const actualIndex = index + halfLength;
return (
<Accordion
key={faq.id}
index={actualIndex}
isActive={activeIndex === actualIndex}
onToggle={handleToggle}
title={faq.title}
content={faq.content}
animationType={animationType}
useInvertedBackground={useInvertedBackground}
className={accordionClassName}
titleClassName={accordionTitleClassName}
iconContainerClassName={accordionIconContainerClassName}
iconClassName={accordionIconClassName}
contentClassName={accordionContentClassName}
/>
);
})}
</div>
)}
</div>
</div>
</section>
);
};
FaqDouble.displayName = "FaqDouble";
export default memo(FaqDouble);

View File

@@ -0,0 +1,187 @@
"use client";
import { memo, useState, useCallback, Fragment } from "react";
import CardStackTextBox from "@/components/cardStack/CardStackTextBox";
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 { TextboxLayout, InvertedBackground } from "@/providers/themeProvider/config/constants";
interface FaqItem {
id: string;
title: string;
content: string;
}
interface FaqSplitMediaProps {
faqs: FaqItem[];
imageSrc?: string;
videoSrc?: string;
imageAlt?: string;
videoAriaLabel?: string;
mediaPosition?: "left" | "right";
title: string;
titleSegments?: TitleSegment[];
description: string;
tag?: string;
tagIcon?: LucideIcon;
buttons?: ButtonConfig[];
textboxLayout: TextboxLayout;
useInvertedBackground: InvertedBackground;
animationType?: "smooth" | "instant";
showCard?: boolean;
ariaLabel?: string;
className?: string;
containerClassName?: string;
textBoxTitleClassName?: string;
titleImageWrapperClassName?: string;
titleImageClassName?: string;
textBoxDescriptionClassName?: string;
textBoxClassName?: string;
textBoxTagClassName?: string;
textBoxButtonContainerClassName?: string;
textBoxButtonClassName?: string;
textBoxButtonTextClassName?: string;
contentClassName?: string;
mediaWrapperClassName?: string;
mediaClassName?: string;
faqsContainerClassName?: string;
accordionClassName?: string;
accordionTitleClassName?: string;
accordionIconContainerClassName?: string;
accordionIconClassName?: string;
accordionContentClassName?: string;
separatorClassName?: string;
}
const FaqSplitMedia = ({
faqs,
imageSrc,
videoSrc,
imageAlt = "",
videoAriaLabel = "FAQ section video",
mediaPosition = "left",
title,
titleSegments,
description,
tag,
tagIcon,
buttons,
textboxLayout,
useInvertedBackground,
animationType = "smooth",
showCard = true,
ariaLabel = "FAQ section",
className = "",
containerClassName = "",
textBoxTitleClassName = "",
titleImageWrapperClassName = "",
titleImageClassName = "",
textBoxDescriptionClassName = "",
textBoxClassName = "",
textBoxTagClassName = "",
textBoxButtonContainerClassName = "",
textBoxButtonClassName = "",
textBoxButtonTextClassName = "",
contentClassName = "",
mediaWrapperClassName = "",
mediaClassName = "",
faqsContainerClassName = "",
accordionClassName = "",
accordionTitleClassName = "",
accordionIconContainerClassName = "",
accordionIconClassName = "",
accordionContentClassName = "",
separatorClassName = "",
}: FaqSplitMediaProps) => {
const [activeIndex, setActiveIndex] = useState<number | null>(null);
const handleToggle = useCallback((index: number) => {
setActiveIndex((prevActiveIndex) =>
prevActiveIndex === index ? null : index
);
}, []);
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)}>
{(title || description) && (
<CardStackTextBox
title={title}
titleSegments={titleSegments}
description={description}
tag={tag}
tagIcon={tagIcon}
buttons={buttons}
textboxLayout={textboxLayout}
useInvertedBackground={useInvertedBackground}
textBoxClassName={textBoxClassName}
titleClassName={textBoxTitleClassName}
titleImageWrapperClassName={titleImageWrapperClassName}
titleImageClassName={titleImageClassName}
descriptionClassName={textBoxDescriptionClassName}
tagClassName={textBoxTagClassName}
buttonContainerClassName={textBoxButtonContainerClassName}
buttonClassName={textBoxButtonClassName}
buttonTextClassName={textBoxButtonTextClassName}
/>
)}
<div className={cls("grid grid-cols-1 md:grid-cols-5 gap-4 md:auto-rows-fr", contentClassName)}>
{mediaPosition === "left" && (
<div className={cls("overflow-hidden rounded-theme-capped card relative h-80 md:h-auto col-span-1 md:col-span-2", mediaWrapperClassName)}>
<MediaContent
imageSrc={imageSrc}
videoSrc={videoSrc}
imageAlt={imageAlt}
videoAriaLabel={videoAriaLabel}
imageClassName={cls("absolute z-1 inset-0 w-full h-full object-cover", mediaClassName)}
/>
</div>
)}
<div className={cls("relative z-1 col-span-1 md:col-span-3 flex flex-col gap-4", faqsContainerClassName)}>
{faqs.map((faq, index) => (
<Fragment key={faq.id}>
<Accordion
index={index}
isActive={activeIndex === index}
onToggle={handleToggle}
title={faq.title}
content={faq.content}
animationType={animationType}
showCard={showCard}
useInvertedBackground={useInvertedBackground}
className={accordionClassName}
titleClassName={accordionTitleClassName}
iconContainerClassName={accordionIconContainerClassName}
iconClassName={accordionIconClassName}
contentClassName={accordionContentClassName}
/>
{!showCard && index < faqs.length - 1 && (
<div className={cls("w-full border-b border-foreground/10", separatorClassName)} />
)}
</Fragment>
))}
</div>
{mediaPosition === "right" && (
<div className={cls("overflow-hidden rounded-theme card relative h-80 md:h-auto col-span-1 md:col-span-2", mediaWrapperClassName)}>
<MediaContent
imageSrc={imageSrc}
videoSrc={videoSrc}
imageAlt={imageAlt}
videoAriaLabel={videoAriaLabel}
imageClassName={cls("absolute z-1 inset-0 w-full h-full object-cover", mediaClassName)}
/>
</div>
)}
</div>
</div>
</section>
);
};
FaqSplitMedia.displayName = "FaqSplitMedia";
export default memo(FaqSplitMedia);

View File

@@ -0,0 +1,152 @@
"use client";
import { memo, useState, useCallback, Fragment } from "react";
import TextAnimation from "@/components/text/TextAnimation";
import Accordion from "@/components/Accordion";
import Button from "@/components/button/Button";
import { cls } from "@/lib/utils";
import { getButtonProps } from "@/lib/buttonUtils";
import { useTheme } from "@/providers/themeProvider/ThemeProvider";
import type { AnimationType } from "@/components/text/types";
import type { ButtonConfig } from "@/components/cardStack/types";
interface FaqItem {
id: string;
title: string;
content: string;
}
interface FaqSplitTextProps {
faqs: FaqItem[];
sideTitle: string;
sideDescription?: string;
buttons?: ButtonConfig[];
textPosition?: "left" | "right";
useInvertedBackground: "noInvert" | "invertDefault" | "invertCard";
animationType?: "smooth" | "instant";
showCard?: boolean;
ariaLabel?: string;
className?: string;
containerClassName?: string;
contentClassName?: string;
textContainerClassName?: string;
sideTitleClassName?: string;
sideDescriptionClassName?: string;
buttonContainerClassName?: string;
buttonClassName?: string;
buttonTextClassName?: string;
faqsContainerClassName?: string;
accordionClassName?: string;
accordionTitleClassName?: string;
accordionIconContainerClassName?: string;
accordionIconClassName?: string;
accordionContentClassName?: string;
separatorClassName?: string;
}
const FaqSplitText = ({
faqs,
sideTitle,
sideDescription,
buttons,
textPosition = "left",
useInvertedBackground,
animationType = "smooth",
showCard = true,
ariaLabel = "FAQ section",
className = "",
containerClassName = "",
contentClassName = "",
textContainerClassName = "",
sideTitleClassName = "",
sideDescriptionClassName = "",
buttonContainerClassName = "",
buttonClassName = "",
buttonTextClassName = "",
faqsContainerClassName = "",
accordionClassName = "",
accordionTitleClassName = "",
accordionIconContainerClassName = "",
accordionIconClassName = "",
accordionContentClassName = "",
separatorClassName = "",
}: FaqSplitTextProps) => {
const [activeIndex, setActiveIndex] = useState<number | null>(null);
const theme = useTheme();
const shouldUseLightText = useInvertedBackground === "invertDefault" || useInvertedBackground === "invertCard";
const handleToggle = useCallback((index: number) => {
setActiveIndex((prevActiveIndex) =>
prevActiveIndex === index ? null : index
);
}, []);
const textContent = (
<div className={cls("w-full md:w-2/5 flex flex-col gap-3", textContainerClassName)}>
<TextAnimation
type={theme.defaultTextAnimation as AnimationType}
text={sideTitle}
variant="trigger"
className={cls("text-6xl font-medium", shouldUseLightText ? "text-background" : "text-foreground", sideTitleClassName)}
/>
{sideDescription && (
<TextAnimation
type={theme.defaultTextAnimation as AnimationType}
text={sideDescription}
variant="words-trigger"
className={cls("text-lg leading-[1.2]", shouldUseLightText ? "text-background" : "text-foreground", sideDescriptionClassName)}
/>
)}
{buttons && buttons.length > 0 && (
<div className={cls("flex gap-4", buttonContainerClassName)}>
{buttons.slice(0, 2).map((button, index) => (
<Button key={`${button.text}-${index}`} {...getButtonProps(button, index, theme.defaultButtonVariant, buttonClassName, buttonTextClassName)} />
))}
</div>
)}
</div>
);
const faqsContent = (
<div className={cls("w-full md:w-3/5 flex flex-col gap-4", faqsContainerClassName)}>
{faqs.map((faq, index) => (
<Fragment key={faq.id}>
<Accordion
index={index}
isActive={activeIndex === index}
onToggle={handleToggle}
title={faq.title}
content={faq.content}
animationType={animationType}
showCard={showCard}
useInvertedBackground={useInvertedBackground}
className={accordionClassName}
titleClassName={accordionTitleClassName}
iconContainerClassName={accordionIconContainerClassName}
iconClassName={accordionIconClassName}
contentClassName={accordionContentClassName}
/>
{!showCard && index < faqs.length - 1 && (
<div className={cls("w-full border-b border-foreground/10", separatorClassName)} />
)}
</Fragment>
))}
</div>
);
return (
<section aria-label={ariaLabel} className={cls("relative py-20", useInvertedBackground === "invertCard" ? "w-content-width-expanded mx-auto rounded-theme-capped bg-foreground" : "w-full", useInvertedBackground === "invertDefault" && "bg-foreground", className)}>
<div className={cls("w-content-width mx-auto", containerClassName)}>
<div className={cls("flex flex-col md:flex-row gap-6 md:gap-10", contentClassName)}>
{textPosition === "left" && textContent}
{faqsContent}
{textPosition === "right" && textContent}
</div>
</div>
</section>
);
};
FaqSplitText.displayName = "FaqSplitText";
export default memo(FaqSplitText);