refactor(ui): улучшить структуру компонентов AppSidebar и PageHeader
Build, Test, and Push CFDM Docker Image / test (push) Successful in 3m11s
Build, Test, and Push CFDM Docker Image / build-and-push (push) Successful in 2m9s
Build, Test, and Push CFDM Docker Image / create-release (push) Skipped
Build, Test, and Push CFDM Docker Image / update-wiki (push) Successful in 6s
Build, Test, and Push CFDM Docker Image / test (push) Successful in 3m11s
Build, Test, and Push CFDM Docker Image / build-and-push (push) Successful in 2m9s
Build, Test, and Push CFDM Docker Image / create-release (push) Skipped
Build, Test, and Push CFDM Docker Image / update-wiki (push) Successful in 6s
Обновлены компоненты AppSidebar и PageHeader для улучшения структуры и адаптивности. В AppSidebar добавлен новый элемент SidebarGroupContent для более четкой организации навигации. В PageHeader упрощена разметка и улучшены стили, включая изменения в отображении заголовка и описания. Также обновлены стили в AppShell для интеграции SearchMenu и TooltipProvider, что улучшает взаимодействие с пользователем.
This commit is contained in:
@@ -8,18 +8,17 @@ import {
|
|||||||
SettingsIcon,
|
SettingsIcon,
|
||||||
} from 'lucide-react'
|
} from 'lucide-react'
|
||||||
import { AppSwitcher } from '@/components/app-switcher'
|
import { AppSwitcher } from '@/components/app-switcher'
|
||||||
import { NavUser } from '@/components/nav-user'
|
|
||||||
import {
|
import {
|
||||||
Sidebar,
|
Sidebar,
|
||||||
SidebarContent,
|
SidebarContent,
|
||||||
SidebarFooter,
|
SidebarFooter,
|
||||||
SidebarGroup,
|
SidebarGroup,
|
||||||
|
SidebarGroupContent,
|
||||||
SidebarGroupLabel,
|
SidebarGroupLabel,
|
||||||
SidebarHeader,
|
SidebarHeader,
|
||||||
SidebarMenu,
|
SidebarMenu,
|
||||||
SidebarMenuButton,
|
SidebarMenuButton,
|
||||||
SidebarMenuItem,
|
SidebarMenuItem,
|
||||||
SidebarRail,
|
|
||||||
} from '@cfdm/ui/components/sidebar'
|
} from '@cfdm/ui/components/sidebar'
|
||||||
|
|
||||||
const infrastructureNav = [
|
const infrastructureNav = [
|
||||||
@@ -65,30 +64,32 @@ function NavSection({
|
|||||||
return (
|
return (
|
||||||
<SidebarGroup>
|
<SidebarGroup>
|
||||||
<SidebarGroupLabel>{label}</SidebarGroupLabel>
|
<SidebarGroupLabel>{label}</SidebarGroupLabel>
|
||||||
<SidebarMenu>
|
<SidebarGroupContent>
|
||||||
{items.map((item) => (
|
<SidebarMenu>
|
||||||
<SidebarMenuItem key={item.to}>
|
{items.map((item) => (
|
||||||
<SidebarMenuButton
|
<SidebarMenuItem key={item.to}>
|
||||||
tooltip={item.label}
|
<SidebarMenuButton
|
||||||
isActive={isNavActive(
|
tooltip={item.label}
|
||||||
pathname,
|
isActive={isNavActive(
|
||||||
item.to,
|
pathname,
|
||||||
item.exact,
|
item.to,
|
||||||
item.matchPrefix,
|
item.exact,
|
||||||
)}
|
item.matchPrefix,
|
||||||
render={
|
)}
|
||||||
<Link
|
render={
|
||||||
to={item.to}
|
<Link
|
||||||
activeOptions={{ exact: item.exact }}
|
to={item.to}
|
||||||
/>
|
activeOptions={{ exact: item.exact }}
|
||||||
}
|
/>
|
||||||
>
|
}
|
||||||
<item.icon />
|
>
|
||||||
<span>{item.label}</span>
|
<item.icon className="size-4" />
|
||||||
</SidebarMenuButton>
|
<span>{item.label}</span>
|
||||||
</SidebarMenuItem>
|
</SidebarMenuButton>
|
||||||
))}
|
</SidebarMenuItem>
|
||||||
</SidebarMenu>
|
))}
|
||||||
|
</SidebarMenu>
|
||||||
|
</SidebarGroupContent>
|
||||||
</SidebarGroup>
|
</SidebarGroup>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -105,10 +106,7 @@ export function AppSidebar() {
|
|||||||
<NavSection label="Инфраструктура" items={infrastructureNav} pathname={pathname} />
|
<NavSection label="Инфраструктура" items={infrastructureNav} pathname={pathname} />
|
||||||
<NavSection label="Операции" items={operationsNav} pathname={pathname} />
|
<NavSection label="Операции" items={operationsNav} pathname={pathname} />
|
||||||
</SidebarContent>
|
</SidebarContent>
|
||||||
<SidebarFooter>
|
<SidebarFooter />
|
||||||
<NavUser />
|
|
||||||
</SidebarFooter>
|
|
||||||
<SidebarRail />
|
|
||||||
</Sidebar>
|
</Sidebar>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,31 +1,30 @@
|
|||||||
import type { CSSProperties, ReactNode } from 'react'
|
import type { CSSProperties, ReactNode } from 'react'
|
||||||
import { AppSidebar } from '@/components/app-sidebar'
|
import { AppSidebar } from '@/components/app-sidebar'
|
||||||
import { SiteHeader } from '@/components/layout/site-header'
|
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'
|
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 }) {
|
export function AppShell({ children }: { children: ReactNode }) {
|
||||||
return (
|
return (
|
||||||
<SidebarProvider
|
<TooltipProvider delay={0}>
|
||||||
className={cn(
|
<SidebarProvider
|
||||||
'[--sidebar:color-mix(in_oklab,var(--color-sidebar)_60%,transparent)]',
|
style={
|
||||||
'[--sidebar-border:transparent]',
|
{
|
||||||
'[--sidebar-accent:color-mix(in_oklab,var(--color-primary)_14%,transparent)]',
|
'--sidebar-width': '240px',
|
||||||
'[--sidebar-accent-foreground:var(--color-primary)]',
|
} as CSSProperties
|
||||||
)}
|
}
|
||||||
style={
|
>
|
||||||
{
|
<AppSidebar />
|
||||||
'--sidebar-width': '240px',
|
<SidebarInset>
|
||||||
} as CSSProperties
|
<SiteHeader />
|
||||||
}
|
<main className="flex flex-1 flex-col gap-4 px-4 py-4 md:gap-6 md:px-6 md:py-5">
|
||||||
>
|
{children}
|
||||||
<AppSidebar />
|
</main>
|
||||||
<SidebarInset>
|
</SidebarInset>
|
||||||
<SiteHeader />
|
<SearchMenu hotkeyOnly />
|
||||||
<main className="flex flex-1 flex-col gap-4 px-4 py-4 md:gap-6 md:px-6 md:py-5">
|
</SidebarProvider>
|
||||||
{children}
|
</TooltipProvider>
|
||||||
</main>
|
|
||||||
</SidebarInset>
|
|
||||||
</SidebarProvider>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -65,8 +65,8 @@ const NAV_ITEMS = [
|
|||||||
},
|
},
|
||||||
] as const
|
] as const
|
||||||
|
|
||||||
/** Command-K search — app-shell-12 SearchMenu, routes only (no demo data). */
|
/** Command-K search — hotkey dialog (no header chrome trigger). Etalon: EvoBGP CommandPalette hotkeyOnly. */
|
||||||
export function SearchMenu() {
|
export function SearchMenu({ hotkeyOnly = false }: { hotkeyOnly?: boolean }) {
|
||||||
const [open, setOpen] = useState(false)
|
const [open, setOpen] = useState(false)
|
||||||
const [query, setQuery] = useState('')
|
const [query, setQuery] = useState('')
|
||||||
const searchInputId = useId()
|
const searchInputId = useId()
|
||||||
@@ -105,20 +105,22 @@ export function SearchMenu() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Button
|
{hotkeyOnly ? null : (
|
||||||
type="button"
|
<Button
|
||||||
variant="ghost"
|
type="button"
|
||||||
size="icon"
|
variant="ghost"
|
||||||
aria-label="Поиск"
|
size="icon"
|
||||||
aria-haspopup="dialog"
|
aria-label="Поиск"
|
||||||
aria-expanded={open}
|
aria-haspopup="dialog"
|
||||||
onClick={() => setOpen(true)}
|
aria-expanded={open}
|
||||||
>
|
onClick={() => setOpen(true)}
|
||||||
<SearchIcon
|
>
|
||||||
className="size-4.5 transition-colors"
|
<SearchIcon
|
||||||
aria-hidden="true"
|
className="size-4.5 transition-colors"
|
||||||
/>
|
aria-hidden="true"
|
||||||
</Button>
|
/>
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
|
||||||
<Dialog open={open} onOpenChange={setOpen}>
|
<Dialog open={open} onOpenChange={setOpen}>
|
||||||
<DialogHeader className="sr-only">
|
<DialogHeader className="sr-only">
|
||||||
|
|||||||
@@ -8,10 +8,10 @@ import {
|
|||||||
BreadcrumbPage,
|
BreadcrumbPage,
|
||||||
BreadcrumbSeparator,
|
BreadcrumbSeparator,
|
||||||
} from '@cfdm/ui/components/breadcrumb'
|
} from '@cfdm/ui/components/breadcrumb'
|
||||||
|
import { Separator } from '@cfdm/ui/components/separator'
|
||||||
import { ModeToggle } from '@/components/mode-toggle'
|
import { ModeToggle } from '@/components/mode-toggle'
|
||||||
import { SystemMonitorPopover } from '@/components/layout/system-monitor-popover'
|
import { SystemMonitorPopover } from '@/components/layout/system-monitor-popover'
|
||||||
import { AppsMenu } from '@/components/layout/apps-menu'
|
import { AppsMenu } from '@/components/layout/apps-menu'
|
||||||
import { SearchMenu } from '@/components/layout/search-menu'
|
|
||||||
import { SidebarTrigger } from '@cfdm/ui/components/sidebar'
|
import { SidebarTrigger } from '@cfdm/ui/components/sidebar'
|
||||||
|
|
||||||
export interface RouteBreadcrumbLoaderData {
|
export interface RouteBreadcrumbLoaderData {
|
||||||
@@ -93,6 +93,7 @@ function useDynamicBreadcrumbLabels() {
|
|||||||
}, [matches])
|
}, [matches])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Header chrome — etalon EvoBGP (Trigger + Separator + Breadcrumb + Apps/Monitor/Mode). */
|
||||||
export function SiteHeader() {
|
export function SiteHeader() {
|
||||||
const pathname = useRouterState({ select: (s) => s.location.pathname })
|
const pathname = useRouterState({ select: (s) => s.location.pathname })
|
||||||
const dynamicLabels = useDynamicBreadcrumbLabels()
|
const dynamicLabels = useDynamicBreadcrumbLabels()
|
||||||
@@ -100,36 +101,34 @@ export function SiteHeader() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<header className="bg-background sticky top-0 z-10 flex h-12 shrink-0 items-center gap-2 border-b px-4 md:px-6">
|
<header className="bg-background sticky top-0 z-10 flex h-12 shrink-0 items-center gap-2 border-b px-4 md:px-6">
|
||||||
<div className="flex min-w-0 items-center gap-2">
|
<SidebarTrigger className="-ml-1" />
|
||||||
<SidebarTrigger className="-ml-1" />
|
<Separator orientation="vertical" className="mr-2 data-[orientation=vertical]:h-4" />
|
||||||
<Breadcrumb>
|
<Breadcrumb>
|
||||||
<BreadcrumbList>
|
<BreadcrumbList>
|
||||||
{crumbs.map((crumb, index) => {
|
{crumbs.map((crumb, index) => {
|
||||||
const isLast = index === crumbs.length - 1
|
const isLast = index === crumbs.length - 1
|
||||||
return (
|
return (
|
||||||
<span key={crumb.href} className="contents">
|
<span key={crumb.href} className="contents">
|
||||||
{index > 0 && (
|
{index > 0 && (
|
||||||
<BreadcrumbSeparator className="hidden md:block" />
|
<BreadcrumbSeparator className="hidden md:block" />
|
||||||
|
)}
|
||||||
|
<BreadcrumbItem
|
||||||
|
className={index === 0 && !isLast ? 'hidden md:block' : undefined}
|
||||||
|
>
|
||||||
|
{isLast ? (
|
||||||
|
<BreadcrumbPage>{crumb.label}</BreadcrumbPage>
|
||||||
|
) : (
|
||||||
|
<BreadcrumbLink render={<Link to={crumb.href} />}>
|
||||||
|
{crumb.label}
|
||||||
|
</BreadcrumbLink>
|
||||||
)}
|
)}
|
||||||
<BreadcrumbItem
|
</BreadcrumbItem>
|
||||||
className={index === 0 ? 'hidden md:block' : undefined}
|
</span>
|
||||||
>
|
)
|
||||||
{isLast ? (
|
})}
|
||||||
<BreadcrumbPage>{crumb.label}</BreadcrumbPage>
|
</BreadcrumbList>
|
||||||
) : (
|
</Breadcrumb>
|
||||||
<BreadcrumbLink render={<Link to={crumb.href} />}>
|
<div className="ml-auto flex items-center gap-2">
|
||||||
{crumb.label}
|
|
||||||
</BreadcrumbLink>
|
|
||||||
)}
|
|
||||||
</BreadcrumbItem>
|
|
||||||
</span>
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
</BreadcrumbList>
|
|
||||||
</Breadcrumb>
|
|
||||||
</div>
|
|
||||||
<div className="text-muted-foreground [&_button_svg]:text-muted-foreground [&_button:active_svg]:text-foreground! [&_button:hover>span>svg]:text-foreground! [&_button:hover>svg]:text-foreground! [&_button[aria-expanded=true]_svg]:text-foreground! [&_button[data-popup-open]_svg]:text-foreground! ml-auto flex items-center gap-2">
|
|
||||||
<SearchMenu />
|
|
||||||
<AppsMenu />
|
<AppsMenu />
|
||||||
<SystemMonitorPopover />
|
<SystemMonitorPopover />
|
||||||
<ModeToggle />
|
<ModeToggle />
|
||||||
|
|||||||
@@ -1,45 +1,22 @@
|
|||||||
import type { ReactNode } from 'react'
|
import type { ReactNode } from 'react'
|
||||||
import { cn } from '@cfdm/ui/lib/utils'
|
|
||||||
|
|
||||||
interface PageHeaderProps {
|
interface PageHeaderProps {
|
||||||
title: string
|
title: string
|
||||||
description?: ReactNode
|
description?: ReactNode
|
||||||
actions?: ReactNode
|
actions?: ReactNode
|
||||||
className?: string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** Page-level section header — etalon EvoBGP. */
|
||||||
* Page-level section header (h1) — shared by dashboard, lists, settings.
|
export function PageHeader({ title, description, actions }: PageHeaderProps) {
|
||||||
* Distinct from FrameTitle (text-sm inside Frame panels).
|
|
||||||
*/
|
|
||||||
export function PageHeader({
|
|
||||||
title,
|
|
||||||
description,
|
|
||||||
actions,
|
|
||||||
className,
|
|
||||||
}: PageHeaderProps) {
|
|
||||||
return (
|
return (
|
||||||
<header
|
<div className="flex flex-col gap-2 md:flex-row md:items-center md:justify-between">
|
||||||
className={cn(
|
<div className="flex flex-col gap-1">
|
||||||
'flex flex-wrap items-start justify-between gap-3 px-1',
|
<h1 className="text-2xl font-semibold tracking-tight">{title}</h1>
|
||||||
className,
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<div className="flex min-w-0 flex-1 flex-col gap-px">
|
|
||||||
<h1 className="text-xl font-semibold tracking-tight md:text-2xl">
|
|
||||||
{title}
|
|
||||||
</h1>
|
|
||||||
{description ? (
|
{description ? (
|
||||||
<p className="text-muted-foreground max-w-2xl text-sm leading-relaxed">
|
<p className="text-muted-foreground text-sm">{description}</p>
|
||||||
{description}
|
|
||||||
</p>
|
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
{actions ? (
|
{actions ? <div className="flex items-center gap-2">{actions}</div> : null}
|
||||||
<div className="flex shrink-0 flex-wrap items-center justify-end gap-2">
|
</div>
|
||||||
{actions}
|
|
||||||
</div>
|
|
||||||
) : null}
|
|
||||||
</header>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
|
||||||
import { toast } from 'sonner'
|
import { toast } from 'sonner'
|
||||||
import { PaletteIcon } from 'lucide-react'
|
import { LogOutIcon, PaletteIcon } from 'lucide-react'
|
||||||
|
|
||||||
import { api } from '@/lib/api-client'
|
import { api } from '@/lib/api-client'
|
||||||
|
import { clearToken } from '@/lib/auth'
|
||||||
import { SettingRow } from '@/components/setting-row'
|
import { SettingRow } from '@/components/setting-row'
|
||||||
import { Switch } from '@cfdm/ui/components/switch'
|
import { Switch } from '@cfdm/ui/components/switch'
|
||||||
|
import { Button } from '@cfdm/ui/components/button'
|
||||||
import {
|
import {
|
||||||
Frame,
|
Frame,
|
||||||
FrameDescription,
|
FrameDescription,
|
||||||
@@ -29,6 +31,7 @@ export const Route = createFileRoute('/_auth/settings/appearance')({
|
|||||||
})
|
})
|
||||||
|
|
||||||
function AppearanceSettingsPage() {
|
function AppearanceSettingsPage() {
|
||||||
|
const navigate = useNavigate()
|
||||||
const queryClient = useQueryClient()
|
const queryClient = useQueryClient()
|
||||||
const { data, isLoading } = useQuery({
|
const { data, isLoading } = useQuery({
|
||||||
queryKey: ['app-settings'],
|
queryKey: ['app-settings'],
|
||||||
@@ -48,6 +51,11 @@ function AppearanceSettingsPage() {
|
|||||||
|
|
||||||
const showQuickActions = data?.showQuickActions !== false
|
const showQuickActions = data?.showQuickActions !== false
|
||||||
|
|
||||||
|
const handleLogout = () => {
|
||||||
|
clearToken()
|
||||||
|
void navigate({ to: '/login' })
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Frame dense spacing="sm" className="w-full">
|
<Frame dense spacing="sm" className="w-full">
|
||||||
<FrameHeader>
|
<FrameHeader>
|
||||||
@@ -62,7 +70,6 @@ function AppearanceSettingsPage() {
|
|||||||
<SettingRow
|
<SettingRow
|
||||||
title="Быстрые действия"
|
title="Быстрые действия"
|
||||||
description="KPI-like плитки быстрых переходов под метриками на главной."
|
description="KPI-like плитки быстрых переходов под метриками на главной."
|
||||||
last
|
|
||||||
>
|
>
|
||||||
<Switch
|
<Switch
|
||||||
checked={showQuickActions}
|
checked={showQuickActions}
|
||||||
@@ -73,6 +80,16 @@ function AppearanceSettingsPage() {
|
|||||||
aria-label="Показывать быстрые действия"
|
aria-label="Показывать быстрые действия"
|
||||||
/>
|
/>
|
||||||
</SettingRow>
|
</SettingRow>
|
||||||
|
<SettingRow
|
||||||
|
title="Сессия"
|
||||||
|
description="Выйти из учётной записи администратора."
|
||||||
|
last
|
||||||
|
>
|
||||||
|
<Button type="button" variant="outline" size="sm" onClick={handleLogout}>
|
||||||
|
<LogOutIcon className="size-4" aria-hidden />
|
||||||
|
Выйти
|
||||||
|
</Button>
|
||||||
|
</SettingRow>
|
||||||
</FieldGroup>
|
</FieldGroup>
|
||||||
</FramePanel>
|
</FramePanel>
|
||||||
</Frame>
|
</Frame>
|
||||||
|
|||||||
@@ -49,18 +49,22 @@ Gating: DB `show_quick_actions` / `showQuickActions` / `ui_show_quick_actions` (
|
|||||||
|
|
||||||
## Shared App Shell chrome
|
## 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_WIDTH` / `--sidebar-width` | `240px` (в `packages/ui` sidebar + Provider style) |
|
||||||
| Sidebar accent | primary **14%** mix + transparent border |
|
| Sidebar / hover colors | theme `--sidebar` / `--sidebar-accent` из `globals.css` — **без** AppShell `color-mix` override |
|
||||||
| Header | `h-12`, `sticky`, `border-b`, `px-4 md:px-6` (без blur / без `h-16`) |
|
| Header | `h-12`, `sticky`, `border-b`, `px-4 md:px-6` |
|
||||||
| Header right | **AppsMenu** → **SystemMonitorPopover** → **ModeToggle** |
|
| 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` |
|
| `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.
|
App Switcher ids: `vps-tracker` · `cfdm` · `evobgp`. Override: `VITE_APP_SWITCHER` JSON.
|
||||||
|
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ import { PanelLeftIcon } from "lucide-react"
|
|||||||
|
|
||||||
const SIDEBAR_COOKIE_NAME = "sidebar_state"
|
const SIDEBAR_COOKIE_NAME = "sidebar_state"
|
||||||
const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7
|
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_MOBILE = "18rem"
|
||||||
const SIDEBAR_WIDTH_ICON = "3rem"
|
const SIDEBAR_WIDTH_ICON = "3rem"
|
||||||
const SIDEBAR_KEYBOARD_SHORTCUT = "b"
|
const SIDEBAR_KEYBOARD_SHORTCUT = "b"
|
||||||
|
|||||||
Reference in New Issue
Block a user