Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(475): Redesign Landing Page for the Blog #488

Merged
merged 38 commits into from
Oct 4, 2024
Merged
Show file tree
Hide file tree
Changes from 32 commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
5f4e1bb
feat(475): Redesign Landing Page for the Blog
neo773 Sep 9, 2024
5efbc75
Merge branch 'develop' into home-blog-redesign
neo773 Sep 9, 2024
deb85b4
fix: changes
neo773 Sep 9, 2024
730b0ea
Merge branch 'develop' into home-blog-redesign
neo773 Sep 9, 2024
88ffe95
chore: cleanup code
neo773 Sep 9, 2024
fdf47c6
Merge branch 'develop' into home-blog-redesign
neo773 Sep 10, 2024
df247cc
Merge branch 'develop' into home-blog-redesign
neo773 Sep 12, 2024
881f42a
Merge branch 'develop' into home-blog-redesign
neo773 Sep 15, 2024
4ca42f0
Merge branch 'develop' into home-blog-redesign
neo773 Sep 16, 2024
a7e0c7f
Merge branch 'develop' into home-blog-redesign
neo773 Sep 18, 2024
ecf0599
mobile adjustments
neo773 Sep 22, 2024
877afee
Merge branch 'develop' into home-blog-redesign
neo773 Sep 22, 2024
eb52618
chore: prettier
neo773 Sep 22, 2024
7025b6f
feat(475): Redesign Landing Page for the Blog
neo773 Sep 9, 2024
09787d4
fix: changes
neo773 Sep 9, 2024
5c4c625
chore: cleanup code
neo773 Sep 9, 2024
cd2b145
mobile adjustments
neo773 Sep 22, 2024
5faef62
chore: prettier
neo773 Sep 22, 2024
c111e82
Merge branch 'develop' into home-blog-redesign
neo773 Sep 25, 2024
ffbf85a
Merge branch 'develop' into home-blog-redesign
neo773 Sep 26, 2024
22b4b11
Merge branch 'develop' into home-blog-redesign
neo773 Sep 26, 2024
732d67f
changes
neo773 Sep 26, 2024
d7ba7a8
Merge branch 'home-blog-redesign' of https://github.com/neo773/tailca…
neo773 Sep 26, 2024
37aebdd
Merge branch 'develop' into home-blog-redesign
amitksingh1490 Sep 27, 2024
07dae18
changes
neo773 Sep 27, 2024
2a6fced
Merge branch 'develop' into home-blog-redesign
neo773 Sep 27, 2024
26b1ad8
changes
neo773 Sep 28, 2024
f4d6c30
changes
neo773 Sep 28, 2024
dbc00b8
Merge branch 'develop' into home-blog-redesign
amitksingh1490 Sep 29, 2024
62f2d83
changesx
neo773 Sep 29, 2024
2d08d78
Merge branch 'home-blog-redesign' of https://github.com/neo773/tailca…
neo773 Sep 29, 2024
36f51ce
Merge branch 'develop' into home-blog-redesign
neo773 Sep 29, 2024
33fccdb
changes
neo773 Sep 29, 2024
b913f47
type annotate docusaurus config
neo773 Sep 29, 2024
e7e8f30
Merge branch 'develop' into home-blog-redesign
neo773 Oct 2, 2024
fc10a89
changes
neo773 Oct 3, 2024
1eef1b9
Merge branch 'develop' into home-blog-redesign
neo773 Oct 3, 2024
e24a2ef
fix: minor changes
amitksingh1490 Oct 4, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docusaurus.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,7 @@ export default {
routeBasePath: "blog",
include: ["**/*.{md,mdx}"],
exclude: ["**/_*.{js,jsx,ts,tsx,md,mdx}", "**/_*/**", "**/*.test.{js,jsx,ts,tsx}", "**/__tests__/**"],
postsPerPage: 10,
postsPerPage: 999,
neo773 marked this conversation as resolved.
Show resolved Hide resolved
blogListComponent: "@theme/BlogListPage",
blogPostComponent: "@theme/BlogPostPage",
blogTagsListComponent: "@theme/BlogTagsListPage",
Expand Down
29 changes: 29 additions & 0 deletions src/css/custom.css
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,29 @@
@tailwind components;
@tailwind utilities;

@layer utilities {
.border-b-solid {
border-bottom-style: solid;
}
.border-t-solid {
border-top-style: solid;
}
.border-l-solid {
border-left-style: solid;
}
.border-r-solid {
border-right-style: solid;
}
.border-x-solid {
border-left-style: solid;
border-right-style: solid;
}
.border-y-solid {
border-top-style: solid;
border-bottom-style: solid;
}
}

:root {
/* Font and Dimension Tokens */
--ifm-font-family-base: "Space Grotesk", sans-serif;
Expand Down Expand Up @@ -698,3 +721,9 @@ span.token.tag.script.language-javascript {
height: 100%;
border: 0;
}

@media (min-width: 768px) {
.border-right {
border-right: 1px solid #e7e7e7;
}
}
15 changes: 15 additions & 0 deletions src/theme/BlogAuthor/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import React from "react"

interface AuthorDisplayProps {
author: {
name?: string
imageURL?: string
}
}

export const BlogAuthor: React.FC<AuthorDisplayProps> = ({author}) => (
<div className="mt-4 flex items-center">
<img src={author.imageURL} alt={author.name} className="mr-2 size-6 rounded-full" />
<span className="font-medium text-black">{author.name}</span>
</div>
)
43 changes: 43 additions & 0 deletions src/theme/BlogCategories/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import type {Props} from "@theme/BlogListPage"
import clsx from "clsx"
import React, {useMemo} from "react"

interface BlogCategoriesProps {
items: Props["items"]
onCategoryClick: (category: string) => void
activeCategory: string | null
}

export function BlogCategories({items, onCategoryClick, activeCategory}: BlogCategoriesProps): JSX.Element {
const categories = useMemo(() => {
const categoryCounts: Record<string, number> = {All: items.length}
items.map((item) => {
const category = item.content.metadata.frontMatter.category as string
if (typeof category === "string") {
categoryCounts[category] = (categoryCounts[category] || 0) + 1
}
})
return categoryCounts
}, [items])

return (
<div className="mb-4 flex items-center space-x-4 border-b border-gray-200">
{Object.entries(categories).map(([name, count]) => (
<div
aria-role="button"
aria-label={`${name} (${count})`}
key={name}
onClick={() => onCategoryClick(name === activeCategory ? "All" : name)}
className={clsx(
"text-sm cursor-pointer appearance-none border-none bg-transparent px-1 font-medium",
activeCategory === name
? "font-medium text-gray-900 border-b-solid border-b-2 border-black"
: "text-gray-500 hover:text-gray-700",
)}
>
{name}
</div>
))}
</div>
)
}
29 changes: 29 additions & 0 deletions src/theme/BlogFeaturedPosts/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import React from "react"
import Link from "@docusaurus/Link"
import type {Props} from "@theme/BlogListPage"
import {BlogAuthor} from "../BlogAuthor"

function BlogFeaturedPosts({items}: {items: Props["items"]}): JSX.Element {
return (
<div className="space-y-4">
<h2 className="text-2xl mb-4 font-bold">Featured Posts</h2>
{items.map((post, index) => (
<Link
to={post.content.metadata.permalink}
key={index}
className="flex flex-col gap-4 text-black !no-underline hover:text-black"
>
<div className="flex items-center gap-2">
{post.content.metadata.authors[0] && <BlogAuthor author={post.content.metadata.authors[0]} />}
</div>
<div>
<div className="text-lg font-semibold">{post.content.metadata.title}</div>
<p className="text-sm mt-1 text-tailCall-light-600">{post.content.metadata.description}</p>
</div>
</Link>
))}
</div>
)
}

export default BlogFeaturedPosts
26 changes: 12 additions & 14 deletions src/theme/BlogLayout/index.tsx
Original file line number Diff line number Diff line change
@@ -1,27 +1,25 @@
import React from "react"
import React, {useEffect, useState} from "react"
import clsx from "clsx"
import Layout from "@theme/Layout"
import BlogRecentPosts from "../BlogRecentPosts"
import {isBlogPost} from "@site/src/utils"
import {useLocation} from "@docusaurus/router"
import type {Props} from "@theme/BlogLayout"

export default function BlogLayout(props: Props): JSX.Element {
const {sidebar, toc, children, ...layoutProps} = props
const hasSidebar = sidebar && sidebar.items.length > 0
const [isBlogPostPage, setIsBlogPostPage] = useState(false)
const location = useLocation()

useEffect(() => {
setIsBlogPostPage(isBlogPost())
}, [location.pathname])

return (
<Layout {...layoutProps}>
<div className="container my-8">
<div className="row justify-center">
<main
className={clsx("col", {
"col--7": hasSidebar,
"col--9 col--offset-1": !hasSidebar,
})}
>
{children}
</main>
{toc && <div className="col col--2">{toc}</div>}
</div>
<div className="container mx-auto my-8 flex flex-col items-center">
<div className={clsx("w-full", isBlogPostPage ? "lg:w-7/12" : "md:w-full")}>{children}</div>
{toc && <div className="w-full md:w-3/12">{toc}</div>}
</div>
<BlogRecentPosts sidebar={sidebar} />
</Layout>
Expand Down
77 changes: 77 additions & 0 deletions src/theme/BlogListPage/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import React from "react"
import clsx from "clsx"

import useDocusaurusContext from "@docusaurus/useDocusaurusContext"
import {PageMetadata, HtmlClassNameProvider, ThemeClassNames} from "@docusaurus/theme-common"
import BlogLayout from "@theme/BlogLayout"
import BlogListPaginator from "@theme/BlogListPaginator"
import SearchMetadata from "@theme/SearchMetadata"
import type {Props} from "@theme/BlogListPage"
import BlogListPageStructuredData from "@theme/BlogListPage/StructuredData"
import BlogFeaturedPosts from "../BlogFeaturedPosts"
import BlogPostList from "../BlogPostList"
import {BlogCategories} from "../BlogCategories"
import {useBlogPosts} from "@site/src/utils/hooks/useBlogPosts"
import {FrontMatter} from "@theme/BlogPostPage"

function BlogListPageMetadata(props: Props): JSX.Element {
const {metadata} = props
const {
siteConfig: {title: siteTitle},
} = useDocusaurusContext()
const {blogDescription, blogTitle, permalink} = metadata
const isBlogOnlyMode = permalink === "/"
const title = isBlogOnlyMode ? siteTitle : blogTitle
return (
<>
<PageMetadata title={title} description={blogDescription} />
<SearchMetadata tag="blog_posts_list" />
</>
)
}

function LoadMoreButton({handleLoadMore}: {handleLoadMore: () => void}): JSX.Element {
return (
<div className="flex justify-center">
<button
onClick={handleLoadMore}
className="mt-4 h-12 cursor-pointer rounded-lg border-2 border-solid border-tailCall-border-dark-100 bg-transparent px-4 py-2 font-space-grotesk text-title-tiny font-bold text-black"
>
Load more blogs
</button>
</div>
)
}

function BlogListPageContent({metadata, items, sidebar}: Props): JSX.Element {
const {activeCategory, visibleItems, filteredItems, handleCategoryClick, handleLoadMore} = useBlogPosts(items)
const featuredItems = items.filter((post) => (post.content.frontMatter as FrontMatter & {featured: boolean}).featured)

return (
<BlogLayout sidebar={sidebar}>
<div className="flex flex-col md:flex-row items-start w-full">
<div className={clsx("w-full md:w-9/12 md:pr-6 border-right", featuredItems.length == 0 ? "md:w-full" : "")}>
<BlogCategories items={items} onCategoryClick={handleCategoryClick} activeCategory={activeCategory} />
<BlogPostList items={filteredItems.slice(0, visibleItems)} />
{visibleItems < filteredItems.length && <LoadMoreButton handleLoadMore={handleLoadMore} />}
<BlogListPaginator metadata={metadata} />
</div>
{featuredItems.length > 0 ? (
<div className="w-full md:w-3/12 hidden md:block md:pl-6">
<BlogFeaturedPosts items={featuredItems} />
</div>
) : null}
</div>
</BlogLayout>
)
}

export default function BlogListPage(props: Props): JSX.Element {
return (
<HtmlClassNameProvider className={clsx(ThemeClassNames.wrapper.blogPages, ThemeClassNames.page.blogListPage)}>
<BlogListPageMetadata {...props} />
<BlogListPageStructuredData {...props} />
<BlogListPageContent {...props} />
</HtmlClassNameProvider>
)
}
10 changes: 3 additions & 7 deletions src/theme/BlogPostItem/Header/Back/index.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,12 @@
import Link from "@docusaurus/Link"
import {ArrowLeft} from "lucide-react"
import React from "react"

export default function BlogBackButton(): JSX.Element {
return (
<div
className="flex items-center gap-2 my-8 cursor-pointer"
onClick={() => {
window.history.back()
}}
>
<Link to="/blog" className="flex items-center gap-2 my-8 cursor-pointer !no-underline" onClick={() => {}}>
<ArrowLeft size={24} color="black" />
<span className="text-content-small text-tailCall-light-600">Back to Blogs</span>
</div>
</Link>
)
}
69 changes: 69 additions & 0 deletions src/theme/BlogPostList/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import React from "react"
import Link from "@docusaurus/Link"
import type {Props} from "@theme/BlogListPage"
import {BlogAuthor} from "../BlogAuthor"
import clsx from "clsx"

const FeaturedPost = ({item}: {item: Props["items"][0]}) => (
<Link
to={item.content.metadata.permalink}
className="md:col-span-2 lg:col-span-3 flex flex-col overflow-hidden !text-black !no-underline"
>
<img
src={item.content.metadata.frontMatter.image}
alt={item.content.metadata.title}
className="w-full object-cover aspect-video"
/>
<PostContent item={item} isFeatured />
</Link>
)

const RegularPost = ({item}: {item: Props["items"][0]}) => (
<Link
to={item.content.metadata.permalink}
className="p-4 md:p-0 md:my-2 flex flex-col overflow-hidden border border-solid border-tailCall-light-400 md:border-none !text-black !no-underline rounded-xl md:rounded-none"
>
<img
src={item.content.metadata.frontMatter.image}
alt={item.content.metadata.title}
className="w-full object-cover aspect-video hidden md:block"
/>
<PostContent item={item} />
</Link>
)

const PostContent = ({item, isFeatured = false}: {item: Props["items"][0]; isFeatured?: boolean}) => (
<div className={clsx("flex flex-grow flex-col justify-between", {"md:mt-5": !isFeatured})}>
<div>
<span className="text-[12px] text-black">
{new Date(item.content.metadata.date).toLocaleDateString("en-US", {
month: "long",
day: "numeric",
year: "numeric",
})}
</span>
<h2 className={clsx("mb-2 line-clamp-2 font-bold", isFeatured ? "text-3xl" : "text-xl")}>
{item.content.metadata.title}
</h2>
<span className="text-sm line-clamp-2 text-tailCall-light-600">
{item.content.metadata.frontMatter.description}
</span>
{item.content.metadata.authors[0] && <BlogAuthor author={item.content.metadata.authors[0]} />}
</div>
</div>
)

function BlogPostList({items}: {items: Props["items"]}): JSX.Element {
const [featuredPost, ...regularPosts] = items

return (
<div className="grid grid-cols-1 gap-4 md:gap-8 md:grid-cols-2 lg:grid-cols-3">
<FeaturedPost item={featuredPost} />
{regularPosts.map((item) => (
<RegularPost key={item.content.metadata.permalink} item={item} />
))}
</div>
)
}

export default BlogPostList
9 changes: 2 additions & 7 deletions src/theme/BlogRecentPosts/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,14 @@ import React, {useEffect} from "react"
import Link from "@docusaurus/Link"
import {useLocation} from "@docusaurus/router"
import type {Props} from "@theme/BlogLayout"
import {isBlogPost} from "@site/src/utils"

export default function BlogRecentPosts({sidebar}: {sidebar: Props["sidebar"]}): JSX.Element {
const [isBlogPostPage, setIsBlogPostPage] = React.useState(false)
const location = useLocation()

useEffect(() => {
const url = new URL(location.pathname, window.location.origin)
const pathSegments = url.pathname.split("/").filter(Boolean)

// Check if it's a blog post: starts with 'blog', has more segments, and isn't a pagination page
const isBlogPost = pathSegments[0] === "blog" && pathSegments.length > 1 && pathSegments[1] !== "page"

setIsBlogPostPage(isBlogPost)
setIsBlogPostPage(isBlogPost())
}, [location.pathname])

return isBlogPostPage ? (
Expand Down
Loading
Loading