Mamkin UI — Design System
Единая страница дизайн-системы mamkin-mini-app: цвета, типографика, foundations, layout, компоненты, иконки — из реального кода
src/. Контекст и метрики продукта — в кейсе Mom’s Helper.
Цветовые токены проекта
Компонент не знает своего цвета — он знает роль («фон карточки», «вторичный текст»), а значение подставляет тема. Поэтому имя одно, а значений два: светлое и тёмное. У акцента ребёнка — семь.
Образец — реальный цвет из src/index.css, на диагональной подложке: иначе полупрозрачные *-bg не видно ни в одной теме. Примитив и HEX — в разделе Figma ↔ Код.
Semantic tokens — Light mode
Двенадцать переменных задают всю светлую тему — фоны, текст, границы.
| Образец | CSS-переменная | Semantic | Где используется |
|---|---|---|---|
--bg-page | bg/page | Фон страницы | |
--bg-card | bg/card | Фон карточки/шита | |
--bg-card2 | bg/card2 | Фон вложенной группы (settings) | |
--bg-input | bg/input | Фон полей ввода, неактивные кнопки | |
--border-card | border/card | Обводка карточек/полей | |
--text-primary | text/primary | Основной текст | |
--text-secondary | text/secondary | Вторичный текст | |
--text-placeholder | text/placeholder | Плейсхолдеры | |
--row-divider | row/divider | Разделители строк списков | |
--nav-bg | nav/bg | Фон нижней навигации | |
--nav-border | nav/border | Обводка нижней навигации | |
--icon-btn-bg | icon-btn/bg | Фон Button variant=subtle |
Semantic tokens — Dark mode
Те же двенадцать имён, другие значения. В Figma это Modes одной переменной, не вторая переменная.
| Образец | CSS-переменная | Semantic | Где используется |
|---|---|---|---|
--bg-page | bg/page | Фон страницы | |
--bg-card | bg/card | Фон карточки/шита | |
--bg-card2 | bg/card2 | Фон вложенной группы (settings) | |
--bg-input | bg/input | Фон полей ввода, неактивные кнопки | |
--border-card | border/card | Обводка карточек/полей | |
--text-primary | text/primary | Основной текст | |
--text-secondary | text/secondary | Вторичный текст | |
--text-placeholder | text/placeholder | Плейсхолдеры | |
--row-divider | row/divider | Разделители строк списков | |
--nav-bg | nav/bg | Фон нижней навигации | |
--nav-border | nav/border | Обводка нижней навигации | |
--icon-btn-bg | icon-btn/bg | Фон Button variant=subtle |
Semantic tokens — Status & CTA
Четыре статуса, у каждого пара: насыщенный цвет для текста и иконки плюс фон плашки — тот же цвет, 15% в тёмной теме и 12% в светлой.
Dark
| Образец | CSS-переменная | Semantic | Где используется |
|---|---|---|---|
--success | success | Подтверждение, идёт/активно | |
--success-bg | success-bg | Подтверждение, идёт/активно — фон плашки | |
--ds-warning | warning | Предупреждение, error-баннер Dashboard | |
--ds-warning-bg | warning-bg | Предупреждение, error-баннер Dashboard — фон плашки | |
--ds-danger | danger | Удаление, ошибки ввода | |
--ds-danger-bg | danger-bg | Удаление, ошибки ввода — фон плашки | |
--ds-info | info | Кормление в EventRow/History | |
--ds-info-bg | info-bg | Кормление в EventRow/History — фон плашки |
Light
| Образец | CSS-переменная | Semantic | Где используется |
|---|---|---|---|
--success | success | Подтверждение, идёт/активно | |
--success-bg | success-bg | Подтверждение, идёт/активно — фон плашки | |
--ds-warning | warning | Предупреждение, error-баннер Dashboard | |
--ds-warning-bg | warning-bg | Предупреждение, error-баннер Dashboard — фон плашки | |
--ds-danger | danger | Удаление, ошибки ввода | |
--ds-danger-bg | danger-bg | Удаление, ошибки ввода — фон плашки | |
--ds-info | info | Кормление в EventRow/History | |
--ds-info-bg | info-bg | Кормление в EventRow/History — фон плашки |
Semantic tokens — Accent ребёнка
Не зависит от темы — выбирается в настройках ребёнка. Строка — вариант, колонка — роль: --ds-accent красит CTA и аватар, --ds-accent-light — подложку, --accent-text — текст поверх неё (светлее: основной на подложке не проходит по контрасту), --ds-accent-border — обводку и кольцо фокуса. В коде это четыре переменные на семь вариантов, значение переключает [data-accent]; в Figma придётся завести все 28, и нейтральный вариант там зовётся …/default, а не gray.
| Вариант | --ds-accent | --ds-accent-light | --accent-text | --ds-accent-border |
|---|---|---|---|---|
grayБез цвета — имя в Figma …/default | accent/default | accent-light/default | accent-text/default | accent-border/default |
purpleФиолетовый | accent/purple | accent-light/purple | accent-text/purple | accent-border/purple |
blueСиний | accent/blue | accent-light/blue | accent-text/blue | accent-border/blue |
greenЗелёный | accent/green | accent-light/green | accent-text/green | accent-border/green |
pinkРозовый | accent/pink | accent-light/pink | accent-text/pink | accent-border/pink |
orangeОранжевый | accent/orange | accent-light/orange | accent-text/orange | accent-border/orange |
tealБирюзовый | accent/teal | accent-light/teal | accent-text/teal | accent-border/teal |
Типографика
Четырнадцать текстовых стилей на всё приложение. Стиль — это не «размер шрифта», а связка размер + вес + интерлиньяж + трекинг: применяется одним кликом и одинаково выглядит везде.
Шрифт
Своего веб-шрифта нет — стек -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif. Интерфейс берёт шрифт платформы: на iOS это SF Pro, на Android — Roboto. Плюс — ноль килобайт загрузки и родное для системы начертание; минус — метрики на разных платформах не совпадают, поэтому нигде нет вёрстки, которая держится на точной ширине строки.
Насыщенность — четыре веса
Иерархию держит вес, а не размер: на 14px три стиля и на 12px ещё три — внутри пары размеров они различаются только весом. Поэтому шкала размеров такая короткая.
Интерлиньяж — часть стиля, а не следствие размера
leading-noneмл
text-smза сегодня
У крупных цифр интерлиньяж сжат до 100%, иначе число «висит» в пустоте и ломает высоту карточки. У текста — 143%, иначе абзац не читается. Одно и то же значение обоим не подходит, поэтому оно и вшито в стиль.
Text Styles 14 стилей
«Образец» — как стиль выглядит на самом деле, «Semantic» — имя Text Style для Figma, «Реализация» — класс, которым он набран в коде. Переменные, из которых стиль собран внутри Figma (font-size/*, weight/*), — в разделе Figma ↔ Код.
| Образец | Semantic | Реализация | Где используется |
|---|---|---|---|
| 254 мл | Number | text-2xl font-bold leading-none | Крупные цифры статистики (StatCard) |
| Статистика | PageTitle | text-xl font-bold | Заголовки экранов и разделов Главной |
| Добавить ребёнка | ModalTitle | text-lg font-bold | Заголовки модалок и разделов (AddChildModal, Settings, Premium) |
| Кормление | SheetHeader | text-base font-semibold | Заголовок шторки и карточки ребёнка |
| 3 ч 40 мин | TileValue | text-sm font-semibold leading-tight | StatusTile — есть в коде, в проде не используется |
| Ночной режим | BodyPrimary | text-sm font-medium | Заголовок строки списка (ListRow); в EventRow — semibold |
| Пока нет записей за сегодня | Body | text-sm | Загрузка, ошибка, пусто, описания форм — самый частый паттерн, около 40 мест |
| Изменить | AccentCaption | text-xs font-semibold | Кнопки статус-бара, бейдж Premium, дельта в Статистике |
| Кормление | ButtonCaption | text-xs font-medium | Подпись под иконкой действия (secondary-кнопки) |
| 2 часа назад | BodySecondary | text-xs | Вторая строка в списках, подписи без явного веса |
| Уведомления | SectionLabel | text-[11px] font-semibold + inline | Заголовки секций, UPPERCASE |
| Главная | TabLabel | text-[11px] font-medium leading-none | Подпись таба нижней навигации |
| PREMIUM | SmallBadge | text-[10px] | Маленький бейдж (Badge size=sm, вес — из базового класса) |
| скоро | MicroBadge | text-[8px] | Бейдж «скоро» (Badge size=xs) |
Layout & grid
Одноколоночный мобильный контейнер с вертикальным скроллом, без классической сетки колонок и без брейкпоинтов — это Telegram Mini App с одним форм-фактором, а не адаптивный сайт под разные вьюпорты. Ниже — реальный экран в масштабе 1:1; разметка включается по одному слою, чтобы каждое правило было видно отдельно, а не всё сразу.
Мокап в масштабе 1:1: ширина фрейма 375px и все размеры внутри равны фактическим px из кода, без уменьшения. Переключатели сверху включают по одному слою разметки — на реальном экране, а не на абстрактной схеме.
Поля страницы — px-4, 16px с каждой стороны, одинаково на всех вкладках. Классической сетки колонок нет: ширину контента задают поля и потолок контейнера max-w-md (448px), как в Apple HIG — margins плюс readable width, а не N колонок.
Вертикальный ритм собран из пяти чисел шкалы space: 20px между секциями, 12px под заголовком секции, 10px и 8px внутри сеток. Отдельного токена «ритм» нет — каждый интервал берёт своё значение из общей шкалы.
Локальные сетки внутри секций — обычный CSS Grid на 1–4 колонки, с тем же gap, что в item spacing. Четыре колонки — максимум, дальше кнопка перестаёт быть тап-таргетом по ширине.
Верхний отступ берётся из Telegram API и работает только в реальном fullscreen-режиме, вне его всегда 0. Нижний читает нативный env(safe-area-inset-bottom) и от режима Telegram не зависит. Измерено на iPhone 12 mini в Telegram: 20px, то есть пилюля навигации отстоит от края экрана на 10px (фикс.) + 20px = 30px, а не на фиксированные 10px. Инсет самой пилюли — 20px по бокам, шире полей контента (16px): так она не сливается с интерфейсом.
Всё, по чему надо попасть пальцем, — не ниже 44px, минимума Apple HIG: строка списка 62px, кнопка быстрой записи 96px, таб навигации 57px. Жёлтым — то, что ниже 44px осознанно: иконка-действие в шапке секции (32px) и бейдж в строке (32px) сидят внутри уже крупной цели и не являются основным действием экрана.
Проверка: паддинг + line-height = высота
Заявленная высота Button — это не «на глаз», а сумма реального вертикального паддинга и реального line-height текста внутри (line-height уже посчитан на Typography). Проверка по всем трём размерам:
| Размер | Паддинг (2×) | Межстрочный | Сумма | Заявлено (min-h) | Сходится? |
|---|---|---|---|---|---|
sm | py-1.5 = 12px | ButtonCaption = 16px | 28px | 32px | +4px запас |
md | py-3 = 24px | BodyPrimary = 20px | 44px | 44px | точно совпадает |
lg | py-3.5 = 28px | BodyPrimary = 20px | 48px | 52px | +4px запас |
У md паддинг подобран так, что паддинг и строка текста сходятся ровно в 44px: минимум тап-таргета Apple HIG получается не отдельным решением, а следствием реальной типографики. У sm и lg — по +4px запаса. То же правило держит и остальные высоты: у всего, по чему надо попасть пальцем, ⩾44px — поэтому h-11/min-h-11 самое частое высотное значение в коде (строка настроек, поле ввода, Button md). Осознанные исключения — Button size=sm (32px) и Switch (24px): оба сидят внутри уже крупной цели, а не работают основным действием экрана. Единой формулы «высота такая-то» нет — каждый компонент берёт паддинг из шкалы space сам, разбор по компонентам — в их карточках в разделе Компоненты.
Компоненты
Компоненты приложения по одному — реальные варианты, размеры и состояния из кода, не выдуманные заново. Разделены на примитивы (неделимый контрол, одна роль) и составные (несколько слотов, повторяющиеся элементы или прямой вызов другого компонента внутри).
Все компоненты разом 22 компонента
Чем компоненты отличаются друг от друга и какие вообще ни на что не реагируют. Статичный — состояний нет вовсе: компонент только рисует, нажатие и фокус живут на элементе вокруг. Состояния названы здесь по смыслу и одинаково, чтобы строки сравнивались; в самих блоках имена повторяют код — checked у Switch, selected у ChipSelect, active у Button и BottomNav.
| Компонент | Тип | Состав | Состояния |
|---|---|---|---|
Button | примитив | Иконка и подпись; 5 вариантов заливки × 3 размера | нажат · фокус · выключен · загрузка · выбран |
Switch | примитив | Трек и бегунок, без слотов | выбран · нажат · фокус · выключен |
Badge | примитив | Только текст; 6 тонов × 3 размера | статичный |
Avatar | примитив | Эмодзи, фото или инициал; 3 размера | статичный |
Field | примитив | Подпись и поверхность — обёртка для Input и SelectField | статичный |
Input | примитив | Подпись и поле ввода | фокус · выключен |
Slider | примитив | Дорожка и бегунок | фокус · выключен |
ErrorNote | примитив | Иконка и текст ошибки | статичный |
Skeleton | примитив | Заглушка загрузки; 3 варианта формы | статичный |
AccentPlate | примитив | Акцентный фон и слот под содержимое | статичный |
PopoverPanel | примитив | Только поверхность попапа, содержимое передаётся снаружи | статичный |
ChipSelect | составной | Ряд пилюль: иконка и подпись; обычный и compact | выбран · нажат · фокус · выключен |
QuickActionButton | составной | Эмодзи, подпись, бейдж; primary и secondary | выбран · нажат · фокус · выключен |
ListRow | составной | Иконка или аватар, заголовок, подпись, заметка, хвост | нажат · фокус · выключен · приглушён |
PageHeader | составной | AccentPlate, переключатель ребёнка, действия, слот под контент | загрузка |
BottomNav | составной | Четыре таба: иконка и подпись | выбран · нажат |
ReorderableRow | составной | ListRow плюс ручка перетаскивания | перетаскивание · фокус · приглушён |
StatCard | составной | Заголовок, значение с единицей, вторая строка, бейдж | пусто · приглушён |
SelectField | составной | Field плюс список опций | раскрыт · фокус · выключен |
TimeSelect | составной | Field плюс колёса часов и минут | раскрыт · фокус · выключен |
SliderField | составной | Подпись, Slider, текстовая подсказка | статичный |
Sheet | составной | Шторка снизу: ручка, шапка, тело, секции, подвал | раскрыт · свайп |
Примитивы11 компонентов
Неделимые элементы: один контрол — одна роль. Внутри нет других компонентов, дальше не раскладываются. Всё, что ниже в разделе «Составные», собрано из них и из этих же токенов.
Button ui-kit/Button.tsx
Кнопка действия — текст, иконка или и то и другое. Без children рендерится как квадратная icon-only кнопка.
Состояния вживую
Настоящие CSS-псевдоклассы, не имитация стилями.
5 вариантов × md
active:opacity-70 — общий для всех вариантов.
3 размера — текст и icon-only (одна и та же высота)
Кнопка — Button (3 размера)
| Вариант | Вертикальный | Горизонтальный | Зазор | Высота | Текст |
|---|---|---|---|---|---|
sm | py-1.5 · 6px | px-3 · 12px | gap-1.5 · 6px | min-h-8 · 32px | font-size/12 · weight/medium |
md | py-3 · 12px | px-5 · 20px | gap-1.5 · 6px | min-h-11 · 44px | font-size/14 · weight/medium |
lg | py-3.5 · 14px | px-6 · 24px | gap-1.5 · 6px | min-h-13 · 52px | font-size/14 · weight/medium |
Радиус rounded-control и вес weight/medium общие у всех трёх. Зазор появляется только с иконкой.
Свойства
| Элемент | Значение | Токен |
|---|---|---|
| Подпись | 14/500 — md и lg; 12/500 — sm | BodyPrimary · ButtonCaption |
| Скругление | 10px | rounded-control |
| Обводка | 1px у secondary и subtle; прозрачная (не отсутствует — иначе active сдвигал бы кнопку на 1px) у primary и danger; нет у ghost | stroke/thin · border/card |
| Высота — минимум, не фикс | 32 / 44 / 52px | min-h-8 · min-h-11 · min-h-13 |
| Отступы по бокам | 12 / 20 / 24px | space/12 · space/20 · space/24 |
| Зазор иконка ↔ подпись | 6px | gap-1-5 |
| Заливка | акцент / карточка / danger / прозрачная / icon-btn | --ds-accent · --bg-card · --ds-danger · --icon-btn-bg |
| Цвет подписи | белый на заливке, иначе основной или вторичный | --text-primary · --text-secondary |
Варианты
| Вариант | Заливка | Обводка | Выключено |
|---|---|---|---|
primary | var(--ds-accent), заливка | transparent (плейсхолдер) | полная смена палитры → нейтральный серый, не притушенный акцент |
secondary | var(--bg-card), заливка | var(--border-card), 1px | просто opacity-50 — и так нейтральный |
danger | var(--ds-danger), заливка | transparent (плейсхолдер) | та же смена палитры, что у primary |
ghost | transparent | нет | opacity-50 |
subtle | var(--icon-btn-bg), полупрозрачная | var(--border-card), 1px | opacity-50 · для кнопок на цветном/акцентном фоне |
Состояния
| Состояние | Что меняется | Токен |
|---|---|---|
default | Базовый вид | — |
pressed | Кнопка гаснет | opacity/70 |
active | Проп подменяет вариант на primary — «выбрано» у переключателей вида графика. Тот же проп и тот же смысл, что у QuickActionButton и BottomNav | --ds-accent |
focus-visible | Кольцо 2px с зазором 2px, бледный акцент | focus-ring · --ds-accent-border |
disabled | primary и danger получают нейтральную заливку вместо акцента; остальные просто гаснут | --bg-input · opacity/50 |
loading | Спиннер вместо содержимого, кнопка не нажимается | — |
Пропы
| Проп | Назначение |
|---|---|
variant | primary · secondary · danger · ghost · subtle |
size | sm · md (по умолчанию) · lg |
icon | Иконка перед подписью; без подписи — квадратная icon-only кнопка |
active | Подменяет вариант на primary — «выбрано» у переключателей вида графика |
loading | Спиннер вместо содержимого, кнопка не нажимается |
fullWidth | Растянуть на всю ширину контейнера |
Switch ui/switch.tsx
Бинарный переключатель вкл/выкл — обёртка над Radix Switch.
ui/switch.tsx
Трек 44×24px, бегунок 20×20px.
Switch — переключатель
| Элемент | Значение | Токен |
|---|---|---|
| Трек | 44 × 24px | w-11 · h-6 |
| Бегунок | 20 × 20px | w-5 · h-5 |
| Сдвиг включённого | 20px | translate-x-5 |
Отступов нет: инсет под бегунок создаёт прозрачная рамка border-2, отсюда 24 − 2×2 = 20px. Библиотечный shadcn поверх Radix.
Свойства
| Элемент | Значение | Токен |
|---|---|---|
| Трек | 44 × 24px | w-11 · h-6 |
| Бегунок | 20 × 20px, белый, сдвиг 20px | h-5 · space/20 |
| Скругление | полное — и трек, и бегунок | rounded-full |
| Заливка вкл / выкл | акцент / фон поля | --ds-accent · --bg-input |
| Тень бегунка | мягкая, 25% чёрного | shadow-lg · opacity/25 |
focus-visible | кольцо 2px внутрь | --ds-accent-border |
disabled | гаснет целиком | opacity/50 |
pressed | нет — его не было ни у одного из двух прежних компонентов, при объединении не добавляли | — |
Состояния
| Состояние | Что меняется | Токен |
|---|---|---|
checked / unchecked | Заливка трека и сдвиг бегунка на 20px | --ds-accent · --bg-input |
focus-visible | Кольцо 2px внутрь | --ds-accent-border |
disabled | Гаснет целиком | opacity/50 |
pressed | Нет — не было ни у одного из двух прежних компонентов, при объединении не добавляли | — |
Пропы
| Проп | Назначение |
|---|---|
checked | Состояние переключателя |
onCheckedChange | Обработчик переключения |
disabled | Заблокирован |
Badge ui-kit/Badge.tsx
Бейдж-пилюля со статусом или тегом. Форма всегда rounded-full и не переопределяется по месту: пилюля не нажимается, прямоугольник рядом — нажимается, разная форма это подсказка. Не интерактивен.
6 tone × 3 size
Свойства
| Элемент | Значение | Токен |
|---|---|---|
| Подпись — md | 12/500 | ButtonCaption |
| Подпись — sm | 10/500 | SmallBadge |
| Подпись — xs | 8/500 | MicroBadge |
| Отступы | 10 × 4px у md · 8 × 2px у sm · 6 × 2px у xs | space/10 · space/8 · space/6 |
| Скругление | полное, всегда | rounded-full |
| Зазор иконка ↔ подпись | 4px | gap-1 |
| Тона (6) | полупрозрачная заливка и насыщенный текст того же цвета | --ds-accent-light · --success-bg · --ds-warning-bg · --ds-danger-bg · --ds-info-bg · --bg-input |
Пропы
| Проп | Назначение |
|---|---|
tone | neutral (по умолчанию) · accent · success · warning · danger · info |
size | xs (8px) · sm (10px) · md (12px, по умолчанию) |
Avatar ui-kit/Avatar.tsx
Кружок с эмодзи или фото ребёнка. Проп onAccent — полупрозрачно-белая заливка, когда аватар сидит на акцентной плашке. Не интерактивен.
sm / md / lg — обычный фон vs onAccent
sm=32px, md=40px, lg=56px.
Свойства
| Элемент | Значение | Токен |
|---|---|---|
| Размеры | 32 / 40 / 56px | h-8 · h-10 · h-14 |
| Эмодзи внутри | 14 / 20 / 30px — по размеру | font-size/14 · font-size/20 |
| Скругление | полное | rounded-full |
| Заливка | акцентная подложка ребёнка; на акцентной плашке — полупрозрачно-белая | --ds-accent-light · opacity/25 |
Пропы
| Проп | Назначение |
|---|---|
size | sm · md (по умолчанию) · lg |
emoji | Эмодзи ребёнка |
photoUrl | Фото вместо эмодзи |
background | Заливка кружка — акцентный цвет ребёнка |
onAccent | Полупрозрачно-белая заливка: аватар сидит на акцентной плашке |
Field «поле формы» ui-kit/Field.tsx
Input и SelectField — одно и то же поле, отличается только способ ввода: руками или выбором из списка. Поверхность общая, но компоненты разные: Input наследует атрибуты <input> и отдаёт DOM-событие, SelectField — готовое значение.
Ручной ввод и выбор из списка
Отступ, радиус, обводка и высота у обеих поверхностей одинаковые.
Свойства
| Элемент | Значение | Токен |
|---|---|---|
| Высота поля | 44px | h-11 |
| Отступ по горизонтали | 16px | px-4 · space/16 |
| Скругление | 10px | rounded-control |
| Обводка | 1px | --border-card |
| Подпись | 12/500, отступ снизу 6px | ButtonCaption · space/6 |
Что содержит
| Экспорт | Назначение |
|---|---|
FIELD_SURFACE | Строка классов поверхности: высота, отступы, радиус, обводка, кегль. Без w-full и фона — их добавляет конкретное поле |
Field | Обёртка с подписью над контролом или группой контролов — например, «Дата рождения» над тремя списками или «Тип питания» над ChipSelect. Нет label — контрол возвращается как есть, без лишнего <div> |
Input и Slider ui-kit/Input.tsx · ui-kit/Slider.tsx
Поле ввода (текст или число) и ползунок. Slider используется голым только там, где не нужна отдельная подпись — в остальных случаях он обёрнут в составной SliderField (см. «Составные»).
Input — с подписью, без, disabled
Фокус — кольцо 2px без зазора: у поля уже есть своя обводка.
Slider — обычный и disabled
Дорожка 6px. Бегунок и его фокус-кольцо стилизуются глобально в index.css.
Поле ввода — text input
| Вариант | Вертикальный | Горизонтальный | Зазор | Высота | Текст |
|---|---|---|---|---|---|
поле ввода | — | px-4 · 16px | — | h-11 · 44px | font-size/14 · вес наследуется |
Вертикального паддинга нет: текст центрируется высотой поля. Тот же набор классов дословно в трёх местах формы.
Свойства
| Элемент | Значение | Токен |
|---|---|---|
| Поле — высота | 44px | h-11 |
| Поле — отступ по бокам | 16px | space/16 |
| Поле — текст | 14/400 | BodyPrimary |
| Поле — обводка и заливка | 1px, фон поля | border/card · --bg-input |
| Подпись над полем | 12/500, отступ снизу 6px | ButtonCaption · space/6 |
| Скругление | 10px | rounded-control |
| Дорожка ползунка | 6px, полное скругление | h-1-5 · rounded-full |
| Кружок ползунка | 18×18px — вне шкалы space, инлайн CSS (::-webkit-slider-thumb/::-moz-range-thumb в index.css, не Tailwind-класс) | --ds-accent |
| Фокус-кольцо кружка | 2px, с зазором 2px от кружка | --ds-accent-border |
Состояния
| Состояние | Что меняется | Токен |
|---|---|---|
default | Базовый вид | — |
focus-visible | Кольцо 2px без зазора — у поля уже есть своя обводка. У ползунка кольцо на кружке, не на дорожке | --ds-accent-border |
disabled | Гаснет, курсор запрета | opacity/60 |
Пропы
| Проп | Назначение |
|---|---|
label | Подпись над полем — только у Input |
value | Значение |
onChange | Обработчик ввода |
placeholder | Подсказка в пустом поле — только у Input |
min / max / step | Диапазон — только у Slider |
disabled | Заблокировано |
ErrorNote ui-kit/ErrorNote.tsx
Сообщение об ошибке под формой — «Введите имя ребёнка», «Не удалось сохранить». Не интерактивен.
Единственный тон
14/400, отступы 12 × 10px, радиус rounded-control. Заливка --ds-danger-bg, текст --ds-danger.
Свойства
| Элемент | Значение | Токен |
|---|---|---|
| Текст | 14/400 | BodyPrimary |
| Отступы | 12 × 10px | space/12 · space/10 |
| Скругление | 10px | rounded-control |
| Заливка и текст | полупрозрачный danger | --ds-danger-bg · --ds-danger |
| Тон | один — success/info/warning не заведены | — |
Пропы
| Проп | Назначение |
|---|---|
children | Текст ошибки |
className | Внешний отступ — задаётся по месту |
Skeleton ui-kit/Skeleton.tsx
Пульсирующая заглушка на время загрузки. Отдельно есть ListRowSkeleton — загрузочное состояние строки списка, живёт в одном файле с ней. Не интерактивен.
text / circle / block
Высота заглушки повторяет кегль: 14px под заголовок, 12px под подпись.
Свойства
| Элемент | Значение | Токен |
|---|---|---|
| Заливка | фон карточки | --bg-card |
| Анимация | пульсация, бесконечная | animate-pulse |
| Скругление | 4px text · полное circle · 10px block | rounded-xs · rounded-full · rounded-control |
| Высота текстовой строки | по кеглю: 14px заголовок, 12px подпись | h-3-5 · h-3 |
Варианты
| Вариант | Скругление | Под что |
|---|---|---|
text | 4px | Строка текста. Высота повторяет кегль: заголовок 14px, подпись 12px |
circle | 999px | Аватар, точка, трек переключателя |
block | 10px | Карточка, кнопка, крупный блок — по умолчанию |
Пропы
| Проп | Назначение |
|---|---|
variant | text · circle · block (по умолчанию) |
className | Размеры заглушки — задаются по месту |
AccentPlate ui-kit/AccentPlate.tsx
Акцентная плашка: скруглённый блок с градиентом поверх акцентного цвета. Сам по себе — только фон и отступы, все состояния на элементах внутри.
Шапка страницы
Самый крупный радиус в системе — rounded-plate, 28px.
Свойства
| Элемент | Значение | Токен |
|---|---|---|
| Скругление | 28px | rounded-plate |
| Отступы | 16px со всех сторон | space/16 |
| Отступ снизу | 20px | space/20 |
| Заливка | градиент поверх акцентного цвета | --accent-gradient |
Пропы
| Проп | Назначение |
|---|---|
children | Содержимое плашки |
className | Раскладка внутри — задаётся по месту |
ACCENT_PLATE_BG | Не проп, а экспорт константы градиента — им же красится ChildSwitcherPopover, когда стоит не на плашке |
PopoverPanel «поверхность выпадающего списка» ui-kit/PopoverPanel.tsx
Стилизованная поверхность попапа — рамка, фон, тень — поверх Radix PopoverContent. Содержимое передаётся снаружи. Примитив, а не составной: критерий — из чего собран сам, а не что импортирует.
Переключатель ребёнка — cmdk Command внутри
Список — cmdk Command/CommandItem, не голые кнопки (была своя логика выделения). Не тронуто — общая только рамка снаружи.
Выбор метрики графика — голые кнопки внутри
Голые кнопки со своим скроллом и состояниями. Общая только рамка.
Пункт меню — попап выбора ребёнка
| Вариант | Вертикальный | Горизонтальный | Зазор | Высота | Текст |
|---|---|---|---|---|---|
пункт попапа | py-2 · 8px | px-2 · 8px | gap-2.5 · 10px | по контенту | font-size/14 · вес наследуется |
Компактнее строки настроек: 8px по горизонтали против 16px. Радиус свой — rounded-control-sm.
Свойства
| Элемент | Значение | Токен |
|---|---|---|
| Заливка | фон карточки | --bg-card |
| Обводка | 1px | --border-card |
| Тень | 0 8px 24px rgba(0,0,0,.18) | shadow/popover |
| Скругление | 10px | rounded-control |
Пропы
| Проп | Назначение |
|---|---|
align, sideOffset | Позиционирование — те же пропы Radix PopoverContent, компонент их только принимает и пробрасывает дальше |
className | Внутренние отступы/ширина — по месту (p-0 overflow-hidden w-56 у списка детей, p-1.5 w-56 у метрик) |
children | Содержимое — своё в каждом случае, компонент ничего не знает про список внутри |
Составные11 компонентов
Собраны из нескольких элементов: повторяющиеся пилюли в сетке, несколько слотов под контент или прямой вызов другого компонента внутри (ListRow → Skeleton, SettingsGroup → ListRow, TimeSelect → SelectField).
ChipSelect «выбрать один из N» ui-kit/ChipSelect.tsx
Сетка кнопок-пилюль — выбор одного варианта из нескольких, активная опция залита акцентом. options/value/onChange, иконка в опции необязательна.
С иконкой — options[].icon задан
Иконка сверху, подпись снизу — FeedingSheet.tsx, EditEventModal.tsx, ChildProfileSection.tsx.
Без иконки — options[].icon не задан
Одна строка по центру, без верхнего слота под иконку.
compact — однострочный сегмент-контрол, PeriodSelector.tsx
32px, иконка инлайн перед подписью, а не сверху отдельным слотом.
Свойства
| Элемент | Значение | Токен |
|---|---|---|
| Подпись — обычный режим | 12/500 | ButtonCaption |
| Подпись — compact | 14/500 | BodyPrimary |
| Высота — минимум, не фикс | 44px обычный · 32px compact — тот же тап-таргет 44px, что у остальных интерактивных элементов (см. Layout) | min-h-11 · min-h-8 |
| Скругление | 10px обычный · 8px у пилюли внутри жёлоба | rounded-control · rounded-control-sm |
| Зазор | 8px в сетке · 2px между пилюлями compact | gap-2 · space/2 |
| Заливка выбранного | сплошной акцент, подпись белая | --ds-accent |
| Заливка невыбранного | фон поля · прозрачная в compact | --bg-input |
| Обводка | нет ни в одном состоянии | stroke/none |
Состояния
| Состояние | Что меняется | Токен |
|---|---|---|
selected | Акцентная заливка, белая подпись | --ds-accent |
pressed | Пилюля гаснет | opacity/70 |
focus-visible | Кольцо 2px: с зазором в обычном режиме, внутрь — в compact, где пилюля сидит в жёлобе | --ds-accent-border |
disabled | Вся сетка гаснет; у отдельной опции compact гаснет только подпись — бейдж «скоро» остаётся читаемым | opacity/50 · opacity/40 |
Пропы
| Проп | Назначение |
|---|---|
options | { id, label, icon?, disabled?, badge? }[] |
value | Выбранный id |
onChange | Обработчик выбора |
compact | Однострочный сегмент-контрол 32px вместо сетки-пилюль 44px |
columns | Число колонок сетки — по умолчанию по числу опций |
disabled | Блокирует всю сетку |
QuickActionButton ui-kit/QuickActionButton.tsx
Плитка быстрой записи на Главной: крупная эмодзи над подписью, вся плитка — одна кнопка. Два «этажа»: primary — фиксированная пара «Кормление/Сон», secondary — настраиваемые. Не вариант Button: у того содержимое всегда в строку, здесь вертикальная плитка фиксированной высоты.
primary — Кормление / Сон
Вторая — в состоянии active: обводка и фон зелёные, эмодзи и подпись меняются. Disabled у этой пары не бывает.
secondary — настраиваемые и «Другое»
80px, обводка 1px нейтральная. Нереализованный пункт гаснет до 40%.
Состав кнопки — Быстрая запись
| Вариант | Вертикальный | Горизонтальный | Зазор | Высота | Текст |
|---|---|---|---|---|---|
primary | 16px | 8px | gap-2 · 8px | 96px | BodyPrimary |
secondary | 10px | 4px | gap-1.5 · 6px | 80px | ButtonCaption |
Паддинг и высота заданы инлайном (padding: '16px 8px'), а не классами — единственный компонент вне шкалы Tailwind.
Свойства
| Элемент | Значение | Токен |
|---|---|---|
| Подпись | 14/500 у primary · 12/500 у secondary | BodyPrimary · ButtonCaption |
| Эмодзи | 26px | font-size/26 |
| Высота | 96px primary · 80px secondary | space/96 · space/80 |
| Обводка | 2px акцентная · 1px нейтральная | stroke/bold · stroke/thin |
| Скругление | 10px | rounded-control |
| Зазор эмодзи ↔ подпись | 8px primary · 6px secondary | gap-2 · gap-1-5 |
| Заливка | фон карточки; success при active | --bg-card · --success-bg |
Состояния
| Состояние | Что меняется | Токен |
|---|---|---|
default | Базовый вид | — |
active | Запись уже идёт — обводка и заливка на success | --success · --success-bg |
pressed | Плитка гаснет | opacity/70 |
focus-visible | Кольцо 2px с зазором | --ds-accent-border |
disabled | Гаснут эмодзи и подпись, бейдж «скоро» остаётся | opacity/40 |
Пропы
| Проп | Назначение |
|---|---|
icon | Эмодзи 26px над подписью |
label | Подпись под иконкой |
variant | primary — 96px, обводка 2px акцентная; secondary — 80px, 1px нейтральная (по умолчанию) |
active | Запись уже идёт — обводка и фон на success |
disabled | Гасит иконку и подпись до 40% |
badge | Бейдж в правом верхнем углу — «скоро». Не гаснет вместе с плиткой |
ListRow «строка списка» ui-kit/ListRow.tsx
Строка списка: иконка или аватар слева → заголовок и подпись → произвольный хвост справа. Самая частая форма в приложении.
Хвост-шеврон, хвост-время и слот leading
Кружок с заливкой — только у аватара в leading; иконка категории идёт без подложки.
Хвост-действие — шиты выбора и настройки списков
Ручка перетаскивания — не хвост, а отдельный проп onDragStart. Погашенный пункт теряет яркость иконки и подписи, но не хвоста.
Строка списка — два паттерна: настройки и История
Второй паттерн — таймлайн событий в Истории:
| Вариант | Вертикальный | Горизонтальный | Зазор | Высота | Текст |
|---|---|---|---|---|---|
строка настроек | py-3 · 12px | px-4 · 16px | gap-3 · 12px | по контенту | BodyPrimary + BodySecondary |
строка Истории | py-3 · 12px | 16px у родителя | gap-3 · 12px | по контенту | font-size/14 · weight/semibold + font-size/12 |
Две реализации одной роли: у строки настроек паддинг свой, у строки Истории 16px приходят от карточки-обёртки.
Свойства
| Элемент | Значение | Токен |
|---|---|---|
| Иконка категории | 18px в колонке 24px | text-lg w-6 |
Аватар (leading) | 36px, круг | rounded-full · фон --ds-accent-light |
| Заголовок | 14px / 500 | BodyPrimary |
| Заголовок, деструктивный | 14px / 500 | --ds-danger |
| Подпись и заметка | 12px, отступ сверху 2px | BodySecondary · space/2 |
| Зазор иконка ↔ текст | 12px (в трёх шитах 8px) | gap-3 / gap-2 |
| Зазор внутри хвоста | 8px | gap-2 |
| Скругление | нет — несёт контейнер группы | rounded-control 10px + overflow-hidden |
| Разделитель | 1px | --row-divider (Настройки, История) · --border-card (шиты) |
| Отступы | 12/16px, 12/0, min-h 44–64px | снаружи через className/style, по контексту (SettingsGroup, шит, карточка дня в Истории) |
| Высота строки | 44px без подписи, 62–63px с подписью | от контента |
Состояния
| Состояние | Что меняется | Токен |
|---|---|---|
default | Базовый вид | — |
pressed | Вся строка гаснет до 60% (active:opacity-60; в Истории переопределено на 70%) | opacity/60 · opacity/70 |
focus-visible | Кольцо 2px внутрь границы, цвет --ds-accent-border. Именно inset: группа строк — overflow-hidden, кольцо с зазором обрезалось бы по краям карточки | --ds-accent-border |
dimmed | Иконка и текстовый блок до 40% — пункт «скоро». Хвост остаётся в полную силу, чтобы бейдж и Switch читались | opacity/40 |
disabled | cursor-not-allowed; работает только вместе с onClick | — |
некликабельная | Без onClick рендерится <div>, а не <button> — нет ни tap-фидбэка, ни фокуса | — |
Пропы
| Проп | Назначение |
|---|---|
icon | Эмодзи в колонке 24px, без подложки — иконка категории |
leading | Произвольный узел вместо колонки-иконки — аватар. Приоритетнее icon |
label | Заголовок, text-sm/medium, обрезается многоточием |
subtitle | Вторая строка под заголовком |
footnote | Третья строка — заметка к записи в Истории |
trailing | Хвост: шеврон, ✓, время, плюс, бейдж, Switch |
onDragStart | Строку можно перетаскивать — в самом конце хвоста появляется ручка 44×44. Обычный колбэк: ListRow про framer-motion ничего не знает |
dimmed | Гасит иконку и подписи до 40% — нереализованный пункт «скоро». Хвост не гасится |
danger | Красный заголовок — деструктивное действие |
onClick | Делает строку кнопкой: tap-фидбэк и фокус-кольцо ring-inset |
disabled | Только вместе с onClick |
PageHeader «шапка страницы» components/PageHeader.tsx
Шапка страницы: акцентная плашка, внутри переключатель ребёнка слева и действия справа, под ними необязательный контент. Лежит в components/, а не в ui-kit/ — знает про домен: ребёнка, Premium, возраст.
Строка — переключатель и действие
История и Статистика: в плашке только строка.
С контентом под строкой
Главная: под строкой статус-карточки. С контентом строка получает отступ снизу 16px.
Состояния
| Состояние | Что меняется | Токен |
|---|---|---|
default | Переключатель и действия | — |
isLoading | Вместо переключателя скелетон 160×40 полупрозрачным белым — плашка уже акцентная, обычный серый скелетон на ней не читается | rgba(255,255,255,0.2) |
без действий | Paywall-ветка Статистики — только переключатель, строка не разъезжается | — |
Пропы
| Проп | Назначение |
|---|---|
activeChild · allChildren | Данные переключателя — какой ребёнок активен и между кем переключаться |
isPremium | От него зависит, доступно ли переключение между детьми |
ageLabel | Возраст под именем ребёнка |
onSwitch | Выбран другой ребёнок |
isSwitching | Переключение уже идёт — переключатель показывает ожидание |
isLoading | Данных ещё нет — вместо переключателя скелетон. На действия не влияет |
actions | Кнопки справа: тема, фильтр, экспорт |
children | Контент под строкой, в той же плашке — статус-карточки Главной |
BottomNav «нижняя навигация» components/BottomNav.tsx
Плавающая пилюля поверх контента, четыре фиксированных таба. Не уезжает со скроллом (position: fixed), поэтому отступ снизу отсчитывается от safe area. Пропов нет вовсе: табы зашиты константой tabs в самом файле. Ни disabled, ни loading — разделы доступны всегда.
Пилюля на экране
Инсет 20px — шире полей контента (16px). Ширина calc(100% − 40px), потолок 408px.
Активный таб и обычный
Активность дважды: цвет и strokeWidth 2.25 против 2. Иконка — квадрат-заглушка, чтобы разница в штрихе читалась.
Состав таба — отступы в масштабе 1:1
| Вариант | Вертикальный | Горизонтальный | Зазор | Высота | Текст |
|---|---|---|---|---|---|
таб | pt-2.5 pb-2.5 · 10px | px-2 · 8px | gap-1 · 4px | по контенту | TabLabel |
Иконка 22px. Инсет 20px принадлежит пилюле, а не табу.
Свойства
| Элемент | Значение | Токен |
|---|---|---|
| Поверхность | Фон, обводка 1px, backdrop-filter: blur(20px) | --nav-bg · --nav-border · stroke/thin |
| Скругление | 24px | rounded-panel |
| Тень | 0 4px 24px rgba(0,0,0,.25) | shadow/nav |
| Ширина | calc(100% − 40px), потолок 408px — инсет 20px с каждой стороны | — |
| Отступ снизу | env(safe-area-inset-bottom) + 10px | space/10 |
| Паддинг таба | 10px сверху и снизу, 8px по бокам | space/10 · space/8 |
| Зазор иконка ↔ подпись | 4px | space/4 |
| Иконка | 22px, lucide | w-5-5 · h-5-5 |
| Подпись | 11px / 500, без интерлиньяжа | TabLabel |
| Слой | z-index 50 — выше контента, ниже шторок и модалок | — |
Состояния
| Состояние | Что меняется | Токен |
|---|---|---|
default | Иконка и подпись вторичным цветом, штрих 2 | --text-secondary |
active | Текущий раздел: цвет акцента и штрих 2.25 — единственное место в приложении, где состояние показано толщиной штриха, а не только цветом | --ds-accent |
pressed | Таб гаснет до 60% | opacity/60 |
Что содержит
| Таб | Маршрут | Иконка |
|---|---|---|
Главная | /dashboard | House |
История | /history | ClipboardList |
Статистика | /stats | ChartColumn |
Настройки | /settings | Settings |
ReorderableRow «строка с перетаскиванием» components/ReorderableRow.tsx
Строка «выбранного» в шторках настройки списков: иконка → подпись → бейдж «скоро» → свич → ручка перетаскивания. Ручка не отдельный компонент, а часть анатомии строки — вобрана в ListRow колбэком onDragStart.
Выбранный пункт — реализованный и «скоро»
Тянуть можно только за ручку — остальная строка остаётся кликабельной.
Список в шторке — перетаскиваемая строка
| Вариант | Вертикальный | Горизонтальный | Зазор | Высота | Текст |
|---|---|---|---|---|---|
ReorderableRow | py-2 · 8px | pl-4 16px · pr-1 4px | gap-2 · 8px | min-h-16 · 64px | BodyPrimary |
Асимметричный паддинг: 16px слева, 4px справа — место под ручку и Switch.
Свойства
| Элемент | Значение | Токен |
|---|---|---|
| Высота строки | 64px | min-h-16 |
| Отступы | 16px слева, 4px справа (под ручку), 8px сверху и снизу | pl-4 pr-1 py-2 |
| Тап-таргет ручки | 44 × 44px при иконке 18px | w-11 h-11 · --text-secondary |
| Фон и разделитель | заливка карточки, 1px между строками | --bg-card · --border-card |
Состояния
| Состояние | Что меняется | Токен |
|---|---|---|
default | Курсор «схватить» над ручкой | cursor-grab |
grabbing | Курсор «тащу», пока строка перетаскивается | active:cursor-grabbing |
dimmed | Иконка и подпись до 40% — пункт «скоро». Бейдж, свич и ручка в полную силу | opacity-40 |
focus-visible | Кольцо 2px внутрь — ручка сидит в строке с обрезкой по краям | --ds-accent-border |
Пропы
| Проп | Назначение |
|---|---|
item | { id, icon, label, implemented } — общая форма кандидата у всех трёх списков |
isLast | Последняя в группе — без нижней границы, её рисует контейнер |
onDisable | Свич выключен — убрать пункт из выбранных |
StatCard «карточка-показатель» ui-kit/StatCard.tsx
Подпись сверху → крупное число с единицей → вторичная строка → отбитый разделителем футер. Два слота под значением намеренно разные: secondary идёт без разделителя, footer — за ним. Сама карточка не интерактивна, она только показывает число.
Итог за период — Главная и Статистика
Слева Главная: итог за день и время последнего раза. Справа Статистика.
Нет данных и не реализовано
Слева метрика живая, данных пока нет. Справа не реализована — гаснет до 40%.
Свойства
| Элемент | Значение | Токен |
|---|---|---|
| Подпись | 12px, отступ снизу 8px | BodySecondary · space/8 |
| Значение | 24/700, интерлиньяж 1 | Number |
| Единица | 16/400 | text-base font-normal · --text-secondary (один сайт, не заведён как стиль) |
| Иконка вместо значения | 24px, 40% | text-2xl opacity-40 |
| Вторичная строка | 12px, отступ сверху 8px | BodySecondary · space/8 |
| Футер | 12px, отступ и поля 8px | mt-2 pt-2 · разделитель --row-divider |
| Бейдж | 8px от верха и правого края | absolute top-2 right-2 |
| Отступы | 16px по кругу | p-4 |
| Скругление и фон | 10px, без обводки | rounded-control · --bg-card |
Состояния
| Состояние | Что меняется | Токен |
|---|---|---|
default | Подпись, значение с единицей, опционально вторичная строка и футер | — |
нет данных | Вместо значения приглушённая иконка, вторичная строка — «Нет записей». Подпись яркая: метрика живая | opacity-40 у иконки |
dimmed («скоро») | Гаснет и подпись — метрика не реализована. Бейдж в углу остаётся в полную силу | opacity-40 у подписи и иконки |
Пропы
| Проп | Назначение |
|---|---|
label | Подпись сверху — «Кормление», «Кормлений/день» |
value | Крупное число. Не задано — вместо него рисуется icon |
unit | Единица рядом с числом — «раз», «мл», «ч». Мельче и не жирная: это не часть числа |
icon | Эмодзи вместо значения — «нет данных» или «не реализовано». Всегда приглушена |
secondary | Строка под значением, без разделителя: «0 мл всего», дельта к прошлому периоду |
footer | Строка под разделителем: когда событие было в последний раз |
badge | Бейдж в правом верхнем углу — «скоро». Не гаснет вместе с карточкой |
dimmed | Гасит подпись до 40% — метрика не реализована. У «нет записей» не ставится |
SelectField и TimeSelect ui-kit/SelectField.tsx · ui-kit/TimeSelect.tsx
Выпадающий список — обёртка над shadcn Select, владеющая его оформлением. TimeSelect — время чч:мм двумя списками; нативный <input type="time"> не используется, потому что в Telegram WebView он рендерится по-разному на разных платформах.
Обычный список и его подпись
44px — та же высота, что у Input.
tone="card" + icon — шапка «Графики»
Постоянно активный контрол в шапке: обводка 2px акцентная, иконка внутри.
Свойства
| Элемент | Значение | Токен |
|---|---|---|
| Высота | 44px — как у Input | h-11 |
| Текст | 14/400 | BodyPrimary |
| Отступ по бокам | 12px | space/12 |
| Обводка | 1px | stroke/thin · border/card |
| Скругление | 10px | rounded-control |
| Заливка триггера | фон поля · фон карточки при tone="card" | --bg-input · --bg-card |
| Выпадающий список | фон карточки, обводка 1px, тень | --bg-card · shadow/popover |
| Пункт списка | 14/400, подсветка выбранного акцентом | --ds-accent-light |
Состояния
| Состояние | Что меняется | Токен |
|---|---|---|
default | Базовый вид | — |
open | Список раскрыт, шеврон повёрнут | — |
focus-visible | Кольцо 2px без зазора — как у Input | --ds-accent-border |
disabled | Гаснет, курсор запрета | opacity/50 |
Пропы
| Проп | Назначение |
|---|---|
options | { value, label }[] |
label | Подпись над списком — та же, что у Input |
icon | Иконка внутри триггера перед значением |
tone | input — фон как у полей (по умолчанию); card — сплошная заливка для списка, стоящего прямо на странице |
placeholder, disabled | Как у обычного поля |
SliderField «ползунок с подписью» ui-kit/SliderField.tsx
Обвязка вокруг Slider: подпись слева, текущее значение справа, ползунок, необязательная подсказка снизу. Не слит с Input и SelectField: у тех общая 44px-коробка с обводкой и фоном, у ползунка своей поверхности нет.
С подсказкой мин/макс — настройки ребёнка
Подсказка — просто слот hint под ползунком; здесь это строка «от–до».
С текстовой подсказкой — объём кормления
Тот же слот hint — здесь вместо диапазона динамический текст, цвет которого меняет вызывающий компонент (норма/ниже/выше).
Свойства
| Элемент | Значение | Токен |
|---|---|---|
| Подпись | 12/400, text-secondary | --text-secondary |
| Значение | 14/700, text-primary | --text-primary |
| Отступ строки подписи до ползунка | 6px | mb-1-5 |
| Ползунок | как отдельный Slider | — |
Пропы
| Проп | Назначение |
|---|---|
label | Подпись слева над ползунком |
valueLabel | Текущее значение справа — отформатировано вызывающим (число + единица) |
hint | Произвольный контент под ползунком — подсказка мин/макс или текст валидации. Несёт свой отступ сверху сам |
...остальное | Все пропы Slider — min/max/step/value/onChange/disabled |
Sheet ui-kit/Sheet.tsx
Шторка снизу — основная форма всех модальных экранов: запись кормления, сна, подгузника, замеры, редактирование. Собрана из слотов Header, Body, Section и Footer. В проекте есть и ui/drawer.tsx поверх той же vaul, но его не импортирует ни один файл.
Header (заголовок + бейджи) → Body → Footer
Header.right — бейджи справа от заголовка. Footer — отдельный слот под Body, кнопка всегда на виду.
Section — подпись + контрол
Один <Sheet.Section label="Тип кормления"> — подпись и контрол в одном слоте.
Список в шторке — переключаемая строка
| Вариант | Вертикальный | Горизонтальный | Зазор | Высота | Текст |
|---|---|---|---|---|---|
строка-переключатель | — | px-4 · 16px | gap-3 · 12px | min-h-13 · 52px | BodyPrimary |
Один и тот же класс px-4 min-h-13 в пяти файлах. gap-3 приходит из базового класса ListRow.
Свойства
| Элемент | Значение | Токен |
|---|---|---|
| Скругление верха | 20px | rounded-t-sheet |
| Заливка | фон карточки | --bg-card |
| Затемнение под шторкой | чёрный 50% | opacity/50 |
| Ручка-индикатор | 48 × 6px, отступы 12 сверху и 16 снизу | h-1-5 · space/12 · space/16 |
| Цвет ручки | обводка карточки | --border-card |
| Body — отступы по бокам/снизу | 20px / 24px | px-5 pb-6 |
| Footer — граница сверху, отступы | 1px, 20px/12px/20px | --border-card · px-5 pt-3 pb-5 |
Состояния
| Состояние | Что меняется | Токен |
|---|---|---|
open / closed | Выезд снизу и затемнение фона | opacity/50 |
swipe | Тянется пальцем за ручку и закрывается свайпом вниз | — |
Пропы
| Слот | Проп | Назначение |
|---|---|---|
Sheet | open, onClose | Состояние шторки |
Sheet | maxHeightRatio | Потолок высоты как доля вьюпорта, по умолчанию 0.8. Формы в несколько шагов просят больше |
Sheet | container | Портал-контейнер — только для изолированного показа в галерее компонентов |
Header | children | Текст заголовка |
Header | subtitle | Строка под заголовком — переключает нижний отступ (mb-1 вместо mb-5) |
Header | right | Бейджи/дата справа от заголовка |
Body | children | Единственный обязательный слот — всё, что не Footer |
Section | label | Необязательна — таймер/слайдер иногда сами несут подпись внутри |
Footer | children | Кнопки — закреплены вне скролла, с верхней границей |
Иконки
В приложении два набора, и это разные вещи. Векторные иконки — lucide-react, ими нарисован интерфейс: навигация, действия, стрелки, состояния. Эмодзи иконками только выглядят: они приходят из данных — тип события, аватар ребёнка — и живут по правилам текста, а не SVG.
lucide-react 25 иконок
Единственный источник векторных иконок, своих SVG в проекте нет. Иконки ниже нарисованы теми же path, что и в приложении — вытащены из node_modules/lucide-react, а не перерисованы.
Навигация
HouseТаб «Главная»ClipboardListТаб «История»ChartColumnТаб «Статистика»SettingsТаб «Настройки», иконка приложения в AppРаскрытие и перелистывание
ChevronDownРаскрыть попап, выпадающий список, фильтрChevronUpСвернуть раскрытый списокChevronLeftПредыдущая страница ИсторииChevronRightСледующая страница, хвост-шеврон строкиДействия
PencilИзменить запись — Главная и СтатистикаEditИзменить — История и Настройки. Другая иконка, чем Pencil, при том же действииPlusДобавить событиеXЗакрыть модалкуRefreshCwСбросить настройки ребёнкаDownloadВыгрузить данные СтатистикиShare2Поделиться — приглашение второго родителяExternalLinkВнешняя ссылка — Premium и НастройкиListFilterФильтр ИсторииGitCompareСравнение периодов в СтатистикеGripVerticalРучка перетаскивания строкиСостояние
CheckВыбранный пункт, галка в спискеLoader2Спиннер внутри кнопкиДанные и тема
BarChart3Переключатель вида графика — столбцы. Тот же глиф, что ChartColumn: BarChart3 — устаревший алиасLineChartПереключатель вида графика — линияSunСветлая темаMoonТёмная темаДве находки при сверке с кодом. BarChart3 и ChartColumn — один и тот же глиф: первое имя в lucide устаревшее, но в проекте импортируются оба, в разных файлах. И наоборот: действие «Изменить» нарисовано двумя разными иконками — Pencil на Главной и в Статистике, Edit (это square-pen, карандаш в рамке) в Истории и Настройках. Ни то, ни другое не решение — просто так сложилось.
Размеры
Размер задаётся пропом size поштучно, общего токена «размер иконки» нет. Шесть значений на 42 места в коде — считая дефолтные 24px там, где проп просто забыли задать.
| Образец | Значение | Реализация | Где используется |
|---|---|---|---|
| 14px | size={14} | Инлайн рядом с текстом — галка выбора, «Изменить», внешняя ссылка. 13 мест | |
| 16px | size={16} | Самый частый: шевроны пагинации, карандаш редактирования, спиннер кнопки. 16 мест | |
| 18px | size={18} | Иконка-действие в шапке и шторках — фильтр, закрыть, добавить, ручка перетаскивания. 5 мест | |
| 20px | size={20} | Только переключатель темы на Главной — Sun и Moon | |
| 22px | size={22} | Только нижняя навигация, все четыре таба (BottomNav) | |
| 24px | проп не задан | Дефолт lucide, всплывает там, где размер забыли задать: библиотечный ui/select.tsx и иконка приложения в App.tsx |
Штрих и цвет
Толщина штриха везде 2 — дефолт lucide, никто его не переопределяет. Единственное исключение — активный таб навигации: strokeWidth 2.25, чтобы состояние читалось не только цветом. Цвет иконка не задаёт вовсе: lucide рендерит stroke="currentColor", поэтому иконка всегда того же цвета, что текст рядом — --text-secondary в спокойном состоянии, --ds-accent в активном.
Эмодзи 11 размеров
Это не иконки и не часть набора: эмодзи приходят из данных (тип события, аватар ребёнка) или стоят заглушкой в пустом состоянии. Отсюда и разнобой размеров — у каждого свой, потому что это текст внутри своего компонента, а не элемент шкалы. Переиспользуется как «стандартная иконка строки списка» только 18px; остальные размеры под одно-два места и токенами не являются.
Размер задан теми же классами, что и текст: 12–16px — text-xs/text-sm/text-base, 18–48px — text-lg…text-5xl, кроме 22px и 26px, у которых инлайновый style={{ fontSize: N }}. То есть эмодзи размечены как типографика, а не как иконки — что и объясняет, почему их размеры не сходятся со шкалой lucide.
Figma ↔ Код
Что вошло в выборку. Весь mamkin-mini-app/src, кроме библиотечных components/ui/* (shadcn/Radix, их не трогали) и служебной галереи pages/dev/*. Публичный лендинг LandingPage.tsx тоже вне шкал: он вне Telegram Mini App и размеры там свои, некратные (1.75rem, 1.8rem, 3rem). Отдельные значения из библиотечных файлов в таблицах всё же есть — они помечены в колонке «Где используется».
Всё, что нужно для переноса в Figma Variables, в одном месте — код-токен, примитив/переменная Figma, значение. Без «см. выше»: цвета, типографика, spacing, opacity и border — полностью, без сокращений.
Словарь колонок один на все справочные таблицы страницы: Образец — как токен выглядит на самом деле, Primitive — имя примитива или ссылка на него, Semantic — именованная переменная Figma, ссылающаяся на примитив, CSS-переменная — что от этого есть в коде (бывает только у семантического тира), Значение — разрешённое значение, Реализация — механизм в коде там, где он не сводится к примитиву, Где используется — места в интерфейсе. В таблицах gap/height/width/margin/padding имя переменной приведено к единому виду для Figma и не всегда 1:1 с классом в коде: дробные значения через дефис (h-1-5 вместо h-1.5 — точка не годится для имени переменной), max-h/max-w без приставки (h-44/w-48), margin/padding без направления (m-N/p-N вместо mb-N/px-N). Настоящий класс из кода в таких случаях — в колонке «Где используется». Таблицы разбора компонентов ниже устроены иначе: там строка — это элемент, состояние или проп, а не токен.
Типографика — Text Styles
Типографическая настройка в Figma — это Text Style: именованный набор Family + Size + Weight + Line height + Letter spacing, применяется одним кликом. Только стили, которые реально повторяются в коде, с готовым line-height под свой размер.
В Figma Text Style к переменной привязаны family (всегда Typography/font-family/base), weight и size — все трое через примитив. Не биндятся вообще (ограничение Figma, не наше решение) — line-height и letter-spacing: всегда сырое число внутри стиля, в процентах. В коде у типографики, в отличие от цвета, вообще нет CSS-переменной — ни одной --font-* в index.css. Реализация — сырые Tailwind-классы прямо в JSX; какие именно и где применяется каждый стиль — в разделе Типографика.
| Semantic | Размер / вес | Line-height | Letter-spacing |
|---|---|---|---|
Number | font-size/24 · weight/bold | 100% | 0% |
PageTitle | font-size/20 · weight/bold | 140% | 0% |
ModalTitle | font-size/18 · weight/bold | 156% (125% у имени ребёнка — не отдельный стиль) | 0% |
SheetHeader | font-size/16 · weight/semibold | 150% | 0% |
TileValue | font-size/14 · weight/semibold | 125% | 0% |
BodyPrimary | font-size/14 · weight/medium | 143% | 0% |
Body | font-size/14 · weight/regular | 143% | 0% |
AccentCaption | font-size/12 · weight/semibold | 133% | 0% |
ButtonCaption | font-size/12 · weight/medium | 133% | 0% |
BodySecondary | font-size/12 · weight/regular | 133% | 0% |
SectionLabel | font-size/11 · weight/semibold | Auto | 5% |
TabLabel | font-size/11 · weight/medium | 100% | 0% |
SmallBadge | font-size/10 · weight/medium | Auto | 0% |
MicroBadge | font-size/8 · weight/medium | 100% | 0% |
Line-height «Auto» — код не переопределяет line-height (наследуется от шрифта), в Figma это отдельный режим поля, не число. 5% letter-spacing у SectionLabel — то же самое, что 0.05em в коде (Figma-проценты у трекинга считаются от font-size, как и em).
Цвет — Primitives
Neutrals — тёмная и светлая шкала отдельно (не совпадают 1:1, поэтому разные примитивы, не тема одного).
Neutrals — Dark
| Образец | Primitive | Значение |
|---|---|---|
color/white | #ffffff | |
color/white 8% | #FFFFFF 8% | |
color/black | #000000 | |
color/dark/500 | #8a8a8e | |
color/dark/400 | #555555 | |
color/dark/600 | #333333 | |
color/dark/700 | #2e2e2e | |
color/dark/800 | #252525 | |
color/dark/750 | #1e1e1e | |
color/dark/900 | #111111 |
Neutrals — Light
| Образец | Primitive | Значение |
|---|---|---|
color/white | #ffffff | |
color/black | #000000 | |
color/black 5% | #000000 5% | |
color/gray/50 | #f9f9f9 | |
color/gray/100 | #f2f2f7 | |
color/gray/150 | #f0f0f0 | |
color/gray/200 | #e5e7eb | |
color/gray/400 | #9ca3af | |
color/gray/500 | #6b7280 | |
color/gray/900 | #111111 |
Цвета — по оттенку
| Образец | Primitive | Значение |
|---|---|---|
color/green/400 | #30D158 | |
color/green/400 15% | #30D158 15% | |
color/green/500 | #34C759 | |
color/green/500 12% | #34C759 12% | |
color/green/600 | #1D9E75 | |
color/green/600 15% | #1D9E75 15% | |
color/green/600 40% | #1D9E75 40% | |
color/blue/300 | #64D2FF | |
color/blue/500 | #0A84FF | |
color/blue/500 15% | #0A84FF 15% | |
color/blue/500 40% | #0A84FF 40% | |
color/blue/600 | #007AFF | |
color/blue/600 12% | #007AFF 12% | |
color/amber/300 | #FFB340 | |
color/amber/500 | #FF9F0A | |
color/amber/500 15% | #FF9F0A 15% | |
color/amber/600 | #FF9500 | |
color/amber/600 12% | #FF9500 12% | |
color/amber/600 15% | #FF9500 15% | |
color/amber/600 40% | #FF9500 40% | |
color/red/500 | #FF453A | |
color/red/500 15% | #FF453A 15% | |
color/red/600 | #FF3B30 | |
color/red/600 12% | #FF3B30 12% | |
color/purple/300 | #A78BFA | |
color/purple/500 | #7B72E9 | |
color/purple/500 15% | #7B72E9 15% | |
color/purple/500 40% | #7B72E9 40% | |
color/rose/300 | #FF6B8A | |
color/rose/500 | #FF375F | |
color/rose/500 15% | #FF375F 15% | |
color/rose/500 40% | #FF375F 40% | |
color/teal/300 | #5AC8FA | |
color/teal/500 | #32ADE6 | |
color/teal/500 15% | #32ADE6 15% | |
color/teal/500 40% | #32ADE6 40% | |
color/neutral-accent/300 | #AEAEB2 | |
color/neutral-accent/500 | #8E8E93 | |
color/neutral-accent/500 15% | #8E8E93 15% | |
color/neutral-accent/500 40% | #8E8E93 40% |
Строки с процентом (напр. color/green/400 15%) — отдельные primitive с прозрачностью, запечённой в HEX/rgba, не наложение opacity поверх основного цвета: Figma не даёт применить Fill Opacity к уже вставленной ссылке на переменную, только к слою целиком.
Цвет — Semantic
CSS-переменная в коде · Semantic (Figma) · Primitive, на который ссылается · где используется. Разрешённое значение цвета — у самого примитива в таблице выше, здесь не повторяется.
Light mode
| CSS-переменная | Semantic | Primitive | Где используется |
|---|---|---|---|
--bg-page | bg/page | color/gray/100 | Фон страницы |
--bg-card | bg/card | color/white | Фон карточки/шита |
--bg-card2 | bg/card2 | color/gray/50 | Фон вложенной группы (settings) |
--bg-input | bg/input | color/gray/100 | Фон полей ввода, неактивные кнопки |
--border-card | border/card | color/gray/200 | Обводка карточек/полей |
--text-primary | text/primary | color/gray/900 | Основной текст |
--text-secondary | text/secondary | color/gray/500 | Вторичный текст |
--text-placeholder | text/placeholder | color/gray/400 | Плейсхолдеры |
--row-divider | row/divider | color/gray/150 | Разделители строк списков |
--nav-bg | nav/bg | color/white | Фон нижней навигации |
--nav-border | nav/border | color/gray/200 | Обводка нижней навигации |
--icon-btn-bg | icon-btn/bg | color/black 5% | Фон Button variant=subtle |
Dark mode
| CSS-переменная | Semantic | Primitive | Где используется |
|---|---|---|---|
--bg-page | bg/page | color/dark/900 | Фон страницы |
--bg-card | bg/card | color/dark/800 | Фон карточки/шита |
--bg-card2 | bg/card2 | color/dark/750 | Фон вложенной группы (settings) |
--bg-input | bg/input | color/dark/700 | Фон полей ввода, неактивные кнопки |
--border-card | border/card | color/dark/600 | Обводка карточек/полей |
--text-primary | text/primary | color/white | Основной текст |
--text-secondary | text/secondary | color/dark/500 | Вторичный текст |
--text-placeholder | text/placeholder | color/dark/400 | Плейсхолдеры |
--row-divider | row/divider | color/dark/600 | Разделители строк списков |
--nav-bg | nav/bg | color/dark/750 | Фон нижней навигации |
--nav-border | nav/border | color/dark/600 | Обводка нижней навигации |
--icon-btn-bg | icon-btn/bg | color/white 8% | Фон Button variant=subtle |
Status & CTA
Один токен — одна строка. accent/accent-light сюда не входят — дубль варианта default из Accent ребёнка ниже. -bg — тот же primitive, что у основного токена, с прозрачностью.
Dark
| CSS-переменная | Semantic | Primitive | Где используется |
|---|---|---|---|
--success | success | color/green/400 | Подтверждение, идёт/активно |
--success-bg | success-bg | color/green/400 15% | Подтверждение, идёт/активно (фон) |
--ds-warning | warning | color/amber/500 | Предупреждение, error-баннер Dashboard |
--ds-warning-bg | warning-bg | color/amber/500 15% | Предупреждение, error-баннер Dashboard (фон) |
--ds-danger | danger | color/red/500 | Удаление, ошибки ввода |
--ds-danger-bg | danger-bg | color/red/500 15% | Удаление, ошибки ввода (фон) |
--ds-info | info | color/blue/500 | Кормление в EventRow/history |
--ds-info-bg | info-bg | color/blue/500 15% | Кормление в EventRow/history (фон) |
Light
| CSS-переменная | Semantic | Primitive | Где используется |
|---|---|---|---|
--success | success | color/green/500 | Подтверждение, идёт/активно |
--success-bg | success-bg | color/green/500 12% | Подтверждение, идёт/активно (фон) |
--ds-warning | warning | color/amber/600 | Предупреждение, error-баннер Dashboard |
--ds-warning-bg | warning-bg | color/amber/600 12% | Предупреждение, error-баннер Dashboard (фон) |
--ds-danger | danger | color/red/600 | Удаление, ошибки ввода |
--ds-danger-bg | danger-bg | color/red/600 12% | Удаление, ошибки ввода (фон) |
--ds-info | info | color/blue/600 | Кормление в EventRow/history |
--ds-info-bg | info-bg | color/blue/600 12% | Кормление в EventRow/history (фон) |
Accent ребёнка (7 вариантов × 4 роли)
Modes на бесплатном тарифе Figma нет — вместо одной переменной с 7 режимами здесь 28 отдельных Semantic-переменных (по 4 на цвет: сам акцент / подложка 15% / текст на подложке / обводка 40%). Смена акцента у ребёнка — это выбор готового набора, не один клик по переменной.
| CSS-переменная | Semantic | Primitive | Где используется |
|---|---|---|---|
--ds-accent | accent/default | color/neutral-accent/500 | Цвет CTA/аватара, пока ребёнку не выбрали свой |
--ds-accent-light | accent-light/default | color/neutral-accent/500 15% | Подложка под accent (default) |
--accent-text | accent-text/default | color/neutral-accent/300 | Текст поверх accent-light (default) |
--ds-accent-border | accent-border/default | color/neutral-accent/500 40% | Обводка инфо-блока/плашки (default) |
--ds-accent | accent/purple | color/purple/500 | Цвет CTA/аватара — вариант «Фиолетовый» |
--ds-accent-light | accent-light/purple | color/purple/500 15% | Подложка под accent (Фиолетовый) |
--accent-text | accent-text/purple | color/purple/300 | Текст поверх accent-light (Фиолетовый) |
--ds-accent-border | accent-border/purple | color/purple/500 40% | Обводка инфо-блока (Фиолетовый) |
--ds-accent | accent/blue | color/blue/500 | Цвет CTA/аватара — вариант «Синий» |
--ds-accent-light | accent-light/blue | color/blue/500 15% | Подложка под accent (Синий) |
--accent-text | accent-text/blue | color/blue/300 | Текст поверх accent-light (Синий) |
--ds-accent-border | accent-border/blue | color/blue/500 40% | Обводка инфо-блока (Синий) |
--ds-accent | accent/green | color/green/600 | Цвет CTA/аватара — вариант «Зелёный» |
--ds-accent-light | accent-light/green | color/green/600 15% | Подложка под accent (Зелёный) |
--accent-text | accent-text/green | color/green/400 | Текст поверх accent-light (Зелёный) |
--ds-accent-border | accent-border/green | color/green/600 40% | Обводка инфо-блока (Зелёный) |
--ds-accent | accent/pink | color/rose/500 | Цвет CTA/аватара — вариант «Розовый» |
--ds-accent-light | accent-light/pink | color/rose/500 15% | Подложка под accent (Розовый) |
--accent-text | accent-text/pink | color/rose/300 | Текст поверх accent-light (Розовый) |
--ds-accent-border | accent-border/pink | color/rose/500 40% | Обводка инфо-блока (Розовый) |
--ds-accent | accent/orange | color/amber/600 | Цвет CTA/аватара — вариант «Оранжевый» |
--ds-accent-light | accent-light/orange | color/amber/600 15% | Подложка под accent (Оранжевый) |
--accent-text | accent-text/orange | color/amber/300 | Текст поверх accent-light (Оранжевый) |
--ds-accent-border | accent-border/orange | color/amber/600 40% | Обводка инфо-блока (Оранжевый) |
--ds-accent | accent/teal | color/teal/500 | Цвет CTA/аватара — вариант «Бирюзовый» |
--ds-accent-light | accent-light/teal | color/teal/500 15% | Подложка под accent (Бирюзовый) |
--accent-text | accent-text/teal | color/teal/300 | Текст поверх accent-light (Бирюзовый) |
--ds-accent-border | accent-border/teal | color/teal/500 40% | Обводка инфо-блока (Бирюзовый) |
--accent-gradient (7 вариантов, 135°, opacity/30 → opacity/15 поверх того же primitive) — Opacity-приёмом не сделать, нужен отдельный Gradient-стиль в Figma, не Number+Color.
Пространство, прозрачность, обводка
Один примитив (space/N), но каждое из семи свойств — свои именованные переменные Figma, каждая со своей ссылкой на примитив (как rounded-control ниже ссылается на space/10). Токен из кода = имя переменной Figma 1:1.
Про колонку «CSS-переменная». Она стоит только у семантического тира — как и у цвета; в таблице примитивов ниже её нет, потому что примитив в коде вообще не существует отдельной переменной. В отличие от цвета, где на каждый семантический токен есть своя --*, здесь настоящая CSS-переменная существует только у скруглений (--radius-*) и у прозрачностей, запечённых в цветовые переменные (--ds-accent-light, --success-bg и т.п.). У gap / height / width / margin / padding и у толщины обводки переменных нет вообще, и прочерк в колонке — не пропуск в документации, а как есть: Tailwind 3 генерирует эти классы статически на этапе сборки, значение подставляется прямо в CSS-правило и во время исполнения ничего не читается. Единая база --space-unit: 0.25rem в index.css объявлена, но реально связывает только шкалу --radius-*, где каждое значение — её множитель; со шкалой Tailwind она совпадает численно, но не связана.
| Primitive | Значение |
|---|---|
space/0 | 0px |
space/2 | 2px |
space/4 | 4px |
space/6 | 6px |
space/8 | 8px |
space/10 | 10px |
space/12 | 12px |
space/14 | 14px |
space/16 | 16px |
space/20 | 20px |
space/22 | 22px |
space/24 | 24px |
space/28 | 28px |
space/32 | 32px |
space/36 | 36px |
space/40 | 40px |
space/44 | 44px |
space/48 | 48px |
space/52 | 52px |
space/56 | 56px |
space/64 | 64px |
space/68 | 68px |
space/72 | 72px |
space/80 | 80px |
space/96 | 96px |
space/112 | 112px |
space/128 | 128px |
space/144 | 144px |
space/160 | 160px |
space/176 | 176px |
space/192 | 192px |
space/224 | 224px |
space/448 | 448px |
corner radius 8 значений
| CSS-переменная | Semantic | Primitive | Где используется |
|---|---|---|---|
| — | rounded-none | space/0 | Только в библиотечном компоненте, в интерфейсе приложения не встречается |
--radius-xs | rounded-xs | space/4 | Мелкие декоративные скругления |
--radius-control-sm | rounded-control-sm | space/8 | Вложенные элементы внутри карточек — пункты списка при hover/active |
--radius-control | rounded-control | space/10 | Базовый радиус — карточки, кнопки, инпуты, попапы по всему приложению |
--radius-sheet | rounded-sheet | space/20 | Верх bottom-sheet. В коде применяется направленным вариантом rounded-t-sheet — скруглены только верхние углы, нижние уходят за край экрана |
--radius-panel | rounded-panel | space/24 | Крупные панели — нижняя навигация, шторка выбора ребёнка |
--radius-plate | rounded-plate | space/28 | Акцентная плашка-шапка — Главная, История, Статистика |
| — | rounded-full | — | Аватары, точки-индикаторы, круглые кнопки, переключатели — не шаг сетки, keyword-значение |
item spacing (gap) 8 значений
| CSS-переменная | Semantic | Primitive | Где используется |
|---|---|---|---|
| — | gap-0-5 | space/2 | Зазор между пилюлями в сегмент-контроле ChipSelect compact — единственное место |
| — | gap-1 | space/4 | Минимальный отступ между мелкими элементами — иконка и текст рядом |
| — | gap-1-5 | space/6 | Отступ между элементами строки настроек |
| — | gap-2 | space/8 | Стандартный отступ внутри блока — самое частое значение |
| — | gap-2-5 | space/10 | Отступ между строками карточки |
| — | gap-3 | space/12 | Отступ между карточками |
| — | gap-4 | space/16 | Отступ между секциями страницы |
| — | gap-5 | space/20 | Одно место — промо-страница Premium, и там это space-y-5: отступ между детьми, а не gap флексбокса |
height 26 значений
| CSS-переменная | Semantic | Primitive | Где используется |
|---|---|---|---|
| — | h-1-5 | space/6 | Ручка-индикатор шторки (единообразно во всех шторках), трек слайдеров в настройках ребёнка |
| — | h-2 | space/8 | Точки-индикаторы статуса, высота полоски в skeleton |
| — | h-3 | space/12 | Skeleton текстовых плейсхолдеров |
| — | h-3-5 | space/14 | Skeleton текстовых плейсхолдеров (вариант длиннее строки) |
| — | h-4 | space/16 | Разделительная вертикальная линия между элементами |
| — | h-5 | space/20 | Skeleton тогла в настройках уведомлений, иконка Premium-фичи |
| — | h-5-5 | space/22 | Skeleton-иконка нижней навигации (парно с width — тот же элемент) |
| — | h-6 | space/24 | Skeleton-заголовки, компактные круглые кнопки/аватары (size=sm) |
| — | h-7 | space/28 | Skeleton-заголовок; бегунок переключателя |
| — | h-8 | space/32 | Компактные кнопки/бейджи; трек переключателя |
| — | h-9 | space/36 | Круглые аватары/иконки среднего размера (карточки настроек, приглашения) |
| — | h-10 | space/40 | Аватары среднего размера, инпуты |
| — | h-11 | space/44 | Минимальная высота тап-таргета (Apple HIG) — самое частое значение: инпуты, кнопки, строки попапов |
| — | h-13 | space/52 | Строки настроек с крупной иконкой, крупная кнопка |
| — | h-14 | space/56 | Крупные круглые аватары (карточка ребёнка), skeleton-плашка блока статистики |
| — | h-16 | space/64 | Строки настроек с превью-иконкой карточки |
| — | h-17 | space/68 | Skeleton статус-строк «Следующее кормление/Следующий сон» в акцентной плашке-шапке |
| — | h-18 | space/72 | Запас снизу под sticky-бар «Сохранить/Отменить» |
| — | h-20 | space/80 | Переиспользуемый размер skeleton-карточек — блоки быстрой записи/статистики |
| — | h-24 | space/96 | Переиспользуемый размер skeleton-карточек по всему приложению |
| — | h-36 | space/144 | Нейтральная заглушка при переключении вкладок (не форма конкретного экрана) |
| — | h-40 | space/160 | Placeholder-блок графика в Истории |
| — | h-44 | space/176 | Ограничение высоты списка в попапе (Статистика) |
| — | h-48 | space/192 | Skeleton графика (Статистика) |
| — | h-full | — | На всю высоту родителя (изображения аватаров) |
| — | h-screen | — | Корневой контейнер приложения; потолок высоты шторок и модалок |
width 23 значения
| CSS-переменная | Semantic | Primitive | Где используется |
|---|---|---|---|
| — | w-2 | space/8 | Точки-индикаторы статуса (парно с height) |
| — | w-5 | space/20 | Skeleton тогла в настройках уведомлений (парно с height, тот же элемент), иконка Premium-фичи |
| — | w-5-5 | space/22 | Skeleton-иконка нижней навигации — совпадает с реальным размером иконок. Не на 4px-сетке — совпадение, не шаг ритма |
| — | w-6 | space/24 | Контейнер эмодзи-иконки в строке настроек (единообразно во всех шторках), компактные круглые кнопки |
| — | w-8 | space/32 | Компактные круглые аватары/иконки (size=xs/sm) |
| — | w-9 | space/36 | Круглые аватары/skeleton тогла среднего размера |
| — | w-10 | space/40 | Аватары среднего размера |
| — | w-11 | space/44 | Крупные квадратные иконки/drag-хендлы настроек |
| — | w-12 | space/48 | Ручка-индикатор шторки (парно с height, единообразно во всех шторках) |
| — | w-13 | space/52 | Трек переключателя |
| — | w-14 | space/56 | Крупные круглые аватары |
| — | w-16 | space/64 | Skeleton текстовых плейсхолдеров |
| — | w-20 | space/80 | Skeleton текстовых плейсхолдеров |
| — | w-24 | space/96 | Skeleton текстовых плейсхолдеров |
| — | w-28 | space/112 | Skeleton текстового плейсхолдера (доступ к профилю) |
| — | w-32 | space/128 | Skeleton заголовков/строк |
| — | w-40 | space/160 | Skeleton заголовков |
| — | w-44 | space/176 | Skeleton заголовков (вариант длиннее) |
| — | w-48 | space/192 | Ограничение ширины второй skeleton-строки (уведомления) — потолок, не жёсткая ширина |
| — | w-56 | space/224 | Ширина всплывающей панели — переключатель ребёнка, сравнение периодов в Статистике |
| — | w-md | space/448 | Максимальная ширина мобильного контейнера приложения |
| — | w-full | — | На всю ширину родителя — самое частое значение |
| — | w-auto | — | Автоматическая ширина (кнопка сравнения периодов в Статистике) |
margin 12 значений
| CSS-переменная | Semantic | Primitive | Где используется |
|---|---|---|---|
| — | m-0 | space/0 | Сброс отступа у последнего элемента списка |
| — | m-0-5 | space/2 | Минимальный зазор, подпись под заголовком строки |
| — | m-1 | space/4 | Отступ между тесно связанными строками (лейбл/значение) |
| — | m-1-5 | space/6 | Подпись поля перед инпутом |
| — | m-2 | space/8 | Пояснительный текст под группой контролов |
| — | m-2-5 | space/10 | Отступ под сеткой быстрых кнопок |
| — | m-3 | space/12 | Отступ между карточками, второе по частоте после 16px |
| — | m-4 | space/16 | Отступ между блоками, самое частое margin-значение в коде |
| — | m-5 | space/20 | Отступ между секциями страницы, см. rhythm/section-gap ниже |
| — | m-6 | space/24 | Отступ перед списком опций в шторке; отступ под заголовком страницы |
| — | m-8 | space/32 | Отступ в самом низу страницы Настроек, перед версией приложения |
| — | m-24 | space/96 | Только в библиотечном компоненте (шторка снизу), не в интерфейсе приложения |
padding 14 значений
| CSS-переменная | Semantic | Primitive | Где используется |
|---|---|---|---|
| — | p-0 | space/0 | Явный сброс паддинга у переиспользуемого компонента |
| — | p-0-5 | space/2 | Микро-паддинг вокруг мелких бейджей/иконок нижней навигации |
| — | p-1 | space/4 | Паддинг компактных иконочных кнопок-«карандашей» |
| — | p-1-5 | space/6 | Паддинг маленьких бейджей/чипов |
| — | p-2 | space/8 | Паддинг компактных кнопок/строк-табов нижней навигации |
| — | p-2-5 | space/10 | Паддинг средних кнопок/строк табов нижней навигации |
| — | p-3 | space/12 | Паддинг строки настроек — самое частое значение после 16px |
| — | p-3-5 | space/14 | Паддинг чуть крупнее стандартной строки |
| — | p-4 | space/16 | Самый частый вариант — то же самое, что layout/margin ниже (поля страницы) |
| — | p-5 | space/20 | Паддинг шторок (bottom sheet) по бокам |
| — | p-6 | space/24 | Паддинг крупных модалок |
| — | p-8 | space/32 | Запас снизу под safe-area/sticky-бар в шторках (единообразно во многих модалках) |
| — | p-10 | space/40 | Отступ под иконку слева; запас снизу в отдельных модалках |
| — | p-16 | space/64 | Пустое состояние (empty state) по центру страницы История/Статистика |
Opacity и толщина обводки — по строению это примитивы (сырое число, ни на что не ссылается), но в Figma-файле их пока нет вообще: вбиваются вручную в каждом использовании. Семантического тира над ними тоже нет — поэтому колонки «Semantic» здесь не бывает, а «Реализация» показывает механизм в коде. rounded-full уже показан выше, в таблице corner radius — отдельной таблицы под него не заводим. Колонка «CSS-переменная» у opacity — не переменная прозрачности как таковой: своей --opacity-* в проекте нет, процент запечён внутрь цветовой переменной (--ds-accent-light — это акцент, уже смешанный с 15%). Где прочерк — значение живёт классом Tailwind или инлайн-стилем, в CSS-переменную не вынесено.
Opacity
| CSS-переменная | Primitive | Значение | Реализация | Где используется |
|---|---|---|---|---|
| — | opacity/0 | 0 | rgba(...,0), inline | Второй (конечный) стоп градиента-маски плашки-шапки (AccentPlate.tsx) |
--icon-btn-bg | opacity/5 | 5 | запечено в переменной, светлая тема | Фон Button variant=subtle, светлая тема |
--icon-btn-bg | opacity/8 | 8 | запечено в переменной (тёмная) + отдельный inline rgba | Фон Button variant=subtle тёмная тема; отдельно — hover-курсор графиков в Статистике (совпадение процента, не общий токен) |
| — | opacity/10 | 10 | — | Не используется в реальном интерфейсе — только пример в dev/Showcase; в проде объединено в --ds-danger-bg (0.12/0.15) |
--success-bg | opacity/12 | 12 | запечено в переменной, светлая тема | Фон статус-плашек, светлая тема — по одной переменной на статус: --success-bg, --ds-warning-bg, --ds-danger-bg, --ds-info-bg |
--success-bg · --ds-accent-light | opacity/15 | 15 | запечено в переменных | Фон статус-плашек тёмная тема (те же 4 переменные); фон --ds-accent-light — все 7 акцентных цветов |
| — | opacity/18 | 18 | rgba(255,255,255,.18), inline | Первый стоп градиента плашки-шапки; полупрозрачные подложки на ней (Dashboard.tsx) |
| — | opacity/20 | 20 | rgba(255,255,255,.2), inline ×6 | Overlay-подложка под skeleton загрузки — не общая переменная, значение вручную скопировано в 6 файлах |
| — | opacity/25 | 25 | rgba, inline | Тень нижней навигации (BottomNav); отдельно — подложка аватара на акцентном фоне (Avatar onAccent) |
| — | opacity/30 | 30 | rgba(255,255,255,.3), inline | Обводка hero-карточки на Главной (Dashboard); бегунок Switch сюда не относится — у него Tailwind shadow-lg, другой механизм |
--ds-accent-border | opacity/40 | 40 | смешанно: класс opacity-40, inline-style и запечено в переменной | Иконки нереализованных функций и disabled-пагинация Истории (класс), disabled-опции в шторках (inline), кольцо фокуса и обводки на акценте (--ds-accent-border, все 7 цветов) |
| — | opacity/50 | 50 | disabled:opacity-50 | Disabled-состояние кнопок — вторая по частоте; у primary/danger кнопок гашение иначе (--bg-input, без opacity) |
| — | opacity/60 | 60 | active:/disabled:opacity-60, отдельно inline rgba | Active/tap и disabled-состояния строк настроек (класс); тёмный скрим модалок — отдельный inline rgba(0,0,0,.6) |
| — | opacity/70 | 70 | active:opacity-70 | Active/tap-нажатие — большинство кнопок приложения, самое частое; единственное значение с одной реализацией везде |
| — | opacity/75 | 75 | JS-константа rgba | Вторичный текст на цветной hero-карточке (Dashboard, ChildSwitcherPopover) |
| — | opacity/92 | 92 | opacity-[92%] | Paywall-баннер «Ранняя история» — единственное место, Tailwind arbitrary value |
| — | opacity/100 | 100 | inline style, ветка «иначе» | Явная отмена унаследованной прозрачности — всегда «иначе» в тернарнике рядом с другим opacity-значением, не отдельный класс |
stroke — толщина обводки
| CSS-переменная | Primitive | Значение | Реализация | Где используется |
|---|---|---|---|---|
| — | stroke/none | 0px | border-0 / border-transparent | Два разных паттерна: last:border-0 — сброс разделителя у последнего пункта списка; border-transparent — ширина остаётся, гасится только цвет |
| — | stroke/thin | 1px | класс border / inline | Базовая обводка карточек, полей, разделителей — доминирующее значение; вперемешку Tailwind-класс и inline 1px solid var(...). Переменная в inline-записи — это цвет (--border-card), не толщина |
| — | stroke/bold | 2px | inline, не border-2 | Активный таймер кормления/сна (QuickActionButton), выбранный тариф Premium — оба места инлайн-стилем, класс border-2 в коде есть, но не здесь |
Тень, градиент, ритм и сетка
Как у opacity и обводки, семантического тира здесь нет: значения одноразовые, ссылаться вниз не на что — поэтому колонки «Semantic» не бывает, а сами токены по строению примитивы. «Реализация» показывается там, где механизм в коде не очевиден из значения.
shadow (elevation) 4 значения
Тень — только у элементов, плавающих поверх контента (навигация, попапы). У обычных карточек сигнал глубины — обводка stroke/thin (значения — в разделе Figma ↔ Код), не тень.
| Primitive | Значение | Где используется |
|---|---|---|
shadow/nav | 0 4px 24px rgba(0,0,0,.25) | Плавающая нижняя навигация (BottomNav.tsx) и её skeleton-заглушка на старте (App.tsx) — дословный дубль значения в обоих местах, не общая константа |
shadow/popover | 0 8px 24px rgba(0,0,0,.18) | Поверхность выпадающих попапов — переключатель ребёнка, выбор метрики графика. Больше не дубль: вынесена в ui-kit/PopoverPanel.tsx |
shadow-lg | Tailwind default | Бегунок Switch (ui/switch.tsx) — единственное место, где тень взята стоковым Tailwind-классом, а не собственным значением |
focus-ring | 0 0 0 2px transparent, 0 0 0 4px var(--ds-accent-border) | Кольцо фокуса Button (см. Components) — тоже box-shadow (ring-2 + ring-offset-2), не обводка; единственная тень с зазором (offset) в приложении |
Библиотечный shadow-md в базовых классах ui/popover.tsx — фактически мёртвый: оба реальных потребителя (shadow/popover выше) переопределяют его инлайн-стилем. Тень DesktopFrame (0 24px 80px rgba(0,0,0,.5)) — вне основной шкалы: рендерится только вне мобильного Telegram (браузер, Telegram Desktop), не часть мобильного интерфейса.
gradient 2 значения
Только 2 реальных градиента, оба акцентные — завязаны на var(--ds-accent) текущего ребёнка, не статичная палитра.
| Primitive | Значение | Где используется |
|---|---|---|
ACCENT_PLATE_BG | 135deg, rgba(255,255,255,.18)→0 60%, поверх var(--ds-accent) | Акцентная плашка-шапка (rounded-plate, см. corner radius) — Главная/История/Статистика, переключатель ребёнка. Единственный градиент, вынесенный в общий JS-экспорт: живёт в ui-kit/AccentPlate.tsx, фон задаёт сам PageHeader/AccentPlate |
--accent-gradient | accent-gradient/* — color/{hue}/500 · 135°, stop1 opacity/30 → stop2 opacity/15 | Промо-карточка Premium (Settings.tsx) — CSS-переменная, 7 версий (по одной на каждый акцентный цвет ребёнка, см. Colors) |
section header & rhythm 3 значения
Вертикальный ритм между секциями страницы — 20px практически везде. За это отвечает не отдельный «межсекционный» класс, а штатное схлопывание CSS-margin (margin collapse) между нижним отступом предыдущего блока и встроенным mt-5 компонента SettingsSectionHeader: браузер берёт максимум из двух соседних margin, не их сумму, поэтому 8/12px нижнего отступа карточки плюс 20px верхнего отступа заголовка дают ровно 20px, а не 28/32px. Эмуляция экрана с этим ритмом — в разделе layout & grid выше.
| Primitive | Значение | Реализация | Где используется |
|---|---|---|---|
heading/page | 20px / 700 | text-xl font-bold | Заголовок страницы — 16px до контента |
heading/section | 11px / 600 | text-[11px] font-semibold | Uppercase-лейбл секции — единообразно по всему приложению |
rhythm/section-gap | 20px | mt-5, схлопывается с нижним отступом предыдущего блока | Интервал перед новой секцией |
Margin collapse работает только между block-уровневыми соседями — исключение здесь: компонент с базовым классом inline-flex, а inline-flex элементы в collapse не участвуют, поэтому интервал перед такой секцией держится на встроенном mt-5 заголовка, а не на схлопывании отступов.
layout 2 значения
Ширина мобильного контейнера и горизонтальные поля страницы. Разбор внутренних отступов компонентов, с картинками, остался в разделе Layout & grid.
| Primitive | Значение | Реализация | Где используется |
|---|---|---|---|
layout/container | 448px | max-w-md | Максимальная ширина мобильного контейнера, центрируется на широких экранах — то же значение, что и w-md в шкале width выше |
layout/margin | 16px | px-4 | Горизонтальные поля страницы на каждой вкладке |