feat(blog): add pagination
This commit is contained in:
parent
5d8d7f552c
commit
e3c5b3f31f
117
src/components/ui/pagination.tsx
Normal file
117
src/components/ui/pagination.tsx
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
import * as React from "react"
|
||||||
|
import { ChevronLeft, ChevronRight, MoreHorizontal } from "lucide-react"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
import { ButtonProps, buttonVariants } from "@/components/ui/button"
|
||||||
|
|
||||||
|
const Pagination = ({ className, ...props }: React.ComponentProps<"nav">) => (
|
||||||
|
<nav
|
||||||
|
role="navigation"
|
||||||
|
aria-label="pagination"
|
||||||
|
className={cn("mx-auto flex w-full justify-center", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
Pagination.displayName = "Pagination"
|
||||||
|
|
||||||
|
const PaginationContent = React.forwardRef<
|
||||||
|
HTMLUListElement,
|
||||||
|
React.ComponentProps<"ul">
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<ul
|
||||||
|
ref={ref}
|
||||||
|
className={cn("flex flex-row items-center gap-1", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
PaginationContent.displayName = "PaginationContent"
|
||||||
|
|
||||||
|
const PaginationItem = React.forwardRef<
|
||||||
|
HTMLLIElement,
|
||||||
|
React.ComponentProps<"li">
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<li ref={ref} className={cn("", className)} {...props} />
|
||||||
|
))
|
||||||
|
PaginationItem.displayName = "PaginationItem"
|
||||||
|
|
||||||
|
type PaginationLinkProps = {
|
||||||
|
isActive?: boolean
|
||||||
|
} & Pick<ButtonProps, "size"> &
|
||||||
|
React.ComponentProps<"a">
|
||||||
|
|
||||||
|
const PaginationLink = ({
|
||||||
|
className,
|
||||||
|
isActive,
|
||||||
|
size = "icon",
|
||||||
|
...props
|
||||||
|
}: PaginationLinkProps) => (
|
||||||
|
<a
|
||||||
|
aria-current={isActive ? "page" : undefined}
|
||||||
|
className={cn(
|
||||||
|
buttonVariants({
|
||||||
|
variant: isActive ? "outline" : "ghost",
|
||||||
|
size,
|
||||||
|
}),
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
PaginationLink.displayName = "PaginationLink"
|
||||||
|
|
||||||
|
const PaginationPrevious = ({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof PaginationLink>) => (
|
||||||
|
<PaginationLink
|
||||||
|
aria-label="Go to previous page"
|
||||||
|
size="default"
|
||||||
|
className={cn("gap-1 pl-2.5", className)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<ChevronLeft className="h-4 w-4" />
|
||||||
|
<span>Previous</span>
|
||||||
|
</PaginationLink>
|
||||||
|
)
|
||||||
|
PaginationPrevious.displayName = "PaginationPrevious"
|
||||||
|
|
||||||
|
const PaginationNext = ({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof PaginationLink>) => (
|
||||||
|
<PaginationLink
|
||||||
|
aria-label="Go to next page"
|
||||||
|
size="default"
|
||||||
|
className={cn("gap-1 pr-2.5", className)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<span>Next</span>
|
||||||
|
<ChevronRight className="h-4 w-4" />
|
||||||
|
</PaginationLink>
|
||||||
|
)
|
||||||
|
PaginationNext.displayName = "PaginationNext"
|
||||||
|
|
||||||
|
const PaginationEllipsis = ({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<"span">) => (
|
||||||
|
<span
|
||||||
|
aria-hidden
|
||||||
|
className={cn("flex h-9 w-9 items-center justify-center", className)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<MoreHorizontal className="h-4 w-4" />
|
||||||
|
<span className="sr-only">More pages</span>
|
||||||
|
</span>
|
||||||
|
)
|
||||||
|
PaginationEllipsis.displayName = "PaginationEllipsis"
|
||||||
|
|
||||||
|
export {
|
||||||
|
Pagination,
|
||||||
|
PaginationContent,
|
||||||
|
PaginationEllipsis,
|
||||||
|
PaginationItem,
|
||||||
|
PaginationLink,
|
||||||
|
PaginationNext,
|
||||||
|
PaginationPrevious,
|
||||||
|
}
|
||||||
@ -1,39 +0,0 @@
|
|||||||
---
|
|
||||||
import { getCollection } from 'astro:content';
|
|
||||||
import BaseLayout from '@/layouts/BaseLayout.astro';
|
|
||||||
import Heading from '@/components/ui/Heading.astro';
|
|
||||||
const allBlogposts = await getCollection('blog');
|
|
||||||
---
|
|
||||||
|
|
||||||
<BaseLayout
|
|
||||||
title="Blog | Astro Deploy"
|
|
||||||
description="Explore insightful articles on our Blog: Dive into a world of knowledge through posts crafted with expertise. Discover, learn, and grow with us.">
|
|
||||||
<div class="container mx-auto px-4 py-16 sm:py-24 lg:px-8 lg:py-32 xl:max-w-7xl">
|
|
||||||
<div class="mb-12 text-center text-zinc-900 dark:text-zinc-200">
|
|
||||||
<Heading level={1}>Blog</Heading>
|
|
||||||
<p>Here are some blogposts, they are located in the repository as mdx files.</p>
|
|
||||||
</div>
|
|
||||||
<hr class="mb-12 dark:border-zinc-700/75" />
|
|
||||||
<div class="grid grid-cols-1 gap-8 md:grid-cols-2">
|
|
||||||
{
|
|
||||||
allBlogposts.map((post) => (
|
|
||||||
<div class="flex flex-col space-y-4">
|
|
||||||
<h2
|
|
||||||
transition:name={post.data.title}
|
|
||||||
class="text-lg font-semibold text-zinc-800 hover:text-zinc-600 dark:text-zinc-200 dark:hover:text-zinc-400 sm:text-xl">
|
|
||||||
<a href={`/blog/${post.slug}/`}>{post.data.title}</a>
|
|
||||||
</h2>
|
|
||||||
<p class="text-sm leading-relaxed text-zinc-600 dark:text-zinc-400">
|
|
||||||
{post.data.description}
|
|
||||||
</p>
|
|
||||||
<a
|
|
||||||
class="text-sm font-medium text-primary-600 hover:text-primary-400 dark:text-primary-400 dark:hover:text-primary-300"
|
|
||||||
href={`/blog/${post.slug}/`}>
|
|
||||||
Read more
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
))
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</BaseLayout>
|
|
||||||
107
src/pages/blog/[page].astro
Normal file
107
src/pages/blog/[page].astro
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
---
|
||||||
|
import { getCollection } from 'astro:content';
|
||||||
|
import BaseLayout from '@/layouts/BaseLayout.astro';
|
||||||
|
import Heading from '@/components/ui/Heading.astro';
|
||||||
|
import {
|
||||||
|
Pagination,
|
||||||
|
PaginationContent,
|
||||||
|
PaginationItem,
|
||||||
|
PaginationLink,
|
||||||
|
PaginationNext,
|
||||||
|
PaginationPrevious,
|
||||||
|
} from '@/components/ui/pagination';
|
||||||
|
|
||||||
|
interface BlogPost {
|
||||||
|
data: {
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
};
|
||||||
|
slug: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getStaticPaths({ paginate }) {
|
||||||
|
const allBlogposts = await getCollection('blog');
|
||||||
|
const postsPerPage = 4;
|
||||||
|
|
||||||
|
return paginate(allBlogposts, {
|
||||||
|
pageSize: postsPerPage,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const {
|
||||||
|
page,
|
||||||
|
}: {
|
||||||
|
page: {
|
||||||
|
data: BlogPost[];
|
||||||
|
url: { prev: string; current: string; next: string };
|
||||||
|
currentPage: number;
|
||||||
|
lastPage: number;
|
||||||
|
};
|
||||||
|
} = Astro.props;
|
||||||
|
---
|
||||||
|
|
||||||
|
<BaseLayout
|
||||||
|
title="Blog | Astro Deploy"
|
||||||
|
description="Explore insightful articles on our Blog: Dive into a world of knowledge through posts crafted with expertise. Discover, learn, and grow with us.">
|
||||||
|
<div class="container mx-auto px-4 py-16 sm:py-24 lg:px-8 lg:py-32 xl:max-w-7xl">
|
||||||
|
<div class="mb-12 text-center text-zinc-900 dark:text-zinc-200">
|
||||||
|
<Heading level={1}>Blog</Heading>
|
||||||
|
<p>Here are some blogposts, they are located in the repository as mdx files.</p>
|
||||||
|
</div>
|
||||||
|
<hr class="mb-12 dark:border-zinc-700/75" />
|
||||||
|
<div class="grid grid-cols-1 gap-8 md:grid-cols-2">
|
||||||
|
{
|
||||||
|
page.data.map((post) => (
|
||||||
|
<div class="flex flex-col space-y-4">
|
||||||
|
<h2
|
||||||
|
transition:name={post.data.title}
|
||||||
|
class="text-lg font-semibold text-zinc-800 hover:text-zinc-600 dark:text-zinc-200 dark:hover:text-zinc-400 sm:text-xl">
|
||||||
|
<a href={`/blog/${post.slug}/`}>{post.data.title}</a>
|
||||||
|
</h2>
|
||||||
|
<p class="text-sm leading-relaxed text-zinc-600 dark:text-zinc-400">
|
||||||
|
{post.data.description}
|
||||||
|
</p>
|
||||||
|
<a
|
||||||
|
class="text-sm font-medium text-primary-600 hover:text-primary-400 dark:text-primary-400 dark:hover:text-primary-300"
|
||||||
|
href={`/blog/${post.slug}/`}>
|
||||||
|
Read more
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
<div class="mt-12 text-black dark:text-white">
|
||||||
|
<Pagination>
|
||||||
|
<PaginationContent>
|
||||||
|
{
|
||||||
|
page.currentPage > 1 && (
|
||||||
|
<>
|
||||||
|
<PaginationItem>
|
||||||
|
<PaginationPrevious href={page.url.prev} />
|
||||||
|
</PaginationItem>
|
||||||
|
<PaginationItem>
|
||||||
|
<PaginationLink href="/blog/">1</PaginationLink>
|
||||||
|
</PaginationItem>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
<PaginationItem>
|
||||||
|
<PaginationLink href={page.url.current} isActive>{page.currentPage} </PaginationLink>
|
||||||
|
</PaginationItem>
|
||||||
|
{
|
||||||
|
page.currentPage < page.lastPage && (
|
||||||
|
<>
|
||||||
|
<PaginationItem>
|
||||||
|
<PaginationLink href={`/blog/${page.lastPage}`}>{page.lastPage}</PaginationLink>
|
||||||
|
</PaginationItem>
|
||||||
|
<PaginationItem>
|
||||||
|
<PaginationNext href={page.url.next} />
|
||||||
|
</PaginationItem>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</PaginationContent>
|
||||||
|
</Pagination>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</BaseLayout>
|
||||||
3
src/pages/blog/index.astro
Normal file
3
src/pages/blog/index.astro
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
---
|
||||||
|
return Astro.redirect('/blog/1');
|
||||||
|
---
|
||||||
Loading…
x
Reference in New Issue
Block a user