# Правила компонентов Bark

Этот документ фиксирует интерфейсные решения, которые раньше можно было
трактовать по-разному. Он дополняет `STYLE_GUIDE.md` и обязателен для новых
продуктов.

## Лоадеры и прогресс

У Bark один loader language для всех продуктов. Нельзя рисовать отдельный
spinner для `barkhr`, `barkstock` или другого модуля: меняется продукт, но не
механика ожидания.

| Сценарий | Компонент | Расположение |
| --- | --- | --- |
| Холодный старт/PWA/auth bootstrap | Brand loader `lg`, 60px | Строго по центру viewport, поверх `--bg` |
| Переход между страницами | Route progress 2.5px | Верх main-content, не поверх sidebar |
| Переход с долгой загрузкой данных | Brand loader `md`, 40px | Центр обновляемой content-region |
| Загрузка известного layout | Skeleton | На месте будущих строк/cards, без layout shift |
| Async-кнопка | Spinner 18px + action label | Внутри нажатой кнопки, ширина кнопки не меняется |
| Старт SSO-авторизации | Spinner 18px + `Входим…` | Внутри существующей login-кнопки до redirect/error |
| Upload/export/report >2s | Determinate progress + `%` | Рядом с задачей; не в глобальном overlay |

Фирменный brand loader повторяет живой `barkone`: accent arc вращается вокруг
монохромного знака. Размер `lg` = 60px, внутренний знак = 43px, один оборот =
0.9s. В dark mode меняются только токены фона/ink/accent.

Обязательное поведение:

- Route transition показывает progress после 150ms, чтобы не мигать на
  мгновенных переходах. После появления loader остаётся видимым минимум 300ms.
- Если route/data transition длится больше 400ms, в обновляемой области
  появляется `md` brand loader или skeleton известной формы.
- Async-кнопка показывает spinner сразу после запуска Promise, получает
  `disabled` и `aria-busy="true"`. Текст становится действием в процессе:
  `Сохраняем…`, `Отправляем…`, `Формируем…`, а не общим `Загрузка…`.
- Если приложение использует SSO, существующая кнопка входа сразу после клика
  показывает spinner 18px и `Входим…`, получает `disabled` +
  `aria-busy="true"` и остаётся в этом состоянии до redirect или ошибки. Это
  обязательный сценарий, а не опциональная полировка.
- При ошибке до redirect восстановить исходную кнопку и показать понятное
  сообщение с возможностью повторить вход. При успешном старте не снимать
  spinner перед уходом страницы; для этого использовать `startButtonLoading()`.
- После возврата с SSO callback/bootstrap использует full-screen brand loader
  `lg`. Button spinner и full-screen loader не показываются одновременно для
  одной фазы авторизации.
- Button loader заменяет leading icon, но сохраняет исходную ширину кнопки.
- После 2 секунд рядом с loader появляется короткое объяснение. После 8–10
  секунд пользователь получает status/retry/cancel, а не бесконечное вращение.
- Один action = один loader. Не показывать одновременно global overlay, region
  loader и button spinner для одной операции.
- Локальная операция не блокирует весь экран. Full-screen overlay разрешён
  только для cold start, восстановления сессии или действительно глобальной
  смены контекста.
- Error завершает loading state и показывает понятное следующее действие.
- `prefers-reduced-motion: reduce` отключает вращение/shimmer; состояние всё
  равно читается через текст и `aria-live`.

Источник: `tokens.loader`, CSS `.bark-loader*`, `.bark-route-progress`,
`.bark-spinner`, `.bark-skeleton`; markup `templates/loader.html`; behavior
`templates/loader.snippet.js`.

SSO redirect pattern:

```js
const stopLoading = startButtonLoading(loginButton, { label: "Входим…" });

try {
  const redirectUrl = await requestSsoRedirect();
  window.location.assign(redirectUrl); // keep loading until the page leaves
} catch (error) {
  stopLoading();
  showAuthorizationError(error);
}
```

![Bark loader placements](assets/loaders.png)

## Шапка страницы и идентичность

Каждая страница продукта начинается с `.bark-page-header`
(`templates/topbar.html`): слева H1 (+ опциональная мета-строка — caption 12px,
значения в IBM Plex Mono через `·`), справа кластер утилит
`.bark-page-header__tools` (ghost-кнопки 36px, Lucide 20px). **Аватара и имени
в шапке нет.**

