160 lines
5.1 KiB
TypeScript
160 lines
5.1 KiB
TypeScript
"use client";
|
|
|
|
import Image from "next/image";
|
|
import Link from "next/link";
|
|
import { useState } from "react";
|
|
import type { MaterialItem } from "@/data/site-content";
|
|
|
|
type MaterialCatalogProps = {
|
|
heroImage: string;
|
|
intro: string;
|
|
deliveryNote: string;
|
|
materials: MaterialItem[];
|
|
};
|
|
|
|
export function MaterialCatalog({
|
|
heroImage,
|
|
intro,
|
|
deliveryNote,
|
|
materials,
|
|
}: MaterialCatalogProps) {
|
|
const [selectedSubcategories, setSelectedSubcategories] = useState<string[]>(
|
|
[],
|
|
);
|
|
|
|
const subcategories = Array.from(
|
|
new Set(materials.map((material) => material.subcategory)),
|
|
).sort((left, right) => left.localeCompare(right));
|
|
|
|
const filteredMaterials =
|
|
selectedSubcategories.length === 0
|
|
? materials
|
|
: materials.filter((material) =>
|
|
selectedSubcategories.includes(material.subcategory),
|
|
);
|
|
|
|
function toggleFilter(subcategory: string) {
|
|
setSelectedSubcategories((current) =>
|
|
current.includes(subcategory)
|
|
? current.filter((value) => value !== subcategory)
|
|
: [...current, subcategory],
|
|
);
|
|
}
|
|
|
|
return (
|
|
<section id="catalog" className="section catalog-section">
|
|
<div className="container catalog-shell">
|
|
<aside className="catalog-sidebar" aria-label="Material filters">
|
|
<div>
|
|
<span className="eyebrow">Filter materials</span>
|
|
<h2>Sort by category</h2>
|
|
<p>{deliveryNote}</p>
|
|
</div>
|
|
|
|
<div className="filter-group">
|
|
<span className="filter-label">Subcategories</span>
|
|
<div className="filter-chips">
|
|
{subcategories.map((subcategory) => (
|
|
<button
|
|
key={subcategory}
|
|
type="button"
|
|
className={`filter-chip ${selectedSubcategories.includes(subcategory) ? "active" : ""
|
|
}`}
|
|
onClick={() => toggleFilter(subcategory)}
|
|
>
|
|
{subcategory}
|
|
</button>
|
|
))}
|
|
</div>
|
|
</div>
|
|
|
|
<button
|
|
type="button"
|
|
className="button button-secondary invert button-block"
|
|
onClick={() => setSelectedSubcategories([])}
|
|
>
|
|
Clear filters
|
|
</button>
|
|
</aside>
|
|
|
|
<div className="catalog-main">
|
|
<div className="catalog-hero">
|
|
<div className="catalog-hero-media">
|
|
<Image
|
|
src={heroImage}
|
|
alt="Material category hero"
|
|
fill
|
|
sizes="(max-width: 1024px) 100vw, 60vw"
|
|
quality={72}
|
|
className="cover-image"
|
|
/>
|
|
</div>
|
|
<div className="catalog-hero-content">
|
|
<span className="eyebrow">Project-ready inventory</span>
|
|
<h2>Materials organized for faster quoting.</h2>
|
|
<p>{intro}</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="catalog-toolbar">
|
|
<p className="catalog-count">
|
|
Showing {filteredMaterials.length}{" "}
|
|
{filteredMaterials.length === 1 ? "material" : "materials"}
|
|
</p>
|
|
<div className="selected-filters" aria-live="polite">
|
|
{selectedSubcategories.map((subcategory) => (
|
|
<span key={subcategory} className="selected-chip">
|
|
{subcategory}
|
|
</span>
|
|
))}
|
|
</div>
|
|
</div>
|
|
|
|
{filteredMaterials.length > 0 ? (
|
|
<div className="material-grid">
|
|
{filteredMaterials.map((material) => (
|
|
<article
|
|
key={material.slug}
|
|
className="material-card material-card--catalog reveal"
|
|
>
|
|
<div className="material-card-media">
|
|
<Image
|
|
src={material.image}
|
|
alt={material.name}
|
|
fill
|
|
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 25vw"
|
|
quality={68}
|
|
className="cover-image"
|
|
/>
|
|
</div>
|
|
<div className="material-card-content">
|
|
<span className="material-card-tag">{material.subcategory}</span>
|
|
<h3>{material.name}</h3>
|
|
<p>{material.description}</p>
|
|
<div className="material-card-meta">
|
|
<span className="unit">
|
|
{material.purchaseUnit}
|
|
</span>
|
|
<Link
|
|
href={`/contact?material=${material.slug}`}
|
|
className="text-link"
|
|
>
|
|
Request quote
|
|
</Link>
|
|
</div>
|
|
</div>
|
|
</article>
|
|
))}
|
|
</div>
|
|
) : (
|
|
<div className="catalog-empty">
|
|
<h3>No materials match that filter yet.</h3>
|
|
<p>Clear the filters to see the full inventory list again.</p>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</section>
|
|
);
|
|
}
|