From 039d2f3dd9b8a22996db5c3de19efc5be2612690 Mon Sep 17 00:00:00 2001 From: Denozordec Date: Fri, 17 Jul 2026 23:15:00 +0700 Subject: [PATCH] refactor: update dashboard quick links and app shell for improved layout and functionality Refactored the DashboardQuickLinks component to simplify icon classes for better semantic clarity. Updated the AppShell component to integrate the AppSwitcher and AppsMenu, enhancing navigation and user experience. Adjusted the layout of the app shell header and main content for improved consistency and alignment. Updated UI design documentation to reflect these changes and ensure adherence to shared design standards. --- apps/web/src/components/app-switcher.tsx | 97 +++++++++++++++++ .../dashboard/dashboard-quick-links.tsx | 15 +-- apps/web/src/components/layout/app-shell.tsx | 43 ++++---- apps/web/src/components/layout/apps-menu.tsx | 93 ++++++++++++++++ .../src/components/layout/command-palette.tsx | 44 ++++---- apps/web/src/hooks/use-app-switcher.ts | 22 ++++ apps/web/src/lib/app-switcher-config.ts | 102 ++++++++++++++++++ apps/web/src/vite-env.d.ts | 9 ++ apps/web/tsconfig.tsbuildinfo | 2 +- docs/ui-design-contract.md | 23 +++- 10 files changed, 401 insertions(+), 49 deletions(-) create mode 100644 apps/web/src/components/app-switcher.tsx create mode 100644 apps/web/src/components/layout/apps-menu.tsx create mode 100644 apps/web/src/hooks/use-app-switcher.ts create mode 100644 apps/web/src/lib/app-switcher-config.ts create mode 100644 apps/web/src/vite-env.d.ts diff --git a/apps/web/src/components/app-switcher.tsx b/apps/web/src/components/app-switcher.tsx new file mode 100644 index 0000000..d11a94e --- /dev/null +++ b/apps/web/src/components/app-switcher.tsx @@ -0,0 +1,97 @@ +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuShortcut, + DropdownMenuTrigger, +} from '@evobgp/ui/components/dropdown-menu' +import { + SidebarMenu, + SidebarMenuButton, + SidebarMenuItem, + useSidebar, +} from '@evobgp/ui/components/sidebar' +import { CheckIcon, ChevronsUpDownIcon } from 'lucide-react' + +import { + APP_SWITCHER_ICONS, + CURRENT_APP_ID, + getCurrentApp, +} from '@/lib/app-switcher-config' +import { useAppSwitcherConfig } from '@/hooks/use-app-switcher' + +/** Sidebar app switcher — same chrome as CFDM / vps-tracker. @see https://reui.io/preview/base/app-shell-12 */ +export function AppSwitcher() { + const { isMobile } = useSidebar() + const { config, isLoading } = useAppSwitcherConfig() + const current = getCurrentApp(config) + const CurrentIcon = APP_SWITCHER_ICONS[current.icon] + + return ( + + + + + } + > +
+ +
+
+ {current.name} + {current.subtitle ? ( + + {current.subtitle} + + ) : null} +
+ +
+ +
+ {isLoading ? 'Загрузка…' : config.menuLabel} +
+ {config.apps.map((app) => { + const Icon = APP_SWITCHER_ICONS[app.icon] + const isCurrent = app.id === CURRENT_APP_ID + + if (isCurrent) { + return ( + + + {app.name} + + + ) + } + + return ( + } + > + + {app.name} + {app.shortcut ? ( + {app.shortcut} + ) : null} + + ) + })} +
+
+
+
+ ) +} diff --git a/apps/web/src/components/dashboard/dashboard-quick-links.tsx b/apps/web/src/components/dashboard/dashboard-quick-links.tsx index 9dc5d64..b073733 100644 --- a/apps/web/src/components/dashboard/dashboard-quick-links.tsx +++ b/apps/web/src/components/dashboard/dashboard-quick-links.tsx @@ -2,6 +2,7 @@ import { Gauge, Network, Play, Plus, Search, Share2, Tags } from 'lucide-react' import { QuickActionGrid, type QuickActionItem } from '@/components/reui-kit' +/** iconClassName: semantic text only (shared chrome with CFDM). @see https://reui.io/preview/base/stats-12 */ const ACTIONS: QuickActionItem[] = [ { id: 'lookup', @@ -9,7 +10,7 @@ const ACTIONS: QuickActionItem[] = [ description: 'Membership в списках и community (entry + snapshot).', to: '/lookup', icon: , - iconClassName: 'bg-primary text-primary-foreground [&_svg]:text-primary-foreground', + iconClassName: 'text-primary', }, { id: 'new-module', @@ -17,7 +18,7 @@ const ACTIONS: QuickActionItem[] = [ description: 'Новый модуль маршрутизации и источники префиксов.', to: '/modules/new', icon: , - iconClassName: 'bg-primary text-primary-foreground [&_svg]:text-primary-foreground', + iconClassName: 'text-primary', }, { id: 'communities', @@ -25,7 +26,7 @@ const ACTIONS: QuickActionItem[] = [ description: 'Справочник communities для политик экспорта.', to: '/directories', icon: , - iconClassName: 'bg-info text-info-foreground [&_svg]:text-info-foreground', + iconClassName: 'text-info', }, { id: 'network', @@ -34,7 +35,7 @@ const ACTIONS: QuickActionItem[] = [ to: '/network', search: { tab: 'overview' }, icon: , - iconClassName: 'bg-success text-success-foreground [&_svg]:text-success-foreground', + iconClassName: 'text-success', }, { id: 'add-peer', @@ -43,7 +44,7 @@ const ACTIONS: QuickActionItem[] = [ to: '/network', search: { tab: 'peers' }, icon: , - iconClassName: 'bg-warning text-warning-foreground [&_svg]:text-warning-foreground', + iconClassName: 'text-warning', }, { id: 'deploy', @@ -52,7 +53,7 @@ const ACTIONS: QuickActionItem[] = [ to: '/operations', search: { tab: 'revisions' }, icon: , - iconClassName: 'bg-focus text-focus-foreground [&_svg]:text-focus-foreground', + iconClassName: 'text-muted-foreground', }, { id: 'monitoring', @@ -61,7 +62,7 @@ const ACTIONS: QuickActionItem[] = [ to: '/monitoring', search: { tab: 'system' }, icon: , - iconClassName: 'bg-destructive text-destructive-foreground [&_svg]:text-destructive-foreground', + iconClassName: 'text-destructive', }, ] diff --git a/apps/web/src/components/layout/app-shell.tsx b/apps/web/src/components/layout/app-shell.tsx index a2f350f..bb4a1d2 100644 --- a/apps/web/src/components/layout/app-shell.tsx +++ b/apps/web/src/components/layout/app-shell.tsx @@ -38,10 +38,13 @@ import { } from '@evobgp/ui/components/breadcrumb' import { Separator } from '@evobgp/ui/components/separator' import { TooltipProvider } from '@evobgp/ui/components/tooltip' +import { cn } from '@evobgp/ui/lib/utils' import { Link, useRouterState } from '@tanstack/react-router' import type { ComponentType, CSSProperties, ReactNode } from 'react' +import { AppSwitcher } from '@/components/app-switcher' +import { AppsMenu } from '@/components/layout/apps-menu' import { CommandPalette, type CommandPaletteItem } from '@/components/layout/command-palette' import { SystemMonitorPopover } from '@/components/layout/system-monitor-popover' import { ModeToggle } from '@/components/mode-toggle' @@ -118,6 +121,11 @@ const COMMAND_ITEMS: CommandPaletteItem[] = ALL_NAV_ITEMS.map((item) => ({ keywords: [item.to.replace(/^\//, '')], })) +/** + * Shared ops chrome — tokens match CFDM / vps-tracker. + * @see https://reui.io/preview/base/app-shell-12 + * @see docs/ui-design-contract.md — Shared App Shell chrome + */ export function AppShell({ children }: { children: ReactNode }) { const pathname = useRouterState({ select: (s) => s.location.pathname }) const activeItem = @@ -134,28 +142,21 @@ export function AppShell({ children }: { children: ReactNode }) { return ( - -
-
- B -
-
- EvoBGP - Плоскость управления -
-
-
- -
+ + {NAV_GROUPS.map((group) => ( @@ -187,8 +188,8 @@ export function AppShell({ children }: { children: ReactNode }) {
-
- +
+ @@ -206,12 +207,16 @@ export function AppShell({ children }: { children: ReactNode }) {
+
-
{children}
+
+ {children} +
+ ) diff --git a/apps/web/src/components/layout/apps-menu.tsx b/apps/web/src/components/layout/apps-menu.tsx new file mode 100644 index 0000000..fa36544 --- /dev/null +++ b/apps/web/src/components/layout/apps-menu.tsx @@ -0,0 +1,93 @@ +import { Link } from '@tanstack/react-router' +import { LayoutGridIcon } from 'lucide-react' +import { Button } from '@evobgp/ui/components/button' +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuGroup, + DropdownMenuItem, + DropdownMenuLabel, + DropdownMenuSeparator, + DropdownMenuTrigger, +} from '@evobgp/ui/components/dropdown-menu' +import { + APP_SWITCHER_ICONS, + CURRENT_APP_ID, +} from '@/lib/app-switcher-config' +import { useAppSwitcherConfig } from '@/hooks/use-app-switcher' + +/** Header apps grid — app-shell-12 AppsMenu. @see https://reui.io/preview/base/app-shell-12 */ +export function AppsMenu() { + const { config, isLoading } = useAppSwitcherConfig() + + return ( + + + } + > + + + + + + {isLoading ? 'Загрузка…' : config.menuLabel} + +
+ {config.apps.map((app) => { + const Icon = APP_SWITCHER_ICONS[app.icon] + const isCurrent = app.id === CURRENT_APP_ID + + if (isCurrent) { + return ( + + + + {app.name} + + ) + } + + return ( + } + className="h-auto flex-col gap-1.5 py-3 text-center [&_svg]:size-5" + > + + + {app.name} + + ) + })} +
+ + } + className="justify-center text-sm font-medium" + > + Настройки + +
+
+
+ ) +} diff --git a/apps/web/src/components/layout/command-palette.tsx b/apps/web/src/components/layout/command-palette.tsx index 5576961..9a94bc9 100644 --- a/apps/web/src/components/layout/command-palette.tsx +++ b/apps/web/src/components/layout/command-palette.tsx @@ -29,9 +29,11 @@ export type CommandPaletteItem = { interface CommandPaletteProps { items: CommandPaletteItem[] className?: string + /** Hotkey-only: no sidebar search trigger (chrome parity with CFDM). */ + hotkeyOnly?: boolean } -export function CommandPalette({ items }: CommandPaletteProps) { +export function CommandPalette({ items, hotkeyOnly = false }: CommandPaletteProps) { const [open, setOpen] = useState(false) const [query, setQuery] = useState('') const searchInputId = useId() @@ -68,25 +70,27 @@ export function CommandPalette({ items }: CommandPaletteProps) { return ( <> - - - - - - ⌘K - - - + {!hotkeyOnly ? ( + + + + + + ⌘K + + + + ) : null} + +export const APP_SWITCHER_ICONS: Record = { + server: ServerIcon, + cloud: CloudIcon, + globe: GlobeIcon, + dashboard: LayoutDashboardIcon, + chart: ChartBarIcon, +} + +const appSwitcherEntrySchema = z.object({ + id: z.string(), + name: z.string(), + subtitle: z.string().optional(), + url: z.string(), + icon: appSwitcherIconSchema.default('server'), + shortcut: z.string().optional(), +}) + +const appSwitcherConfigSchema = z.object({ + menuLabel: z.string().default('Приложения'), + apps: z.array(appSwitcherEntrySchema).min(1), +}) + +export type AppSwitcherEntry = z.infer +export type AppSwitcherConfig = z.infer + +/** Shared defaults across ops apps — chrome app switcher. */ +export const DEFAULT_APP_SWITCHER_CONFIG: AppSwitcherConfig = { + menuLabel: 'Приложения', + apps: [ + { + id: 'vps-tracker', + name: 'VPS Tracker', + subtitle: 'Учёт виртуальных серверов', + url: 'http://192.168.100.67:3001', + icon: 'server', + shortcut: '⌘1', + }, + { + id: 'cfdm', + name: 'CF Domain Manager', + subtitle: 'Управление доменами', + url: 'http://192.168.100.67:6363', + icon: 'cloud', + shortcut: '⌘2', + }, + { + id: 'evobgp', + name: 'EvoBGP', + subtitle: 'BGP маршрутизация', + url: 'http://192.168.100.67:3000', + icon: 'globe', + shortcut: '⌘3', + }, + ], +} + +export function parseAppSwitcherConfig(raw?: string): AppSwitcherConfig { + if (!raw?.trim()) { + return DEFAULT_APP_SWITCHER_CONFIG + } + + try { + const parsed = JSON.parse(raw) as unknown + return appSwitcherConfigSchema.parse(parsed) + } catch (error) { + console.warn('Invalid VITE_APP_SWITCHER, using defaults:', error) + return DEFAULT_APP_SWITCHER_CONFIG + } +} + +export function getAppSwitcherConfig(): AppSwitcherConfig { + return parseAppSwitcherConfig(import.meta.env.VITE_APP_SWITCHER) +} + +export function getAppUrl( + appId: string, + config: AppSwitcherConfig = getAppSwitcherConfig(), +): string | undefined { + return config.apps.find((app) => app.id === appId)?.url +} + +export function getCurrentApp( + config: AppSwitcherConfig = getAppSwitcherConfig(), +): AppSwitcherEntry { + return config.apps.find((app) => app.id === CURRENT_APP_ID) ?? config.apps[0]! +} diff --git a/apps/web/src/vite-env.d.ts b/apps/web/src/vite-env.d.ts new file mode 100644 index 0000000..1935306 --- /dev/null +++ b/apps/web/src/vite-env.d.ts @@ -0,0 +1,9 @@ +/// + +interface ImportMetaEnv { + readonly VITE_APP_SWITCHER?: string +} + +interface ImportMeta { + readonly env: ImportMetaEnv +} diff --git a/apps/web/tsconfig.tsbuildinfo b/apps/web/tsconfig.tsbuildinfo index ea812b4..7955207 100644 --- a/apps/web/tsconfig.tsbuildinfo +++ b/apps/web/tsconfig.tsbuildinfo @@ -1 +1 @@ -{"root":["./src/main.tsx","./src/routetree.gen.ts","./src/components/badge-tabs.tsx","./src/components/category-badge.tsx","./src/components/confirm-dialog.tsx","./src/components/counted-line-tabs.tsx","./src/components/data-grid-cell.tsx","./src/components/data-grid-shell.tsx","./src/components/data-grid-toolbar.tsx","./src/components/drawer-layout.tsx","./src/components/empty-state.tsx","./src/components/form-drawer.tsx","./src/components/kpi-stat-grid.tsx","./src/components/loading-button.tsx","./src/components/mode-toggle.tsx","./src/components/page-header.tsx","./src/components/page-shell.tsx","./src/components/panel-card.tsx","./src/components/query-state.tsx","./src/components/section-cards.tsx","./src/components/select-field.tsx","./src/components/skeletons.tsx","./src/components/status-badge.tsx","./src/components/theme-provider.tsx","./src/components/truncated-text.tsx","./src/components/access/access-api-keys-card.tsx","./src/components/access/access-api-keys-grid.tsx","./src/components/access/api-key-create-dialog.tsx","./src/components/access/api-key-token-dialog.tsx","./src/components/analytics/analytics-activity-list.tsx","./src/components/analytics/analytics-card-shell.tsx","./src/components/analytics/analytics-kpi-row.tsx","./src/components/analytics/analytics-progress.tsx","./src/components/analytics/analytics-segment-control.tsx","./src/components/analytics/chart-bar-strip.tsx","./src/components/analytics/chart-donut-metric.tsx","./src/components/analytics/dashboard-network-capacity-card.tsx","./src/components/analytics/dashboard-operations-flow-card.tsx","./src/components/analytics/dashboard-platform-card.tsx","./src/components/analytics/index.ts","./src/components/analytics/monitoring-health-card.tsx","./src/components/analytics/network-overview-analytics-card.tsx","./src/components/analytics/operations-analytics-card.tsx","./src/components/dashboard/card-dot-field.tsx","./src/components/dashboard/dashboard-activity-timeline.tsx","./src/components/dashboard/dashboard-frame-panel.tsx","./src/components/dashboard/dashboard-kpi-grid.tsx","./src/components/dashboard/dashboard-kpi-sparkline-row.tsx","./src/components/dashboard/dashboard-modules-grid.tsx","./src/components/dashboard/dashboard-network-health.tsx","./src/components/dashboard/dashboard-network-panel.tsx","./src/components/dashboard/dashboard-operations-breakdown.tsx","./src/components/dashboard/dashboard-quick-links.tsx","./src/components/dashboard/dashboard-recent-jobs-grid.tsx","./src/components/dashboard/dashboard-recent-revisions-grid.tsx","./src/components/directories/directories-communities-grid.tsx","./src/components/directories/directories-doh-grid.tsx","./src/components/examples/c-input-group-37.tsx","./src/components/examples/c-select-4.tsx","./src/components/examples/c-tabs-2.tsx","./src/components/examples/c-tabs-6.tsx","./src/components/examples/c-tabs-7.tsx","./src/components/firewall/firewall-clients-grid.tsx","./src/components/firewall/firewall-rule-create-dialog.tsx","./src/components/firewall/firewall-rules-grid.tsx","./src/components/layout/app-shell.tsx","./src/components/layout/command-palette.tsx","./src/components/layout/system-monitor-popover.tsx","./src/components/lookup/lookup-matches-grid.tsx","./src/components/lookup/lookup-search-form.tsx","./src/components/lookup/lookup-summary-kpi.tsx","./src/components/modules/community-select.tsx","./src/components/modules/module-as-entry-dialog.tsx","./src/components/modules/module-cdn-source-dialog.tsx","./src/components/modules/module-domain-entry-dialog.tsx","./src/components/modules/module-entries-grid.tsx","./src/components/modules/module-entries-section.tsx","./src/components/modules/module-ip-range-entry-dialog.tsx","./src/components/modules/module-kpi-cards.tsx","./src/components/modules/modules-list-grid.tsx","./src/components/monitoring/monitoring-ready-grid.tsx","./src/components/network/network-peers-card.tsx","./src/components/network/network-peers-grid.tsx","./src/components/network/network-speakers-card.tsx","./src/components/network/network-speakers-grid.tsx","./src/components/network/peer-form-dialog.tsx","./src/components/network/speaker-form-dialog.tsx","./src/components/operations/operations-jobs-card.tsx","./src/components/operations/operations-jobs-grid.tsx","./src/components/operations/operations-revisions-grid.tsx","./src/components/patterns/donut-breakdown-card.tsx","./src/components/patterns/illustrated-empty-state.tsx","./src/components/patterns/index.ts","./src/components/patterns/kpi-sparkline-card.tsx","./src/components/patterns/metric-tone-styles.ts","./src/components/patterns/panel-corners.tsx","./src/components/patterns/projects-empty-state.tsx","./src/components/patterns/segmented-progress-card.tsx","./src/components/reui/alert.tsx","./src/components/reui/autocomplete.tsx","./src/components/reui/badge.tsx","./src/components/reui/date-selector.tsx","./src/components/reui/filters.tsx","./src/components/reui/frame.tsx","./src/components/reui/icon-stack.tsx","./src/components/reui/number-field.tsx","./src/components/reui/rating.tsx","./src/components/reui/timeline.tsx","./src/components/reui/data-grid/data-grid-column-filter.tsx","./src/components/reui/data-grid/data-grid-column-header.tsx","./src/components/reui/data-grid/data-grid-column-visibility.tsx","./src/components/reui/data-grid/data-grid-pagination.tsx","./src/components/reui/data-grid/data-grid-scroll-area.tsx","./src/components/reui/data-grid/data-grid-table-dnd-rows.tsx","./src/components/reui/data-grid/data-grid-table-dnd.tsx","./src/components/reui/data-grid/data-grid-table-virtual.tsx","./src/components/reui/data-grid/data-grid-table.tsx","./src/components/reui/data-grid/data-grid.tsx","./src/components/reui-kit/detail-panel.tsx","./src/components/reui-kit/filter-utils.ts","./src/components/reui-kit/index.ts","./src/components/reui-kit/kpi-stat-grid.tsx","./src/components/reui-kit/ops-dashboard.tsx","./src/components/reui-kit/quick-action-grid.tsx","./src/components/reui-kit/resource-page.tsx","./src/components/reui-kit/settings-shell.tsx","./src/components/schedule/schedule-agenda-panel.tsx","./src/components/schedule/schedule-calendar-view.tsx","./src/components/schedule/schedule-jobs-card.tsx","./src/components/schedule/schedule-jobs-grid.tsx","./src/components/schedule/schedule-modules-grid.tsx","./src/components/settings/appearance-settings-tab.tsx","./src/components/settings/connection-settings-tab.tsx","./src/components/settings/sections-settings-tab.tsx","./src/components/settings/session-settings-tab.tsx","./src/components/settings/settings-kv-grid.tsx","./src/components/settings/settings-page-shell.tsx","./src/components/settings/settings-setting-field.tsx","./src/components/settings/settings-tabs-data.tsx","./src/components/ui/svgs/anthropicblack.tsx","./src/components/ui/svgs/anthropicwhite.tsx","./src/components/ui/svgs/convex.tsx","./src/components/ui/svgs/discord.tsx","./src/components/ui/svgs/gemini.tsx","./src/components/ui/svgs/googlecloud.tsx","./src/components/ui/svgs/hono.tsx","./src/components/ui/svgs/loom.tsx","./src/components/ui/svgs/mintlify.tsx","./src/components/ui/svgs/n8n.tsx","./src/components/ui/svgs/neon.tsx","./src/components/ui/svgs/openai.tsx","./src/components/ui/svgs/openaidark.tsx","./src/components/ui/svgs/paper.tsx","./src/components/ui/svgs/planetscale.tsx","./src/components/ui/svgs/planetscaledark.tsx","./src/components/ui/svgs/prisma.tsx","./src/components/ui/svgs/prismadark.tsx","./src/components/ui/svgs/remixdark.tsx","./src/components/ui/svgs/remixlight.tsx","./src/components/ui/svgs/resendiconblack.tsx","./src/components/ui/svgs/resendiconwhite.tsx","./src/components/ui/svgs/slack.tsx","./src/components/ui/svgs/stripe.tsx","./src/components/ui/svgs/supabase.tsx","./src/components/ui/svgs/zoom.tsx","./src/hooks/use-client-data-grid.ts","./src/hooks/use-copy-to-clipboard.ts","./src/hooks/use-file-upload.ts","./src/hooks/use-mobile.ts","./src/lib/api-client.ts","./src/lib/data-grid-defaults.ts","./src/lib/queryclient.ts","./src/lib/router.ts","./src/lib/ui-labels.ts","./src/lib/ui-surface.ts","./src/lib/access/api-key-labels.ts","./src/lib/metrics/deployment-progress.ts","./src/lib/metrics/index.ts","./src/lib/metrics/job-status-breakdown.ts","./src/lib/metrics/module-type-breakdown.ts","./src/lib/metrics/peer-capacity-bars.ts","./src/lib/metrics/peer-session-breakdown.ts","./src/lib/metrics/readiness-breakdown.ts","./src/lib/metrics/recent-platform-activity.ts","./src/lib/metrics/types.ts","./src/lib/modules/display.ts","./src/lib/modules/helpers.ts","./src/queries/api-keys.ts","./src/queries/auth.ts","./src/queries/directories.ts","./src/queries/firewall.ts","./src/queries/lookup.ts","./src/queries/modules.ts","./src/queries/monitoring.ts","./src/queries/network.ts","./src/queries/operations.ts","./src/queries/overview.ts","./src/queries/settings.ts","./src/routes/__root.tsx","./src/routes/_auth.tsx","./src/routes/index.tsx","./src/routes/_auth/access.tsx","./src/routes/_auth/dashboard.tsx","./src/routes/_auth/directories.tsx","./src/routes/_auth/firewall.tsx","./src/routes/_auth/lookup.tsx","./src/routes/_auth/monitoring.tsx","./src/routes/_auth/network.tsx","./src/routes/_auth/operations.tsx","./src/routes/_auth/schedule.tsx","./src/routes/_auth/settings.tsx","./src/routes/_auth/tenant-settings.tsx","./src/routes/_auth/modules/$moduleid.tsx","./src/routes/_auth/modules/index.tsx","./src/routes/_auth/modules/new.tsx","./src/types/api.ts","./vite.config.ts"],"version":"5.9.3"} \ No newline at end of file +{"root":["./src/main.tsx","./src/routetree.gen.ts","./src/vite-env.d.ts","./src/components/app-switcher.tsx","./src/components/badge-tabs.tsx","./src/components/category-badge.tsx","./src/components/confirm-dialog.tsx","./src/components/counted-line-tabs.tsx","./src/components/data-grid-cell.tsx","./src/components/data-grid-shell.tsx","./src/components/data-grid-toolbar.tsx","./src/components/drawer-layout.tsx","./src/components/empty-state.tsx","./src/components/form-drawer.tsx","./src/components/kpi-stat-grid.tsx","./src/components/loading-button.tsx","./src/components/mode-toggle.tsx","./src/components/page-header.tsx","./src/components/page-shell.tsx","./src/components/panel-card.tsx","./src/components/query-state.tsx","./src/components/section-cards.tsx","./src/components/select-field.tsx","./src/components/skeletons.tsx","./src/components/status-badge.tsx","./src/components/theme-provider.tsx","./src/components/truncated-text.tsx","./src/components/access/access-api-keys-card.tsx","./src/components/access/access-api-keys-grid.tsx","./src/components/access/api-key-create-dialog.tsx","./src/components/access/api-key-token-dialog.tsx","./src/components/analytics/analytics-activity-list.tsx","./src/components/analytics/analytics-card-shell.tsx","./src/components/analytics/analytics-kpi-row.tsx","./src/components/analytics/analytics-progress.tsx","./src/components/analytics/analytics-segment-control.tsx","./src/components/analytics/chart-bar-strip.tsx","./src/components/analytics/chart-donut-metric.tsx","./src/components/analytics/dashboard-network-capacity-card.tsx","./src/components/analytics/dashboard-operations-flow-card.tsx","./src/components/analytics/dashboard-platform-card.tsx","./src/components/analytics/index.ts","./src/components/analytics/monitoring-health-card.tsx","./src/components/analytics/network-overview-analytics-card.tsx","./src/components/analytics/operations-analytics-card.tsx","./src/components/dashboard/card-dot-field.tsx","./src/components/dashboard/dashboard-activity-timeline.tsx","./src/components/dashboard/dashboard-frame-panel.tsx","./src/components/dashboard/dashboard-kpi-grid.tsx","./src/components/dashboard/dashboard-kpi-sparkline-row.tsx","./src/components/dashboard/dashboard-modules-grid.tsx","./src/components/dashboard/dashboard-network-health.tsx","./src/components/dashboard/dashboard-network-panel.tsx","./src/components/dashboard/dashboard-operations-breakdown.tsx","./src/components/dashboard/dashboard-quick-links.tsx","./src/components/dashboard/dashboard-recent-jobs-grid.tsx","./src/components/dashboard/dashboard-recent-revisions-grid.tsx","./src/components/directories/directories-communities-grid.tsx","./src/components/directories/directories-doh-grid.tsx","./src/components/examples/c-input-group-37.tsx","./src/components/examples/c-select-4.tsx","./src/components/examples/c-tabs-2.tsx","./src/components/examples/c-tabs-6.tsx","./src/components/examples/c-tabs-7.tsx","./src/components/firewall/firewall-clients-grid.tsx","./src/components/firewall/firewall-rule-create-dialog.tsx","./src/components/firewall/firewall-rules-grid.tsx","./src/components/layout/app-shell.tsx","./src/components/layout/apps-menu.tsx","./src/components/layout/command-palette.tsx","./src/components/layout/system-monitor-popover.tsx","./src/components/lookup/lookup-matches-grid.tsx","./src/components/lookup/lookup-search-form.tsx","./src/components/lookup/lookup-summary-kpi.tsx","./src/components/modules/community-select.tsx","./src/components/modules/module-as-entry-dialog.tsx","./src/components/modules/module-cdn-source-dialog.tsx","./src/components/modules/module-domain-entry-dialog.tsx","./src/components/modules/module-entries-grid.tsx","./src/components/modules/module-entries-section.tsx","./src/components/modules/module-ip-range-entry-dialog.tsx","./src/components/modules/module-kpi-cards.tsx","./src/components/modules/modules-list-grid.tsx","./src/components/monitoring/monitoring-ready-grid.tsx","./src/components/network/network-peers-card.tsx","./src/components/network/network-peers-grid.tsx","./src/components/network/network-speakers-card.tsx","./src/components/network/network-speakers-grid.tsx","./src/components/network/peer-form-dialog.tsx","./src/components/network/speaker-form-dialog.tsx","./src/components/operations/operations-jobs-card.tsx","./src/components/operations/operations-jobs-grid.tsx","./src/components/operations/operations-revisions-grid.tsx","./src/components/patterns/donut-breakdown-card.tsx","./src/components/patterns/illustrated-empty-state.tsx","./src/components/patterns/index.ts","./src/components/patterns/kpi-sparkline-card.tsx","./src/components/patterns/metric-tone-styles.ts","./src/components/patterns/panel-corners.tsx","./src/components/patterns/projects-empty-state.tsx","./src/components/patterns/segmented-progress-card.tsx","./src/components/reui/alert.tsx","./src/components/reui/autocomplete.tsx","./src/components/reui/badge.tsx","./src/components/reui/date-selector.tsx","./src/components/reui/filters.tsx","./src/components/reui/frame.tsx","./src/components/reui/icon-stack.tsx","./src/components/reui/number-field.tsx","./src/components/reui/rating.tsx","./src/components/reui/timeline.tsx","./src/components/reui/data-grid/data-grid-column-filter.tsx","./src/components/reui/data-grid/data-grid-column-header.tsx","./src/components/reui/data-grid/data-grid-column-visibility.tsx","./src/components/reui/data-grid/data-grid-pagination.tsx","./src/components/reui/data-grid/data-grid-scroll-area.tsx","./src/components/reui/data-grid/data-grid-table-dnd-rows.tsx","./src/components/reui/data-grid/data-grid-table-dnd.tsx","./src/components/reui/data-grid/data-grid-table-virtual.tsx","./src/components/reui/data-grid/data-grid-table.tsx","./src/components/reui/data-grid/data-grid.tsx","./src/components/reui-kit/detail-panel.tsx","./src/components/reui-kit/filter-utils.ts","./src/components/reui-kit/index.ts","./src/components/reui-kit/kpi-stat-grid.tsx","./src/components/reui-kit/ops-dashboard.tsx","./src/components/reui-kit/quick-action-grid.tsx","./src/components/reui-kit/resource-page.tsx","./src/components/reui-kit/settings-shell.tsx","./src/components/schedule/schedule-agenda-panel.tsx","./src/components/schedule/schedule-calendar-view.tsx","./src/components/schedule/schedule-jobs-card.tsx","./src/components/schedule/schedule-jobs-grid.tsx","./src/components/schedule/schedule-modules-grid.tsx","./src/components/settings/appearance-settings-tab.tsx","./src/components/settings/connection-settings-tab.tsx","./src/components/settings/sections-settings-tab.tsx","./src/components/settings/session-settings-tab.tsx","./src/components/settings/settings-kv-grid.tsx","./src/components/settings/settings-page-shell.tsx","./src/components/settings/settings-setting-field.tsx","./src/components/settings/settings-tabs-data.tsx","./src/components/ui/svgs/anthropicblack.tsx","./src/components/ui/svgs/anthropicwhite.tsx","./src/components/ui/svgs/convex.tsx","./src/components/ui/svgs/discord.tsx","./src/components/ui/svgs/gemini.tsx","./src/components/ui/svgs/googlecloud.tsx","./src/components/ui/svgs/hono.tsx","./src/components/ui/svgs/loom.tsx","./src/components/ui/svgs/mintlify.tsx","./src/components/ui/svgs/n8n.tsx","./src/components/ui/svgs/neon.tsx","./src/components/ui/svgs/openai.tsx","./src/components/ui/svgs/openaidark.tsx","./src/components/ui/svgs/paper.tsx","./src/components/ui/svgs/planetscale.tsx","./src/components/ui/svgs/planetscaledark.tsx","./src/components/ui/svgs/prisma.tsx","./src/components/ui/svgs/prismadark.tsx","./src/components/ui/svgs/remixdark.tsx","./src/components/ui/svgs/remixlight.tsx","./src/components/ui/svgs/resendiconblack.tsx","./src/components/ui/svgs/resendiconwhite.tsx","./src/components/ui/svgs/slack.tsx","./src/components/ui/svgs/stripe.tsx","./src/components/ui/svgs/supabase.tsx","./src/components/ui/svgs/zoom.tsx","./src/hooks/use-app-switcher.ts","./src/hooks/use-client-data-grid.ts","./src/hooks/use-copy-to-clipboard.ts","./src/hooks/use-file-upload.ts","./src/hooks/use-mobile.ts","./src/lib/api-client.ts","./src/lib/app-switcher-config.ts","./src/lib/data-grid-defaults.ts","./src/lib/queryclient.ts","./src/lib/router.ts","./src/lib/ui-labels.ts","./src/lib/ui-surface.ts","./src/lib/access/api-key-labels.ts","./src/lib/metrics/deployment-progress.ts","./src/lib/metrics/index.ts","./src/lib/metrics/job-status-breakdown.ts","./src/lib/metrics/module-type-breakdown.ts","./src/lib/metrics/peer-capacity-bars.ts","./src/lib/metrics/peer-session-breakdown.ts","./src/lib/metrics/readiness-breakdown.ts","./src/lib/metrics/recent-platform-activity.ts","./src/lib/metrics/types.ts","./src/lib/modules/display.ts","./src/lib/modules/helpers.ts","./src/queries/api-keys.ts","./src/queries/auth.ts","./src/queries/directories.ts","./src/queries/firewall.ts","./src/queries/lookup.ts","./src/queries/modules.ts","./src/queries/monitoring.ts","./src/queries/network.ts","./src/queries/operations.ts","./src/queries/overview.ts","./src/queries/settings.ts","./src/routes/__root.tsx","./src/routes/_auth.tsx","./src/routes/index.tsx","./src/routes/_auth/access.tsx","./src/routes/_auth/dashboard.tsx","./src/routes/_auth/directories.tsx","./src/routes/_auth/firewall.tsx","./src/routes/_auth/lookup.tsx","./src/routes/_auth/monitoring.tsx","./src/routes/_auth/network.tsx","./src/routes/_auth/operations.tsx","./src/routes/_auth/schedule.tsx","./src/routes/_auth/settings.tsx","./src/routes/_auth/tenant-settings.tsx","./src/routes/_auth/modules/$moduleid.tsx","./src/routes/_auth/modules/index.tsx","./src/routes/_auth/modules/new.tsx","./src/types/api.ts","./vite.config.ts"],"version":"5.9.3"} \ No newline at end of file diff --git a/docs/ui-design-contract.md b/docs/ui-design-contract.md index ef3c9d3..c936f72 100644 --- a/docs/ui-design-contract.md +++ b/docs/ui-design-contract.md @@ -48,9 +48,28 @@ Ops / list / dashboard / detail / settings — только **Frame**, не shad Gating: KV `ui_show_quick_actions` in `global_settings` via `PATCH /v1/settings` (default `true`). +## Shared App Shell chrome + +Эталон: CFDM + ReUI [app-shell-12](https://reui.io/preview/base/app-shell-12) · monitor/switchers [app-shell-7](https://reui.io/preview/base/app-shell-7). + +При переключении между vps-tracker / CFDM / EvoBGP меняются **только** sidebar nav и `main` content. ClassNames chrome идентичны. + +| Токен / зона | Значение | +|--------------|----------| +| `--sidebar-width` | `240px` | +| Sidebar accent | primary **14%** mix + transparent border | +| Header | `h-12`, `sticky`, `border-b`, `px-4 md:px-6` (без blur / без `h-16`) | +| Header right | **AppsMenu** → **SystemMonitorPopover** → **ModeToggle** | +| `main` | `gap-4 md:gap-6`, `px-4 py-4 md:px-6 md:py-5` | +| Sidebar header | `AppSwitcher` (не static brand) | + +App Switcher ids: `vps-tracker` · `cfdm` · `evobgp`. Override: `VITE_APP_SWITCHER` JSON. + +QuickActionGrid icons: только semantic **text** (`text-info` / `text-primary` / …) на kit `bg-muted` — без solid `bg-primary` fills. Preview: [stats-12](https://reui.io/preview/base/stats-12). + ## System monitor -`SystemMonitorPopover` in app-shell header next to `ModeToggle`. Preview: https://reui.io/preview/base/app-shell-12 · https://reui.io/preview/base/app-shell-7 +`SystemMonitorPopover` in app-shell header next to `ModeToggle` (после AppsMenu). Preview: https://reui.io/preview/base/app-shell-12 · https://reui.io/preview/base/app-shell-7 ## MCP workflow @@ -64,7 +83,7 @@ Primitives: MCP `plugin-shadcn-shadcn` + `@evobgp/ui`. ## Spacing -- Main: `gap-4 md:gap-6`, `px-4 md:px-6` +- AppShell main: `gap-4 md:gap-6`, `px-4 py-4 md:px-6 md:py-5` (shared chrome) - 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-*`