Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
0ec3280
feat: add tags to project metadata #69
Oct 11, 2025
2a6ad06
Merge branch 'main' into feature/tag-badges
Oct 11, 2025
560a8c5
feat: update project tags and add sorting
Oct 11, 2025
0764d44
fix: improve pagination logic for portfolio and tag pages
Oct 11, 2025
b0dfbed
fix: enhance pagination display logic for blog pages
Oct 11, 2025
8b9c380
chore: update copyright year in LICENSE file to reflect current owner…
markteekman Oct 11, 2025
01d66ef
refactor: reorder integrations in astro.config.mjs for improved clarity
markteekman Oct 11, 2025
327ee17
refactor: update import paths in DefaultLayout.astro for consistency
markteekman Oct 11, 2025
79d0517
refactor: reorganize project display and tag filtering layout in port…
markteekman Oct 11, 2025
9d6fc5a
feat: enhance breadrumb with custom breadcrumb labels and slugify tag…
Oct 11, 2025
bea4d7a
Merge branch 'feature/tag-badges' of github.com:incluud/accessible-as…
Oct 11, 2025
b1e7043
refactor: streamline tag display and pagination layout in portfolio a…
markteekman Oct 11, 2025
8ea4942
feat: add joinLastBreadcrumb option to Breadcrumbs for improved navig…
Oct 11, 2025
6c7edc3
Merge branch 'feature/tag-badges' of github.com:incluud/accessible-as…
Oct 11, 2025
0066619
refactor: simplify tag navigation by removing unnecessary paragraph e…
markteekman Oct 12, 2025
d6e9634
Merge branch 'feature/tag-badges' of https://github.com/incluud/acces…
markteekman Oct 12, 2025
b263aaa
feat: implement pagination for portfolio pages to enhance navigation …
markteekman Oct 12, 2025
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 LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
MIT License

Copyright (c) 2023 Mark Teekman
Copyright (c) 2021-2025 Incluud / Mark Teekman

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
11 changes: 5 additions & 6 deletions astro.config.mjs
Original file line number Diff line number Diff line change
@@ -1,17 +1,16 @@
import { defineConfig } from 'astro/config'
import mdx from '@astrojs/mdx'
import { fileURLToPath } from 'url'
import compress from 'astro-compress'
import icon from 'astro-icon'
import mdx from '@astrojs/mdx'
import sitemap from '@astrojs/sitemap'
import tailwindcss from '@tailwindcss/vite'
import { fileURLToPath } from 'url'

import sitemap from '@astrojs/sitemap';

// https://astro.build/config
export default defineConfig({
compressHTML: true,
site: 'https://accessible-astro-starter.incluud.dev',
integrations: [mdx(), icon(), compress(), sitemap()],
integrations: [compress(), icon(), mdx(), sitemap()],
vite: {
css: {
preprocessorOptions: {
Expand All @@ -36,4 +35,4 @@ export default defineConfig({
},
},
},
})
})
1 change: 1 addition & 0 deletions src/content.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ const projects = defineCollection({
title: z.string(),
author: z.string(),
description: z.string(),
tags: z.array(z.string()).default([]),
}),
})

Expand Down
1 change: 1 addition & 0 deletions src/content/projects/project-01.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
title: Voice-First Smart Home Assistant
author: John Doe
description: A comprehensive smart home solution designed specifically for users with visual impairments, featuring voice-first interactions and haptic feedback
tags: ["accessibility", "smart-home", "iot"]
---

## Project Overview
Expand Down
1 change: 1 addition & 0 deletions src/content/projects/project-02.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
title: Sign Language Bridge
author: Jane Smith
description: An AI-powered real-time sign language translation system that enables seamless communication between deaf and hearing individuals
tags: ["accessibility", "ai", "computer-vision"]
---

## Project Overview
Expand Down
1 change: 1 addition & 0 deletions src/content/projects/project-03.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
title: AccessGuard
author: Alex Rivera
description: A comprehensive web security solution that ensures both robust protection and WCAG 2.1 Level AAA compliance for enterprise websites
tags: ["security", "accessibility"]
---