Контракт идентичности — **ровно одна точка идентичности в оболочке**:

- Идентичность живёт в футере сайдбара: блок `.bark-sidebar__user`
  (`templates/sidebar.html`) — аватар 40px (фото из SSO / инициалы) + имя
  (Onest 13/600, одна строка, ellipsis) + email (11/500 `--muted`, ellipsis) +
  icon-only `Выйти` справа. Имя и email — из текущей SSO-сессии.
- `barkone` — задокументированное легаси-исключение: живой кабинет сохраняет
  аватар в правом верхнем углу шапки. Новые продукты его не копируют.
- Полное имя может дополнительно появляться в приветствии главной
  (`Добрый вечер, {имя}` + мета-строка) как контент страницы — больше нигде.
- Если в легаси-приложении Avatar уже отображается в другом месте — оставить
  одно существующее место; две точки идентичности одновременно запрещены.

## Аватар сотрудника

- Сохранять существующую геометрию и оформление компонента `Avatar` приложения;
  если приложения ещё нет или Avatar отсутствует — использовать `.bark-avatar`
  (круг; sm 24 / md 32 / lg 40; в шапке — 40px).
- Для авторизованного сотрудника фотография приходит только из текущей
  Nozhmaster SSO-сессии; полный контракт описан в `SSO_AVATAR.md`.
- Непустой `response.user.picture` (`$user['picture']` в `client_sso_v2`)
  передаётся напрямую во внутренний `<img src>`.
- Пустое значение или ошибка изображения показывают инициалы в том же Avatar,
  без сдвига layout и без отдельной заглушки другого размера.
- Инициалы считаются **только** функцией `initialsFromName()` из
  `templates/sso-avatar.adapter.js`: первая буква первого и последнего слова,
  верхний регистр («Владимир Шурыгин» → «ВШ»; одно слово → первые две буквы).
  Не изобретать свою формулу в продукте.
- Внутренний `img`: `loading="lazy"`, `decoding="async"`,
  `referrerpolicy="no-referrer"`.
- JPEG и SVG обрабатываются одинаково; формат не определяется по URL.

## Чарты

**Решение принято: единая библиотека — Apache ECharts (5+).**

- Новые продукты и новые графики используют ECharts с темой Bark:
  `templates/chart-theme.snippet.js` → `registerBarkTheme(echarts)` +
  `barkChartDefaults()`. Тема строится из живых CSS-токенов, поэтому light/dark
  работают автоматически.
- Пакет `echarts` продукт поставляет сам (self-hosted vendor bundle);
  подключение с CDN запрещено.
- Легаси-приложения сохраняют свою библиотеку (не менять её только ради
  брендинга — прежнее правило) и стилизуют её под те же токены.
- Палитра серий фиксированная и упорядоченная: 1 — `--accent`, далее
  `#2a6fdb`, `#8a63d2`, `#5c6b7a`. **Больше 4 серий на одном графике нельзя** —
  разбивайте на несколько графиков или агрегируйте.

Разрешённые типы:

| Тип | Когда использовать |
| --- | --- |
| Line | Динамика одного или нескольких показателей во времени |
| Area | Одна основная временная серия; заливка не более 12% opacity |
| Vertical bar | Сравнение небольшого числа категорий или периодов |
| Horizontal bar | Рейтинг, длинные подписи, сравнение 5+ категорий |
| Stacked bar | Состав общего значения; сумма частей действительно важна |
| Donut | Только доли целого, 2–5 сегментов, сумма = 100% |
| Scatter | Связь двух числовых показателей, аналитический сценарий |
| Sparkline | Компактный тренд рядом с KPI, без отдельных осей |

Обязательные правила:

- Primary series = `--accent`; comparison = `--ink2`/`--muted`.
- Status colors `--ok`, `--warn`, `--bad` используются только для состояния,
  не как декоративная палитра серий.
- Product color не становится цветом данных: он остаётся у favicon и module word.
- Grid = `--line-2`; подписи осей и числовые tooltip values = IBM Plex Mono.
- Легенда нужна только при двух и более сериях; по возможности подписывать линии напрямую.
- Tooltip показывает значение, единицу, период и comparison; keyboard focus обязателен.
- Анимация только при первом появлении, 150–250ms; real-time charts не должны
  постоянно “прыгать” или пересобирать scale.

