Files
EvoBGP/apps/web/src/components/reui-kit/settings-shell.tsx
T
DenozordecandCursor 35e262b5ae
CI / changes (push) Successful in 9s
CI / commitlint (push) Skipped
CI / go (push) Skipped
CI / bird2 (push) Skipped
CI / openapi (push) Successful in 47s
CI / web (push) Successful in 1m4s
CI / release (push) Successful in 5m19s
fix(web): update Russian translations and improve UI components
Обновлены переводы на русский язык для различных компонентов, включая KPI, карточки сети и панели управления. Исправлены описания и метки для улучшения пользовательского интерфейса. Также добавлены новые элементы для поддержки локализации в компонентах, таких как DataGrid и ResourcePage.

Co-authored-by: Cursor <[email protected]>
2026-08-18 00:32:00 +07:00

126 lines
3.9 KiB
TypeScript

import type { ReactNode } from 'react'
import { Link, Outlet, useRouterState } from '@tanstack/react-router'
import { ServerCogIcon, SettingsIcon } from 'lucide-react'
import { useIsMobile } from '@evobgp/ui/hooks/use-mobile'
import { cn } from '@evobgp/ui/lib/utils'
import { PageShell } from '@/components/page-shell'
import { PageHeader } from '@/components/page-header'
export interface SettingsTabConfig {
id: string
to: '/settings' | '/tenant-settings'
label: string
icon?: ReactNode
}
/** Default nav for EvoBGP settings routes (`/settings`, `/tenant-settings`). */
const DEFAULT_TABS: SettingsTabConfig[] = [
{
id: 'ui',
to: '/settings',
label: 'Настройки UI',
icon: <SettingsIcon className="size-4" aria-hidden="true" />,
},
{
id: 'tenant',
to: '/tenant-settings',
label: 'Настройки BIRD',
icon: <ServerCogIcon className="size-4" aria-hidden="true" />,
},
]
interface SettingsShellProps {
title?: string
description?: string
tabs?: SettingsTabConfig[]
}
/**
* Settings shell — Frame surface, no hairline Separator under header.
* @see https://reui.io/preview/base/settings-16
* @see https://reui.io/preview/base/settings-3
*/
export function SettingsShell({
title = 'Настройки',
description,
tabs = DEFAULT_TABS,
}: SettingsShellProps) {
const isMobile = useIsMobile()
const pathname = useRouterState({ select: (s) => s.location.pathname })
const headerDescription =
description ??
(pathname.startsWith('/tenant-settings')
? 'Глобальные параметры BIRD и плоскости управления'
: 'Подключение UI и параметры плоскости управления')
return (
<PageShell>
<div className="mx-auto flex w-full max-w-4xl flex-col gap-5">
<PageHeader title={title} description={headerDescription} />
<div
className={cn(
'flex gap-5',
isMobile ? 'flex-col' : 'flex-row items-start',
)}
>
{tabs.length > 1 ? (
<nav
aria-label="Разделы настроек"
className={cn(
'flex gap-1',
isMobile
? 'scrollbar-none -mx-1 overflow-x-auto overflow-y-hidden pb-1'
: 'w-44 shrink-0 flex-col',
)}
>
{tabs.map((tab) => {
const isActive = pathname.startsWith(tab.to)
const className = cn(
'flex items-center gap-2 rounded-lg px-3 py-2 text-sm transition-colors',
isMobile && 'shrink-0',
!isMobile && 'w-full',
isActive
? 'bg-muted text-foreground font-medium shadow-sm ring-1 ring-border/60'
: 'text-muted-foreground hover:bg-muted/60 hover:text-foreground',
)
if (tab.to === '/tenant-settings') {
return (
<Link
key={tab.id}
to="/tenant-settings"
search={{ tab: 'bird' }}
aria-current={isActive ? 'page' : undefined}
className={className}
>
{tab.icon}
{tab.label}
</Link>
)
}
return (
<Link
key={tab.id}
to="/settings"
search={{ tab: 'connection' }}
aria-current={isActive ? 'page' : undefined}
className={className}
>
{tab.icon}
{tab.label}
</Link>
)
})}
</nav>
) : null}
<div className="min-w-0 flex-1">
<Outlet />
</div>
</div>
</div>
</PageShell>
)
}