Composants
Sur cette page
sitelo-ui est une bibliothèque de composants pour sitelo. Chaque composant est une fonction qui renvoie une chaîne de HTML : il s’imbrique donc directement dans un arbre javascript-to-html, sans rien entre les deux — pas de compilateur, pas de runtime, pas d’hydratation. Ce que vous écrivez est exactement ce qui atterrit dans dist/.
Elle est livrée avec sitelo, sous le point d’entrée sitelo/ui.
Démarrage
npm install sitelo javascript-to-htmlPlacez styles() dans le head et appelez les composants dans le body. C’est toute la configuration :
import { body, head, html, meta, title } from 'javascript-to-html'
import { styles, container, stack, heading, text, button } from 'sitelo/ui'
export default () => html({ lang: 'fr' },
head(
meta({ charset: 'utf-8' }),
meta({ name: 'viewport', content: 'width=device-width, initial-scale=1' }),
title('Mon site'),
styles(),
),
body(
container({ size: 'md' },
stack({ gap: 'md' },
heading({ level: 1 }, 'Bonjour'),
text({ variant: 'lead' }, 'Une page construite à partir de composants.'),
button({ href: '/docs' }, 'Lire la documentation'),
),
),
),
)Les noms des composants correspondent volontairement à ce qu’ils rendent, ce qui fait que quelques-uns — button, input, table, link, code, select, progress — entrent en collision avec les fonctions d’élément de javascript-to-html. Importez la bibliothèque comme espace de noms quand vous avez besoin des deux :
import * as ui from 'sitelo/ui'
ui.card(
ui.cardHeader({ title: 'Routage', subtitle: 'Basé sur les fichiers' }),
ui.cardBody(ui.text('src/about.ht.js devient /about.')),
ui.cardFooter({ divided: true }, ui.button({ size: 'sm' }, 'En savoir plus')),
)La convention d’appel
Chaque composant prend un objet de props optionnel suivi de ses enfants, exactement comme un élément javascript-to-html. Les props que le composant comprend sont consommées par leur nom ; tout le reste est transmis à l’élément rendu sous forme d’attribut, si bien que id, data-*, aria-* et les attributs d’événement fonctionnent sans que la bibliothèque ait à les énumérer :
button({ id: 'save', 'data-analytics': 'save-click', onclick: 'save()' }, 'Enregistrer')
// <button type="button" id="save" data-analytics="save-click" onclick="save()" class="su-btn …">Une prop dont le composant ne reconnaît pas la valeur — variant: 'nonsense' — retombe sur la valeur par défaut au lieu de lever une erreur. Une faute de frappe cosmétique ne devrait pas faire échouer un build.
Styles
styles() renvoie un élément <style> contenant toute la feuille, minifiée. Cela représente environ 7 ko sur le réseau et ne peut pas disparaître de dist/ : c’est pourquoi c’est le comportement par défaut. Si vous préférez la lier une seule fois et laisser le navigateur la mettre en cache d’une page à l’autre, importez le CSS depuis un fichier d’entrée groupé et Vite l’émettra :
// src/main.js — inclus dans le bundle par Vite, mis en cache entre les pages
import 'sitelo/ui/styles.css'Utilisez l’un ou l’autre, pas les deux.
Thèmes
Tout repose sur des propriétés personnalisées CSS déclarées sur :root : cinq palettes, une échelle d’espacement, des rayons, la typographie et les ombres. theme() écrit leurs surcharges et accepte des noms en camelCase (radiusMd → --su-radius-md), des objets de palette ou des propriétés personnalisées littérales :
import { styles, theme } from 'sitelo/ui'
head(
styles(),
// Après styles(), pour que ces valeurs l’emportent.
theme({
primary: { base: '#5b5bd6', hover: '#4a4ac4', fg: '#ffffff' },
radiusMd: '2px',
fontSans: '"Inter", system-ui, sans-serif',
}, {
dark: { primary: { base: '#8f8ff0' } },
}),
)Le mode sombre se résout tout seul à partir de prefers-color-scheme. Donner à data-theme ou data-su-theme la valeur light ou dark sur n’importe quel ancêtre l’emporte — c’est exactement ce que fait themeToggle() :
import { styles, themeScript, themeToggle } from 'sitelo/ui'
head(
themeScript(), // applique le choix enregistré avant le premier rendu
styles(),
)
// …n’importe où dans le body
themeToggle()JavaScript, et le peu qu’il en faut
La plupart des composants n’en ont pas besoin. La modale et le tiroir sont des éléments popover : le navigateur gère l’ouverture, l’arrière-plan, le clic à l’extérieur et Échap. L’accordéon est un <details name>. Les menus sont des <details>. Les infobulles sont en CSS.
Quatre choses réclament vraiment un script, et chacune va le chercher elle-même :
- les onglets dont les panneaux changent sur place
- le bouton de fermeture d’une alerte que l’on peut masquer
- la fermeture d’un menu par un clic à l’extérieur ou Échap
- le bouton de bascule de thème
Rien à ajouter à votre fichier d’entrée : l’import est l’attribut d’événement :
<!-- rendered by alert({ dismissible: true }) -->
<button class="su-alert-dismiss"
onclick="import('/su/alert.js').then(m=>m.dismiss(this))">
×
</button>sitelo sert ces modules depuis /su/ pendant le développement et copie dans la build uniquement ceux que vos pages référencent réellement. Chacun pèse bien moins d’un kilo-octet, aucun n’est chargé avant la première interaction, et jusque-là chaque composant s’affiche correctement : les onglets à panneaux montrent celui que le serveur a marqué actif, les menus s’ouvrent et se ferment seuls, et le bouton de fermeture ne fait rien.
L’exception est toast(), car rien sur la page ne le déclenche pour vous :
// src/main.js
import { toast } from 'sitelo/ui/client'Exemples
Les formulaires relient eux-mêmes leurs libellés, leurs ids, leurs textes d’aide et leurs messages d’erreur :
import { card, cardBody, cardFooter, button, stack, textField, selectField } from 'sitelo/ui'
card(
cardBody(
stack({ gap: 'md' },
textField({ label: 'E-mail', name: 'email', type: 'email', help: 'Jamais partagé.' }),
textField({ label: 'Site', name: 'site', startAdornment: 'https://', error: 'Ce n’est pas une URL.' }),
selectField({ label: 'Formule', name: 'plan', options: ['Gratuit', 'Pro'], value: 'Pro' }),
),
),
cardFooter({ divided: true }, button({ type: 'submit' }, 'Enregistrer')),
)Une modale est un popover, et son déclencheur est n’importe quel bouton qui pointe vers son id :
import { button, modal } from 'sitelo/ui'
button({ popovertarget: 'confirm' }, 'Supprimer…')
modal({
id: 'confirm',
title: 'Supprimer cette page ?',
footer: button({ color: 'danger' }, 'Supprimer'),
}, 'Cette action est irréversible.')Les onglets existent sous deux formes : des liens, ou des panneaux.
// Onglets liens : une page par onglet, aucun script.
tabs({ items: [
{ label: 'Docs', href: '/docs', active: true },
{ label: 'API', href: '/api' },
] })
// Onglets à panneaux : échange sur place, et ils vont chercher le code eux-mêmes.
tabs({ value: 'use', items: [
{ id: 'install', label: 'Installer', panel: code('npm install sitelo') },
{ id: 'use', label: 'Utiliser', panel: code("import * as ui from 'sitelo/ui'") },
] })Les tableaux prennent columns et rows, avec une fonction render partout où une cellule demande plus qu’une valeur :
table({
striped: true,
columns: [
{ key: 'page', header: 'Page' },
{ key: 'size', header: 'Taille', align: 'end' },
{ header: 'Statut', render: (row) => chip({ color: row.ok ? 'success' : 'danger' }, row.ok ? 'ok' : 'échec') },
],
rows: pages,
})Le dossier examples/ui du dépôt affiche tous les composants sur une seule page — c’est le moyen le plus rapide de voir l’ensemble.
Référence des composants
Tous les exports, par groupe. Les props sont typées : sitelo/ui livre des fichiers .d.ts, de sorte qu’un éditeur complète variant, color et size aussi bien en JavaScript qu’en TypeScript.
| Groupe | Composants |
|---|---|
| Mise en page | container, stack, grid, divider, card, cardHeader, cardTitle, cardSubtitle, cardMedia, cardBody, cardFooter, aspectRatio |
| Typographie | text, heading, link, code, inlineCode, kbd, visuallyHidden, prose |
| Champs | button, iconButton, buttonGroup, field, input, textarea, select, textField, textareaField, selectField, checkbox, radio, toggle, choiceGroup, slider, sliderField, toggleButton, toggleGroup |
| Affichage de données | avatar, avatarGroup, badge, chip, tooltip, table, list, listItem, figure |
| Retour d’information | alert, progress, spinner, skeleton, toasts, empty |
| Navigation | breadcrumbs, pagination, tabs, appBar, appBarNav, appBarSpacer, appBarActions, navLink, themeToggle |
| Superpositions | modal, drawer, closeButton, menu, menuItem, menuSeparator, accordion, accordionItem, collapsible |
| Sections | hero, footer, siteFooter, footerColumn, footerBottom, stat, statGroup, steps, timeline, timelineItem, mockup |
| Styles | styles, stylesheet, theme, themeScript |
Deux noms s’écartent de ce à quoi on s’attend : l’interrupteur s’appelle toggle, parce que switch est un mot réservé et ne peut pas servir de liaison d’import ; et le lien stylé est exporté à la fois comme link et comme textLink, pour cohabiter avec le link de javascript-to-html. table, input, select et progress disposent de la même échappatoire : dataTable, textInput, selectField, progressBar.