Запрещены 3D-чарты, radar, speedometer/gauge, exploded pie, pie/donut с более чем
5 сегментами, декоративные градиенты и dual axis без явно описанной причины.

## Выход из системы

- Во всех авторизованных Bark-продуктах действие называется только `Выйти`
  (как `aria-label` + `title`) и использует Lucide `LogOut`.
- Форма — **icon-only кнопка 40×40px** в правом конце блока
  `.bark-sidebar__user`; видимого текстового лейбла нет. Hover/focus —
  `--bad-soft` + `--bad`; продуктовый цвет её не меняет.
- Mobile: та же кнопка в идентичном блоке footer'а navigation/settings drawer.
  Не переносить её в список навигации или случайное меню профиля.
- Использовать `.bark-sidebar__logout` из `tokens/brand.css` и markup из
  `templates/sidebar.html`; продукт не меняет цвет, размер или иконку.
- Это `button`, который вызывает существующий session logout handler приложения.
  Не подменять его обычной ссылкой и не добавлять confirmation modal по умолчанию.
- Если завершение сессии асинхронное, сразу включить `disabled` +
  `aria-busy="true"` и spinner 18px вместо иконки; `aria-label` на время
  операции — `Выходим…`. При ошибке восстановить кнопку и показать понятный error
  state. Для vanilla/DOM использовать `withButtonLoading` из
  `templates/loader.snippet.js`.

## Модалки

Модалка используется для короткого блокирующего решения: подтверждение,
небольшое редактирование, просмотр ограниченного контекста. Длинный workflow,
таблица или форма более чем примерно из 6 полей открывается на странице или в
drawer, а не в модалке.

- Width: `sm` 400px, `md` 560px (default), `lg` 720px.
- Max height: `85dvh`; body scrolls independently.
- Header and action footer remain visible while the modal body scrolls.
- Backdrop: ink at 56% opacity; click outside closes only non-destructive flows.
- Close = Lucide `X`; Escape closes; focus is trapped and restored to the trigger.
- Default actions: secondary/cancel first, primary last. Destructive action uses
  `--bad` and requires an explicit verb, not “OK”.
- Mobile: full-width sheet/full-screen dialog with safe-area padding.
- No nested modals. Opening a second modal means the first flow needs redesign.

## Поля ввода

- Default height 40px; dense/table 32px; large primary form 48px.
- Radius `--radius-control` (8px), background `--field`, border `--line`.
- Label is always visible above the field: Onest 12px/600. Placeholder is an
  example, never a replacement for the label.
- Focus: accent border + 3px `--accent-soft` ring. Error: `--bad` border and
  helper text. Success styling is not shown during ordinary typing.
- Helper/error text: Onest 12px/500. Reserve its place when validation is likely,
  so the form does not jump.
- Disabled = 0.55 opacity and no interaction. Read-only remains legible and
  selectable; it is visually different from disabled.
- Numeric values, money, percentages, dates/times and IDs use IBM Plex Mono;
  names, comments and ordinary text remain Onest.
- Textarea min-height 96px and resizes vertically. Select, date picker, search,
  clear, eye and calendar icons come from Lucide.
- Required fields are marked in the label; validation runs on submit or blur,
  not on every keystroke before the user has interacted.

## Плотность и каркас страницы

Плотность — константа бренда, а не настроение конкретной страницы. Числа
закреплены в `docs/STYLE_GUIDE.md` §Page shell & density и в
`tokens/brand.tokens.json` → `layout`/`density`:

- `.bark-main`: паддинг `24px 32px 40px` (моб. 16px), разрыв между секциями 24px,
  сетки карточек с gap 16px.
- Панель: header `16px 20px`, body 20px, footer `12px 20px`; единственный более
  плотный режим — `--dense` (body `12px 16px`). Ничего «воздушнее» стандартного
  режима не существует.
- Рабочий экран не использует отступы крупнее 40px; воздух больше 40px разрешён
  только в empty states и на auth-экранах.
- Проверка: на 1440×900 первый экран дашборда показывает шапку и минимум два
  ряда контента. Если нет — экран слишком воздушный; уплотняйте по таблице, а не
  увеличивайте шрифты.

