Reference Page Grid Component Implementation
Summary
Implemented a flexible ReferenceGrid component system to unify the display of vocabulary and concept entries, enhancing the reference page with consistent styling, improved navigation, and optimized content rendering.
Why Care
This implementation significantly improves user experience by providing a consistent interface for browsing reference content. The modular design makes future additions to the reference library straightforward, while the responsive grid layout ensures optimal viewing across devices. The unified approach to handling different content collections reduces code duplication and improves maintainability.
Implementation
Changes Made
- Created a new reusable grid component:
/site/src/components/reference/ReferenceGrid.astro
- Updated existing reference components to work with the new grid:
/site/src/components/reference/ConceptPreviewCard.astro
/site/src/components/reference/VocabularyPreviewCard.astro
- Modified routing and page rendering:
/site/src/pages/more-about/index.astro
/site/src/pages/more-about/[...slug].astro
/site/src/utils/routing/routeManager.ts
- Integrated with existing content collections:
vocabulary
concepts
Technical Details
ReferenceGrid Component
typescript
// In /site/src/components/reference/ReferenceGrid.astro
---
/**
* ReferenceGrid.astro
*
* A reusable component to display a grid of reference items (vocabulary or concepts).
* It takes an array of items and renders the appropriate preview card for each.
* Styling is handled via local CSS using project variables, replacing Tailwind.
*
* @component
* @param {ReferenceItem[]} items - Array of items to display in the grid.
* @param {string} [class] - Optional class to apply to the root div.
*/
import type { CollectionEntry } from 'astro:content';
import VocabularyPreviewCard from '@components/reference/VocabularyPreviewCard.astro';
import ConceptPreviewCard from '@components/reference/ConceptPreviewCard.astro';
// Define the structure for each item passed to this grid component
interface ReferenceItem {
id: string; // Original filename-based ID from the collection entry
slug: string; // The URL slug generated by Astro (e.g., 'my-term')
collection: 'vocabulary' | 'concepts'; // The source collection
data: {
title: string;
slug?: string; // Optional slug defined in frontmatter
aliases?: string[]; // Specific to vocabulary
description?: string; // Specific to concepts
};
}
// Define the component's props interface
interface Props {
items: ReferenceItem[];
class?: string; // Add optional class prop
}
// Get the items array from the component's props
const { items, class: className } = Astro.props;
// Helper function to format the ReferenceItem for the preview cards
function formatEntryForCard(item: ReferenceItem) {
return {
id: item.id,
collection: item.collection,
slug: item.slug,
data: {
...item.data,
slug: item.data.slug,
},
};
}
---
<div class:list={[
"grid",
"grid-cols-1", // Default: 1 column
"md:grid-cols-2", // Medium screens: 2 columns
"lg:grid-cols-3", // Large screens: 3 columns
"gap-4", // Apply gap using Tailwind utility
className // Include any passed-in class
]}>
{items.map((item) => (
<div>
{/* Conditionally render the correct preview card based on the item's collection */}
{item.collection === 'vocabulary' ? (
<VocabularyPreviewCard entry={formatEntryForCard(item)} />
) : item.collection === 'concepts' ? (
<ConceptPreviewCard entry={formatEntryForCard(item)} />
) : (
<p>Error: Unknown item type</p> // Fallback for safety
)}
</div>
))}
</div>
Preview Card Components
Both
ConceptPreviewCard.astro
and VocabularyPreviewCard.astro
follow a consistent pattern: typescript
// In /site/src/components/reference/ConceptPreviewCard.astro (similar pattern for VocabularyPreviewCard)
---
/**
* ConceptPreviewCard.astro
*
* This component renders a preview card for a concept from the concepts collection.
* It displays the concept title and description (if any) in a consistent card format.
*/
// Define the props interface for type safety
interface Props {
entry: {
id: string;
data: {
title: string;
description?: string;
slug?: string;
};
};
}
// Destructure the entry from props
const { entry } = Astro.props;
// Generate the URL for the concept
const url = `/more-about/${entry.data.slug || entry.id.replace(/\.md$/, '').replace(/\s+/g, '-')}`;
---
<div class="concept-card">
<h3 class="concept-card__title">
<a href={url} class="concept-card__link">
{entry.data.title}
</a>
</h3>
<p class="concept-card__description">
{entry.data.description || "Learn more about this concept..."}
</p>
</div>
<style>
/* Card styling with consistent visual language */
.concept-card {
background-color: var(--clr-lossless-primary-dark, #19141D);
padding: 1rem;
border-radius: 0.5rem;
border: 1px solid transparent;
transition: background-color 0.2s ease-in-out,
border-color 0.2s ease-in-out,
color 0.2s ease-in-out,
transform 0.2s ease-in-out;
}
/* Hover effects */
.concept-card:hover {
background-color: #2d2730;
border-color: var(--clr-lossless-accent--brightest);
color: var(--clr-lossless-primary-glass--lighter);
transform: translateY(-2px) scale(1.05);
}
/* Additional styling for title, links, etc. */
</style>
Integration with Content Collections
The reference grid system integrates with Astro's content collections to fetch and display entries:
typescript
// In /site/src/pages/more-about/index.astro
---
import { getCollection } from 'astro:content';
import ReferenceGrid from '@components/reference/ReferenceGrid.astro';
// Fetch entries from collections
const vocabularyEntries = await getCollection('vocabulary');
const conceptsEntries = await getCollection('concepts');
// Process entries to add titles from filenames if missing and sort
function processEntries<T extends 'vocabulary' | 'concepts'>(entries: CollectionEntry<T>[]) {
// Processing logic...
return entries;
}
// Format entries for the grid component
const vocabularyItems = processedVocabularyEntries.map(entry => ({
id: entry.id,
slug: (entry as any).slug,
collection: entry.collection,
data: entry.data,
}));
const conceptItems = processedConceptsEntries.map(entry => ({
id: entry.id,
slug: (entry as any).slug,
collection: entry.collection,
data: entry.data,
}));
---
<Layout title="Reference - Vocabulary & Concepts">
<!-- Page content -->
<section class="mb-12">
<h2 class="text-2xl font-bold mb-4">Vocabulary</h2>
<p class="mb-6">Terms and definitions used throughout our work.</p>
<ReferenceGrid items={vocabularyItems} />
</section>
<section>
<h2 class="text-2xl font-bold mb-4">Concepts</h2>
<p class="mb-6">Important ideas and frameworks we use in our work.</p>
<ReferenceGrid items={conceptItems} />
</section>
</Layout>
Dynamic Routing
The dynamic routing system handles individual reference entries:
typescript
// In /site/src/pages/more-about/[...slug].astro
export async function getStaticPaths() {
// Fetch entries from collections
const vocabularyEntries = await getCollection('vocabulary');
const conceptsEntries = await getCollection('concepts' as any);
// Process vocabulary entries to generate paths
const vocabularyPaths = vocabularyEntries.map(entry => {
// Generate slug from the entry ID if not present
const filename = entry.id.replace(/\.md$/, '');
const slug = entry.data.slug || filename.toLowerCase().replace(/\s+/g, '-');
// Set title if not provided
if (!entry.data.title) {
entry.data.title = toProperCase(path.basename(filename));
}
return {
params: { slug },
props: { entry, contentType: 'vocabulary' }
};
});
// Similar processing for concepts entries
// Combine both path arrays
return [...vocabularyPaths, ...conceptsPaths];
}
Integration Points
- The ReferenceGrid component integrates with the existing content collections system
- The preview cards maintain consistent styling with the rest of the site
- The routing system ensures proper URL generation and navigation
- The implementation respects the existing project structure and naming conventions
Documentation
- The ReferenceGrid component includes comprehensive JSDoc comments explaining its purpose and usage
- Type definitions ensure proper data flow and help prevent errors
- The styling is consistent with the project's design system
- The implementation follows the project's established patterns for content rendering
Future Enhancements
- Add filtering capabilities to the reference grid
- Implement search functionality for reference entries
- Add pagination for large collections
- Create category-based grouping for more organized browsing
- Enhance the preview cards with additional metadata display options