justin.deal/src/components/homelab/CategorySection.astro

103 lines
2.7 KiB
Plaintext
Raw Normal View History

---
import { type Service } from "../../lib/types";
import ServiceCard from "../common/ServiceCard.astro";
2025-05-03 14:06:52 -07:00
import ScrollReveal from "../common/ScrollReveal.astro";
/**
* CategorySection component displays a collapsible section of services grouped by category
* @component
* @example
* ```astro
* <CategorySection
* category="Development"
* apps={[
* {
* name: "Gitea",
* link: "https://code.justin.deal",
* icon: "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/gitea.svg",
* alt: "Gitea"
* }
* ]}
* />
* ```
*/
interface Props {
/**
* The category name to display as the section title
*/
category: string;
/**
* Array of service objects to display in this category
*/
apps: Service[];
}
const { category, apps } = Astro.props;
// Pre-compute values during server-side rendering
const categoryId = `category-${category.toLowerCase().replace(/\s+/g, '-')}`;
const categoryLower = category.toLowerCase();
---
<div class="mb-8 category-section" data-category={categoryLower} x-data="{ open: true }">
<button
@click="open = !open"
class="text-xl font-semibold mb-4 w-full text-left flex items-center justify-between"
aria-expanded="true"
:aria-expanded="open.toString()"
aria-controls={categoryId}
>
{category}
<svg
:class="{ 'rotate-180': open }"
class="w-5 h-5 transform transition-transform duration-300"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
</svg>
</button>
<div
x-show="open"
x-transition
id={categoryId}
>
<div class="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-4 gap-4 transition-all duration-300">
{apps.length > 0 ? (
apps.map(app => {
const appName = app.name.toLowerCase();
const appTags = app.tags ? app.tags.join(' ').toLowerCase() : '';
return (
2025-05-03 14:06:52 -07:00
<ScrollReveal
animation="fade-up"
delay={100 * apps.indexOf(app)}
duration={500}
threshold={0.1}
>
2025-05-03 14:06:52 -07:00
<div
class="app-card transition-all duration-300"
data-app-name={appName}
data-app-tags={appTags}
data-app-category={categoryLower}
>
<ServiceCard
name={app.name}
href={app.link}
img={app.icon}
alt={app.name}
/>
</div>
</ScrollReveal>
);
})
) : (
<p class="text-center col-span-full">Coming soon...</p>
)}
</div>
</div>
</div>