--- description: Единые UI-паттерны web — shared components, docs workflow, матрица стандартизации globs: apps/web/**/* alwaysApply: false --- # Frontend UI Patterns См. также: [`frontend-shadcn.mdc`](frontend-shadcn.mdc), [`shadcn-mcp.mdc`](shadcn-mcp.mdc), [`reui-mcp.mdc`](reui-mcp.mdc), [Components](https://ui.shadcn.com/docs/components), [ReUI llms.txt](https://reui.io/llms.txt). ## Docs workflow (обязательно) 0. Codegraph / поиск существующих shared/domain / `reui-kit` 1. Skill ReUI + shadcn — component selection 2. **Primary:** MCP `user-reui` — search → `get_block` / `compose_page` (`surface: "frame"`) — **всегда cite `previewUrl` + `docsUrl`** 3. Primitives: MCP `plugin-shadcn-shadcn` + `pnpm dlx shadcn@latest docs ` 4. CLI: `cd apps/web && pnpm dlx shadcn@latest add @reui/...` → сверить API 5. Код по examples + docs (только после MCP ↔ docs) 6. Context7 — **только** TanStack Router/Query, Recharts 7. MCP `validate_usage` / `get_audit_checklist` — перед merge ## Surface Проект использует **ReUI Frame** (`surface: frame`), не shadcn Card как оболочку list/ops-экранов. Эталон списка: [data-grid-filtering-2](https://reui.io/preview/base/data-grid-filtering-2). ## Обязательные референсы по зонам | Зона | Preview | |------|---------| | KPI | [stats-12](https://reui.io/preview/base/stats-12) — EvoBGP hybrid SoT | | Quick Actions | [stats-12](https://reui.io/preview/base/stats-12) · [card-12](https://reui.io/preview/base/card-12) → `QuickActionGrid` | | List | [data-grid-filtering-2](https://reui.io/preview/base/data-grid-filtering-2) | | Settings | [settings-16](https://reui.io/preview/base/settings-16), [Application Settings](https://reui.io/blocks/application/settings) | | Settings rows / Health-check | [settings-2](https://reui.io/preview/base/settings-2), [settings-3](https://reui.io/preview/base/settings-3) | | Auth | [auth-13](https://reui.io/preview/base/auth-13) | | Empty | [empty-state-12](https://reui.io/preview/base/empty-state-12) | | Forms | [form-7](https://reui.io/preview/base/form-7) | | Shell | [app-shell-12](https://reui.io/preview/base/app-shell-12) | **SettingRow:** `FieldSeparator` opt-in (`separated`); не между toggle-row и nested fields (Health-check). Settings-секции — отдельные Frame + `gap`, без hairline под PageHeader. ## Иерархия компонентов ``` @cdnmanager/ui/components/* ← shadcn CLI (packages/ui) @/components/reui/* ← ReUI CLI @reui/* (apps/web) apps/web/src/components/ ← shared + domain + layout page-shell.tsx reui-kit/ resource-page.tsx ← list: Frame + line Tabs + Filters + DataGrid kanban-board.tsx ← kanban + KanbanBoardSkeleton detail-panel.tsx ← detail: Frame header/metrics settings-shell.tsx ops-dashboard.tsx ← KPI stats-12 + charts catalog-board-toggle.tsx empty-state.tsx query-state.tsx confirm-dialog.tsx status-badge.tsx form-sheet.tsx form-field.tsx loading-button.tsx layout/ ← app-shell, site-header, app-sidebar domain-* ← бизнес-компоненты ``` ## Матрица стандартизации | Элемент | Shared | Primitive | |---------|--------|-----------| | Page wrapper | `PageShell` | — | | List page | `ResourcePage` | ReUI `Frame` + `data-grid` + `filters` + shadcn `Tabs` `variant="line"` | | Catalog / Board | `CatalogBoardToggle` + `ResourcePage` / `KanbanBoard` | `?view=board` на `/groups`, `/services` | | Kanban | `KanbanBoard` / `KanbanBoardSkeleton` | ReUI `kanban` + `Frame` | | Detail | `DetailPanel` | ReUI `Frame` | | Settings | `SettingsShell` | — | | Dashboard KPI | `OpsDashboard` / `KpiStatGrid` | ReUI Frame [stats-12](https://reui.io/preview/base/stats-12) hybrid | | Quick Actions | `QuickActionGrid` | Frame tiles + Badge «Перейти» | | Empty | `EmptyState` | `Empty` | | Loading / Error | `QueryState` / kit skeletons | `Skeleton`, `Alert` | | Status | `StatusBadge` | ReUI `Badge` (`success`/`info`/`warning`) | | Create/Edit | `FormSheet` + `*-edit-sheet.tsx` | `Sheet`, `Field` | | Form field | `FormField` | `Field`, `Input`, `Select` | | Submit button | `LoadingButton` | `Button`, `Spinner` | | Delete confirm | `ConfirmDialog` | `AlertDialog` | | Nav | `AppSidebar` (`isActive` обязателен) | `Sidebar` | | Breadcrumbs | `SiteHeader` | `Breadcrumb` | | Dates | `lib/format.ts` | — | ## Header actions Независимые CTA в header → `flex shrink-0 flex-wrap items-center justify-end gap-2`. **Не** оборачивать в `ButtonGroup` (он склеивает кнопки). `ButtonGroup` — только для связанных контролок (filter chips и т.п.). Max 1 primary (`default`) на экран; остальные `outline` / `ghost`. Toggle «Доска» / «К каталогу» — всегда `outline` в `primaryAction` / `KanbanBoard.toolbarActions` (не отдельный Frame-shell). ## Line tabs (project standard) Эталон: [c-tabs-2](https://reui.io/preview/base/components/c-tabs-2) + counted [filtering-2](https://reui.io/preview/base/data-grid-filtering-2). ```tsx Label {count} ``` - Примитив: [`packages/ui/.../tabs.tsx`](../../packages/ui/src/components/tabs.tsx) — line = `h-auto`, без `flex-1`, без `dark:data-active:bg-input/30`, underline `after:bottom-0` - Active = яркий текст + **foreground underline**, без фона - Count pill всегда `bg-muted` - Active state — Base UI `data-active`, не Radix `data-[state=active]` - Не трогать internals `reui/date-selector` ## Catalog / Board (`/groups`, `/services`) | Режим | UI | Search | |-------|-----|--------| | Catalog (default) | `ResourcePage` + primary create | omit / `view=catalog` | | Board | `KanbanBoard` DnD | `?view=board` | - Groups tabs: Все / С доменами / Пустые - Services tabs: Все / Включены / Выключены / Без группы - DnD только на board; kanban hooks/cards не удалять ## Dashboard KPI Эталон: [stats-12](https://reui.io/preview/base/stats-12) — icon tile + value + label + optional badge/footer; клик через `to` (`Link`) / `onSelect`. Compact strip: [card-35](https://reui.io/preview/base/card-35). ## Sidebar - Каждый `SidebarMenuButton` получает `isActive` от pathname (`useRouterState`) - `--sidebar-accent` в `AppShell` — заметный mix primary (~14%), не 5% ## Overlay selection | Сценарий | Компонент | |----------|-----------| | Create/edit форма | `Sheet` | | Destructive confirm | `AlertDialog` via `ConfirmDialog` | | Modal preview | `Dialog` | ## Block registry | Зона | Block / эталон | |------|----------------| | Shell | [app-shell-12](https://reui.io/preview/base/app-shell-12) | | List + tabs + filters | [data-grid-filtering-2](https://reui.io/preview/base/data-grid-filtering-2) | | Line tabs | [c-tabs-2](https://reui.io/preview/base/components/c-tabs-2) | | KPI | [stats-12](https://reui.io/preview/base/stats-12) — на dashboard, certificates, domains, groups, services | | Login | [auth-13](https://reui.io/preview/base/auth-13) | | Settings | [settings-16](https://reui.io/preview/base/settings-16) / [settings-6](https://reui.io/preview/base/settings-6) | | Empty | [empty-state-12](https://reui.io/preview/base/empty-state-12) | ## Spacing ``` AppShell main: gap-4 md:gap-6, px-4 md:px-6, py-4 md:py-5 (--sidebar-width: 240px; header h-12; AppSwitcher + AppsMenu — Shared App Shell chrome) PageShell: gap-4 md:gap-6 Title/desc: gap-px Card/Frame grid: gap-4 (dashboard denser: gap-2 md:gap-3) FieldGroup: gap-4 Item list: gap-2 Toolbar / header actions: gap-2 Tabs list (line): gap-5 ``` Shared chrome (vps-tracker / CDNManager / EvoBGP): см. [`docs/ui-design-contract.md`](../../docs/ui-design-contract.md) — секция **Shared App Shell chrome**. Preview: [app-shell-12](https://reui.io/preview/base/app-shell-12). **Запрещено:** `space-y-*`, raw colors (`bg-emerald-*`), custom empty divs, page-level Spinner / plain «Загрузка…» без Skeleton. ## UX/UI (состояния данных) Каждый блок: **default, hover, focus, disabled, empty, loading, error**. - **Loading** — `Skeleton` / `ResourcePage` skeleton / `KanbanBoardSkeleton` / `OpsDashboard` skeleton, не Spinner на странице - **Empty** — `EmptyState` с CTA - **Zero-results** — message внутри DataGrid (+ «Сбросить») - **Error** — `QueryState` / `Alert` + `onRetry` - **Overflow** — `truncate`, `max-w-*`, `Tooltip`; `tabular-nums` для чисел - **Density** — operational (`dense` Frame/DataGrid); max 1 primary CTA - **A11y** — `aria-invalid`, `aria-label`/`sr-only` на icon-only, `aria-current="page"` на active nav/settings ## Button hierarchy (max 1 primary per screen) 1. `default` — главный CTA 2. `outline` — вторичные действия 3. `ghost` / `link` — навигация, cancel 4. `destructive` — только с `ConfirmDialog`