Componenti
In questa pagina
sitelo-ui è una libreria di componenti per sitelo. Ogni componente è una funzione che restituisce una stringa di HTML, quindi si annida direttamente in un albero javascript-to-html senza nulla in mezzo — nessun compilatore, nessun runtime, nessuna idratazione. Quello che costruisci è quello che finisce in dist/.
Arriva insieme a sitelo, sotto il punto di ingresso sitelo/ui.
Avvio rapido
npm install sitelo javascript-to-htmlMetti styles() nella head e chiama i componenti nel body. La configurazione è tutta qui:
import { body, head, html, meta, title } from 'javascript-to-html'
import { styles, container, stack, heading, text, button } from 'sitelo/ui'
export default () => html({ lang: 'it' },
head(
meta({ charset: 'utf-8' }),
meta({ name: 'viewport', content: 'width=device-width, initial-scale=1' }),
title('Il mio sito'),
styles(),
),
body(
container({ size: 'md' },
stack({ gap: 'md' },
heading({ level: 1 }, 'Ciao'),
text({ variant: 'lead' }, 'Una pagina costruita con i componenti.'),
button({ href: '/docs' }, 'Leggi la documentazione'),
),
),
),
)I nomi dei componenti combaciano di proposito con ciò che renderizzano, il che significa che alcuni di essi — button, input, table, link, code, select, progress — vanno a sbattere contro le funzioni-elemento di javascript-to-html. Importa la libreria come namespace quando ti servono entrambe:
import * as ui from 'sitelo/ui'
ui.card(
ui.cardHeader({ title: 'Routing', subtitle: 'Basato sui file' }),
ui.cardBody(ui.text('src/about.ht.js diventa /about.')),
ui.cardFooter({ divided: true }, ui.button({ size: 'sm' }, 'Leggi di più')),
)La convenzione di chiamata
Ogni componente prende un oggetto di props facoltativo seguito dai figli, esattamente come un elemento di javascript-to-html. Le props che il componente conosce vengono consumate per nome; tutto il resto passa all’elemento renderizzato come attributo, quindi id, data-*, aria-* e gli attributi di evento funzionano senza che la libreria debba elencarli:
button({ id: 'save', 'data-analytics': 'save-click', onclick: 'save()' }, 'Salva')
// <button type="button" id="save" data-analytics="save-click" onclick="save()" class="su-btn …">Una prop di cui il componente non riconosce il valore — variant: 'nonsense' — ripiega sul valore predefinito invece di sollevare un errore. Un refuso estetico non deve far fallire una build.
Stili
styles() restituisce un <link> a un solo file, che il browser tiene in cache fra una pagina e l’altra; il plugin di sitelo lo serve in sviluppo e lo scrive nella build, sotto un nome con hash del contenuto che puoi servire come immutable. { inline: true } restituisce invece uno <style> che contiene l’intero foglio — circa 11 kB sulla rete, nessuna richiesta in più, e niente che possa sparire da dist/:
import { styles } from 'sitelo/ui'
head(
// tutto il foglio di stile nella pagina — nessuna richiesta
styles({ inline: true }),
)Il foglio copre ogni componente della libreria, e un sito ne usa una manciata. pruneCss: true in sitelo.config.js scrive solo le regole che la build riesce ad agganciare: il plugin legge le classi su- dalle pagine che ha appena scritto — e dagli script accanto a esse, per le classi che un toast o uno step aggiungono dopo — e scarta ogni regola il cui selettore ne nomina una che nessuno porta. Si decide dall’output più che dagli import, quindi button({ variant: 'soft' }) tiene .su-btn--soft e un semplice button() no. Un foglio collegato viene potato rispetto a tutto il sito e riceve un nuovo hash, con ogni <link> riscritto di conseguenza; uno incorporato viene potato sulla sua sola pagina. Una dozzina di componenti fa 2–3 kB gzippati. Lo sviluppo serve il foglio intero; il markup che la build non vede mai, come quello di un’island server, ha bisogno che le sue classi siano nominate in keep:
export default {
pruneCss: true,
// oppure indica classi che la build non vede mai; `*` per un prefisso
// pruneCss: { keep: ['su-card', 'su-btn*'] },
}Temi
Tutto è guidato da proprietà personalizzate CSS su :root — cinque palette, una scala di spaziature, raggi, caratteri e ombre. theme() scrive i loro override, e accetta nomi in camelCase (radiusMd → --su-radius-md), oggetti palette, oppure proprietà personalizzate letterali:
import { styles, theme } from 'sitelo/ui'
head(
styles(),
// Dopo styles(), così vincono queste.
theme({
primary: { base: '#5b5bd6', hover: '#4a4ac4', fg: '#ffffff' },
radiusMd: '2px',
fontSans: '"Inter", system-ui, sans-serif',
}, {
dark: { primary: { base: '#8f8ff0' } },
}),
)La modalità scura si risolve da sola a partire da prefers-color-scheme. Impostare data-theme o data-su-theme a light o dark su un qualunque antenato la scavalca — ed è quello che fa themeToggle():
import { styles, themeScript, themeToggle } from 'sitelo/ui'
head(
themeScript(), // applica la scelta salvata prima del primo paint
styles(),
)
// …in qualunque punto del body
themeToggle()Oppure cambia tutto l’aspetto in un colpo solo: styles({ preset: 'neumorphism' }) collega il foglio di un preset subito dopo quello principale, ogni componente lo segue e theme() continua a funzionare sopra. Lo vedi dal vivo nella pagina Neumorfismo.
JavaScript, e quanto poco ce n’è
Alla maggior parte dei componenti non serve affatto. Il modale e il pannello laterale sono elementi popover, quindi apertura, sfondo, clic fuori ed Escape li gestisce il browser. La fisarmonica è <details name>. I menu sono <details>. I tooltip sono CSS. Le schede i cui pannelli si scambiano sul posto sono un gruppo di radio: ogni scheda è una <label>, e il pannello che segue la radio selezionata è quello che il CSS mostra.
Tre cose vogliono uno script, e ciascuna se lo va a prendere da sé:
- il pulsante di chiusura di un avviso richiudibile
- la chiusura di un menu con un clic fuori o con Escape
- il cambio di tema
Non c’è niente da aggiungere al tuo file di ingresso — l’import è l’attributo di evento:
<!-- rendered by alert({ dismissible: true }) -->
<button class="su-alert-dismiss"
onclick="import('/su/alert.js').then(m=>m.dismiss(this))">
×
</button>sitelo serve quei moduli da /su/ mentre sviluppi, e copia nella build quelli che le tue pagine referenziano davvero. Ognuno sta ben sotto il kilobyte, nessuno viene scaricato prima della prima interazione, e ogni componente si renderizza correttamente finché non lo è: i menu si aprono e si chiudono da soli, il pulsante di chiusura non fa nulla.
L’eccezione è toast(), perché non c’è niente nella pagina che lo faccia scattare al posto tuo:
// src/main.js
import { toast } from 'sitelo/ui/client'Esempi
I form collegano da sé le proprie etichette, gli id, i testi di aiuto e i messaggi di errore:
import { card, cardBody, cardFooter, button, stack, textField, selectField } from 'sitelo/ui'
card(
cardBody(
stack({ gap: 'md' },
textField({ label: 'Email', name: 'email', type: 'email', help: 'Mai condivisa.' }),
textField({ label: 'Sito', name: 'site', startAdornment: 'https://', error: 'Non è un URL.' }),
selectField({ label: 'Piano', name: 'plan', options: ['Gratuito', 'Pro'], value: 'Pro' }),
),
),
cardFooter({ divided: true }, button({ type: 'submit' }, 'Salva')),
)Un modale è un popover e il suo innesco è un qualunque pulsante che punta al suo id:
import { button, modal } from 'sitelo/ui'
button({ popovertarget: 'confirm' }, 'Elimina…')
modal({
id: 'confirm',
title: 'Eliminare questa pagina?',
footer: button({ color: 'danger' }, 'Elimina'),
}, 'L’operazione non può essere annullata.')Le schede arrivano in due forme — link, oppure pannelli:
// Schede-link: una pagina per scheda, nessuno script.
tabs({ items: [
{ label: 'Documentazione', href: '/docs', active: true },
{ label: 'API', href: '/api' },
] })
// Schede-pannello: scambio sul posto, con un gruppo di radio e CSS.
tabs({ value: 'use', items: [
{ id: 'install', label: 'Installazione', panel: code('npm install sitelo') },
{ id: 'use', label: 'Uso', panel: code("import * as ui from 'sitelo/ui'") },
] })Le tabelle prendono columns e rows, con una funzione render ovunque una cella abbia bisogno di più di un valore:
table({
striped: true,
columns: [
{ key: 'page', header: 'Pagina' },
{ key: 'size', header: 'Dimensione', align: 'end' },
{ header: 'Stato', render: (row) => chip({ color: row.ok ? 'success' : 'danger' }, row.ok ? 'ok' : 'fallita') },
],
rows: pages,
})La cartella examples/ui nel repository renderizza ogni componente su una sola pagina — è il modo più rapido per vedere tutto l’insieme.
Riferimento dei componenti
Ogni export, per gruppo. Le props sono tipizzate: sitelo/ui include file .d.ts, quindi un editor completa variant, color e size al posto tuo tanto in JavaScript quanto in TypeScript.
| Gruppo | Componenti |
|---|---|
| Layout | container, stack, grid, divider, card, cardHeader, cardTitle, cardSubtitle, cardMedia, cardBody, cardFooter, aspectRatio |
| Tipografia | text, heading, link, code, inlineCode, kbd, visuallyHidden, prose |
| Campi | button, iconButton, buttonGroup, field, input, textarea, select, textField, textareaField, selectField, checkbox, radio, toggle, choiceGroup, slider, sliderField, toggleButton, toggleGroup |
| Visualizzazione dati | avatar, avatarGroup, badge, chip, tooltip, table, list, listItem, figure |
| Feedback | alert, progress, skeleton, toasts, empty |
| Navigazione | breadcrumbs, pagination, tabs, appBar, appBarNav, appBarSpacer, appBarActions, navLink, themeToggle |
| Sovrapposizioni | modal, drawer, closeButton, menu, menuItem, menuSeparator, accordion, accordionItem, collapsible |
| Sezioni | hero, footer, siteFooter, footerColumn, footerBottom, stat, statGroup, steps, timeline, timelineItem, mockup |
| Stile | styles, stylesheet, stylesUrl, theme, themeScript |
Due nomi si discostano da quel che ti aspetteresti: l’interruttore è toggle, perché switch è una parola riservata e non può essere un binding di import; e l’ancora con stile è esportata sia come link sia come textLink, così può stare accanto al link di javascript-to-html. table, input, select e progress hanno la stessa via d’uscita: dataTable, textInput, selectField, progressBar.
Extra
Un secondo punto di ingresso, sitelo/ui-extras, contiene i componenti che non sono per tutti — texture ed effetti, a cominciare da una grana da pellicola. Ognuno porta un foglio di stile suo, grainStyles() accanto a styles(), così una pagina collega solo ciò che usa, e sitelo/ui-extras/client porta le loro chiamate lato pagina. Sono catalogati in sitelo UI extras.