src/components/social-proof/TestimonialsGrid.astro
---
/**
* TestimonialsGrid
* Reviews/testimonials in een masonry-stijl grid of rij.
*
* Props:
* - headline?: string
* - testimonials: Array<Testimonial>
* - columns?: 2 | 3
*/
interface Testimonial {
quote: string;
author: string;
role?: string;
company?: string;
avatar?: string;
rating?: 1 | 2 | 3 | 4 | 5;
}
interface Props {
headline?: string;
testimonials: Testimonial[];
columns?: 2 | 3;
}
const { headline, testimonials, columns = 3 } = Astro.props;
---
<section class={`testi testi--cols-${columns}`} data-testi>
<div class="testi__inner">
{headline && <h2 class="testi__headline">{headline}</h2>}
<ul class="testi__grid">
{testimonials.map(t => (
<li class="testi__card">
{t.rating && (
<div class="testi__stars" aria-label={`${t.rating} van 5 sterren`}>
{'★'.repeat(t.rating)}{'☆'.repeat(5 - t.rating)}
</div>
)}
<blockquote class="testi__quote">"{t.quote}"</blockquote>
<footer class="testi__author">
{t.avatar && (
<img src={t.avatar} alt={t.author} class="testi__avatar" loading="lazy" />
)}
<div>
<cite class="testi__name">{t.author}</cite>
{(t.role || t.company) && (
<p class="testi__meta">{[t.role, t.company].filter(Boolean).join(', ')}</p>
)}
</div>
</footer>
</li>
))}
</ul>
</div>
</section>
<style>
.testi { padding: 5rem 1.5rem; background: var(--color-bg); }
.testi__inner { max-width: 1280px; margin: 0 auto; }
.testi__headline {
font-size: clamp(1.75rem, 3vw, 2.5rem);
font-weight: 800;
letter-spacing: -0.03em;
text-align: center;
margin: 0 0 3rem;
}
.testi__grid {
list-style: none;
margin: 0; padding: 0;
display: grid;
gap: 1.5rem;
grid-template-columns: 1fr;
}
@media (min-width: 640px) {
.testi--cols-2 .testi__grid,
.testi--cols-3 .testi__grid { grid-template-columns: 1fr 1fr; }
}
@media (min-width: 960px) {
.testi--cols-3 .testi__grid { grid-template-columns: repeat(3, 1fr); }
}
.testi__card {
padding: 1.75rem;
background: color-mix(in srgb, var(--color-text) 4%, transparent);
border-radius: calc(var(--radius) * 2);
display: flex;
flex-direction: column;
gap: 1rem;
}
.testi__stars { color: #f59e0b; font-size: 0.875rem; letter-spacing: 0.05em; }
.testi__quote {
font-size: 1rem;
line-height: 1.65;
color: var(--color-text);
margin: 0;
flex: 1;
}
.testi__author {
display: flex;
align-items: center;
gap: 0.75rem;
}
.testi__avatar {
width: 2.5rem; height: 2.5rem;
border-radius: 50%;
object-fit: cover;
flex-shrink: 0;
}
.testi__name {
font-style: normal;
font-weight: 700;
font-size: 0.9375rem;
display: block;
}
.testi__meta {
font-size: 0.8125rem;
color: var(--color-muted);
margin: 0;
}
</style>
Props
| Prop | Type | Default | Beschrijving |
|---|---|---|---|
testimonials * | Testimonial[] | — | Array van testimonials. Zie type in component. |
headline | string | — | Sectie headline |
columns | 2 | 3 | 3 | Aantal kolommen |
* = verplicht