TeamGrid Sections
Team grid — foto, naam, rol, bio, socials. 3 visuele stijlen: card, minimal, photo.
src/components/sections/TeamGrid.astro
---
/**
* TeamGrid
* Team overzicht — foto's, namen, rollen, optionele socials.
*/
interface TeamMember {
name: string;
role: string;
bio?: string;
image?: string;
linkedin?: string;
twitter?: string;
email?: string;
}
interface Props {
eyebrow?: string;
headline?: string;
sub?: string;
members: TeamMember[];
columns?: 2 | 3 | 4;
style?: 'card' | 'minimal' | 'photo';
}
const {
eyebrow,
headline,
sub,
members = [],
columns = 3,
style = 'card',
} = Astro.props;
---
<section class:list={['tg', `tg--style-${style}`]} data-component="team-grid">
<div class="tg__inner">
{(eyebrow || headline || sub) && (
<div class="tg__header">
{eyebrow && <p class="tg__eyebrow">{eyebrow}</p>}
{headline && <h2 class="tg__title" set:html={headline} />}
{sub && <p class="tg__sub">{sub}</p>}
</div>
)}
<div class="tg__grid" style={`--cols: ${columns}`}>
{members.map(m => (
<div class="tg__member">
<div class="tg__photo-wrap">
{m.image ? (
<img src={m.image} alt={m.name} class="tg__photo" loading="lazy" />
) : (
<div class="tg__photo-placeholder">
<svg width="40" height="40" viewBox="0 0 24 24" fill="currentColor" opacity="0.3">
<path d="M12 12c2.7 0 4.8-2.1 4.8-4.8S14.7 2.4 12 2.4 7.2 4.5 7.2 7.2 9.3 12 12 12zm0 2.4c-3.2 0-9.6 1.6-9.6 4.8v2.4h19.2v-2.4c0-3.2-6.4-4.8-9.6-4.8z"/>
</svg>
</div>
)}
</div>
<div class="tg__info">
<h3 class="tg__name">{m.name}</h3>
<p class="tg__role">{m.role}</p>
{m.bio && <p class="tg__bio">{m.bio}</p>}
{(m.linkedin || m.twitter || m.email) && (
<div class="tg__socials">
{m.linkedin && (
<a href={m.linkedin} class="tg__social" target="_blank" rel="noopener" aria-label="LinkedIn">
<svg width="15" height="15" viewBox="0 0 24 24" fill="currentColor">
<path d="M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433c-1.144 0-2.063-.926-2.063-2.065 0-1.138.92-2.063 2.063-2.063 1.14 0 2.064.925 2.064 2.063 0 1.139-.925 2.065-2.064 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 23.227 23 22.222 0h.003z"/>
</svg>
</a>
)}
{m.twitter && (
<a href={m.twitter} class="tg__social" target="_blank" rel="noopener" aria-label="Twitter/X">
<svg width="15" height="15" viewBox="0 0 24 24" fill="currentColor">
<path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-4.714-6.231-5.401 6.231H2.744l7.737-8.835L1.254 2.25H8.08l4.259 5.633 5.905-5.633zm-1.161 17.52h1.833L7.084 4.126H5.117z"/>
</svg>
</a>
)}
{m.email && (
<a href={`mailto:${m.email}`} class="tg__social" aria-label="E-mail">
<svg width="15" height="15" viewBox="0 0 24 24" fill="currentColor">
<path d="M20 4H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2zm0 4l-8 5-8-5V6l8 5 8-5v2z"/>
</svg>
</a>
)}
</div>
)}
</div>
</div>
))}
</div>
</div>
</section>
<style>
.tg {
background: var(--color-bg, #fff);
padding: 5rem 1.5rem;
}
.tg__inner { max-width: 1200px; margin: 0 auto; }
.tg__header {
text-align: center;
margin-bottom: 3.5rem;
max-width: 640px;
margin-left: auto;
margin-right: auto;
}
.tg__eyebrow {
font-size: 0.75rem;
font-weight: 700;
letter-spacing: 0.1em;
text-transform: uppercase;
color: var(--color-accent, #6366f1);
margin-bottom: 0.75rem;
}
.tg__title {
font-size: clamp(1.875rem, 3vw, 2.75rem);
font-weight: 800;
letter-spacing: -0.03em;
color: var(--color-primary, #0a0a0a);
margin-bottom: 0.875rem;
}
.tg__title :global(em) { font-style: normal; color: var(--color-accent, #6366f1); }
.tg__sub {
font-size: 1.0625rem;
color: var(--color-muted, #6b7280);
line-height: 1.6;
}
.tg__grid {
display: grid;
grid-template-columns: repeat(var(--cols, 3), 1fr);
gap: 2rem;
}
/* Card style */
.tg--style-card .tg__member {
border: 1px solid rgba(0,0,0,0.07);
border-radius: calc(var(--radius, 0.5rem) * 1.5);
overflow: hidden;
transition: box-shadow 0.2s;
}
.tg--style-card .tg__member:hover {
box-shadow: 0 8px 28px rgba(0,0,0,0.08);
}
.tg--style-card .tg__info { padding: 1.25rem; }
/* Photo style */
.tg--style-photo .tg__member {
text-align: center;
}
.tg--style-photo .tg__info { padding-top: 1rem; }
/* Photo */
.tg__photo-wrap {
aspect-ratio: 3/4;
overflow: hidden;
background: #f5f5f7;
}
.tg--style-card .tg__photo-wrap { aspect-ratio: 4/3; }
.tg--style-photo .tg__photo-wrap {
border-radius: calc(var(--radius, 0.5rem) * 2);
aspect-ratio: 1;
}
.tg__photo {
width: 100%;
height: 100%;
object-fit: cover;
object-position: top;
display: block;
transition: transform 0.4s ease;
}
.tg__member:hover .tg__photo { transform: scale(1.03); }
.tg__photo-placeholder {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
background: #f0f0f3;
}
/* Minimal */
.tg--style-minimal .tg__photo-wrap {
width: 64px;
height: 64px;
border-radius: 50%;
flex-shrink: 0;
aspect-ratio: unset;
}
.tg--style-minimal .tg__member {
display: flex;
gap: 1rem;
align-items: flex-start;
padding: 1.25rem;
border-bottom: 1px solid rgba(0,0,0,0.07);
}
.tg__name {
font-size: 1rem;
font-weight: 700;
color: var(--color-primary, #0a0a0a);
margin-bottom: 0.25rem;
}
.tg__role {
font-size: 0.875rem;
color: var(--color-accent, #6366f1);
font-weight: 500;
margin-bottom: 0.5rem;
}
.tg__bio {
font-size: 0.875rem;
line-height: 1.6;
color: var(--color-muted, #6b7280);
margin-bottom: 0.875rem;
}
.tg__socials {
display: flex;
gap: 0.5rem;
}
.tg__social {
width: 30px;
height: 30px;
background: rgba(0,0,0,0.05);
border-radius: var(--radius, 0.5rem);
display: flex;
align-items: center;
justify-content: center;
color: var(--color-muted, #6b7280);
text-decoration: none;
transition: background 0.15s, color 0.15s;
}
.tg__social:hover {
background: var(--color-accent, #6366f1);
color: #fff;
}
@media (max-width: 900px) {
.tg__grid { grid-template-columns: repeat(2, 1fr); }
}
@media (max-width: 540px) {
.tg__grid { grid-template-columns: 1fr; }
}
</style>
Props
| Prop | Type | Default | Beschrijving |
|---|---|---|---|
members * | TeamMember[] | — | Team leden |
style | 'card' | 'minimal' | 'photo' | 'card' | Visuele stijl |
columns | 2 | 3 | 4 | 3 | Aantal kolommen |
* = verplicht