← alle entries

footer-taped

FC-0055footerprimitivev1.0.0

Voettekst als een vel dat met twee stukken plakband op de pagina zit: merk + tagline, kolommen met links en 'binnenkort'-labels, daaronder copyright, juridische links en sociale iconen.

zelfde component — andere tokens
blocks/footer-taped/footer-taped.tsx
/**
 * FooterTaped — een voettekst als een vel dat met twee stukken plakband op
 * de pagina zit: merk + tagline links, kolommen met links (en 'binnenkort'-
 * labels voor wat er nog komt), daaronder de regel met copyright, juridische
 * links en sociale iconen.
 *
 * De bron was één bedrijf (ActivationLed) met ingebakken links, lucide-
 * iconen en #222222-plakband. Hier: alles data van het project, iconen als
 * slot, plakband in currentColor op --foreground. Links via
 * `linkComponent` (a, of Next Link).
 */

import type * as React from "react";

export interface FooterLink {
  label: string;
  href: string;
  /** 'binnenkort': niet klikbaar, met een scheef labeltje. */
  soon?: boolean;
}

export interface FooterColumn {
  title: string;
  soon?: boolean;
  links: readonly FooterLink[];
}

export interface FooterSocial {
  label: string;
  href: string;
  icon: React.ReactNode;
}

export interface FooterTapedProps extends React.HTMLAttributes<HTMLElement> {
  brand: React.ReactNode;
  tagline?: React.ReactNode;
  columns: readonly FooterColumn[];
  copyright?: React.ReactNode;
  legal?: readonly FooterLink[];
  socials?: readonly FooterSocial[];
  soonLabel?: string;
  linkComponent?: React.ElementType;
}

/* Het plakband: één vorm, tweemaal geplaatst, in de tekstkleur. */
function Tape({ className }: { className?: string }) {
  return (
    <svg aria-hidden="true" className={className} viewBox="0 0 95 80" width="80" height="36" fill="none">
      <path d="M1 45L70.282 5L88.282 36.177L19 76.177L1 45Z" fill="currentColor" />
    </svg>
  );
}

export function FooterTaped({ brand, tagline, columns, copyright, legal = [], socials = [], soonLabel = "binnenkort", linkComponent: Link = "a", className, ...rest }: FooterTapedProps) {
  const Soon = ({ draai }: { draai: 1 | -1 }) => (
    <span className="fp-foot__soon" style={{ transform: `rotate(${3 * draai}deg)` }}>{soonLabel}</span>
  );
  return (
    <footer {...rest} className={["fp-foot", className].filter(Boolean).join(" ")}>
      <div className="fp-foot__vel">
        <Tape className="fp-foot__tape fp-foot__tape--links" />
        <Tape className="fp-foot__tape fp-foot__tape--rechts" />
        <div className="fp-foot__merk">
          <div className="fp-foot__naam">{brand}</div>
          {tagline && <p className="fp-foot__tagline">{tagline}</p>}
        </div>
        <div className="fp-foot__kolommen">
          {columns.map((k, ki) => (
            <div key={k.title} className="fp-foot__kolom">
              <h4 className="fp-foot__kop">
                {k.title}
                {k.soon && <Soon draai={ki % 2 ? -1 : 1} />}
              </h4>
              <ul className="fp-foot__links">
                {k.links.map((l, li) => (
                  <li key={l.label}>
                    {l.soon || k.soon ? (
                      <span className="fp-foot__link fp-foot__link--uit">{l.label}{l.soon && <Soon draai={li % 2 ? 1 : -1} />}</span>
                    ) : (
                      <Link href={l.href} className="fp-foot__link">{l.label}</Link>
                    )}
                  </li>
                ))}
              </ul>
            </div>
          ))}
        </div>
      </div>
      <div className="fp-foot__onder">
        <div className="fp-foot__onder-links">
          {copyright && <p className="fp-foot__copy">{copyright}</p>}
          {legal.length > 0 && (
            <ul className="fp-foot__legal">
              {legal.map((l) => (
                <li key={l.label}><Link href={l.href} className="fp-foot__legal-link">{l.label}</Link></li>
              ))}
            </ul>
          )}
        </div>
        {socials.length > 0 && (
          <ul className="fp-foot__social">
            {socials.map((s) => (
              <li key={s.label}>
                <a href={s.href} target="_blank" rel="noopener noreferrer" aria-label={s.label} className="fp-foot__social-link">{s.icon}</a>
              </li>
            ))}
          </ul>
        )}
      </div>
    </footer>
  );
}
blocks/footer-taped/footer-taped.css
/** FooterTaped — importeren in de app-root (registry-site: app/globals.css). */

