Files
easy-png-tools/docs/design-system.md
T

10 KiB
Raw Blame History

План: единая дизайн-система

Статус: план к выполнению. Следующий этап после MVP, до UX-работ из backlog (реактивные параметры, слайдеры/пипетка, раскладка).

1. Цели и принципы

  1. Один источник правды — все визуальные константы живут в токенах web/src/app.css; ни один компонент не пишет «магические» цвета/размеры напрямую.
  2. Никакого дублирования — повторяющийся визуальный рецепт оформляется один раз: либо токен, либо глобальный утилитарный класс, либо компонент в ui/.
  3. Простые компоненты важнее умных — если проще сделать отдельные SliderField и TextField, делаем два маленьких компонента, а не один конфигурируемый монстр.
  4. Scoped-стили — только для раскладки конкретного компонента (сетка, отступы между блоками). Внешний вид контролов и текстов — из дизайн-системы.

2. Аудит: что дублируется прямо сейчас

  • «Приглушённый текст» (--text-muted + размер 0.85–0.95rem) — повторён в семи местах: ToolPage (.description, .hint), главная (.lead, .card-desc), лейаут (.nav-link, footer), Preview (.dims), ParamForm (label), InfoPanel (dt).
  • «Заголовок секции капсом» (uppercase, letter-spacing, muted) — точная копипаста в ToolPage и на главной.
  • Базовые инпуты (padding, border, radius) — живут только внутри ParamForm; слайдеры/пипетка из backlog потребуют копирования.
  • Блок «label + отступы» — дублировался бы в каждом *Field; решается обёрткой Field.svelte.
  • «Панель» (surface + border + radius) — три ручные реализации одного паттерна: DropZone, карточки главной, строки InfoPanel.
  • Кнопки — глобальные классы .primary/.secondary без компонента; состояния «занят/недоступен» каждый описывает заново.
  • Ошибка-баннер — только в ToolPage, а понадобится минимум ещё раз в workspace.

3. Токены (расширение app.css)

Существующие остаются; добавляются недостающие группы:

:root {
  /* типографика */
  --text-xs: 0.8rem;
  --text-s: 0.85rem;
  --text-m: 0.95rem;
  --text-l: 1.1rem;
  --text-xl: 1.25rem;
  --text-xxl: 1.75rem;
  --leading-tight: 1.25;

  /* тени */
  --shadow-card: 0 4px 14px rgb(37 99 235 / 12%);

  /* фокус */
  --focus-ring: 2px solid var(--accent);

  /* контрольные размеры контролов */
  --control-height: 2.4rem;
  --control-max-width: 16rem;
}

Правило именования: семантика, не значение (--text-muted, а не --gray-500).

4. Глобальные утилиты (app.css)

Только то, что не требует JS и не является интерактивным:

  • .text-muted — приглушённый текст (размер наследуется);
  • .text-caption — мелкий текст --text-s;
  • .heading-section — заголовок секции капсом (вместо двух копипаст);
  • .panel — поверхность с бордером/радиусом (DropZone, карточки, InfoPanel-строки);
  • .error-banner — баннер ошибки;
  • .visually-hidden — скрытый, но читаемый скринридерами.

Элементные стили из текущего app.css (голые input, select { ... }) убрать — внешний вид контролов переезжает в компоненты ui/.

5. Состав UI-компонентов (web/src/lib/components/ui/)

Все — тонкие обёртки над нативными элементами, без состояний кроме своих:

Компонент Пропы Заменяет
Field.svelte id, label, hint?, слот контрола label + раскладка поля; единая точка для hint'ов и ошибок валидации
Button.svelte variant: 'primary' | 'secondary', disabled, busy, слот текста глобальные .primary/.secondary, локальный busy в DownloadButton
TextField.svelte id, label, type: 'number' | 'text', min/max/step, value = $bindable() number/text ветки ParamForm
SliderField.svelte id, label, min/max/step, value = $bindable(), показ значения рядом ничего — база для backlog п.2 (яркость, качество, порог)
SelectField.svelte id, label, options, value = $bindable() select-ветка ParamForm
CheckboxField.svelte id, label, checked = $bindable() чекбокс ParamForm
ColorField.svelte id, label, value = $bindable() color-ветка ParamForm; точка будущего расширения пипеткой
EmptyState.svelte title, hint, слот действия «Обработка…», будущие пустые панели исходника/результата (backlog п.3)

Field — чисто раскладочная обёртка (<div class="field"><label for>…</label><slot /></div>), ничего не знает о типе контрола. На ней построены TextField, SliderField, SelectField, ColorField. Исключение — CheckboxField: у чекбокса label оборачивает контрол (кликабельная строка), поэтому у него своя раскладка.

6. Миграция существующего

  1. ParamForm — переписывается на Field + *Field-компоненты; собственная стилизация контролов и label удаляется.
  2. DownloadButton и кнопки ToolPage — на Button.
  3. DropZone, карточки главной, строки InfoPanel — на .panel + свои раскладочные стили.
  4. ToolPage, главная, лейаут — приглушённые тексты на .text-*, заголовки секций на .heading-section, баннер на .error-banner.
  5. Из app.css удалить глобальные классы кнопок и голые стили инпутов после перехода.

7. Порядок работ

Этап Содержимое Проверка
A. Фундамент Токены + утилиты в app.css pnpm check
B. Примитивы Восемь компонентов в components/ui/ pnpm check; каждый используется хотя бы раз после этапа C
C. Миграция Переделка пяти файлов из §6 Визуально: страницы выглядят как раньше; test/check/build зелёные
D. Чистка Удаление устаревших классов и дублей из scoped-стилей grep: в components/ нет hex-цветов и var(--space…) вне раскладки

8. Критерии готовности

  • Ни один компонент не задаёт цвет/радиус/шрифт мимо токенов или классов системы.
  • Все интерактивные контролы на страницах — экземпляры ui/*.
  • grep -E '#[0-9a-f]{3,6}' web/src/lib/components не находит ничего, кроме токенов в app.css.
  • Визуальной регрессии нет (ручная сверка всех страниц до/после), тесты и билд зелёные.

9. Что сознательно не делаем

  • Никаких CSS-фреймворков и препроцессоров — обычный CSS, как решено на старте.
  • Никаких тем/тёмного режима — но токены сразу семантические, чтобы тема добавилась заменой значений :root.
  • Никаких сложных составных компонентов (модалки, таблицы) — они появятся, когда потребуется реальная задача.