## Project Overview
Expand Down
1 change: 1 addition & 0 deletions src/content/projects/project-04.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
title: Accessible Learning Hub
author: Sarah Johnson
description: An inclusive e-learning platform that adapts to various learning disabilities and provides personalized learning experiences for all students
tags: ["accessibility", "education"]
---

## Project Overview
Expand Down
1 change: 1 addition & 0 deletions src/content/projects/project-05.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
title: FinanceForAll
author: Viktor Nakamoto
description: A fully accessible financial management platform that makes banking and investment tools available to users with various disabilities
tags: ["accessibility", "finance"]
---

## Project Overview
Expand Down
1 change: 1 addition & 0 deletions src/content/projects/project-06.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
title: HealthAccess
author: Dr. Rachel Zhang
description: An accessible healthcare management system designed for patients with various disabilities to independently manage their medical care
tags: ["accessibility", "healthcare"]
---

## Project Overview
Expand Down
1 change: 1 addition & 0 deletions src/content/projects/project-07.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
title: SmartHome Access
author: Emma Green
description: An inclusive smart home automation system that enables people with mobility impairments to control their entire home environment independently
tags: ["accessibility", "smart-home", "iot"]
---

## Project Overview
Expand Down
6 changes: 3 additions & 3 deletions src/layouts/DefaultLayout.astro
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
---
import '../styles/tailwind.css'
import '../assets/scss/index.scss'
import SiteMeta from '../components/SiteMeta.astro'
import Header from '../components/Header.astro'
import Footer from '../components/Footer.astro'
import SiteMeta from '@components/SiteMeta.astro'
import Header from '@components/Header.astro'
import Footer from '@components/Footer.astro'
import { ClientRouter } from 'astro:transitions'

const {
Expand Down
91 changes: 59 additions & 32 deletions src/pages/portfolio/[...page].astro
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
import DefaultLayout from '@layouts/DefaultLayout.astro'
import PageHeader from '@components/PageHeader.astro'
import { Card, Pagination } from 'accessible-astro-components'
import { Badge, Card, Heading, Link, Pagination } from 'accessible-astro-components'
import { getCollection } from 'astro:content'
import type { GetStaticPaths, Page } from 'astro'
import type { CollectionEntry } from 'astro:content'
Expand All @@ -16,6 +16,8 @@ import projectImage6 from '@assets/images/projects/project-image-6.png'

export const getStaticPaths = (async ({ paginate }) => {
const projects = await getCollection('projects')
const tags = projects.map((project) => project.data.tags).flat()
const uniqueTags = [...new Set(tags)].sort((a, b) => a.localeCompare(b))
Comment thread
peterpadberg marked this conversation as resolved.
Outdated

const projectImages = [projectImage1, projectImage2, projectImage3, projectImage4, projectImage5, projectImage6]

Expand All @@ -26,14 +28,18 @@ export const getStaticPaths = (async ({ paginate }) => {
}
})

return paginate(projectsWithImages, { pageSize: 6 })
return paginate(projectsWithImages, {
pageSize: 6,
props: { uniqueTags },
})
}) satisfies GetStaticPaths

interface Props {
page: Page<CollectionEntry<'projects'> & { featuredImage: any }>
uniqueTags: string[]
}

const { page } = Astro.props
const { page, uniqueTags } = Astro.props
---

