219 lines
7.0 KiB
TypeScript
219 lines
7.0 KiB
TypeScript
"use client";
|
|
|
|
import React, { useEffect, useRef } from 'react';
|
|
import gsap from 'gsap';
|
|
import { ScrollTrigger } from 'gsap/ScrollTrigger';
|
|
import TimelineHorizontalCardStack from '@/components/cardStack/layouts/timelines/TimelineHorizontalCardStack';
|
|
|
|
gsap.registerPlugin(ScrollTrigger);
|
|
|
|
interface BlogCardThreeProps {
|
|
blogs: Array<{
|
|
id: string;
|
|
category: string;
|
|
title: string;
|
|
excerpt: string;
|
|
imageSrc: string;
|
|
imageAlt?: string;
|
|
authorName: string;
|
|
authorAvatar: string;
|
|
date: string;
|
|
onBlogClick?: () => void;
|
|
}>;
|
|
carouselMode?: 'auto' | 'buttons';
|
|
uniformGridCustomHeightClasses?: string;
|
|
animationType: 'none' | 'opacity' | 'slide-up' | 'scale-rotate' | 'blur-reveal';
|
|
title: string;
|
|
titleSegments?: Array<{ type: 'text'; content: string } | { type: 'image'; src: string; alt?: string }>;
|
|
description: string;
|
|
tag?: string;
|
|
tagIcon?: React.ComponentType<any>;
|
|
tagAnimation?: 'none' | 'opacity' | 'slide-up' | 'blur-reveal';
|
|
buttons?: Array<{ text: string; onClick?: () => void; href?: string }>;
|
|
buttonAnimation?: 'none' | 'opacity' | 'slide-up' | 'blur-reveal';
|
|
textboxLayout: 'default' | 'split' | 'split-actions' | 'split-description' | 'inline-image';
|
|
useInvertedBackground: boolean;
|
|
ariaLabel?: string;
|
|
className?: string;
|
|
containerClassName?: string;
|
|
cardClassName?: string;
|
|
imageWrapperClassName?: string;
|
|
imageClassName?: string;
|
|
categoryClassName?: string;
|
|
cardTitleClassName?: string;
|
|
excerptClassName?: string;
|
|
authorContainerClassName?: string;
|
|
authorAvatarClassName?: string;
|
|
authorNameClassName?: string;
|
|
dateClassName?: string;
|
|
textBoxTitleImageWrapperClassName?: string;
|
|
textBoxTitleImageClassName?: string;
|
|
textBoxDescriptionClassName?: string;
|
|
gridClassName?: string;
|
|
carouselClassName?: string;
|
|
controlsClassName?: string;
|
|
textBoxClassName?: string;
|
|
textBoxTagClassName?: string;
|
|
textBoxButtonContainerClassName?: string;
|
|
textBoxButtonClassName?: string;
|
|
textBoxButtonTextClassName?: string;
|
|
}
|
|
|
|
const BlogCardThree: React.FC<BlogCardThreeProps> = ({
|
|
blogs,
|
|
carouselMode = 'buttons',
|
|
uniformGridCustomHeightClasses = 'min-h-95 2xl:min-h-105',
|
|
animationType,
|
|
title,
|
|
titleSegments,
|
|
description,
|
|
tag,
|
|
tagIcon: TagIcon,
|
|
tagAnimation,
|
|
buttons,
|
|
buttonAnimation,
|
|
textboxLayout,
|
|
useInvertedBackground,
|
|
ariaLabel = 'Blog section',
|
|
className = '',
|
|
containerClassName = '',
|
|
cardClassName = '',
|
|
imageWrapperClassName = '',
|
|
imageClassName = '',
|
|
categoryClassName = '',
|
|
cardTitleClassName = '',
|
|
excerptClassName = '',
|
|
authorContainerClassName = '',
|
|
authorAvatarClassName = '',
|
|
authorNameClassName = '',
|
|
dateClassName = '',
|
|
textBoxTitleImageWrapperClassName = '',
|
|
textBoxTitleImageClassName = '',
|
|
textBoxDescriptionClassName = '',
|
|
gridClassName = '',
|
|
carouselClassName = '',
|
|
controlsClassName = '',
|
|
textBoxClassName = '',
|
|
textBoxTagClassName = '',
|
|
textBoxButtonContainerClassName = '',
|
|
textBoxButtonClassName = '',
|
|
textBoxButtonTextClassName = '',
|
|
}) => {
|
|
const sectionRef = useRef<HTMLDivElement>(null);
|
|
const cardsRef = useRef<(HTMLDivElement | null)[]>([]);
|
|
|
|
useEffect(() => {
|
|
if (animationType === 'none' || !sectionRef.current) return;
|
|
|
|
const cards = cardsRef.current.filter((card): card is HTMLDivElement => card !== null);
|
|
if (cards.length === 0) return;
|
|
|
|
cards.forEach((card, index) => {
|
|
gsap.set(card, { opacity: 0, y: 20 });
|
|
|
|
ScrollTrigger.create({
|
|
trigger: card,
|
|
onEnter: () => {
|
|
gsap.to(card, {
|
|
opacity: 1,
|
|
y: 0,
|
|
duration: 0.6,
|
|
delay: index * 0.1,
|
|
ease: 'power2.out',
|
|
});
|
|
},
|
|
});
|
|
});
|
|
|
|
return () => {
|
|
cards.forEach(() => ScrollTrigger.getAll().forEach(trigger => trigger.kill()));
|
|
};
|
|
}, [animationType]);
|
|
|
|
const gridVariant = blogs.length <= 4 ? 'uniform-all-items-equal' : 'uniform-all-items-equal';
|
|
const mode = blogs.length >= 5 ? 'auto' : carouselMode;
|
|
|
|
const blogChildren = blogs.map((blog, index) => (
|
|
<div
|
|
key={blog.id}
|
|
ref={el => {
|
|
cardsRef.current[index] = el;
|
|
}}
|
|
className={`cursor-pointer group ${cardClassName}`}
|
|
onClick={blog.onBlogClick}
|
|
>
|
|
{/* Image wrapper with overlay arrow */}
|
|
<div className={`relative overflow-hidden rounded-lg ${imageWrapperClassName}`}>
|
|
<img
|
|
src={blog.imageSrc}
|
|
alt={blog.imageAlt || blog.title}
|
|
className={`w-full h-full object-cover transition-transform duration-300 group-hover:scale-110 ${imageClassName}`}
|
|
/>
|
|
<div className="absolute inset-0 bg-black/20 opacity-0 group-hover:opacity-100 transition-opacity duration-300 flex items-center justify-center">
|
|
<div className="text-white text-3xl">→</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Category badge */}
|
|
<div className={`mt-4 inline-block px-3 py-1 rounded-full bg-blue-500/20 text-blue-400 text-sm font-semibold ${categoryClassName}`}>
|
|
{blog.category}
|
|
</div>
|
|
|
|
{/* Title */}
|
|
<h3 className={`mt-3 text-xl font-bold text-gray-900 dark:text-white line-clamp-2 ${cardTitleClassName}`}>
|
|
{blog.title}
|
|
</h3>
|
|
|
|
{/* Excerpt */}
|
|
<p className={`mt-2 text-gray-600 dark:text-gray-300 text-sm line-clamp-2 ${excerptClassName}`}>
|
|
{blog.excerpt}
|
|
</p>
|
|
|
|
{/* Author info */}
|
|
<div className={`mt-4 flex items-center gap-3 ${authorContainerClassName}`}>
|
|
<img
|
|
src={blog.authorAvatar}
|
|
alt={blog.authorName}
|
|
className={`w-8 h-8 rounded-full object-cover ${authorAvatarClassName}`}
|
|
/>
|
|
<div className="flex-1 min-w-0">
|
|
<p className={`text-sm font-semibold text-gray-900 dark:text-white truncate ${authorNameClassName}`}>
|
|
{blog.authorName}
|
|
</p>
|
|
<p className={`text-xs text-gray-500 dark:text-gray-400 ${dateClassName}`}>{blog.date}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
));
|
|
|
|
return (
|
|
<section ref={sectionRef} className={`py-12 md:py-20 ${className}`} aria-label={ariaLabel}>
|
|
<TimelineHorizontalCardStack
|
|
children={blogChildren}
|
|
title={title}
|
|
titleSegments={titleSegments}
|
|
description={description}
|
|
tag={tag}
|
|
tagIcon={TagIcon}
|
|
tagAnimation={tagAnimation}
|
|
buttons={buttons}
|
|
buttonAnimation={buttonAnimation}
|
|
textboxLayout={textboxLayout}
|
|
useInvertedBackground={useInvertedBackground}
|
|
ariaLabel={ariaLabel}
|
|
className={className}
|
|
containerClassName={containerClassName}
|
|
textBoxClassName={textBoxClassName}
|
|
titleImageWrapperClassName={textBoxTitleImageWrapperClassName}
|
|
titleImageClassName={textBoxTitleImageClassName}
|
|
descriptionClassName={textBoxDescriptionClassName}
|
|
tagClassName={textBoxTagClassName}
|
|
buttonContainerClassName={textBoxButtonContainerClassName}
|
|
buttonClassName={textBoxButtonClassName}
|
|
buttonTextClassName={textBoxButtonTextClassName}
|
|
/>
|
|
</section>
|
|
);
|
|
};
|
|
|
|
export default BlogCardThree; |