Zoeken...  ⌘K GitHub

ContactSplitPhoto Forms

Contact sectie met full-height coverfoto links en formulier rechts. Contactinfo overlay op foto.

/contact-split-photo
src/components/forms/ContactSplitPhoto.astro
---
/**
 * ContactSplitPhoto
 * Contact sectie — full-height foto links, formulier rechts.
 */
interface Props {
  headline: string;
  sub?: string;
  imageSrc: string;
  imageAlt?: string;
  fields?: { label: string; name: string; type?: string; required?: boolean; }[];
  submitLabel?: string;
  phone?: string;
  email?: string;
  address?: string;
}

const {
  headline,
  sub,
  imageSrc,
  imageAlt = '',
  fields = [
    { label: 'Naam', name: 'name', type: 'text', required: true },
    { label: 'E-mail', name: 'email', type: 'email', required: true },
    { label: 'Bericht', name: 'message', type: 'textarea', required: true },
  ],
  submitLabel = 'Verstuur bericht',
  phone,
  email,
  address,
} = Astro.props;
---

<section class="csp" data-component="contact-split-photo">
  <div class="csp__inner">
    <!-- Photo -->
    <div class="csp__photo">
      <img src={imageSrc} alt={imageAlt} class="csp__img" />
      <div class="csp__photo-overlay">
        <div class="csp__contact-info">
          {phone && (
            <a href={`tel:${phone}`} class="csp__info-item">
              <svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor" opacity="0.7">
                <path d="M6.6 10.8c1.4 2.8 3.8 5.1 6.6 6.6l2.2-2.2c.3-.3.7-.4 1-.2 1.1.4 2.3.6 3.6.6.6 0 1 .4 1 1V20c0 .6-.4 1-1 1-9.4 0-17-7.6-17-17 0-.6.4-1 1-1h3.5c.6 0 1 .4 1 1 0 1.3.2 2.5.6 3.6.1.3 0 .7-.2 1L6.6 10.8z"/>
              </svg>
              {phone}
            </a>
          )}
          {email && (
            <a href={`mailto:${email}`} class="csp__info-item">
              <svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor" opacity="0.7">
                <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>
              {email}
            </a>
          )}
          {address && (
            <p class="csp__info-item">
              <svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor" opacity="0.7">
                <path d="M12 2C8.13 2 5 5.13 5 9c0 5.25 7 13 7 13s7-7.75 7-13c0-3.87-3.13-7-7-7zm0 9.5c-1.38 0-2.5-1.12-2.5-2.5s1.12-2.5 2.5-2.5 2.5 1.12 2.5 2.5-1.12 2.5-2.5 2.5z"/>
              </svg>
              {address}
            </p>
          )}
        </div>
      </div>
    </div>

    <!-- Form -->
    <div class="csp__form-col">
      <h2 class="csp__headline">{headline}</h2>
      {sub && <p class="csp__sub">{sub}</p>}

      <form class="csp__form" novalidate>
        {fields.map(field => (
          <div class="csp__field">
            <label class="csp__label" for={`csp-${field.name}`}>
              {field.label}
              {field.required && <span class="csp__req">*</span>}
            </label>
            {field.type === 'textarea' ? (
              <textarea
                id={`csp-${field.name}`}
                name={field.name}
                class="csp__input csp__textarea"
                required={field.required}
                rows={5}
              ></textarea>
            ) : (
              <input
                id={`csp-${field.name}`}
                name={field.name}
                type={field.type ?? 'text'}
                class="csp__input"
                required={field.required}
              />
            )}
          </div>
        ))}

        <button type="submit" class="csp__submit">{submitLabel}</button>
      </form>
    </div>
  </div>
</section>

<style>
  .csp {
    background: var(--color-bg, #fff);
    overflow: hidden;
  }

  .csp__inner {
    display: grid;
    grid-template-columns: 1fr 1fr;
    min-height: 640px;
  }

  /* Photo */
  .csp__photo {
    position: relative;
    overflow: hidden;
  }

  .csp__img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
  }

  .csp__photo-overlay {
    position: absolute;
    inset: 0;
    background: linear-gradient(to top, rgba(0,0,0,0.7) 0%, transparent 50%);
    display: flex;
    align-items: flex-end;
    padding: 2.5rem;
  }

  .csp__contact-info {
    display: flex;
    flex-direction: column;
    gap: 0.75rem;
  }

  .csp__info-item {
    display: flex;
    align-items: center;
    gap: 0.625rem;
    color: rgba(255,255,255,0.85);
    font-size: 0.9rem;
    text-decoration: none;
    transition: color 0.15s;
  }

  .csp__info-item:hover { color: #fff; }

  /* Form col */
  .csp__form-col {
    padding: 4rem 3.5rem;
    background: var(--color-bg, #fff);
  }

  .csp__headline {
    font-size: clamp(1.75rem, 2.5vw, 2.5rem);
    font-weight: 800;
    letter-spacing: -0.03em;
    line-height: 1.15;
    color: var(--color-primary, #0a0a0a);
    margin-bottom: 0.875rem;
  }

  .csp__sub {
    font-size: 1rem;
    color: var(--color-muted, #6b7280);
    line-height: 1.6;
    margin-bottom: 2rem;
  }

  .csp__field {
    margin-bottom: 1.25rem;
  }

  .csp__label {
    display: block;
    font-size: 0.8125rem;
    font-weight: 600;
    color: var(--color-primary, #0a0a0a);
    margin-bottom: 0.5rem;
  }

  .csp__req { color: var(--color-accent, #6366f1); margin-left: 0.2em; }

  .csp__input {
    width: 100%;
    padding: 0.75rem 1rem;
    border: 1.5px solid rgba(0,0,0,0.1);
    border-radius: var(--radius, 0.5rem);
    font-size: 0.9375rem;
    font-family: inherit;
    color: var(--color-primary, #0a0a0a);
    background: #fff;
    transition: border-color 0.15s;
    outline: none;
  }

  .csp__input:focus {
    border-color: var(--color-accent, #6366f1);
  }

  .csp__textarea {
    resize: vertical;
    min-height: 120px;
  }

  .csp__submit {
    width: 100%;
    background: var(--color-accent, #6366f1);
    color: #fff;
    border: none;
    border-radius: var(--radius, 0.5rem);
    padding: 0.9375rem;
    font-size: 1rem;
    font-weight: 700;
    font-family: inherit;
    cursor: pointer;
    transition: filter 0.2s, transform 0.2s;
    margin-top: 0.5rem;
  }

  .csp__submit:hover {
    filter: brightness(1.1);
    transform: translateY(-1px);
  }

  @media (max-width: 768px) {
    .csp__inner { grid-template-columns: 1fr; }
    .csp__photo { min-height: 280px; }
    .csp__form-col { padding: 2.5rem 1.5rem; }
  }
</style>

Props

Prop Type Default Beschrijving
headline * string Formulier titel
imageSrc * string Cover foto URL
sub string Ondertitel
phone string Telefoonnummer in overlay
email string E-mail in overlay
address string Adres in overlay

* = verplicht