Build, Test, and Push CFDM Docker Image / test (push) Failing after 37s
Build, Test, and Push CFDM Docker Image / build-and-push (push) Has been skipped
Build, Test, and Push CFDM Docker Image / update-wiki (push) Has been skipped
Build, Test, and Push CFDM Docker Image / create-release (push) Has been skipped
- Updated frontend UI patterns documentation to reflect new components and their usage. - Enhanced CertKpiCards for better KPI visualization and streamlined data handling. - Refactored OpsDashboard and ResourcePage for improved layout consistency and loading states. - Introduced CatalogBoardToggle for better navigation between views in the Groups and Services pages. - Improved Tabs component styles for better responsiveness and accessibility. Co-authored-by: Cursor <cursoragent@cursor.com>
173 lines
7.7 KiB
Plaintext
173 lines
7.7 KiB
Plaintext
---
|
||
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 `codegraph_explore` — найти существующие shared/domain-компоненты
|
||
1. Skill [`.agents/skills/shadcn/SKILL.md`](../.agents/skills/shadcn/SKILL.md) + ReUI skill — component selection
|
||
2. MCP `plugin-shadcn-shadcn` / `user-reui` — search → examples → add command (`surface: "frame"`)
|
||
3. CLI: `cd apps/web && pnpm dlx shadcn@latest docs <component>` / `add @reui/...` → сверить API
|
||
4. Код по examples + docs API (только после совпадения MCP ↔ docs)
|
||
5. Context7 — **только** TanStack Router/Query, Recharts (не shadcn/ReUI primitives)
|
||
6. MCP `get_audit_checklist` — перед merge
|
||
7. Codegraph `codegraph_status` — Pending sync пустой
|
||
|
||
## Surface
|
||
|
||
Проект использует **ReUI Frame** (`surface: frame`), не shadcn Card как оболочку list/ops-экранов. Эталон списка: [data-grid-filtering-2](https://reui.io/preview/base/data-grid-filtering-2).
|
||
|
||
## Иерархия компонентов
|
||
|
||
```
|
||
@cfdm/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 card-35 + 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 [card-35](https://reui.io/preview/base/card-35) |
|
||
| 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
|
||
<TabsList variant="line" className="gap-5">
|
||
<TabsTrigger value="…" className="text-muted-foreground hover:text-foreground h-auto gap-2 px-0 pb-3">
|
||
<span>Label</span>
|
||
<span className="bg-muted text-muted-foreground … tabular-nums rounded-md">{count}</span>
|
||
</TabsTrigger>
|
||
</TabsList>
|
||
```
|
||
|
||
- Примитив: [`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
|
||
|
||
Эталон: [card-35](https://reui.io/preview/base/card-35) — muted **label** + крупный **value** + optional **hint**; клик по карточке через `to` (`Link`), без отдельных hint-links.
|
||
|
||
## 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 | [card-35](https://reui.io/preview/base/card-35) |
|
||
| Login | auth-13 / login-03 |
|
||
|
||
## Spacing
|
||
|
||
```
|
||
AppShell main: gap-4 md:gap-6, px-4 md:px-6, py-4 md:py-5
|
||
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
|
||
```
|
||
|
||
**Запрещено:** `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`
|