## Таблицы данных (база)

`.bark-table` — базовый контракт; фильтры, pagination и bulk actions пока в
открытых решениях.

- Заголовок: caption-стиль (Onest 11/700, uppercase, 0.06em, `--faint`),
  высота 40px. Строки: 44px, в `--dense` 36px.
- Ячейки: Onest 14; паддинг `0 12px`, крайние колонки `0 20px` (совпадает с
  паддингом панели). Разделитель строк `--line-2`; **без** zebra-полос.
- Числа, деньги, ID, проценты и время — IBM Plex Mono (`.bark-data`,
  `font-feature-settings: "tnum"`), выравнивание вправо; текст — влево;
  статусы — `.bark-pill`.
- Hover строки — `--surface-2`; выбранная строка — `--accent-soft`
  (`aria-selected="true"`). Таблица в панели ставится вплотную (без
  двойного паддинга `__body`).

## KPI-карточки

`.bark-kpi` внутри `.bark-panel` — стандартная единица дашборда:

- Паддинг `16px 20px`; label — caption 12/500 `--muted`; значение —
  IBM Plex Mono 24/600 с `tnum`; опционально `.bark-pill` с дельтой и
  caption-подпись `--faint`.
- Сетка KPI — `.bark-grid` (minmax 220px, gap 16px). KPI-подсветка берёт
  `--kpi*`, статус дельты — `--ok`/`--bad`.

## Пустые состояния

`.bark-empty` — полезное и короткое состояние внутри своего региона:

- Иконка Lucide 20px в круге 40px `--chip`; заголовок Onest 14/600 `--ink2`;
  подсказка 12px `--muted` шириной до 360px; опционально одна ghost-кнопка `--sm`.
- Empty state заполняет свой регион (панель, таблицу), но никогда не весь экран,
  и не превращается в маркетинговую иллюстрацию.

## Фильтры списков и таблиц

Тулбар `.bark-toolbar` внутри панели, над таблицей (`templates/table-toolbar.html`):

- Слева — поиск (min 220px, placeholder «Поиск…», обязательный `aria-label`) и
  до **трёх** inline-фильтров (select/segmented). Четвёртый и далее — одна
  кнопка «Фильтры» с popover; фильтр-drawer не используется.
- Все контролы тулбара — 32px (`--sm`); паддинг тулбара `12px 20px`, gap 8px.
- Применённые фильтры видны как accent-чипы `.bark-filter-chip` (28px, крестик
  внутри, `aria-label` «Убрать фильтр: …»). При ≥1 активном фильтре рядом
  появляется ghost-кнопка `Сбросить`.
- **Мульти-выбор (2–6 опций) — только toggle-чипы** `.bark-toggle-chip`:
  кнопка-пилюля 28px с `aria-pressed`; включённая — `--accent-soft` +
  accent-текст + галочка Lucide 14px, выключенная — `--chip` + `--ink2`.
  Чекбоксы в рамках/квадратах в тулбаре запрещены. Больше 6 опций — один
  popover «Фильтры». Переключение применяется сразу; toggle-чипы сами
  показывают своё состояние и не дублируются в ряд применённых фильтров.
- Перед группой чипов — подпись `.bark-toolbar__group-label` (caption 12/500
  `--muted`, с двоеточием: «Направления:»), не жирный заголовок. Отдельные
  info-иконки в тулбаре не используются — пояснение живёт в подписи или
  `title`.
- Состояние фильтров отражается в URL query — ссылку на отфильтрованный список
  можно переслать.
- Пустой результат фильтрации — `.bark-empty` внутри области таблицы с
  действием «Сбросить фильтры», а не пустая таблица.

## Сортировка и действия строк

Сортировка (`.bark-table__sort`, `templates/table-toolbar.html`):

- В таблице **ровно одна активная сортировка**; multi-sort запрещён. У активной
  колонки `th` несёт `aria-sort="ascending|descending"`, стрелка и подпись —
  accent.
- Весь заголовок сортируемой колонки — кнопка. Неактивная сортируемая колонка
  показывает `arrow-up-down` 14px в `--faint`; несортируемая — без кнопки и
  иконки.
