ReviewStars Social Proof
Samengevatte sterrenwaardering met score en recentste reviews.
src/components/social-proof/ReviewStars.astro
---
/**
* ReviewStars
* Samengevatte sterrenwaardering met score en aantal reviews.
*/
interface Props {
score: number;
maxScore?: number;
reviewCount: number;
platform?: string;
reviews?: { text: string; author: string; rating?: number }[];
bg?: 'white' | 'light';
}
const { score, maxScore = 5, reviewCount, platform = 'Google', reviews = [], bg = 'white' } = Astro.props;
const fullStars = Math.floor(score);
const hasHalf = score % 1 >= 0.5;
---
<section class={`rvs rvs--${bg}`}>
<div class="rvs-inner">
<div class="rvs-summary">
<div class="rvs-score-block">
<span class="rvs-score">{score.toFixed(1)}</span>
<span class="rvs-max">/{maxScore}</span>
</div>
<div class="rvs-detail">
<div class="rvs-stars">
{Array.from({length: fullStars}).map(() => <span class="rvs-star">★</span>)}
{hasHalf && <span class="rvs-star rvs-star--half">★</span>}
</div>
<p class="rvs-count">{reviewCount} beoordelingen op {platform}</p>
</div>
</div>
{reviews.length > 0 && (
<div class="rvs-reviews">
{reviews.map(r => (
<div class="rvs-review">
{r.rating && (
<div class="rvs-review-stars">
{Array.from({length: r.rating}).map(() => <span class="rvs-star rvs-star--sm">★</span>)}
</div>
)}
<p class="rvs-review-text">"{r.text}"</p>
<span class="rvs-review-author">— {r.author}</span>
</div>
))}
</div>
)}
</div>
</section>
<style>
.rvs { padding: 4rem 2rem; }
.rvs--white { background: #fff; border-top: 1px solid #e5e7eb; }
.rvs--light { background: #f8fafc; border-top: 1px solid #e5e7eb; }
.rvs-inner { max-width: 1100px; margin: 0 auto; }
.rvs-summary { display: flex; align-items: center; gap: 1.5rem; justify-content: center; margin-bottom: 3rem; }
.rvs-score-block { display: flex; align-items: baseline; gap: 0.125rem; }
.rvs-score { font-size: 4rem; font-weight: 900; color: #0a0a0a; line-height: 1; letter-spacing: -0.04em; }
.rvs-max { font-size: 1.25rem; color: #9ca3af; }
.rvs-detail { }
.rvs-stars { display: flex; gap: 0.25rem; margin-bottom: 0.375rem; }
.rvs-star { color: #f59e0b; font-size: 1.5rem; line-height: 1; }
.rvs-star--half { opacity: 0.4; }
.rvs-star--sm { font-size: 0.875rem; }
.rvs-count { font-size: 0.875rem; color: #6b7280; margin: 0; }
.rvs-reviews { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: 1.5rem; }
.rvs-review { background: #fff; border: 1px solid #e5e7eb; border-radius: 0.75rem; padding: 1.5rem; }
.rvs--white .rvs-review { background: #f8fafc; }
.rvs-review-stars { display: flex; gap: 0.2rem; margin-bottom: 0.75rem; }
.rvs-review-text { font-size: 0.9375rem; color: #374151; line-height: 1.6; font-style: italic; margin: 0 0 0.75rem; }
.rvs-review-author { font-size: 0.8125rem; color: #9ca3af; font-weight: 600; }
</style>
Props
| Prop | Type | Default | Beschrijving |
|---|---|---|---|
score * | number | — | Gemiddelde score |
reviewCount * | number | — | Totaal aantal reviews |
reviews | { text: string; author: string; rating?: number }[] | — | Recente reviews |
maxScore | number | 5 | Maximale score |
platform | string | — | Platform naam |
bg | 'white' | 'light' | — | Achtergrond variant |
* = verplicht