Update: Blog page featured layout ui, mobile menu padding and font change

This commit is contained in:
Wayne Sutton
2025-12-26 16:41:06 -08:00
parent bfe88d0217
commit b35dd17332
24 changed files with 748 additions and 73 deletions

View File

@@ -0,0 +1,89 @@
import { Link } from "react-router-dom";
import { format, parseISO } from "date-fns";
interface BlogHeroCardProps {
slug: string;
title: string;
description: string;
date: string;
tags: string[];
readTime?: string;
image?: string;
excerpt?: string;
authorName?: string;
authorImage?: string;
}
// Hero card component for featured blog post on /blog page
// Displays as a large card with image on left, content on right (like Giga.ai/news)
export default function BlogHeroCard({
slug,
title,
description,
date,
tags,
readTime,
image,
excerpt,
authorName,
authorImage,
}: BlogHeroCardProps) {
return (
<Link to={`/${slug}`} className="blog-hero-card">
{/* Hero image on the left */}
{image && (
<div className="blog-hero-image-wrapper">
<img
src={image}
alt={title}
className="blog-hero-image"
loading="eager"
/>
</div>
)}
{/* Content on the right */}
<div className="blog-hero-content">
{/* Tags displayed as labels */}
{tags.length > 0 && (
<div className="blog-hero-tags">
{tags.slice(0, 2).map((tag) => (
<span key={tag} className="blog-hero-tag">
{tag}
</span>
))}
</div>
)}
{/* Date */}
<time className="blog-hero-date">
{format(parseISO(date), "MMM d, yyyy").toUpperCase()}
</time>
{/* Title */}
<h2 className="blog-hero-title">{title}</h2>
{/* Description or excerpt */}
<p className="blog-hero-excerpt">{excerpt || description}</p>
{/* Author info and read more */}
<div className="blog-hero-footer">
{authorName && (
<div className="blog-hero-author">
{authorImage && (
<img
src={authorImage}
alt={authorName}
className="blog-hero-author-image"
/>
)}
<span className="blog-hero-author-name">{authorName}</span>
</div>
)}
{readTime && <span className="blog-hero-read-time">{readTime}</span>}
<span className="blog-hero-read-more">Read more</span>
</div>
</div>
</Link>
);
}

View File

@@ -221,10 +221,12 @@ export default function Layout({ children }: LayoutProps) {
</nav>
</MobileMenu>
{/* Use wider layout for stats page, normal layout for other pages */}
{/* Use wider layout for stats and blog pages, normal layout for other pages */}
<main
className={
location.pathname === "/stats" ? "main-content-wide" : "main-content"
location.pathname === "/stats" || location.pathname === "/blog"
? "main-content-wide"
: "main-content"
}
>
{children}

View File

@@ -16,6 +16,8 @@ interface Post {
interface PostListProps {
posts: Post[];
viewMode?: "list" | "cards";
columns?: 2 | 3; // Number of columns for card view (default: 3)
showExcerpts?: boolean; // Show excerpts in card view (default: true)
}
// Group posts by year
@@ -33,7 +35,12 @@ function groupByYear(posts: Post[]): Record<string, Post[]> {
);
}
export default function PostList({ posts, viewMode = "list" }: PostListProps) {
export default function PostList({
posts,
viewMode = "list",
columns = 3,
showExcerpts = true,
}: PostListProps) {
// Sort posts by date descending
const sortedPosts = [...posts].sort(
(a, b) => new Date(b.date).getTime() - new Date(a.date).getTime()
@@ -41,8 +48,11 @@ export default function PostList({ posts, viewMode = "list" }: PostListProps) {
// Card view: render all posts in a grid
if (viewMode === "cards") {
// Apply column class for 2 or 3 columns
const cardGridClass =
columns === 2 ? "post-cards post-cards-2col" : "post-cards";
return (
<div className="post-cards">
<div className={cardGridClass}>
{sortedPosts.map((post) => (
<Link key={post._id} to={`/${post.slug}`} className="post-card">
{/* Thumbnail image displayed as square using object-fit: cover */}
@@ -58,7 +68,8 @@ export default function PostList({ posts, viewMode = "list" }: PostListProps) {
)}
<div className="post-card-content">
<h3 className="post-card-title">{post.title}</h3>
{(post.excerpt || post.description) && (
{/* Only show excerpt if showExcerpts is true */}
{showExcerpts && (post.excerpt || post.description) && (
<p className="post-card-excerpt">
{post.excerpt || post.description}
</p>