Zoeken...  ⌘K GitHub

TeamGrid Sections

Team grid — foto, naam, rol, bio, socials. 3 visuele stijlen: card, minimal, photo.

/team-grid
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