feat(web): добавить App Switcher для перехода между приложениями
Build, Test, and Push CFDM Docker Image / test (push) Successful in 3m33s
Build, Test, and Push CFDM Docker Image / build-and-push (push) Successful in 1m57s
Build, Test, and Push CFDM Docker Image / update-wiki (push) Successful in 7s
Build, Test, and Push CFDM Docker Image / create-release (push) Has been skipped

Dropdown в sidebar с настраиваемым списком ссылок через VITE_APP_SWITCHER; dev-порт 5174 для перехода с VPS Tracker.

Co-authored-by: Cursor <[email protected]>
This commit is contained in:
Denozordec
2026-06-30 15:11:55 +07:00
co-authored by Cursor
parent 72a4b3403b
commit e707766418
7 changed files with 269 additions and 17 deletions
+2 -17
View File
@@ -5,8 +5,8 @@ import {
FolderTreeIcon,
ServerIcon,
ShieldCheckIcon,
CloudIcon,
} from 'lucide-react'
import { AppSwitcher } from '@/components/app-switcher'
import { NavUser } from '@/components/nav-user'
import {
Sidebar,
@@ -73,22 +73,7 @@ export function AppSidebar() {
return (
<Sidebar collapsible="icon">
<SidebarHeader>
<SidebarMenu>
<SidebarMenuItem>
<SidebarMenuButton size="lg" render={<Link to="/" />}>
<div
className="flex aspect-square size-8 items-center justify-center rounded-md bg-primary text-primary-foreground"
aria-hidden
>
<CloudIcon className="size-4" />
</div>
<div className="flex flex-col gap-0.5 leading-none">
<span className="font-semibold">CF Domain Manager</span>
<span className="text-xs text-muted-foreground">Управление доменами</span>
</div>
</SidebarMenuButton>
</SidebarMenuItem>
</SidebarMenu>
<AppSwitcher />
</SidebarHeader>
<SidebarContent>
<NavSection label="Инфраструктура" items={infrastructureNav} />
+96
View File
@@ -0,0 +1,96 @@
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuShortcut,
DropdownMenuTrigger,
} from '@cfdm/ui/components/dropdown-menu'
import {
SidebarMenu,
SidebarMenuButton,
SidebarMenuItem,
useSidebar,
} from '@cfdm/ui/components/sidebar'
import { CheckIcon, ChevronsUpDownIcon } from 'lucide-react'
import {
APP_SWITCHER_ICONS,
CURRENT_APP_ID,
getAppSwitcherConfig,
getCurrentApp,
} from '@/lib/app-switcher-config'
export function AppSwitcher() {
const { isMobile } = useSidebar()
const config = getAppSwitcherConfig()
const current = getCurrentApp(config)
const CurrentIcon = APP_SWITCHER_ICONS[current.icon]
return (
<SidebarMenu>
<SidebarMenuItem>
<DropdownMenu>
<DropdownMenuTrigger
render={
<SidebarMenuButton size="lg" className="aria-expanded:bg-muted" />
}
>
<div
className="flex aspect-square size-8 items-center justify-center rounded-md bg-primary text-primary-foreground"
aria-hidden
>
<CurrentIcon className="size-4" />
</div>
<div className="grid flex-1 text-left text-sm leading-tight">
<span className="truncate font-semibold">{current.name}</span>
{current.subtitle ? (
<span className="truncate text-xs text-muted-foreground">
{current.subtitle}
</span>
) : null}
</div>
<ChevronsUpDownIcon className="ml-auto size-4" />
</DropdownMenuTrigger>
<DropdownMenuContent
className="min-w-56 rounded-lg"
side={isMobile ? 'bottom' : 'right'}
align="start"
sideOffset={4}
>
<DropdownMenuLabel className="text-xs text-muted-foreground">
{config.menuLabel}
</DropdownMenuLabel>
<DropdownMenuGroup>
{config.apps.map((app) => {
const Icon = APP_SWITCHER_ICONS[app.icon]
const isCurrent = app.id === CURRENT_APP_ID
if (isCurrent) {
return (
<DropdownMenuItem key={app.id} disabled>
<Icon />
{app.name}
<CheckIcon className="ml-auto size-4" />
</DropdownMenuItem>
)
}
return (
<DropdownMenuItem key={app.id} render={<a href={app.url} />}>
<Icon />
{app.name}
{app.shortcut ? (
<DropdownMenuShortcut>{app.shortcut}</DropdownMenuShortcut>
) : null}
</DropdownMenuItem>
)
})}
</DropdownMenuGroup>
</DropdownMenuContent>
</DropdownMenu>
</SidebarMenuItem>
</SidebarMenu>
)
}