From 60e15ca40a1bd6ba92bf19cad72e85456f421c34 Mon Sep 17 00:00:00 2001 From: Denozordec Date: Fri, 17 Jul 2026 23:56:29 +0700 Subject: [PATCH] =?UTF-8?q?refactor(ui):=20=D1=83=D0=BB=D1=83=D1=87=D1=88?= =?UTF-8?q?=D0=B8=D1=82=D1=8C=20=D1=81=D1=82=D1=80=D1=83=D0=BA=D1=82=D1=83?= =?UTF-8?q?=D1=80=D1=83=20=D0=BA=D0=BE=D0=BC=D0=BF=D0=BE=D0=BD=D0=B5=D0=BD?= =?UTF-8?q?=D1=82=D0=BE=D0=B2=20AppSidebar=20=D0=B8=20PageHeader?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Обновлены компоненты AppSidebar и PageHeader для улучшения структуры и адаптивности. В AppSidebar добавлен новый элемент SidebarGroupContent для более четкой организации навигации. В PageHeader упрощена разметка и улучшены стили, включая изменения в отображении заголовка и описания. Также обновлены стили в AppShell для интеграции SearchMenu и TooltipProvider, что улучшает взаимодействие с пользователем. --- apps/web/src/components/app-sidebar.tsx | 58 +++++++++--------- apps/web/src/components/layout/app-shell.tsx | 43 +++++++------- .../web/src/components/layout/search-menu.tsx | 34 ++++++----- .../web/src/components/layout/site-header.tsx | 59 +++++++++---------- apps/web/src/components/page-header.tsx | 39 +++--------- .../src/routes/_auth/settings/appearance.tsx | 23 +++++++- docs/ui-design-contract.md | 18 +++--- packages/ui/src/components/sidebar.tsx | 2 +- 8 files changed, 136 insertions(+), 140 deletions(-) diff --git a/apps/web/src/components/app-sidebar.tsx b/apps/web/src/components/app-sidebar.tsx index bcdaf50..d4fd7fa 100644 --- a/apps/web/src/components/app-sidebar.tsx +++ b/apps/web/src/components/app-sidebar.tsx @@ -8,18 +8,17 @@ import { SettingsIcon, } from 'lucide-react' import { AppSwitcher } from '@/components/app-switcher' -import { NavUser } from '@/components/nav-user' import { Sidebar, SidebarContent, SidebarFooter, SidebarGroup, + SidebarGroupContent, SidebarGroupLabel, SidebarHeader, SidebarMenu, SidebarMenuButton, SidebarMenuItem, - SidebarRail, } from '@cfdm/ui/components/sidebar' const infrastructureNav = [ @@ -65,30 +64,32 @@ function NavSection({ return ( {label} - - {items.map((item) => ( - - - } - > - - {item.label} - - - ))} - + + + {items.map((item) => ( + + + } + > + + {item.label} + + + ))} + + ) } @@ -105,10 +106,7 @@ export function AppSidebar() { - - - - + ) } diff --git a/apps/web/src/components/layout/app-shell.tsx b/apps/web/src/components/layout/app-shell.tsx index ea1a421..b7d28fa 100644 --- a/apps/web/src/components/layout/app-shell.tsx +++ b/apps/web/src/components/layout/app-shell.tsx @@ -1,31 +1,30 @@ import type { CSSProperties, ReactNode } from 'react' import { AppSidebar } from '@/components/app-sidebar' import { SiteHeader } from '@/components/layout/site-header' -import { cn } from '@cfdm/ui/lib/utils' +import { SearchMenu } from '@/components/layout/search-menu' +import { TooltipProvider } from '@cfdm/ui/components/tooltip' import { SidebarInset, SidebarProvider } from '@cfdm/ui/components/sidebar' +/** Shared ops chrome — etalon EvoBGP. @see docs/ui-design-contract.md */ export function AppShell({ children }: { children: ReactNode }) { return ( - - - - -
- {children} -
-
-
+ + + + + +
+ {children} +
+
+ +
+
) } diff --git a/apps/web/src/components/layout/search-menu.tsx b/apps/web/src/components/layout/search-menu.tsx index 8b35f76..8b45d41 100644 --- a/apps/web/src/components/layout/search-menu.tsx +++ b/apps/web/src/components/layout/search-menu.tsx @@ -65,8 +65,8 @@ const NAV_ITEMS = [ }, ] as const -/** Command-K search — app-shell-12 SearchMenu, routes only (no demo data). */ -export function SearchMenu() { +/** Command-K search — hotkey dialog (no header chrome trigger). Etalon: EvoBGP CommandPalette hotkeyOnly. */ +export function SearchMenu({ hotkeyOnly = false }: { hotkeyOnly?: boolean }) { const [open, setOpen] = useState(false) const [query, setQuery] = useState('') const searchInputId = useId() @@ -105,20 +105,22 @@ export function SearchMenu() { return ( <> - + {hotkeyOnly ? null : ( + + )} diff --git a/apps/web/src/components/layout/site-header.tsx b/apps/web/src/components/layout/site-header.tsx index c576922..8205d70 100644 --- a/apps/web/src/components/layout/site-header.tsx +++ b/apps/web/src/components/layout/site-header.tsx @@ -8,10 +8,10 @@ import { BreadcrumbPage, BreadcrumbSeparator, } from '@cfdm/ui/components/breadcrumb' +import { Separator } from '@cfdm/ui/components/separator' import { ModeToggle } from '@/components/mode-toggle' import { SystemMonitorPopover } from '@/components/layout/system-monitor-popover' import { AppsMenu } from '@/components/layout/apps-menu' -import { SearchMenu } from '@/components/layout/search-menu' import { SidebarTrigger } from '@cfdm/ui/components/sidebar' export interface RouteBreadcrumbLoaderData { @@ -93,6 +93,7 @@ function useDynamicBreadcrumbLabels() { }, [matches]) } +/** Header chrome — etalon EvoBGP (Trigger + Separator + Breadcrumb + Apps/Monitor/Mode). */ export function SiteHeader() { const pathname = useRouterState({ select: (s) => s.location.pathname }) const dynamicLabels = useDynamicBreadcrumbLabels() @@ -100,36 +101,34 @@ export function SiteHeader() { return (
-
- - - - {crumbs.map((crumb, index) => { - const isLast = index === crumbs.length - 1 - return ( - - {index > 0 && ( - + + + + + {crumbs.map((crumb, index) => { + const isLast = index === crumbs.length - 1 + return ( + + {index > 0 && ( + + )} + + {isLast ? ( + {crumb.label} + ) : ( + }> + {crumb.label} + )} - - {isLast ? ( - {crumb.label} - ) : ( - }> - {crumb.label} - - )} - - - ) - })} - - -
-
- + + + ) + })} + + +
diff --git a/apps/web/src/components/page-header.tsx b/apps/web/src/components/page-header.tsx index ce11dc8..bb3674c 100644 --- a/apps/web/src/components/page-header.tsx +++ b/apps/web/src/components/page-header.tsx @@ -1,45 +1,22 @@ import type { ReactNode } from 'react' -import { cn } from '@cfdm/ui/lib/utils' interface PageHeaderProps { title: string description?: ReactNode actions?: ReactNode - className?: string } -/** - * Page-level section header (h1) — shared by dashboard, lists, settings. - * Distinct from FrameTitle (text-sm inside Frame panels). - */ -export function PageHeader({ - title, - description, - actions, - className, -}: PageHeaderProps) { +/** Page-level section header — etalon EvoBGP. */ +export function PageHeader({ title, description, actions }: PageHeaderProps) { return ( -
-
-

- {title} -

+
+
+

{title}

{description ? ( -

- {description} -

+

{description}

) : null}
- {actions ? ( -
- {actions} -
- ) : null} -
+ {actions ?
{actions}
: null} +
) } diff --git a/apps/web/src/routes/_auth/settings/appearance.tsx b/apps/web/src/routes/_auth/settings/appearance.tsx index 68ef36a..7343837 100644 --- a/apps/web/src/routes/_auth/settings/appearance.tsx +++ b/apps/web/src/routes/_auth/settings/appearance.tsx @@ -1,11 +1,13 @@ -import { createFileRoute } from '@tanstack/react-router' +import { createFileRoute, useNavigate } from '@tanstack/react-router' import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query' import { toast } from 'sonner' -import { PaletteIcon } from 'lucide-react' +import { LogOutIcon, PaletteIcon } from 'lucide-react' import { api } from '@/lib/api-client' +import { clearToken } from '@/lib/auth' import { SettingRow } from '@/components/setting-row' import { Switch } from '@cfdm/ui/components/switch' +import { Button } from '@cfdm/ui/components/button' import { Frame, FrameDescription, @@ -29,6 +31,7 @@ export const Route = createFileRoute('/_auth/settings/appearance')({ }) function AppearanceSettingsPage() { + const navigate = useNavigate() const queryClient = useQueryClient() const { data, isLoading } = useQuery({ queryKey: ['app-settings'], @@ -48,6 +51,11 @@ function AppearanceSettingsPage() { const showQuickActions = data?.showQuickActions !== false + const handleLogout = () => { + clearToken() + void navigate({ to: '/login' }) + } + return ( @@ -62,7 +70,6 @@ function AppearanceSettingsPage() { + + + diff --git a/docs/ui-design-contract.md b/docs/ui-design-contract.md index 02035d8..ac9f98b 100644 --- a/docs/ui-design-contract.md +++ b/docs/ui-design-contract.md @@ -49,18 +49,22 @@ Gating: DB `show_quick_actions` / `showQuickActions` / `ui_show_quick_actions` ( ## 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). +Эталон: **EvoBGP** production AppShell + ReUI [app-shell-12](https://reui.io/preview/base/app-shell-12). -При переключении между vps-tracker / CFDM / EvoBGP меняются **только** sidebar nav и `main` content. ClassNames chrome идентичны. +При переключении между vps-tracker / CFDM / EvoBGP меняются **только** sidebar nav labels/hrefs и `main` content. Разметка, ширина, фон и hover 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** | +| `SIDEBAR_WIDTH` / `--sidebar-width` | `240px` (в `packages/ui` sidebar + Provider style) | +| Sidebar / hover colors | theme `--sidebar` / `--sidebar-accent` из `globals.css` — **без** AppShell `color-mix` override | +| Header | `h-12`, `sticky`, `border-b`, `px-4 md:px-6` | +| Header left | `SidebarTrigger` + `Separator` + Breadcrumb | +| Header right | **AppsMenu** → **SystemMonitorPopover** → **ModeToggle** (без Search в chrome) | +| Sidebar | AppSwitcher → groups (`SidebarGroupContent`) → icons `size-4` → **пустой** `SidebarFooter` | | `main` | `gap-4 md:gap-6`, `px-4 py-4 md:px-6 md:py-5` | -| Sidebar header | `AppSwitcher` (не static brand) | +| Search | hotkey ⌘K / Ctrl+K only (не кнопка в header) | + +Запрещено в chrome: `SidebarRail`, `NavUser` footer, sync-row footer, Search/Ctrl+K pill в header, issues Badge в header, muted/hover cascade на right-cluster, Provider `color-mix` для `--sidebar*`. App Switcher ids: `vps-tracker` · `cfdm` · `evobgp`. Override: `VITE_APP_SWITCHER` JSON. diff --git a/packages/ui/src/components/sidebar.tsx b/packages/ui/src/components/sidebar.tsx index fc7735e..b18899d 100644 --- a/packages/ui/src/components/sidebar.tsx +++ b/packages/ui/src/components/sidebar.tsx @@ -25,7 +25,7 @@ import { PanelLeftIcon } from "lucide-react" const SIDEBAR_COOKIE_NAME = "sidebar_state" const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7 -const SIDEBAR_WIDTH = "16rem" +const SIDEBAR_WIDTH = "240px" const SIDEBAR_WIDTH_MOBILE = "18rem" const SIDEBAR_WIDTH_ICON = "3rem" const SIDEBAR_KEYBOARD_SHORTCUT = "b"