- Первый клик: числа и даты — **по убыванию** (свежее/большее сверху), текст —
  по возрастанию. Повторный клик меняет направление.
- Смена сортировки сохраняет фильтры, возвращает на страницу 1; состояние — в
  URL query. У каждой таблицы есть сортировка по умолчанию (обычно дата, по
  убыванию).

Действия строк (`.bark-table__actions`):

- Последняя колонка, выравнивание вправо, ширина по содержимому. До **двух**
  частых действий — icon-only ghost 32px (Lucide 18px, `aria-label` + `title`),
  видимы всегда, а не только на hover.
- Три и больше действий — один kebab (`more-vertical`) с меню `.bark-menu`:
  min-width 180px, пункты 36px, слой `--z-dropdown`, Esc закрывает, фокус
  возвращается на kebab.
- Деструктивные действия («Удалить») — только в меню: пункт `--bad`, отделён
  разделителем, всегда с подтверждением по правилам модалок. Инлайн-иконка
  удаления в строке запрещена.
- Если у строки есть детальная страница — кликабельна вся строка (кроме зоны
  действий); действия не дублируют «открыть».

## Пагинация

`.bark-pagination` — нижняя строка панели (`templates/table-toolbar.html`):

- Обязательна при более чем 50 строках; infinite scroll в таблицах данных не
  используется. Размеры страниц фиксированы: 25 / 50 / 100, по умолчанию 50.
- Слева счётчик диапазона в IBM Plex Mono: `1–50 из 1 234`. Справа — select
  размера страницы (32px) и кнопки prev/next 32px (Lucide chevron) с
  `aria-label`; на краях — `disabled`.
- Номерных кнопок страниц нет — только prev/next и счётчик; переход не
  сбрасывает фильтры и сортировку.
- Смена страницы >400ms показывает region loader/skeleton по правилам лоадеров.

## Уведомления: toast и inline alert

Правило выбора: **toast — событие, alert — состояние, модалка — решение,
подсказка у поля — валидация.** Никогда не смешивать.

Toast (`templates/toast.html` + `templates/toast.snippet.js`, единый на всё
приложение):

- Стек справа внизу, отступ 16px, gap 8px, ширина ≤360px, максимум 3 —
  старые удаляются первыми. Слой — `--z-toast`.
- `ok`/`info` исчезают через 5s, `warn` — через 8s, `bad` висит до закрытия.
- Заголовок обязателен (≤60 знаков); текст ≤140 и одно действие — опциональны.
  Кнопка закрытия есть всегда.
- `ok/info/warn` → `role="status"`, `bad` → `role="alert"`. Появление 200ms
  `--ease-out`; `prefers-reduced-motion` отключает анимацию.
- Toast не используется для валидации форм, блокирующих решений и постоянных
  состояний. Успех обычного сохранения формы — inline-подтверждение или
  обновлённые данные, toast — для фоновых/долгих операций (экспорт, импорт,
  фоновая синхронизация).

Inline alert `.bark-alert` (`--info/--ok/--warn/--bad`):

- Постоянное состояние региона: деградация, устаревшие данные, read-only.
  Живёт внутри своего региона, не стекается, не исчезает сам — уходит вместе с
  состоянием. Паддинг `12px 16px`, radius 8px, иконка Lucide 18px в цвет
  статуса.

## Motion

- Длительности: hover/focus 120ms; появление popover/dropdown/модалки 200ms;
  reveal региона/страницы 300ms. Easing: `cubic-bezier(0.2, 0, 0, 1)`
  (`--motion-*`, `--ease-out`).
- Transition только для `color`, `background`, `border-color`, `box-shadow`,
  `opacity`, `transform` — не для всех свойств сразу.
- Слои — только из шкалы `--z-*` (sticky 20, dropdown 600, drawer 800, modal
  1000, popover 1100, toast 1200, loader 9999).
- `prefers-reduced-motion: reduce` отключает декоративную анимацию.

## Открытые решения

Требуют отдельного выбора владельца системы:

- единая chart library для React/Vue/vanilla;
- нужны ли drawers как отдельный обязательный component tier;
- единый date/time picker и формат диапазонов;
- сортировка, sticky columns, row actions и bulk actions таблиц (база, фильтры
  и пагинация закреплены выше);
- состав и поведение profile popover.
