Files
klz-cables.com/components/cards/BlogCard.tsx
2025-12-29 18:18:48 +01:00

144 lines
4.0 KiB
TypeScript

'use client';
import React from 'react';
import { BaseCard, BaseCardProps, CardSize, CardLayout } from './BaseCard';
import { Badge, BadgeGroup } from '@/components/ui';
import { formatDate } from '@/lib/utils';
import { Post } from '@/lib/data';
// BlogCard specific props
export interface BlogCardProps extends Omit<BaseCardProps, 'title' | 'description' | 'image' | 'footer'> {
/** Post data from WordPress */
post: Post;
/** Display date */
showDate?: boolean;
/** Display categories */
showCategories?: boolean;
/** Read more text */
readMoreText?: string;
/** Excerpt length */
excerptLength?: number;
/** Locale for formatting */
locale?: string;
}
// Helper to extract categories from post (mock implementation since Post doesn't have categories)
const getPostCategories = (post: Post): string[] => {
// In a real implementation, this would come from the post data
// For now, return a mock category based on post ID
return post.id % 2 === 0 ? ['News', 'Updates'] : ['Blog', 'Tips'];
};
// Helper to get featured image URL
const getFeaturedImageUrl = (post: Post): string | undefined => {
// In a real implementation, this would use getMediaById
// For now, return a placeholder or the featured image if available
if (post.featuredImage) {
// This would be resolved through the data layer
return `/media/${post.featuredImage}.jpg`;
}
return undefined;
};
// Helper to truncate text
const truncateText = (text: string, length: number): string => {
if (text.length <= length) return text;
return text.slice(0, length - 3) + '...';
};
export const BlogCard: React.FC<BlogCardProps> = ({
post,
size = 'md',
layout = 'vertical',
showDate = true,
showCategories = true,
readMoreText = 'Read More',
excerptLength = 150,
locale = 'de',
className = '',
...props
}) => {
// Get post data
const title = post.title;
const excerpt = post.excerptHtml ? post.excerptHtml.replace(/<[^>]*>/g, '') : '';
const truncatedExcerpt = truncateText(excerpt, excerptLength);
const featuredImageUrl = getFeaturedImageUrl(post);
const categories = showCategories ? getPostCategories(post) : [];
const date = showDate ? formatDate(post.datePublished, locale === 'de' ? 'de-DE' : 'en-US') : '';
// Build badge component for categories
const badge = showCategories && categories.length > 0 ? (
<BadgeGroup gap="xs">
{categories.map((category, index) => (
<Badge
key={index}
variant="neutral"
size={size === 'sm' ? 'sm' : 'md'}
>
{category}
</Badge>
))}
</BadgeGroup>
) : null;
// Build header with date
const header = date ? (
<span className="text-xs text-gray-500 font-medium">
{date}
</span>
) : null;
// Build footer with read more link
const footer = (
<span className="text-sm font-medium text-primary hover:text-primary-dark transition-colors">
{readMoreText}
</span>
);
// Build description
const description = truncatedExcerpt ? (
<div
className="text-gray-600"
dangerouslySetInnerHTML={{ __html: truncatedExcerpt }}
/>
) : null;
return (
<BaseCard
title={title}
description={description}
image={featuredImageUrl}
imageAlt={title}
size={size}
layout={layout}
href={post.path}
badge={badge}
header={header}
footer={footer}
hoverable={true}
variant="elevated"
className={className}
{...props}
/>
);
};
// BlogCard variations
export const BlogCardVertical: React.FC<BlogCardProps> = (props) => (
<BlogCard {...props} layout="vertical" />
);
export const BlogCardHorizontal: React.FC<BlogCardProps> = (props) => (
<BlogCard {...props} layout="horizontal" />
);
export const BlogCardSmall: React.FC<BlogCardProps> = (props) => (
<BlogCard {...props} size="sm" />
);
export const BlogCardLarge: React.FC<BlogCardProps> = (props) => (
<BlogCard {...props} size="lg" />
);
// Export types
export type { CardSize, CardLayout };