Компоненты
На этой странице
sitelo-ui — библиотека компонентов для sitelo. Каждый компонент это функция, возвращающая строку HTML, поэтому он встраивается прямо в дерево javascript-to-html без прослойки: без компилятора, без рантайма, без гидратации. Что вы собрали, то и попадает в dist/.
Она поставляется вместе с sitelo, точка входа — sitelo/ui.
Быстрый старт
npm install sitelo javascript-to-htmlПоместите styles() в head и вызывайте компоненты в body. Это вся настройка:
import { body, head, html, meta, title } from 'javascript-to-html'
import { styles, container, stack, heading, text, button } from 'sitelo/ui'
export default () => html({ lang: 'ru' },
head(
meta({ charset: 'utf-8' }),
meta({ name: 'viewport', content: 'width=device-width, initial-scale=1' }),
title('Мой сайт'),
styles(),
),
body(
container({ size: 'md' },
stack({ gap: 'md' },
heading({ level: 1 }, 'Привет'),
text({ variant: 'lead' }, 'Страница, собранная из компонентов.'),
button({ href: '/docs' }, 'Читать документацию'),
),
),
),
)Имена компонентов намеренно совпадают с тем, что они рендерят, поэтому некоторые из них — button, input, table, link, code, select, progress — конфликтуют с функциями-элементами javascript-to-html. Импортируйте библиотеку как пространство имён, когда нужны обе:
import * as ui from 'sitelo/ui'
ui.card(
ui.cardHeader({ title: 'Маршрутизация', subtitle: 'На основе файлов' }),
ui.cardBody(ui.text('src/about.ht.js становится /about.')),
ui.cardFooter({ divided: true }, ui.button({ size: 'sm' }, 'Подробнее')),
)Соглашение о вызове
Каждый компонент принимает необязательный объект пропсов, а за ним — детей, ровно как элемент javascript-to-html. Пропсы, которые компонент понимает, разбираются по имени; всё остальное попадает на отрендеренный элемент как атрибут, поэтому id, data-*, aria-* и атрибуты событий работают без того, чтобы библиотека их перечисляла:
button({ id: 'save', 'data-analytics': 'save-click', onclick: 'save()' }, 'Сохранить')
// <button type="button" id="save" data-analytics="save-click" onclick="save()" class="su-btn …">Пропс, значение которого компонент не знает — variant: 'nonsense' — откатывается к значению по умолчанию, а не бросает исключение. Косметическая опечатка не должна ронять сборку.
Стили
styles() возвращает элемент <style> со всей таблицей стилей, минифицированной. Это примерно 7 кБ по сети, и она не может потеряться в dist/ — поэтому так по умолчанию. Если хотите подключить её один раз и позволить браузеру кэшировать её между страницами, импортируйте CSS из собираемого входного файла, и Vite его выпустит:
// src/main.js — собирается Vite и кэшируется между страницами
import 'sitelo/ui/styles.css'Используйте что-то одно, не оба способа сразу.
Темы
Всё держится на пользовательских свойствах CSS в :root: пять палитр, шкала отступов, радиусы, типографика и тени. theme() пишет переопределения для них и принимает имена в camelCase (radiusMd → --su-radius-md), объекты палитр или буквальные пользовательские свойства:
import { styles, theme } from 'sitelo/ui'
head(
styles(),
// После styles(), чтобы победили эти значения.
theme({
primary: { base: '#5b5bd6', hover: '#4a4ac4', fg: '#ffffff' },
radiusMd: '2px',
fontSans: '"Inter", system-ui, sans-serif',
}, {
dark: { primary: { base: '#8f8ff0' } },
}),
)Тёмная тема определяется сама по prefers-color-scheme. Значение light или dark в data-theme или data-su-theme на любом предке перекрывает её — именно это и делает themeToggle():
import { styles, themeScript, themeToggle } from 'sitelo/ui'
head(
themeScript(), // применяет сохранённый выбор до первой отрисовки
styles(),
)
// …где угодно в body
themeToggle()JavaScript, и как его мало
Большинству компонентов он не нужен. Модальное окно и панель — это элементы popover, так что открытие, подложку, клик снаружи и Escape берёт на себя браузер. Аккордеон — это <details name>. Меню — <details>. Подсказки сделаны на CSS.
Скрипт действительно нужен четырём вещам — и каждая забирает его сама:
- вкладкам, панели которых переключаются на месте
- кнопке закрытия у скрываемого уведомления
- закрытию меню по клику снаружи или по Escape
- переключателю темы
Во входной файл добавлять нечего — импорт и есть атрибут события:
<!-- rendered by alert({ dismissible: true }) -->
<button class="su-alert-dismiss"
onclick="import('/su/alert.js').then(m=>m.dismiss(this))">
×
</button>sitelo отдаёт эти модули из /su/ во время разработки и копирует в сборку только те, на которые ваши страницы действительно ссылаются. Каждый заметно меньше килобайта, ни один не загружается до первого взаимодействия, и до этого момента все компоненты рендерятся корректно: вкладки с панелями показывают ту, что сервер отметил активной, меню открываются и закрываются сами, а кнопка закрытия ничего не делает.
Исключение — toast(): на странице нет ничего, что вызвало бы его за вас:
// src/main.js
import { toast } from 'sitelo/ui/client'Примеры
Формы сами связывают свои подписи, идентификаторы, подсказки и сообщения об ошибках:
import { card, cardBody, cardFooter, button, stack, textField, selectField } from 'sitelo/ui'
card(
cardBody(
stack({ gap: 'md' },
textField({ label: 'Эл. почта', name: 'email', type: 'email', help: 'Никому не передаётся.' }),
textField({ label: 'Сайт', name: 'site', startAdornment: 'https://', error: 'Это не URL.' }),
selectField({ label: 'Тариф', name: 'plan', options: ['Бесплатный', 'Pro'], value: 'Pro' }),
),
),
cardFooter({ divided: true }, button({ type: 'submit' }, 'Сохранить')),
)Модальное окно — это popover, а его триггером служит любая кнопка, указывающая на его id:
import { button, modal } from 'sitelo/ui'
button({ popovertarget: 'confirm' }, 'Удалить…')
modal({
id: 'confirm',
title: 'Удалить эту страницу?',
footer: button({ color: 'danger' }, 'Удалить'),
}, 'Это действие необратимо.')Вкладки бывают двух видов — ссылки или панели:
// Вкладки-ссылки: по странице на вкладку, без единого скрипта.
tabs({ items: [
{ label: 'Документация', href: '/docs', active: true },
{ label: 'API', href: '/api' },
] })
// Вкладки с панелями: переключаются на месте и сами подгружают нужный код.
tabs({ value: 'use', items: [
{ id: 'install', label: 'Установка', panel: code('npm install sitelo') },
{ id: 'use', label: 'Использование', panel: code("import * as ui from 'sitelo/ui'") },
] })Таблицы принимают columns и rows, а функция render нужна там, где ячейке мало одного значения:
table({
striped: true,
columns: [
{ key: 'page', header: 'Страница' },
{ key: 'size', header: 'Размер', align: 'end' },
{ header: 'Статус', render: (row) => chip({ color: row.ok ? 'success' : 'danger' }, row.ok ? 'ok' : 'ошибка') },
],
rows: pages,
})Каталог examples/ui в репозитории рендерит все компоненты на одной странице — самый быстрый способ увидеть весь набор.
Справочник компонентов
Все экспорты, по группам. Пропсы типизированы: sitelo/ui поставляется с файлами .d.ts, поэтому редактор подсказывает variant, color и size и в JavaScript, и в TypeScript.
| Группа | Компоненты |
|---|---|
| Раскладка | container, stack, grid, divider, card, cardHeader, cardTitle, cardSubtitle, cardMedia, cardBody, cardFooter, aspectRatio |
| Типографика | text, heading, link, code, inlineCode, kbd, visuallyHidden, prose |
| Поля ввода | button, iconButton, buttonGroup, field, input, textarea, select, textField, textareaField, selectField, checkbox, radio, toggle, choiceGroup, slider, sliderField, toggleButton, toggleGroup |
| Отображение данных | avatar, avatarGroup, badge, chip, tooltip, table, list, listItem, figure |
| Обратная связь | alert, progress, spinner, skeleton, toasts, empty |
| Навигация | breadcrumbs, pagination, tabs, appBar, appBarNav, appBarSpacer, appBarActions, navLink, themeToggle |
| Оверлеи | modal, drawer, closeButton, menu, menuItem, menuSeparator, accordion, accordionItem, collapsible |
| Секции | hero, footer, siteFooter, footerColumn, footerBottom, stat, statGroup, steps, timeline, timelineItem, mockup |
| Стили | styles, stylesheet, theme, themeScript |
Два имени отличаются от ожидаемых: переключатель называется toggle, потому что switch — зарезервированное слово и не может быть импортируемым именем; а стилизованная ссылка экспортируется и как link, и как textLink, чтобы уживаться с link из javascript-to-html. У table, input, select и progress есть тот же запасной выход: dataTable, textInput, selectField, progressBar.