<DefaultLayout
Expand All @@ -47,36 +53,57 @@ const { page } = Astro.props
/>
<section class="my-12">
<div class="container">
<p class="text-sm"><em>Project {page.start + 1} through {page.end + 1} of {page.total} total projects</em></p>
<ul class="my-3 grid grid-cols-1 gap-6 md:grid-cols-2 lg:grid-cols-3">
{
page.data.map((project) => (
<li>
<Card
imageComponent={project.featuredImage}
url={'/portfolio/' + project.id}
title={project.data.title}
headingLevel="h2"
footer={'Author: ' + project.data.author}
fullHeight={true}
>
{project.data.description}
</Card>
</li>
))
}
</ul>
<div class="mt-12 grid place-content-center">
<Pagination
firstPage={page.url.prev ? '/portfolio' : null}
previousPage={page.url.prev ? page.url.prev : null}
nextPage={page.url.next ? page.url.next : null}
lastPage={page.url.next ? `/portfolio/${Math.ceil(page.total / page.size)}` : null}
currentPage={`${page.currentPage}`}
totalPages={`${Math.ceil(page.total / page.size)}`}
ariaLabel="Portfolio pagination"
/>
<div class="grid grid-cols-1 gap-8 md:grid-cols-[300px_1fr]">
<div class="mb-6">
<Heading level="h2" size="h5" class="mb-2">Tags</Heading>
<div class="flex flex-wrap gap-4">
{
uniqueTags.map((tag: string) => (
<Link isButton size="sm" href={'/portfolio/tag/' + tag}>
{tag}
</Link>
))
}
</div>
</div>
<div class="mb-6">
<p class="text-sm"><em>Project {page.start + 1} through {page.end + 1} of {page.total} total projects</em></p>
<ul class="mt-3 grid grid-cols-1 gap-6 lg:grid-cols-2">
{
page.data.map((project) => (
<li>
<Card
imageComponent={project.featuredImage}
url={'/portfolio/' + project.id}
title={project.data.title}
headingLevel="h2"
footer={'Author: ' + project.data.author}
fullHeight={true}
>
<span slot="meta">
{project.data.tags.map((tag) => (
<Badge>{tag}</Badge>
))}
</span>
{project.data.description}
</Card>
</li>
))
}
</ul>
</div>
</div>
</div>
<div class="mt-12 grid place-content-center">
<Pagination
firstPage={page.url.prev ? '/portfolio' : null}
previousPage={page.url.prev ? page.url.prev : null}
nextPage={page.url.next ? page.url.next : null}
lastPage={page.url.next ? `/portfolio/${Math.ceil(page.total / page.size)}` : null}
currentPage={`${page.currentPage}`}
totalPages={`${Math.ceil(page.total / page.size)}`}
ariaLabel="Portfolio pagination"
/>
</div>
</section>
</DefaultLayout>
148 changes: 148 additions & 0 deletions src/pages/portfolio/tag/[tag]/[...page].astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
---
import DefaultLayout from '@layouts/DefaultLayout.astro'
import PageHeader from '@components/PageHeader.astro'
import { Badge, Card, Heading, Link, Pagination } from 'accessible-astro-components'
import { getCollection } from 'astro:content'
import type { GetStaticPaths, Page } from 'astro'
import type { CollectionEntry } from 'astro:content'

// Import images directly for optimization
import projectImage1 from '@assets/images/projects/project-image-1.png'
import projectImage2 from '@assets/images/projects/project-image-2.png'
import projectImage3 from '@assets/images/projects/project-image-3.png'
import projectImage4 from '@assets/images/projects/project-image-4.png'
import projectImage5 from '@assets/images/projects/project-image-5.png'
import projectImage6 from '@assets/images/projects/project-image-6.png'

export const getStaticPaths = (async ({ paginate }) => {
const projects = await getCollection('projects')
const allTags = projects.map((project) => project.data.tags).flat()
const uniqueTags = [...new Set(allTags)].sort((a, b) => a.localeCompare(b))
Comment thread
peterpadberg marked this conversation as resolved.
Outdated

const projectImages = [projectImage1, projectImage2, projectImage3, projectImage4, projectImage5, projectImage6]

const projectsWithImages = projects.map((project, index) => {
return {
...project,
featuredImage: projectImages[index % projectImages.length],
}
})

// Generate paginated pages for each tag
const allPaginatedPages = []

for (const tag of uniqueTags) {
const filteredProjects = projectsWithImages.filter((project) => project.data.tags.includes(tag))

const paginatedPages = paginate(filteredProjects, {
pageSize: 6,
params: { tag },
props: {
currentTag: tag,
uniqueTags,
},
})

allPaginatedPages.push(...paginatedPages)
}

return allPaginatedPages
}) satisfies GetStaticPaths

interface Props {
page: Page<CollectionEntry<'projects'> & { featuredImage: any }>
currentTag: string
uniqueTags: string[]
}

