Salta ai contenuti

Componenti e isole

Componente Tipo Props principali
BaseLayout.astro layout title, description, image
Navbar.astro struttura legge site.nav e site.cta
Footer.astro struttura legge getSiteSettings() e site
Hero.astro sezione title, subtitle, eyebrow, image, cta, variant (home/page)
SectionHeader.astro sezione eyebrow, title, intro, action, align
Faq.astro sezione items: { q, a }[]
PricingTable.astro sezione rows: Tariffa[], testi opzionali
PromoCard.astro card promo: Promo
Gallery.astro sezione items: Spazio[]
Team.astro sezione members: Collaboratore[]
Timeline.astro sezione entries: TimelineEntry[]
WhatsAppButton.astro utility whatsapp, label (si nasconde se vuoto)
AccessibilityBadge.astro utility label
PartnerCarousel.tsx isola partners: { name, img, url? }[]
EventFilters.tsx isola events: Evento[], categories: string[]
ContactForm.tsx isola subjects, privacyUrl, heading
CookieBanner.tsx isola cookieUrl
ConsentMap.tsx isola src, title, mapClass

Tutti gli stili sono token semantici: vedi Design system.

Regola: se non c’è stato né evento, non è un’isola.

Serve Scelta
Solo HTML/CSS, anche con <details> o anchor componente Astro
Stato locale, filtri, submit, localStorage isola Preact
Serve subito al primo paint client:load
Sta sotto la piega client:visible

Nel template:

  • CookieBanner → client:load (deve comparire subito).
  • ContactForm, EventFilters, PartnerCarousel, ConsentMap → client:visible.
  • Tutto il resto è HTML statico.

ContactForm fa:

  1. validazione client (nome, email, messaggio, consenso privacy);
  2. precompila l’oggetto da query string (?servizio=, ?evento=);
  3. POST a ${PUBLIC_WORDPRESS_API_BASE_URL}/wp-json/headless/v1/contact;
  4. mostra errori di campo restituiti dal server o un errore generico;
  5. invia un campo honeypot (azienda) che gli utenti non vedono.

Il server (mu-plugin) rivalida tutto, applica un rate limit di 60 secondi per IP, salva il messaggio nel CPT contatto e invia una mail. Il client non è mai la fonte di verità.

  • lib/consent.ts gestisce una chiave in localStorage (site-cookie-consent) e due eventi: site:consent e site:open-preferences.
  • CookieBanner si apre se non c’è scelta; il footer riapre le preferenze.
  • ConsentMap non carica nulla da Google finché non c’è consenso esplicito.
  • Nessuno script esterno, nessuna CMP di terze parti.

EventFilters riceve eventi già filtrati dalla build e le categorie da site.eventCategories. Il filtro è client-side: nessuna richiesta al server, nessun caricamento progressivo. Mostra:

  • il prossimo evento in evidenza;
  • la griglia dei prossimi;
  • l’archivio dei passati, se presente.

Le categorie devono combaciare tra WordPress (cat) e src/data/site.ts.

Marquee continuo con pausa on hover. Con prefers-reduced-motion diventa una lista scrollabile da tastiera. Il componente calcola quante copie servono per coprire il contenitore, quindi non lascia buchi a destra.

  1. Il componente riceve dati tipizzati (mai any).
  2. Non fa fetch: i dati arrivano dalla pagina via getCollection.
  3. Usa i token, non colori.
  4. Prevede lo stato vuoto: se non ci sono dati, la sezione non compare.
  5. Se serve interattività, valuta se basta HTML nativo (<details>, <dialog>, form).
  6. Aggiungi la sezione alla pagina e, se vuoi, alla home.
---
import type { Testimonianza } from "../domain/Testimonianza";
interface Props {
items: Testimonianza[];
}
const { items } = Astro.props;
---
{items.length > 0 && (
<section class="bg-panel py-24">
<div class="mx-auto max-w-7xl px-6">
<div class="grid gap-8 md:grid-cols-2">
{items.map((item) => (
<blockquote class="rounded-2xl border border-line bg-surface p-8 text-muted">
“{item.quote}”
<footer class="mt-4 text-sm font-bold text-fg">{item.author}</footer>
</blockquote>
))}
</div>
</div>
</section>
)}