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

74 lines
1.9 KiB
Plaintext
Raw Normal View History

---
interface Props {
category: string;
apps: Array<{
name: string;
link: string;
icon: string;
alt: string;
tags?: string[];
}>;
}
const { category, apps } = Astro.props;
import ServiceCard from "../common/ServiceCard.astro";
// 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">
{apps.length > 0 ? (
apps.map(app => {
const appName = app.name.toLowerCase();
const appTags = app.tags ? app.tags.join(' ').toLowerCase() : '';
return (
<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>
);
})
) : (
<p class="text-center col-span-full">Coming soon...</p>
)}
</div>
</div>
</div>