.fp-foot { width: 100%; max-width: 64rem; margin: 2rem auto; color: var(--foreground); }
.fp-foot__vel {
  position: relative;
  display: flex; flex-direction: column; gap: 2rem;
  padding: 2.5rem 1.5rem;
  background: var(--card); color: var(--card-foreground, var(--foreground));
  border: 1px solid var(--border); border-radius: calc(var(--radius) * 2);
}
@media (min-width: 768px) { .fp-foot__vel { flex-direction: row; justify-content: space-between; padding: 2.5rem 2.5rem; } }

/* Het plakband: tekstkleur op 85%, schuin over de hoeken. */
.fp-foot__tape { position: absolute; top: -1rem; display: none; color: color-mix(in srgb, var(--foreground) 85%, transparent); }
@media (min-width: 768px) { .fp-foot__tape { display: block; } }
.fp-foot__tape--links { left: -2rem; transform: scale(0.75); }
.fp-foot__tape--rechts { right: -2rem; transform: rotate(90deg) scale(0.75); }

.fp-foot__merk { display: flex; flex-direction: column; gap: 0.5rem; max-width: 20rem; }
.fp-foot__naam { font-size: 1.5rem; font-weight: 800; letter-spacing: -0.01em; }
.fp-foot__tagline { margin: 0; font-weight: 500; color: var(--muted-foreground); }

.fp-foot__kolommen { display: flex; flex-direction: column; gap: 1.5rem; }
@media (min-width: 768px) { .fp-foot__kolommen { flex-direction: row; gap: 4rem; } }
.fp-foot__kolom { display: flex; flex-direction: column; gap: 0.5rem; }
@media (min-width: 768px) { .fp-foot__kolom { gap: 1rem; } }
.fp-foot__kop { margin: 0; display: flex; align-items: center; gap: 0.375rem; font: inherit; font-size: 0.875rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.04em; color: var(--muted-foreground); white-space: nowrap; }
.fp-foot__links { list-style: none; margin: 0; padding: 0; display: flex; flex-wrap: wrap; gap: 0.5rem 1rem; }
@media (min-width: 768px) { .fp-foot__links { flex-direction: column; } }
.fp-foot__link { display: inline-flex; align-items: center; gap: 0.375rem; font-size: 0.875rem; font-weight: 500; color: var(--muted-foreground); text-decoration: none; white-space: nowrap; transition: color var(--motion-instant-duration) var(--motion-instant); }
a.fp-foot__link:hover { color: var(--foreground); }
.fp-foot__link--uit { cursor: default; }
.fp-foot__soon { display: inline-flex; padding: 0.125rem 0.625rem; font-size: 0.6875rem; font-weight: 500; text-transform: none; letter-spacing: 0; color: var(--muted-foreground); background: var(--muted); border-radius: 999px; }

.fp-foot__onder { display: flex; flex-direction: column; align-items: flex-start; gap: 1rem; margin: 0.75rem 0; padding: 0 1rem; font-size: 0.875rem; color: var(--muted-foreground); }
@media (min-width: 768px) { .fp-foot__onder { flex-direction: row; align-items: center; justify-content: space-between; padding: 0 2rem; } }
.fp-foot__onder-links { display: flex; flex-direction: column; gap: 0.5rem; }
@media (min-width: 640px) { .fp-foot__onder-links { flex-direction: row; align-items: center; gap: 2rem; } }
.fp-foot__copy { margin: 0; white-space: nowrap; }
.fp-foot__legal { list-style: none; margin: 0; padding: 0; display: flex; gap: 1rem; }
.fp-foot__legal-link { color: inherit; text-decoration: none; transition: color var(--motion-instant-duration) var(--motion-instant); }
.fp-foot__legal-link:hover { color: var(--foreground); }
.fp-foot__social { list-style: none; margin: 0; padding: 0; display: flex; align-items: center; gap: 1rem; }
.fp-foot__social-link { display: flex; width: 1.25rem; height: 1.25rem; color: inherit; transition: color var(--motion-instant-duration) var(--motion-instant); }
.fp-foot__social-link:hover { color: var(--foreground); }