Komponenty
Na tej stronie
sitelo-ui to biblioteka komponentów dla sitelo. Każdy komponent jest funkcją zwracającą ciąg HTML, więc zagnieżdża się wprost w drzewie javascript-to-html bez niczego po drodze — bez kompilatora, bez runtime’u, bez hydratacji. To, co budujesz, ląduje w dist/.
Jest dołączone do sitelo, pod punktem wejścia sitelo/ui.
Szybki start
npm install sitelo javascript-to-htmlWstaw styles() do head i wywołuj komponenty w body. To cała konfiguracja:
import { body, head, html, meta, title } from 'javascript-to-html'
import { styles, container, stack, heading, text, button } from 'sitelo/ui'
export default () => html({ lang: 'pl' },
head(
meta({ charset: 'utf-8' }),
meta({ name: 'viewport', content: 'width=device-width, initial-scale=1' }),
title('Moja strona'),
styles(),
),
body(
container({ size: 'md' },
stack({ gap: 'md' },
heading({ level: 1 }, 'Cześć'),
text({ variant: 'lead' }, 'Strona zbudowana z komponentów.'),
button({ href: '/docs' }, 'Przeczytaj dokumentację'),
),
),
),
)Nazwy komponentów celowo odpowiadają temu, co renderują, co oznacza, że kilka z nich — button, input, table, link, code, select, progress — zderza się z funkcjami elementów z javascript-to-html. Gdy potrzebujesz obu, zaimportuj bibliotekę jako przestrzeń nazw:
import * as ui from 'sitelo/ui'
ui.card(
ui.cardHeader({ title: 'Routing', subtitle: 'Oparty na plikach' }),
ui.cardBody(ui.text('src/about.ht.js staje się /about.')),
ui.cardFooter({ divided: true }, ui.button({ size: 'sm' }, 'Czytaj dalej')),
)Konwencja wywołania
Każdy komponent przyjmuje opcjonalny obiekt propsów, a po nim dzieci — dokładnie jak element javascript-to-html. Propsy, które komponent rozumie, są konsumowane po nazwie; cała reszta przechodzi na renderowany element jako atrybut, więc id, data-*, aria-* i atrybuty zdarzeń działają bez tego, żeby biblioteka musiała je wyliczać:
button({ id: 'save', 'data-analytics': 'save-click', onclick: 'save()' }, 'Zapisz')
// <button type="button" id="save" data-analytics="save-click" onclick="save()" class="su-btn …">Props, którego wartości komponent nie rozpoznaje — variant: 'nonsense' — wraca do wartości domyślnej, zamiast rzucać błędem. Kosmetyczna literówka nie powinna przerywać buildu.
Style
styles() zwraca <link> do jednego pliku, który przeglądarka buforuje na wszystkich stronach; wtyczka sitelo serwuje go w dev i zapisuje do buildu, pod nazwą z hashem treści, którą możesz serwować jako immutable. { inline: true } zwraca zamiast tego <style> z całym arkuszem — około 11 kB w sieci, żadnego dodatkowego żądania i nic, co mogłoby zginąć z dist/:
import { styles } from 'sitelo/ui'
head(
// cały arkusz w stronie — żadnego żądania
styles({ inline: true }),
)Arkusz obejmuje każdy komponent biblioteki, a witryna używa garstki. pruneCss: true w sitelo.config.js zapisuje tylko te reguły, które build potrafi dopasować: wtyczka czyta klasy su- ze stron, które właśnie zapisała — i ze skryptów obok nich, dla klas dokładanych później przez powiadomienie albo krok — i odrzuca każdą regułę, której selektor wymienia klasę, której nikt nie nosi. Decyduje o tym wynik, a nie importy, więc button({ variant: 'soft' }) zachowuje .su-btn--soft, a zwykły button() nie. Podlinkowany arkusz jest przycinany względem całej witryny i dostaje nowy hash, a każdy <link> zostaje przepisany; wbudowany przycina się do własnej strony. Kilkanaście komponentów to 2–3 kB po gzipie. Dev serwuje cały arkusz; znaczniki, których build nigdy nie widzi — na przykład z wyspy serwerowej — wymagają wymienienia swoich klas w keep:
export default {
pruneCss: true,
// albo wskaż klasy, których build nigdy nie widzi; `*` dla prefiksu
// pruneCss: { keep: ['su-card', 'su-btn*'] },
}Motywy
Wszystkim sterują własne właściwości CSS na :root — pięć palet, skala odstępów, promienie, krój i cienie. theme() zapisuje dla nich nadpisania i przyjmuje nazwy w camelCase (radiusMd → --su-radius-md), obiekty palet albo dosłowne właściwości własne:
import { styles, theme } from 'sitelo/ui'
head(
styles(),
// Po styles(), więc te wygrywają.
theme({
primary: { base: '#5b5bd6', hover: '#4a4ac4', fg: '#ffffff' },
radiusMd: '2px',
fontSans: '"Inter", system-ui, sans-serif',
}, {
dark: { primary: { base: '#8f8ff0' } },
}),
)Tryb ciemny rozstrzyga się sam na podstawie prefers-color-scheme. Ustawienie data-theme albo data-su-theme na light lub dark na dowolnym przodku to nadpisuje — i właśnie to robi themeToggle():
import { styles, themeScript, themeToggle } from 'sitelo/ui'
head(
themeScript(), // stosuje zapisany wybór przed pierwszym malowaniem
styles(),
)
// …gdziekolwiek w body
themeToggle()Albo zmień cały wygląd za jednym razem: styles({ preset: 'neumorphism' }) dołącza arkusz presetu zaraz po głównym, każdy komponent za nim podąża, a theme() dalej działa na wierzchu. Na żywo zobaczysz to na stronie Neumorfizm.
JavaScript i jak mało go tu jest
Większość komponentów nie potrzebuje go wcale. Okno modalne i szuflada to elementy popover, więc otwieraniem, tłem, kliknięciem poza i Escape zajmuje się przeglądarka. Akordeon to <details name>. Menu to <details>. Podpowiedzi to CSS. Zakładki, których panele podmieniają się w miejscu, to grupa radio: każda zakładka to <label>, a panel następujący po zaznaczonym radiu jest tym, który CSS pokazuje.
Trzy rzeczy chcą skryptu i każda sama po niego sięga:
- przycisk zamykający w alercie, który można zamknąć
- zamykanie menu kliknięciem poza nim albo Escape
- przełącznik motywu
Nie ma czego dodawać do pliku wejściowego — importem jest atrybut zdarzenia:
<!-- rendered by alert({ dismissible: true }) -->
<button class="su-alert-dismiss"
onclick="import('/su/alert.js').then(m=>m.dismiss(this))">
×
</button>sitelo serwuje te moduły z /su/ podczas pracy i kopiuje do buildu te, do których Twoje strony faktycznie się odwołują. Każdy waży znacznie poniżej kilobajta, żaden nie jest pobierany przed pierwszą interakcją, a każdy komponent renderuje się poprawnie, dopóki to nie nastąpi: menu otwierają się i zamykają same, a przycisk zamykania nic nie robi.
Wyjątkiem jest toast(), bo nic na stronie nie uruchamia go za Ciebie:
// src/main.js
import { toast } from 'sitelo/ui/client'Przykłady
Formularze same łączą swoje etykiety, identyfikatory, teksty pomocy i komunikaty błędów:
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: 'Nigdy nieudostępniany.' }),
textField({ label: 'Strona', name: 'site', startAdornment: 'https://', error: 'To nie jest URL.' }),
selectField({ label: 'Plan', name: 'plan', options: ['Darmowy', 'Pro'], value: 'Pro' }),
),
),
cardFooter({ divided: true }, button({ type: 'submit' }, 'Zapisz')),
)Okno modalne to popover, a jego wyzwalaczem jest dowolny przycisk wskazujący na jego id:
import { button, modal } from 'sitelo/ui'
button({ popovertarget: 'confirm' }, 'Usuń…')
modal({
id: 'confirm',
title: 'Usunąć tę stronę?',
footer: button({ color: 'danger' }, 'Usuń'),
}, 'Tego nie da się cofnąć.')Zakładki występują w dwóch postaciach — odnośniki albo panele:
// Zakładki-odnośniki: jedna strona na zakładkę, bez skryptu.
tabs({ items: [
{ label: 'Dokumentacja', href: '/docs', active: true },
{ label: 'API', href: '/api' },
] })
// Zakładki-panele: podmiana w miejscu, na grupie radio i CSS.
tabs({ value: 'use', items: [
{ id: 'install', label: 'Instalacja', panel: code('npm install sitelo') },
{ id: 'use', label: 'Użycie', panel: code("import * as ui from 'sitelo/ui'") },
] })Tabele przyjmują columns i rows, a funkcja render wchodzi wszędzie tam, gdzie komórka potrzebuje czegoś więcej niż wartości:
table({
striped: true,
columns: [
{ key: 'page', header: 'Strona' },
{ key: 'size', header: 'Rozmiar', align: 'end' },
{ header: 'Status', render: (row) => chip({ color: row.ok ? 'success' : 'danger' }, row.ok ? 'ok' : 'błąd') },
],
rows: pages,
})Katalog examples/ui w repozytorium renderuje każdy komponent na jednej stronie — to najszybszy sposób, by zobaczyć cały zestaw.
Referencja komponentów
Każdy eksport, grupami. Propsy są otypowane: sitelo/ui dostarcza pliki .d.ts, więc edytor podpowiada variant, color i size zarówno w JavaScripcie, jak i w TypeScripcie.
| Grupa | Komponenty |
|---|---|
| Układ | container, stack, grid, divider, card, cardHeader, cardTitle, cardSubtitle, cardMedia, cardBody, cardFooter, aspectRatio |
| Typografia | text, heading, link, code, inlineCode, kbd, visuallyHidden, prose |
| Pola formularza | button, iconButton, buttonGroup, field, input, textarea, select, textField, textareaField, selectField, checkbox, radio, toggle, choiceGroup, slider, sliderField, toggleButton, toggleGroup |
| Prezentacja danych | avatar, avatarGroup, badge, chip, tooltip, table, list, listItem, figure |
| Komunikaty | alert, progress, skeleton, toasts, empty |
| Nawigacja | breadcrumbs, pagination, tabs, appBar, appBarNav, appBarSpacer, appBarActions, navLink, themeToggle |
| Warstwy | modal, drawer, closeButton, menu, menuItem, menuSeparator, accordion, accordionItem, collapsible |
| Sekcje | hero, footer, siteFooter, footerColumn, footerBottom, stat, statGroup, steps, timeline, timelineItem, mockup |
| Stylowanie | styles, stylesheet, stylesUrl, theme, themeScript |
Dwie nazwy odbiegają od oczekiwań: przełącznik to toggle, bo switch jest słowem zastrzeżonym i nie może być wiązaniem importu; a ostylowana kotwica jest eksportowana i jako link, i jako textLink, żeby mogła stać obok link z javascript-to-html. table, input, select i progress mają to samo wyjście awaryjne: dataTable, textInput, selectField, progressBar.
Dodatki
Drugi punkt wejścia, sitelo/ui-extras, mieści komponenty nie dla wszystkich — faktury i efekty, na początek ziarno filmowe. Każdy przynosi własny arkusz stylów, grainStyles() obok styles(), więc strona podlinkowuje tylko to, czego używa, a sitelo/ui-extras/client niesie ich wywołania po stronie strony. Są skatalogowane w sitelo UI extras.