"use client";
import { memo, useMemo } from "react";
import Image from "next/image";
import TextAnimation from "./text/TextAnimation";
import Button from "./button/Button";
import Tag from "./shared/Tag";
import AvatarGroup from "./shared/AvatarGroup";
import { cls } from "@/lib/utils";
import { getButtonProps } from "@/lib/buttonUtils";
import { LucideIcon } from "lucide-react";
import type { AnimationType } from "./text/types";
import { useTheme } from "@/providers/themeProvider/ThemeProvider";
import type { ButtonConfig } from "@/types/button";
import type { Avatar } from "./shared/AvatarGroup";
import type { TextboxLayout, InvertedBackground } from "@/providers/themeProvider/config/constants";
type TitleSegment =
| { type: "text"; content: string }
| { type: "image"; src: string; alt?: string };
interface TextBoxProps {
title: string;
titleSegments?: TitleSegment[];
description: string;
type?: AnimationType;
textboxLayout?: TextboxLayout;
useInvertedBackground?: InvertedBackground;
className?: string;
titleClassName?: string;
titleImageWrapperClassName?: string;
titleImageClassName?: string;
descriptionClassName?: string;
duration?: number;
start?: string;
end?: string;
gradientColors?: {
from: string;
to: string;
};
children?: React.ReactNode;
center?: boolean;
tag?: string;
tagIcon?: LucideIcon;
tagClassName?: string;
buttons?: ButtonConfig[];
buttonContainerClassName?: string;
buttonClassName?: string;
buttonTextClassName?: string;
avatars?: Avatar[];
avatarText?: string;
avatarGroupClassName?: string;
avatarsAbove?: boolean;
}
const TextBox = ({
title,
titleSegments,
description,
type,
textboxLayout = "default",
useInvertedBackground,
className = "",
titleClassName = "",
titleImageWrapperClassName = "",
titleImageClassName = "",
descriptionClassName = "",
duration = 1,
start = "top 80%",
end = "top 20%",
gradientColors,
children,
center = false,
tag,
tagIcon: TagIcon,
tagClassName = "",
buttons,
buttonContainerClassName = "",
buttonClassName = "",
buttonTextClassName = "",
avatars,
avatarText,
avatarGroupClassName = "",
avatarsAbove = false,
}: TextBoxProps) => {
const theme = useTheme();
// Shared tag component
const tagElement = useMemo(() => tag && (
), [tag, TagIcon, useInvertedBackground, textboxLayout, tagClassName]);
// Shared title component
const titleElement = useMemo(() => (
), [type, theme.defaultTextAnimation, title, textboxLayout, center, useInvertedBackground, titleClassName, duration, start, end, gradientColors]);
// Inline image title component (used when textboxLayout === "inline-image")
const inlineImageTitleElement = useMemo(() => titleSegments && titleSegments.length > 0 ? (
{titleSegments.map((segment, index) => {
const imageIndex = titleSegments
.slice(0, index + 1)
.filter(s => s.type === "image").length - 1;
const element = segment.type === "text" ? (
{segment.content}
) : (
);
return (
{index > 0 && " "}
{element}
);
})}
) : null, [titleSegments, useInvertedBackground, titleClassName, titleImageWrapperClassName, titleImageClassName]);
// Shared description component
const descriptionElement = useMemo(() => (
), [type, theme.defaultTextAnimation, description, center, textboxLayout, useInvertedBackground, descriptionClassName, duration, start, end, gradientColors]);
// Shared avatars component
const avatarsElement = useMemo(() => avatars && avatars.length > 0 ? (
) : null, [avatars, avatarText, textboxLayout, center, avatarGroupClassName, avatarsAbove]);
// Shared buttons/children component
const actionsElement = useMemo(() => buttons && buttons.length > 0 ? (
{/* Limit to 2 buttons for optimal layout */}
{buttons.slice(0, 2).map((button, index) => (
))}
) : (
children
), [buttons, textboxLayout, center, buttonContainerClassName, theme.defaultButtonVariant, buttonClassName, buttonTextClassName, children]);
// Split layout
if (textboxLayout === "split") {
return (
{tagElement}
{titleElement}
{descriptionElement}
{actionsElement}
);
}
// Split actions layout - tag and buttons required, no description
if (textboxLayout === "split-actions") {
return (
{tagElement}
{titleElement}
{actionsElement}
);
}
// Split description layout - tag + title left, description only right (no buttons)
if (textboxLayout === "split-description") {
return (
{tagElement}
{titleElement}
{descriptionElement}
);
}
// Inline image layout - centered heading with inline images and optional buttons
if (textboxLayout === "inline-image") {
return (
{inlineImageTitleElement}
{actionsElement}
);
}
// Default layout
return (
{avatarsAbove && avatarsElement}
{tagElement}
{titleElement}
{descriptionElement}
{actionsElement}
{!avatarsAbove && avatarsElement}
);
};
TextBox.displayName = "TextBox";
export default memo(TextBox);