feat(ui): обновить KPI с card-35 на stats-12 во всех компонентах
Build, Test, and Push CFDM Docker Image / test (push) Successful in 3m27s
Build, Test, and Push CFDM Docker Image / build-and-push (push) Successful in 1m52s
Build, Test, and Push CFDM Docker Image / create-release (push) Skipped
Build, Test, and Push CFDM Docker Image / update-wiki (push) Failing after 6s

Заменены ссылки на блоки card-35 на stats-12 в документации и компонентах, включая KpiStatGrid, CertKpiCards, DomainKpiCards, GroupKpiCards и ServiceKpiCards. Удален неиспользуемый компонент AppCard. Добавлены иконки для улучшения визуализации KPI.

Co-authored-by: Cursor <[email protected]>
This commit is contained in:
Denozordec
2026-07-17 15:30:25 +07:00
co-authored by Cursor
parent 704624cbe7
commit d8340e8a0b
13 changed files with 197 additions and 80 deletions
+3 -3
View File
@@ -58,7 +58,7 @@ Monorepo — [`frontend-monorepo.mdc`](frontend-monorepo.mdc). ReUI — [`reui-m
cd apps/web
pnpm dlx shadcn@latest add button field input ...
pnpm dlx shadcn@latest add @reui/frame @reui/data-grid @reui/filters
pnpm dlx shadcn@latest add @reui/card-35 @reui/settings-16 @reui/auth-13
pnpm dlx shadcn@latest add @reui/stats-12 @reui/card-35 @reui/settings-16 @reui/auth-13
pnpm dlx shadcn@latest apply b2fA --only theme -y
```
@@ -76,7 +76,7 @@ pnpm dlx shadcn@latest apply b2fA --only theme -y
| `<table>`, raw select | DataGrid / `Select` |
| `bg-emerald-*` | semantic / ReUI `variant` |
| Card как ops-shell | ReUI `Frame` |
| Hand-roll KPI | `KpiStatGrid` / [card-35](https://reui.io/preview/base/card-35) |
| Hand-roll KPI | `KpiStatGrid` / [stats-12](https://reui.io/preview/base/stats-12) |
| `space-y-*` | `flex` + `gap-*` |
| `@/components/ui/*` | `@cfdm/ui/components/*` |
@@ -86,7 +86,7 @@ pnpm dlx shadcn@latest apply b2fA --only theme -y
|------|------|---------|
| Shell | `layout/app-shell.tsx` | [app-shell-12](https://reui.io/preview/base/app-shell-12) |
| Login | `routes/login.tsx` | [auth-13](https://reui.io/preview/base/auth-13) |
| Dashboard | `routes/_auth/index.tsx` | [card-35](https://reui.io/preview/base/card-35) / dashboard-1 |
| Dashboard | `routes/_auth/index.tsx` | [stats-12](https://reui.io/preview/base/stats-12) / dashboard-1 |
| Lists | `ResourcePage` | [data-grid-filtering-2](https://reui.io/preview/base/data-grid-filtering-2) |
| Settings | `settings/integrations.tsx` | [settings-16](https://reui.io/preview/base/settings-16) |
+5 -5
View File
@@ -27,7 +27,7 @@ alwaysApply: false
| Зона | Preview |
|------|---------|
| KPI | [card-35](https://reui.io/preview/base/card-35) |
| KPI | [stats-12](https://reui.io/preview/base/stats-12) (primary); [card-35](https://reui.io/preview/base/card-35) compact |
| 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) |
@@ -50,7 +50,7 @@ apps/web/src/components/ ← shared + domain + layout
kanban-board.tsx ← kanban + KanbanBoardSkeleton
detail-panel.tsx ← detail: Frame header/metrics
settings-shell.tsx
ops-dashboard.tsx ← KPI card-35 + charts
ops-dashboard.tsx ← KPI stats-12 + charts
catalog-board-toggle.tsx
empty-state.tsx
query-state.tsx
@@ -73,7 +73,7 @@ apps/web/src/components/ ← shared + domain + layout
| 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) |
| Dashboard KPI | `OpsDashboard` / `KpiStatGrid` | ReUI Frame [stats-12](https://reui.io/preview/base/stats-12) |
| Empty | `EmptyState` | `Empty` |
| Loading / Error | `QueryState` / kit skeletons | `Skeleton`, `Alert` |
| Status | `StatusBadge` | ReUI `Badge` (`success`/`info`/`warning`) |
@@ -127,7 +127,7 @@ Toggle «Доска» / «К каталогу» — всегда `outline` в `p
## Dashboard KPI
Эталон: [card-35](https://reui.io/preview/base/card-35) — muted **label** + крупный **value** + optional **hint**; клик по карточке через `to` (`Link`), без отдельных hint-links.
Эталон: [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
@@ -149,7 +149,7 @@ Toggle «Доска» / «К каталогу» — всегда `outline` в `p
| 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) — на dashboard, certificates, domains, groups, services |
| 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) |
+3 -3
View File
@@ -36,7 +36,7 @@ alwaysApply: true
| Data Grid | `@reui` | `@/components/reui/data-grid/*` → `ResourcePage` |
| Filters | `@reui` | `@/components/reui/filters` |
| Frame surface | `@reui` | `@/components/reui/frame` |
| KPI | block [card-35](https://reui.io/preview/base/card-35) | `reui-kit/KpiStatGrid` |
| KPI | block [stats-12](https://reui.io/preview/base/stats-12) | `reui-kit/KpiStatGrid` |
| Semantic badge / alert | `@reui` | `@/components/reui/badge`, `@/components/reui/alert` |
| Number / date / autocomplete / color / kanban | `@reui` | `@/components/reui/*` |
@@ -68,7 +68,7 @@ alwaysApply: true
**Kit:** `ResourcePage`, `KpiStatGrid`, `OpsDashboard`, `KanbanBoard`, `DetailPanel`, `SettingsShell`
**Blocks (reference):** `card-35`, `auth-13`, `app-shell-12`, `settings-16`, `settings-8`, `empty-state-12`, `form-7`, `data-grid-filtering-2`, `dashboard-1`, …
**Blocks (reference):** `stats-12`, `card-35`, `auth-13`, `app-shell-12`, `settings-16`, `settings-8`, `empty-state-12`, `form-7`, `data-grid-filtering-2`, `dashboard-1`, …
## License
@@ -83,7 +83,7 @@ REUI_LICENSE_KEY=
| Зона | Preview |
|------|---------|
| KPI | https://reui.io/preview/base/card-35 |
| KPI | https://reui.io/preview/base/stats-12 |
| List | https://reui.io/preview/base/data-grid-filtering-2 |
| Settings | https://reui.io/preview/base/settings-16 |
| Auth | https://reui.io/preview/base/auth-13 |
-50
View File
@@ -1,50 +0,0 @@
import type { ComponentProps } from 'react'
import {
Card,
CardAction,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from '@cfdm/ui/components/card'
import { cn } from '@cfdm/ui/lib/utils'
export type AppCardProps = ComponentProps<typeof Card>
export type AppCardHeaderProps = ComponentProps<typeof CardHeader>
export type AppCardTitleProps = ComponentProps<typeof CardTitle>
export type AppCardDescriptionProps = ComponentProps<typeof CardDescription>
export type AppCardActionProps = ComponentProps<typeof CardAction>
export type AppCardContentProps = ComponentProps<typeof CardContent>
export type AppCardFooterProps = ComponentProps<typeof CardFooter>
export function AppCard({ className, ...props }: AppCardProps) {
return <Card className={cn(className)} {...props} />
}
export function AppCardHeader({ className, ...props }: AppCardHeaderProps) {
return <CardHeader className={cn(className)} {...props} />
}
export function AppCardTitle({ className, ...props }: AppCardTitleProps) {
return <CardTitle className={cn(className)} {...props} />
}
export function AppCardDescription({
className,
...props
}: AppCardDescriptionProps) {
return <CardDescription className={cn(className)} {...props} />
}
export function AppCardAction({ className, ...props }: AppCardActionProps) {
return <CardAction className={cn(className)} {...props} />
}
export function AppCardContent({ className, ...props }: AppCardContentProps) {
return <CardContent className={cn(className)} {...props} />
}
export function AppCardFooter({ className, ...props }: AppCardFooterProps) {
return <CardFooter className={cn(className)} {...props} />
}
+15 -1
View File
@@ -1,5 +1,10 @@
import { useMemo } from 'react'
import { ShieldCheckIcon } from 'lucide-react'
import {
AlertTriangleIcon,
CheckCircle2Icon,
ShieldAlertIcon,
ShieldCheckIcon,
} from 'lucide-react'
import { KpiStatGrid, type KpiStatCard } from '@/components/reui-kit/kpi-stat-grid'
const ACTIVE_STATUSES = new Set(['active', 'ok', 'synced'])
@@ -28,6 +33,7 @@ interface CertKpiCardsProps {
onSelectTab: (tabId: string) => void
}
/** KPI strip — ReUI PRO [stats-12](https://reui.io/preview/base/stats-12) */
export function CertKpiCards({
summary,
total,
@@ -47,6 +53,8 @@ export function CertKpiCards({
label: 'Всего',
value: allCount,
hint: 'Проверенные хосты',
icon: <ShieldCheckIcon aria-hidden />,
iconClassName: 'text-info',
onSelect: () => onSelectTab('all'),
selected: activeTab === 'all',
},
@@ -55,6 +63,8 @@ export function CertKpiCards({
label: 'OK',
value: okCount,
hint: 'Активные сертификаты',
icon: <CheckCircle2Icon aria-hidden />,
iconClassName: 'text-success',
onSelect: () => onSelectTab('active'),
selected: activeTab === 'active',
},
@@ -63,6 +73,8 @@ export function CertKpiCards({
label: 'Предупреждение',
value: warningCount,
hint: 'Истекают скоро',
icon: <AlertTriangleIcon aria-hidden />,
iconClassName: 'text-warning',
onSelect: () => onSelectTab('warning'),
selected: activeTab === 'warning',
},
@@ -71,6 +83,8 @@ export function CertKpiCards({
label: 'Проблемы',
value: problemCount,
hint: 'Ошибка или истёк',
icon: <ShieldAlertIcon aria-hidden />,
iconClassName: 'text-destructive',
onSelect: () => onSelectTab('expired'),
selected: activeTab === 'expired',
},
+15 -2
View File
@@ -1,5 +1,10 @@
import { useMemo } from 'react'
import { GlobeIcon } from 'lucide-react'
import {
CheckCircle2Icon,
FolderOpenIcon,
GlobeIcon,
ServerIcon,
} from 'lucide-react'
import { KpiStatGrid, type KpiStatCard } from '@/components/reui-kit/kpi-stat-grid'
import type { DomainListItem } from '@/lib/schemas'
@@ -10,7 +15,7 @@ interface DomainKpiCardsProps {
onSelectTab: (tabId: string) => void
}
/** KPI strip — ReUI PRO [card-35](https://reui.io/preview/base/card-35) */
/** KPI strip — ReUI PRO [stats-12](https://reui.io/preview/base/stats-12) */
export function DomainKpiCards({
domains,
activeTab,
@@ -29,6 +34,8 @@ export function DomainKpiCards({
label: 'Всего',
value: list.length,
hint: 'Импортированные зоны',
icon: <GlobeIcon aria-hidden />,
iconClassName: 'text-info',
onSelect: () => onSelectTab('all'),
selected: activeTab === 'all',
},
@@ -37,6 +44,8 @@ export function DomainKpiCards({
label: 'Без группы',
value: withoutGroup,
hint: 'Не назначена группа',
icon: <FolderOpenIcon aria-hidden />,
iconClassName: 'text-warning',
onSelect: () => onSelectTab('without_group'),
selected: activeTab === 'without_group',
},
@@ -45,6 +54,8 @@ export function DomainKpiCards({
label: 'Active',
value: active,
hint: 'Статус Cloudflare',
icon: <CheckCircle2Icon aria-hidden />,
iconClassName: 'text-success',
onSelect: () => onSelectTab('all'),
selected: false,
},
@@ -53,6 +64,8 @@ export function DomainKpiCards({
label: 'С сервисами',
value: withServices,
hint: 'Есть привязки',
icon: <ServerIcon aria-hidden />,
iconClassName: 'text-primary',
onSelect: () => onSelectTab('all'),
selected: false,
},
+8 -2
View File
@@ -1,5 +1,5 @@
import { useMemo } from 'react'
import { FolderTreeIcon } from 'lucide-react'
import { FolderOpenIcon, FolderTreeIcon, FoldersIcon } from 'lucide-react'
import { KpiStatGrid, type KpiStatCard } from '@/components/reui-kit/kpi-stat-grid'
import type { GroupCatalogRow } from '@/components/columns/groups-columns'
@@ -10,7 +10,7 @@ interface GroupKpiCardsProps {
onSelectTab: (tabId: string) => void
}
/** KPI strip — ReUI PRO [card-35](https://reui.io/preview/base/card-35) */
/** KPI strip — ReUI PRO [stats-12](https://reui.io/preview/base/stats-12) */
export function GroupKpiCards({
rows,
activeTab,
@@ -27,6 +27,8 @@ export function GroupKpiCards({
label: 'Всего',
value: rows.length,
hint: 'Группы доменов',
icon: <FolderTreeIcon aria-hidden />,
iconClassName: 'text-info',
onSelect: () => onSelectTab('all'),
selected: activeTab === 'all',
},
@@ -35,6 +37,8 @@ export function GroupKpiCards({
label: 'С доменами',
value: withDomains,
hint: 'Непустые группы',
icon: <FoldersIcon aria-hidden />,
iconClassName: 'text-success',
onSelect: () => onSelectTab('with_domains'),
selected: activeTab === 'with_domains',
},
@@ -43,6 +47,8 @@ export function GroupKpiCards({
label: 'Пустые',
value: empty,
hint: 'Без доменов',
icon: <FolderOpenIcon aria-hidden />,
iconClassName: 'text-muted-foreground',
onSelect: () => onSelectTab('empty'),
selected: activeTab === 'empty',
},
@@ -1,6 +1,7 @@
import type { ReactNode } from 'react'
import { Link } from '@tanstack/react-router'
import { Frame, FramePanel } from '@/components/reui/frame'
import { Item, ItemMedia } from '@cfdm/ui/components/item'
import { cn } from '@cfdm/ui/lib/utils'
import { Skeleton } from '@cfdm/ui/components/skeleton'
@@ -13,6 +14,9 @@ export interface KpiStatCard {
search?: Record<string, unknown>
onSelect?: () => void
selected?: boolean
icon?: ReactNode
iconClassName?: string
footer?: ReactNode
}
/** @deprecated Use KpiStatCard */
@@ -27,16 +31,39 @@ interface KpiStatGridProps {
skeletonCount?: number
}
const DEFAULT_ICON_CLASS = 'text-muted-foreground [&_svg]:text-current'
/** KPI body — ReUI PRO [stats-12](https://reui.io/preview/base/stats-12) */
function KpiStatCardBody({ card }: { card: KpiStatCard }) {
const footerContent =
card.footer ??
(card.hint ? (
<span className="text-muted-foreground text-xs leading-snug">{card.hint}</span>
) : null)
return (
<div className="relative z-10 flex flex-col gap-3">
<span className="text-muted-foreground text-sm">{card.label}</span>
<span className="text-foreground text-xl leading-tight font-semibold tabular-nums">
{card.value}
</span>
{card.hint ? (
<span className="text-muted-foreground text-xs leading-snug">{card.hint}</span>
<div className="relative z-10 flex h-full flex-col items-start gap-4">
{card.icon ? (
<Item
className={cn(
'border-background bg-muted flex size-10.5 items-center justify-center border-2 p-0 shadow-[0_1px_3px_0_rgba(0,0,0,0.14)] dark:border [&_svg]:size-4',
card.iconClassName ?? DEFAULT_ICON_CLASS,
)}
>
<ItemMedia variant="icon" className="size-auto">
{card.icon}
</ItemMedia>
</Item>
) : null}
<div className="flex flex-col gap-0.5">
<span className="text-foreground text-2xl leading-none font-bold tabular-nums">
{card.value}
</span>
<span className="text-muted-foreground text-sm font-medium">{card.label}</span>
</div>
{footerContent ? <div className="mt-auto w-full">{footerContent}</div> : null}
</div>
)
}
@@ -44,13 +71,12 @@ function KpiStatCardBody({ card }: { card: KpiStatCard }) {
function KpiStatCardItem({ card }: { card: KpiStatCard }) {
const interactive = Boolean(card.to || card.onSelect)
const panelClass = cn(
'relative isolate flex flex-col gap-3',
'relative isolate flex h-full flex-col',
card.selected && 'ring-primary/30 bg-muted/30 ring-1',
interactive &&
'hover:bg-muted/40 focus-within:ring-ring transition-colors focus-within:ring-2',
)
// card-35: FramePanel — прямой child грида; ссылка/кнопка внутри панели
if (card.to) {
return (
<FramePanel className={panelClass}>
@@ -91,9 +117,12 @@ function KpiStatGridSkeleton({ count }: { count: number }) {
<Frame className="@container w-full">
<div className="grid gap-1 @2xl:grid-cols-2 @4xl:grid-cols-4">
{Array.from({ length: count }).map((_, index) => (
<FramePanel key={index} className="flex flex-col gap-3">
<Skeleton className="h-4 w-20" />
<Skeleton className="h-7 w-14" />
<FramePanel key={index} className="flex flex-col items-start gap-4">
<Skeleton className="size-10.5 rounded-lg" />
<div className="flex w-full flex-col gap-1.5">
<Skeleton className="h-7 w-14" />
<Skeleton className="h-4 w-20" />
</div>
<Skeleton className="h-3 w-28" />
</FramePanel>
))}
+15 -2
View File
@@ -1,5 +1,10 @@
import { useMemo } from 'react'
import { ServerIcon } from 'lucide-react'
import {
FolderOpenIcon,
PowerIcon,
PowerOffIcon,
ServerIcon,
} from 'lucide-react'
import { KpiStatGrid, type KpiStatCard } from '@/components/reui-kit/kpi-stat-grid'
import type { ServiceCatalogRow } from '@/components/columns/services-columns'
@@ -10,7 +15,7 @@ interface ServiceKpiCardsProps {
onSelectTab: (tabId: string) => void
}
/** KPI strip — ReUI PRO [card-35](https://reui.io/preview/base/card-35) */
/** KPI strip — ReUI PRO [stats-12](https://reui.io/preview/base/stats-12) */
export function ServiceKpiCards({
rows,
activeTab,
@@ -29,6 +34,8 @@ export function ServiceKpiCards({
label: 'Всего',
value: rows.length,
hint: 'Сервисы',
icon: <ServerIcon aria-hidden />,
iconClassName: 'text-info',
onSelect: () => onSelectTab('all'),
selected: activeTab === 'all',
},
@@ -37,6 +44,8 @@ export function ServiceKpiCards({
label: 'Включены',
value: enabled,
hint: 'Активные',
icon: <PowerIcon aria-hidden />,
iconClassName: 'text-success',
onSelect: () => onSelectTab('enabled'),
selected: activeTab === 'enabled',
},
@@ -45,6 +54,8 @@ export function ServiceKpiCards({
label: 'Выключены',
value: disabled,
hint: 'Отключены',
icon: <PowerOffIcon aria-hidden />,
iconClassName: 'text-muted-foreground',
onSelect: () => onSelectTab('disabled'),
selected: activeTab === 'disabled',
},
@@ -53,6 +64,8 @@ export function ServiceKpiCards({
label: 'Без группы',
value: ungrouped,
hint: down > 0 ? `Down: ${down}` : 'Не в группе',
icon: <FolderOpenIcon aria-hidden />,
iconClassName: 'text-warning',
onSelect: () => onSelectTab('ungrouped'),
selected: activeTab === 'ungrouped',
},
+15
View File
@@ -0,0 +1,15 @@
/**
* CFDM UI surface contract (ReUI Pro).
*
* Ops / list / dashboard / detail / settings: **Frame** only.
* KPI: **stats-12** via `reui-kit/KpiStatGrid` (card-35 = compact strip).
* Lists: **data-grid-filtering-2** via `reui-kit/ResourcePage`.
*
* @see https://reui.io/preview/base/stats-12
* @see https://reui.io/preview/base/data-grid-filtering-2
* @see docs/ui-design-contract.md
*/
export const UI_SURFACE = 'frame' as const
/** Grid for ReUI stats-12 KPI rows (36 tiles). */
export const kpiStatGridClassName = '@container w-full'
+11
View File
@@ -5,6 +5,9 @@ import {
ActivityIcon,
AlertTriangleIcon,
FolderTreeIcon,
GlobeIcon,
ServerIcon,
ShieldCheckIcon,
} from 'lucide-react'
import {
certificatesQueryOptions,
@@ -173,6 +176,8 @@ function DashboardPage() {
ungroupedCount > 0
? `${ungroupedCount} без группы`
: 'Все в группах',
icon: <GlobeIcon aria-hidden />,
iconClassName: 'text-info',
to: '/domains',
},
{
@@ -180,6 +185,8 @@ function DashboardPage() {
label: 'Группы',
value: groups?.length ?? 0,
hint: `${groupChartData.filter((g) => g.count > 0).length} с зонами`,
icon: <FolderTreeIcon aria-hidden />,
iconClassName: 'text-primary',
to: '/groups',
},
{
@@ -191,6 +198,8 @@ function DashboardPage() {
: ungroupedServiceCount
? `${ungroupedServiceCount} без группы`
: undefined,
icon: <ServerIcon aria-hidden />,
iconClassName: 'text-success',
to: '/services',
search: { domainId: undefined },
},
@@ -202,6 +211,8 @@ function DashboardPage() {
certWarnings > 0
? `${certOk} в норме · ${certWarnings} внимания`
: `${certOk} в норме`,
icon: <ShieldCheckIcon aria-hidden />,
iconClassName: 'text-warning',
to: '/certificates',
},
]
+4
View File
@@ -2,6 +2,10 @@
Wiki home — synced from repository on `main` when this file changes.
## UI
Design contract (Frame surface, ReUI kit): [`docs/ui-design-contract.md`](ui-design-contract.md)
## Overview
Manage Cloudflare zones, DNS records, domain groups, and TLS certificate expiry from a single UI.
+62
View File
@@ -0,0 +1,62 @@
# UI Design Contract (ops apps)
Единый контракт для vps-tracker, CFDM и EvoBGP. Surface: **ReUI Frame**. Kit API: `apps/web/src/components/reui-kit/`.
Карта: [llms.txt](https://reui.io/llms.txt) · [Styling](https://reui.io/docs/styling) · [License](https://reui.io/docs/license-setup) · [Blocks](https://reui.io/blocks)
## Surface
```ts
// apps/web/src/lib/ui-surface.ts
export const UI_SURFACE = 'frame' as const
```
Ops / list / dashboard / detail / settings — только **Frame**, не shadcn Card как shell. Не смешивать Card и Frame на одном ops-экране.
## Canonical PRO references
| Зона | Block | Preview |
|------|-------|---------|
| Shell | `app-shell-12` (+ cmdk/monitor где нужно) | https://reui.io/preview/base/app-shell-12 |
| KPI | `stats-12` (primary); `card-35` compact strip | https://reui.io/preview/base/stats-12 · https://reui.io/preview/base/card-35 |
| Dashboard | `dashboard-1` | https://reui.io/preview/base/dashboard-1 |
| Lists | `data-grid-filtering-2` | https://reui.io/preview/base/data-grid-filtering-2 |
| Settings | `settings-16` + SettingRow | https://reui.io/preview/base/settings-16 |
| 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` → Sheet/Drawer | https://reui.io/preview/base/form-7 |
## Kit API (`reui-kit/`)
| Component | Role |
|-----------|------|
| `ResourcePage` | Frame + line tabs + Filters + DataGrid |
| `KpiStatGrid` | stats-12 KPI tiles |
| `OpsDashboard` | KPI + charts + attention queue |
| `SettingsShell` | settings nav + Outlet |
| `DetailPanel` | detail Frame sections |
| `filter-utils` | apply/clear ReUI Filters |
## MCP workflow
1. MCP `user-reui``search` / `get_block` / `get_component` with `surface: "frame"`
2. Cite `previewUrl` + `docsUrl`
3. CLI from `apps/web`: `pnpm dlx shadcn@latest add @reui/...`
4. Adapt into kit — do not hand-roll KPI/grid/settings rows
5. `validate_usage` / `get_audit_checklist`
Primitives: MCP `plugin-shadcn-shadcn` + `@cfdm/ui`.
## Spacing
- Main: `gap-4 md:gap-6`, `px-4 md:px-6`
- No `space-y-*` / `space-x-*` — use `flex` + `gap-*`
- Max 1 primary CTA per screen
- Semantic tokens only (`variant="success"|"info"|"warning"`) — no raw `bg-emerald-*`
## Forbidden
- Card as ops list/dashboard shell
- Hand-rolled data tables when ReUI DataGrid exists
- Hand-rolled KPI grids when `KpiStatGrid` exists
- Mixing Card and Frame surfaces on one ops screen