Added Article navigation
All checks were successful
Build And Publish / BuildAndPublish (push) Successful in 2m45s

This commit is contained in:
Ryan Freeman 2024-10-01 22:00:02 +01:00 committed by r-freeman
parent 9bd9188921
commit 5e8da79a82
5 changed files with 106 additions and 8 deletions

View File

@ -0,0 +1,7 @@
import {getAllArticles} from '@/lib/getAllArticles'
export async function GET(request: Request) {
const articles = await getAllArticles(false)
return new Response(JSON.stringify(articles), {status: 200})
}

View File

@ -5,6 +5,7 @@ import {Prose} from '@/components/ui/Prose'
import {Views} from '@/components/ui/Views' import {Views} from '@/components/ui/Views'
import {ArrowDownIcon} from '@/components/icons/ArrowDownIcon' import {ArrowDownIcon} from '@/components/icons/ArrowDownIcon'
import {formatDate} from '@/lib/formatDate' import {formatDate} from '@/lib/formatDate'
import ArticleNav from '@/components/ui/ArticleNav'
type ArticleLayout = { type ArticleLayout = {
title: string title: string
@ -12,7 +13,6 @@ type ArticleLayout = {
description: string description: string
slug: string slug: string
children?: ReactNode children?: ReactNode
ogImage?: string
} }
const gradients = [ const gradients = [
@ -26,10 +26,8 @@ const gradients = [
export function ArticleLayout({ export function ArticleLayout({
title, title,
date, date,
description,
slug, slug,
children, children,
ogImage
}: ArticleLayout) { }: ArticleLayout) {
return ( return (
@ -60,6 +58,7 @@ export function ArticleLayout({
</header> </header>
<Prose className="mt-8" data-mdx-content>{children}</Prose> <Prose className="mt-8" data-mdx-content>{children}</Prose>
</article> </article>
<ArticleNav slug={slug}/>
</div> </div>
</div> </div>
</Container> </Container>

View File

@ -0,0 +1,89 @@
'use client'
import React, {ReactElement, useEffect, useState} from 'react'
import fetcher from '@/lib/fetcher'
import useSWR from 'swr'
import {Card} from '@/components/ui/Card'
import clsx from 'clsx'
type Article = {
slug: string
authors: string
title: string
date: string
description: string
}
type FetchArticlesResponse = {
data: Article[]
error: string
isLoading: boolean
}
type ArticleNavProps = {
slug: string
}
type PaginationProps = {
next: Article | null
prev: Article | null
}
function useFetchArticles() {
const {data, error, isLoading} = useSWR(`/api/articles/`, fetcher) as FetchArticlesResponse
return {
articles: data,
isLoading,
isError: error
}
}
export default function ArticleNav({slug}: ArticleNavProps): ReactElement | null {
const {articles, isLoading, isError} = useFetchArticles()
const [{next, prev}, setPagination] = useState<PaginationProps>({next: null, prev: null})
useEffect(() => {
const findAdjacentArticles = (articles: Article[], slug: string) => {
if (articles) {
const index = articles.findIndex(article => article.slug === slug)
const next = index < articles.length - 1 ? articles[index + 1] : null
const prev = index > 0 ? articles[index - 1] : null
setPagination({next, prev})
}
}
findAdjacentArticles(articles, slug)
}, [articles, slug])
if (isError) return null
return (
<section className="mt-24">
<ul
role="list"
className={clsx('grid grid-cols-1 gap-x-12 gap-y-16',
(prev !== null && next !== null) ? 'sm:grid-cols-2' : '')}
>
{prev !== null &&
<Card as="li" key={prev.slug}>
<h2 className="text-base font-semibold transition group-hover:text-indigo-500 text-zinc-800 dark:text-zinc-100">
<Card.Link href={`/writing/${prev.slug}`}
ariaLabel={`Previous article: ${prev.title}`}>{prev.title}</Card.Link>
</h2>
</Card>
}
{next !== null &&
<Card as="li" key={next.slug}>
<h2 className="text-base font-semibold transition group-hover:text-indigo-500 text-zinc-800 dark:text-zinc-100">
<Card.Link href={`/writing/${next.slug}`}
ariaLabel={`Next article: ${next.title}`}>{next.title}</Card.Link>
</h2>
</Card>
}
</ul>
</section>
)
}

View File

@ -13,6 +13,7 @@ type Card = {
type CardLink = { type CardLink = {
href: string href: string
ariaLabel?: string
children: ReactNode children: ReactNode
} }
@ -74,12 +75,12 @@ export function Card({
) )
} }
Card.Link = function CardLink({href, children}: CardLink) { Card.Link = function CardLink({href, children, ariaLabel}: CardLink) {
return ( return (
<> <>
<div <div
className="absolute -inset-y-6 -inset-x-4 z-0 scale-95 bg-zinc-50 opacity-0 transition group-hover:scale-100 group-hover:opacity-100 dark:bg-zinc-800/50 sm:-inset-x-6 sm:rounded-2xl"/> className="absolute -inset-y-6 -inset-x-4 z-0 scale-95 bg-zinc-50 opacity-0 transition group-hover:scale-100 group-hover:opacity-100 dark:bg-zinc-800/50 sm:-inset-x-6 sm:rounded-2xl"/>
<Link href={href}> <Link href={href} aria-label={ariaLabel}>
<span className="absolute -inset-y-6 -inset-x-4 z-20 sm:-inset-x-6 sm:rounded-2xl"/> <span className="absolute -inset-y-6 -inset-x-4 z-20 sm:-inset-x-6 sm:rounded-2xl"/>
<span className="relative z-10">{children}</span> <span className="relative z-10">{children}</span>
</Link> </Link>
@ -89,7 +90,8 @@ Card.Link = function CardLink({href, children}: CardLink) {
Card.Title = function CardTitle({as: Component = 'h2', href, children}: CardTitle) { Card.Title = function CardTitle({as: Component = 'h2', href, children}: CardTitle) {
return ( return (
<Component className="group-hover:text-indigo-500 text-base font-semibold tracking-tight text-zinc-800 dark:text-zinc-100"> <Component
className="group-hover:text-indigo-500 text-base font-semibold tracking-tight text-zinc-800 dark:text-zinc-100">
{href ? <Card.Link href={href}>{children}</Card.Link> : children} {href ? <Card.Link href={href}>{children}</Card.Link> : children}
</Component> </Component>
) )

View File

@ -12,12 +12,13 @@ async function importArticle(articleFilename: string) {
} }
} }
export async function getAllArticles() { export async function getAllArticles(dateDesc = true) {
let articleFilenames = await glob(['*.mdx', '*/page.mdx'], { let articleFilenames = await glob(['*.mdx', '*/page.mdx'], {
cwd: path.join(process.cwd(), './app/writing'), cwd: path.join(process.cwd(), './app/writing'),
}) })
let articles = await Promise.all(articleFilenames.map(importArticle)) let articles = await Promise.all(articleFilenames.map(importArticle))
return articles.sort((a, z) => a.date < z.date ? 1 : -1) return dateDesc ? articles.sort((a, z) => a.date < z.date ? 1 : -1)
: articles.sort((a, z) => a.date > z.date ? 1 : -1)
} }