const { page, currentTag, uniqueTags } = Astro.props
---

<DefaultLayout
title={`Portfolio - ${currentTag}`}
description={`Projects filtered by ${currentTag} tag. Discover our work in ${currentTag} and related technologies.`}
>
<PageHeader
title={`Portfolio: ${currentTag}`}
subtitle={`Showing ${page.total} project${page.total !== 1 ? 's' : ''} tagged with "${currentTag}". <a href="/portfolio">View all projects</a> or filter by other tags below.`}
bgType="bordered"
Comment on lines +73 to +75

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Correct the subtitle project count.

page.total is the total number of items across all paginated pages, so on page 1 we only display 6 projects yet tell the user we're showing (say) 12. Swap to the per-page count to keep the UI truthful.

-    subtitle={`Showing ${page.total} project${page.total !== 1 ? 's' : ''} tagged with "${currentTag.label}". <a href="/portfolio">View all projects</a> or filter by other tags below.`}
+    subtitle={`Showing ${page.end - page.start + 1} project${page.end - page.start + 1 !== 1 ? 's' : ''} tagged with "${currentTag.label}". <a href="/portfolio">View all projects</a> or filter by other tags below.`}

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In src/pages/portfolio/tag/[tag]/[...page].astro around lines 73-75, the
subtitle uses page.total (total across all pages) but should show the number of
projects on the current page; replace page.total with the current-page count
(e.g. the length of the array that holds projects for this page, such as
page.items.length or page.data.length depending on your pagination shape) so the
subtitle reads "Showing X project(s) tagged with…" where X is the per-page
count.

/>
<section class="my-12">
<div class="container">
<div class="grid grid-cols-1 gap-8 md:grid-cols-[300px_1fr]">
<div class="mb-6">
<Heading level="h2" size="h5" class="mb-2">Tags</Heading>
<p class="mb-4">
<Link href="/portfolio">← All projects</Link>
</p>
<div class="flex flex-wrap gap-4">
{
uniqueTags.map((tag: string) => {
const isCurrentTag = tag === currentTag
return isCurrentTag ? (
<Link isButton size="sm" href={'/portfolio/tag/' + tag} disabled class="disabled">
{tag}
</Link>
) : (
<Link isButton size="sm" href={'/portfolio/tag/' + tag}>
{tag}
</Link>
)
})
}
</div>
</div>
<div class="mb-6">
<p class="text-sm">
<em
>Project {page.start + 1} through {page.end + 1} of {page.total} total projects tagged with "{
currentTag
}"</em
>
</p>
<ul class="mt-3 grid grid-cols-1 gap-6 lg:grid-cols-2">
{
page.data.map((project) => (
<li>
<Card
imageComponent={project.featuredImage}
url={'/portfolio/' + project.id}
title={project.data.title}
headingLevel="h2"
footer={'Author: ' + project.data.author}
fullHeight={true}
>
<span slot="meta">
{project.data.tags.map((tag) => {
const isCurrentTag = tag === currentTag
return isCurrentTag ? <Badge class="bg-blue-600 text-white">{tag}</Badge> : <Badge>{tag}</Badge>
})}
</span>
{project.data.description}
</Card>
</li>
))
}
</ul>
</div>
</div>

<!-- Pagination -->
{
page && Math.ceil(page.total / 6) > 1 && (
Comment thread
peterpadberg marked this conversation as resolved.
Outdated
<div class="mt-12 grid place-content-center">
<Pagination
firstPage={page.url.prev ? `/portfolio/tag/${currentTag}` : null}
previousPage={page.url.prev ? page.url.prev : null}
nextPage={page.url.next ? page.url.next : null}
lastPage={page.url.next ? `/portfolio/tag/${currentTag}/${Math.ceil(page.total / 6)}` : null}
Comment thread
peterpadberg marked this conversation as resolved.
Outdated
currentPage={`${page.currentPage}`}
totalPages={`${Math.ceil(page.total / 6)}`}
Comment thread
peterpadberg marked this conversation as resolved.
Outdated
ariaLabel={`${currentTag} portfolio pagination`}
/>
</div>
)
}
</div>
</section>
</DefaultLayout>