Add src/components/CardStackTextBox.tsx
This commit is contained in:
163
src/components/CardStackTextBox.tsx
Normal file
163
src/components/CardStackTextBox.tsx
Normal file
@@ -0,0 +1,163 @@
|
||||
"use client";
|
||||
|
||||
import React from "react";
|
||||
|
||||
interface TitleSegment {
|
||||
text: string;
|
||||
highlight?: boolean;
|
||||
}
|
||||
|
||||
interface Button {
|
||||
label: string;
|
||||
href?: string;
|
||||
onClick?: () => void;
|
||||
variant?: "primary" | "secondary";
|
||||
}
|
||||
|
||||
interface CardStackTextBoxProps {
|
||||
title: string;
|
||||
titleSegments?: TitleSegment[];
|
||||
description: string;
|
||||
tag?: string;
|
||||
tagIcon?: React.ReactNode;
|
||||
tagAnimation?: string;
|
||||
buttons?: Button[];
|
||||
buttonAnimation?: string;
|
||||
textboxLayout?: "default" | "centered" | "compact";
|
||||
useInvertedBackground?: boolean;
|
||||
className?: string;
|
||||
titleClassName?: string;
|
||||
titleImageWrapperClassName?: string;
|
||||
titleImageClassName?: string;
|
||||
descriptionClassName?: string;
|
||||
tagClassName?: string;
|
||||
buttonContainerClassName?: string;
|
||||
buttonClassName?: string;
|
||||
buttonTextClassName?: string;
|
||||
}
|
||||
|
||||
export function CardStackTextBox({
|
||||
title,
|
||||
titleSegments,
|
||||
description,
|
||||
tag,
|
||||
tagIcon,
|
||||
tagAnimation,
|
||||
buttons,
|
||||
buttonAnimation,
|
||||
textboxLayout = "default",
|
||||
useInvertedBackground = false,
|
||||
className = "",
|
||||
titleClassName = "",
|
||||
titleImageWrapperClassName = "",
|
||||
titleImageClassName = "",
|
||||
descriptionClassName = "",
|
||||
tagClassName = "",
|
||||
buttonContainerClassName = "",
|
||||
buttonClassName = "",
|
||||
buttonTextClassName = "",
|
||||
}: CardStackTextBoxProps) {
|
||||
const baseClasses = `
|
||||
w-full h-full p-6 flex flex-col justify-between
|
||||
${useInvertedBackground ? "bg-slate-900 text-white" : "bg-white text-slate-900"}
|
||||
rounded-lg transition-all duration-300
|
||||
`;
|
||||
|
||||
const layoutClasses = {
|
||||
default: "items-start",
|
||||
centered: "items-center text-center",
|
||||
compact: "items-start gap-2",
|
||||
};
|
||||
|
||||
const titleDefaultClasses = `
|
||||
text-2xl font-bold mb-4 leading-tight
|
||||
${titleClassName}
|
||||
`;
|
||||
|
||||
const descriptionDefaultClasses = `
|
||||
text-base leading-relaxed mb-4 flex-grow
|
||||
${useInvertedBackground ? "text-slate-300" : "text-slate-600"}
|
||||
${descriptionClassName}
|
||||
`;
|
||||
|
||||
const tagDefaultClasses = `
|
||||
inline-flex items-center gap-2 px-3 py-1 rounded-full text-sm font-medium mb-4
|
||||
${useInvertedBackground ? "bg-slate-800 text-slate-200" : "bg-slate-100 text-slate-700"}
|
||||
${tagClassName}
|
||||
`;
|
||||
|
||||
const buttonContainerDefaultClasses = `
|
||||
flex gap-3 w-full
|
||||
${textboxLayout === "centered" ? "justify-center" : "justify-start"}
|
||||
${buttonContainerClassName}
|
||||
`;
|
||||
|
||||
const buttonDefaultClasses = `
|
||||
px-4 py-2 rounded-lg font-medium transition-all duration-200
|
||||
${buttonClassName}
|
||||
`;
|
||||
|
||||
const buttonTextDefaultClasses = `
|
||||
${buttonTextClassName}
|
||||
`;
|
||||
|
||||
const renderTitle = () => {
|
||||
if (titleSegments && titleSegments.length > 0) {
|
||||
return (
|
||||
<h3 className={titleDefaultClasses}>
|
||||
{titleSegments.map((segment, index) => (
|
||||
<span
|
||||
key={index}
|
||||
className={segment.highlight ? "text-blue-500" : ""}
|
||||
>
|
||||
{segment.text}
|
||||
</span>
|
||||
))}
|
||||
</h3>
|
||||
);
|
||||
}
|
||||
return <h3 className={titleDefaultClasses}>{title}</h3>;
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={`${baseClasses} ${layoutClasses[textboxLayout]} ${className}`}>
|
||||
<div className="w-full">
|
||||
{tag && (
|
||||
<div className={tagDefaultClasses}>
|
||||
{tagIcon && <span>{tagIcon}</span>}
|
||||
<span>{tag}</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{renderTitle()}
|
||||
|
||||
<p className={descriptionDefaultClasses}>{description}</p>
|
||||
</div>
|
||||
|
||||
{buttons && buttons.length > 0 && (
|
||||
<div className={buttonContainerDefaultClasses}>
|
||||
{buttons.map((button, index) => (
|
||||
<button
|
||||
key={index}
|
||||
onClick={button.onClick}
|
||||
className={`
|
||||
${buttonDefaultClasses}
|
||||
${
|
||||
button.variant === "secondary"
|
||||
? useInvertedBackground
|
||||
? "bg-slate-700 hover:bg-slate-600 text-white"
|
||||
: "bg-slate-200 hover:bg-slate-300 text-slate-900"
|
||||
: useInvertedBackground
|
||||
? "bg-blue-600 hover:bg-blue-700 text-white"
|
||||
: "bg-blue-500 hover:bg-blue-600 text-white"
|
||||
}
|
||||
`}
|
||||
>
|
||||
<span className={buttonTextDefaultClasses}>{button.label}</span>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user