@feugene/granularity-tokens
Словарь токенов и темы дизайн-системы @feugene/granularity: какие токены есть, какие значения у
них в каждой теме и режиме, и чем собрать свою тему так, чтобы она не развалилась при следующем
выпуске пакета.
Тема здесь — дизайн-язык целиком: примитивы, роли и производные состояния. Светлый и тёмный — её
режимы. Что входит в тему, что есть у режима, что такое пара и тема сборки —
docs/concepts.md. Пакет не знает о компонентах: его читают ядро, спутники,
портал, инструменты и автор своей темы, и никому из них не нужен UI-кит, чтобы узнать цвет роли.
yarn add @feugene/granularity-tokens
Приложению на ядре ставить его отдельно не нужно — он приходит зависимостью
@feugene/granularity. Явно он нужен, когда вы собираете свою тему или читаете токены как данные.
Что внутри
| Что | Где | Подпуть |
|---|---|---|
| Реестр без значений: примитивы, роли, формулы производных | tokens/*.json | @feugene/granularity-tokens |
| Каталог тем: id, название, версия, целевые пакеты, режимы, ссылка на дизайн-систему | themes/*/theme.json | @feugene/granularity-tokens (grThemeCatalog) |
| Данные темы со значениями, значениями компонентов и фолбэками | themes/<slug>/ | @feugene/granularity-tokens/themes/<slug>/theme.json; по выбору в рантайме — loadThemeData(id) |
Сборка темы и режима: defineTheme, extendMode, createMode, tone, проверки | src/theme/ | @feugene/granularity-tokens/theme |
Тема из папки: loadTheme — загрузчик granum и defineTheme | src/theme/node.ts | @feugene/granularity-tokens/theme/node |
Копия темы: theme:copy | scripts/theme-copy.mjs | бинарь gr-theme-copy |
| Подключение готового CSS в браузере | src/theme/apply.ts | @feugene/granularity-tokens/theme/apply |
Каталог тем и раскладка папки темы — themes/README.md. Что тема обязана
объявить, какие роли где применяются, ловушки контраста, свой режим и своя тема —
docs/themes.md. Справочник всех токенов со значениями каждой пары
«тема × режим» — docs/tokens.md.
Реестр и значения
Реестр не тащит значений: каталог тем растёт, а бандл того, кто читает имена, — нет. Значения подключаются по требованию.
import { grRoleTokens, type GrThemeData, resolveModes } from '@feugene/granularity-tokens'
import slateData from '@feugene/granularity-tokens/themes/slate/theme.json'
// JSON типизируется по содержимому: `colorScheme` в нём просто строка.
const slate = slateData as GrThemeData
const dark = resolveModes(slate).get('dark')!
const surfaces = grRoleTokens
.filter(token => token.section === 'Surface roles')
.map(token => ({ name: token.name, value: dark[token.name.slice(2)] }))
Ключи в данных темы — без -- (gr-bg), как их ждёт granum; в реестре имя полное (--gr-bg).
Тему, которую выбирают в рантайме, — страница показывает значения той темы, что включена
сейчас, — грузит loadThemeData: у каждой темы каталога свой чанк, и загружается только
запрошенная. Тема не из каталога — отказ со списком тем.
import { loadThemeData, resolveModes } from '@feugene/granularity-tokens'
const clay = await loadThemeData('clay')
const light = resolveModes(clay).get('light')!
Своя тема
Папка темы — исходник темы granum, та же раскладка, что у встроенных: theme.json, root.json,
modes/<режим>.json, значения компонентов в components/<пакет>/<Компонент>/. С
"extends": "slate" объявляются только отличия, остальное дотягивается из базы и идёт за её
правками; копия — gr-theme-copy slate <id> — независима от источника.
import { loadTheme } from '@feugene/granularity-tokens/theme/node'
const brand = loadTheme(new URL('./src/themes/brand/', import.meta.url))
loadTheme проверяет полноту по реестру, контраст по WCAG и различимость тонов в каждом режиме и
падает с именем токена и режима — неполная или нечитаемая тема не доезжает до пользователя. В
sources он отдаёт прочитанные файлы: правка любого из них обязана пересобрать тему. В extends —
тема каталога в цепочке: granum дополняет тему её данными. Результат идёт в themes
конфига granum — основной или дополнительной темой. @feugene/granum — необязательная
peer-зависимость: её требуют только theme/node и gr-theme-copy.
Режим поверх готового — extendMode (с нуля — createMode), семья ролей тона из одного цвета —
tone. Базой может быть режим встроенной темы, { theme, mode } любой темы или другой режим:
import { extendMode, tone } from '@feugene/granularity-tokens/theme'
const surfaces = { '--gr-bg': '#041e2b', '--gr-fg': '#e8f4fa', '--gr-card': '#0a2f42' }
export const ocean = extendMode({
name: 'ocean',
base: 'dark',
tokens: { ...surfaces, ...tone('azure', '#38bdf8', { base: surfaces }) },
})
ocean.definition // → modes.define.ocean записи темы в конфиге granum: extends, colorScheme, только отличия
ocean.fallbacks // → modes.define.ocean.fallbacks: hover и active от ролей режима
ocean.css // CSS под парой slate-ocean — для подключения без granum
Как подключить
Пакет — провайдер granum без компонентов, владелец каталога тем: ядро
@feugene/granularity объявляет его зависимостью, и приложению достаточно
провайдера ядра. Темы сборки, их режимы и основную тему перечисляет themes конфига granum
([{ theme: '<slug>', modes: ['light', 'dark'] }]); темы по умолчанию у каталога нет, и сборка без
themes — ошибка со списком тем. Тема и режим
включаются парой data-theme="<slug>-<режим>", переключает их useTheme() ядра. Без granum ядро отдаёт готовый CSS темы —
@feugene/granularity/themes/<slug>.css.