feat(Components): Add pagination component

This component accept a Page<T> type from [astro](https://docs.astro.build/en/guides/routing/#complete-api-reference)
This commit is contained in:
Alexander Navarro 2024-03-19 20:43:04 -03:00
parent 642e15656e
commit 944b553e73
9 changed files with 258 additions and 83 deletions

View file

@ -8,4 +8,7 @@ export default defineConfig({
prefetch: true,
integrations: [react(), astroI18next()],
experimental: {},
redirects: {
'/projects': '/projects/1',
},
});

View file

@ -50,6 +50,9 @@ $border-radius: 0.5rem;
--prj-primary: #{getColor('teal')};
--prj-primary-text: #{getColor('base')};
--prj-secondary: #{getColor('mauve')};
--prj-secondary-text: #{getColor('base')};
--prj-danger: #{getColor('red')};
--prj-danger-text: #{getColor('base')};

View file

@ -159,7 +159,7 @@ a {
transition: text-shadow 0.2s;
--anim-shadow-color: var(--prj-accent-bg);
&:hover {
&:not(.clean):hover {
text-shadow: 1px 1px 8px var(--anim-shadow-color);
}
}

View file

@ -1,24 +1,36 @@
---
interface Props {
className?: string;
href?: string;
}
const { className = '' } = Astro.props;
const { className = '', href } = Astro.props;
---
<button class:list={className}>
<slot />
</button>
{
href !== undefined ? (
<a href={href} class:list={['clean', 'btn', className]}>
<slot />
</a>
) : (
<button class:list={className}>
<slot />
</button>
)
}
<style lang="scss">
button {
button,
.btn {
display: inline-block;
text-decoration: none;
font-size: 1rem;
padding: var(--prj-spacing-1) var(--prj-spacing-3);
background-color: var(--prj-accent-bg);
color: var(--prj-accent-text);
border-radius: 6px;
border-color: var(--prj-accent-bg);
border: 1px solid var(--prj-accent-bg);
cursor: pointer;

View file

@ -52,25 +52,25 @@ const links = [
</div>
<script>
const normilizeUrl = (url: string): string => {
let newUrl = `${url}`;
if (newUrl.endsWith('/')) {
newUrl = newUrl.slice(0, -1);
}
return newUrl;
};
const setActiveLink = () => {
const links =
document.querySelectorAll<HTMLAnchorElement>(`#main-navbar a`);
links.forEach((link) =>
normilizeUrl(link.pathname) === normilizeUrl(location.pathname)
links.forEach((link) => {
if (link.pathname === '/' && location.pathname === '/') {
link.classList.add('active');
return;
}
if (link.pathname === '/' && location.pathname !== '/') {
link.classList.remove('active');
return;
}
location.pathname.startsWith(link.pathname)
? link.classList.add('active')
: link.classList.remove('active'),
);
: link.classList.remove('active');
});
};
// Add active class to the current link
document.addEventListener('astro:page-load', setActiveLink, { once: true });

View file

@ -0,0 +1,133 @@
---
import { type Page } from 'astro';
interface Props {
page: Page;
paginationOffset?: number;
urlPattern: string;
}
const { page, urlPattern, paginationOffset = 3 } = Astro.props;
const pages = [];
const lowerEnd = Math.max(page.currentPage - paginationOffset, 1);
const highEnd = Math.min(page.currentPage + paginationOffset, page.lastPage);
const generateUrl = (index: number) => {
return urlPattern.replace('{}', index.toString());
};
for (let index = lowerEnd; index <= highEnd; index++) {
pages.push({
index,
url: generateUrl(index),
});
}
---
<nav role="navigation" aria-label="Pagination" class="w-100 my-4">
<ul class="list-unstyle hstack justify-content-center">
{
page.url.prev !== undefined && (
<li>
<a
href={page.url.prev}
class="prev-page"
aria-label="Go to previous page"
>
« Prev
</a>
</li>
)
}{
lowerEnd !== 1 && (
<>
<li>
<a
href={generateUrl(1)}
class="prev-page"
aria-label={`Go to page 1`}
>
1
</a>
</li>
<li>
<span class="start-ellipsis">…</span>
</li>
</>
)
}
{
pages.map((item) => (
<li>
<a
class:list={[{ current: item.index === page.currentPage }]}
href={item.url}
aria-label={`Go to page ${item.index}`}
>
{item.index}
</a>
</li>
))
}
{
highEnd !== page.lastPage && (
<>
<li>
<span class="start-ellipsis">…</span>
</li>
<li>
<a
href={generateUrl(page.lastPage)}
class="next-page"
aria-label={`Go to page ${page.lastPage}`}
>
{page.lastPage}
</a>
</li>
</>
)
}
{
page.url.next !== undefined && (
<li>
<a
href={page.url.next}
class="next-page"
aria-label="Go to next page"
>
Next »
</a>
</li>
)
}
</ul>
</nav>
<style lang="scss">
li {
margin-bottom: 0;
}
a {
border: 1px solid var(--prj-link-text);
padding: var(--prj-spacing-1) var(--prj-spacing-2);
border-radius: var(--prj-border-radius);
text-decoration: none;
transition: background-color 400ms, color 400ms;
&.current {
background-color: var(--prj-secondary);
border: 1px solid var(--prj-secondary);
color: var(--prj-secondary-text);
}
&:hover {
background-color: var(--prj-link-text);
border: 1px solid var(--prj-link-text);
color: var(--prj-accent-text);
text-shadow: none;
}
}
</style>

View file

@ -1,17 +1,21 @@
---
import { getCollection } from "astro:content";
import { t, changeLanguage } from "i18next";
import Layout from "../layouts/Layout.astro";
import Card from "../components/Card.astro";
import Button from "../components/Button/Button.astro";
import { Image } from "astro:assets";
import portrait from "../assets/images/portrait.jpg";
import { getCollection } from 'astro:content';
import { t, changeLanguage } from 'i18next';
import Layout from '../layouts/Layout.astro';
import Card from '../components/Card.astro';
import Button from '../components/Button/Button.astro';
import { Image } from 'astro:assets';
import portrait from '../assets/images/portrait.jpg';
changeLanguage("en");
changeLanguage('en');
const blog = await getCollection("blog", ({ data }) => import.meta.env.PROD ? data.draft !== true : true);
const blog = await getCollection('blog', ({ data }) =>
import.meta.env.PROD ? data.draft !== true : true,
);
// TODO: show the pinned ones, not the recents
const portafolio = await getCollection("portafolio", ({ data }) => import.meta.env.PROD ? data.draft !== true : true);
const portafolio = await getCollection('portafolio', ({ data }) =>
import.meta.env.PROD ? data.draft !== true : true,
);
---
<Layout title="aleidk">
@ -49,8 +53,8 @@ const portafolio = await getCollection("portafolio", ({ data }) => import.meta.e
/>
<h3 class="fs-4 text-center my-1">Project N°1</h3>
<p class="text-justify">
cillum sint consectetur cupidatat.
Lorem ipsum dolor sit amet, qui minim labore adipisicing minim sint
cillum sint consectetur cupidatat. Lorem ipsum dolor sit amet, qui
minim labore adipisicing minim sint
</p>
<div class="text-end">
@ -94,7 +98,7 @@ const portafolio = await getCollection("portafolio", ({ data }) => import.meta.e
</div>
<div class="mt-4 text-center">
<Button className="px-4 py-2 fs-5 ">View Work</Button>
<Button className="px-4 py-2 fs-5" href="/projects">View Work</Button>
</div>
</section>

View file

@ -0,0 +1,69 @@
---
import type { InferGetStaticPropsType, GetStaticPaths } from 'astro';
import { changeLanguage } from 'i18next';
import { getCollection } from 'astro:content';
import Layout from '@layouts/Layout.astro';
import Card from '@components/Card.astro';
import Pagination from '@components/Pagination.astro';
changeLanguage('en');
export const getStaticPaths = (async ({ paginate }) => {
const rawEntries = await getCollection('portafolio', ({ data }) => {
return import.meta.env.PROD ? data.draft !== true : true;
});
const entries = rawEntries.map((item, idx) => ({
...item.data,
id: idx + 1,
slug: item.slug,
}));
return paginate(entries, { pageSize: 6 });
}) satisfies GetStaticPaths;
type Props = InferGetStaticPropsType<typeof getStaticPaths>;
const { page } = Astro.props;
---
<Layout title="List of blog entries">
<h1 class="text-center">Projects</h1>
<section class="clean">
<div class="grid grid-cols-1 grid-lg-cols-3 gap-4">
{
page.data.map((item) => (
<div>
<Card className="anim-hover-zoom">
<a class="clean" href={`/projects/${item.slug}`}>
<img
src="https://placehold.co/600x400"
alt="project img"
class="border-radius respect-width"
slot="img-header"
/>
<h3 class="fs-4 text-center my-1">Project N°1</h3>
<p class="text-justify">
cillum sint consectetur cupidatat. Lorem ipsum dolor sit amet,
qui minim labore adipisicing minim sint
</p>
<div class="text-end">
<a href={`/projects/${item.slug}`}>See more...</a>
</div>
</a>
</Card>
</div>
))
}
</div>
<Pagination page={page} urlPattern="/projects/{}" />
</section>
</Layout>
<style lang="scss">
a.clean:hover {
text-decoration: none;
}
</style>

View file

@ -1,49 +0,0 @@
---
import { changeLanguage } from 'i18next';
import { getCollection } from 'astro:content';
import Layout from '@layouts/Layout.astro';
import Card from '@components/Card.astro';
changeLanguage('en');
const rawEntries = await getCollection('portafolio', ({ data }) => {
return import.meta.env.PROD ? data.draft !== true : true;
});
const entries = rawEntries.map((item, idx) => ({
...item.data,
id: idx + 1,
slug: item.slug,
}));
console.log(entries.at(0));
---
<Layout title="List of blog entries">
<h1 class="text-center">Projects</h1>
<section class="clean grid grid-cols-1 grid-lg-cols-3">
{
entries.map((item) => (
<div>
<Card className="anim-hover-zoom">
<img
src="https://placehold.co/600x400"
alt="project img"
class="border-radius respect-width"
slot="img-header"
/>
<h3 class="fs-4 text-center my-1">Project N°1</h3>
<p class="text-justify">
cillum sint consectetur cupidatat. Lorem ipsum dolor sit amet, qui
minim labore adipisicing minim sint
</p>
<div class="text-end">
<a href={`/projects/${item.slug}`}>See more...</a>
</div>
</Card>
</div>
))
}
</section>
</Layout>