Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1a142e68a9 | ||
|
|
46e7b2aa6b |
@@ -9,3 +9,8 @@
|
||||
{"sessionId":"c10bcb","runId":"post-fix","hypothesisId":"H1","location":"badge-tabs.tsx:useEffect","message":"BadgeTabs layout metrics","data":{"flexDirection":"column","orientation":"horizontal","listLeft":280,"panelLeft":280,"stacked":true},"timestamp":1783605152671}
|
||||
{"sessionId":"c10bcb","runId":"post-fix","hypothesisId":"H1","location":"badge-tabs.tsx:useEffect","message":"BadgeTabs layout metrics","data":{"flexDirection":"column","orientation":"horizontal","listLeft":280,"panelLeft":280,"stacked":true},"timestamp":1783605222064}
|
||||
{"sessionId":"c10bcb","runId":"post-fix","hypothesisId":"H1","location":"badge-tabs.tsx:useEffect","message":"BadgeTabs layout metrics","data":{"flexDirection":"column","orientation":"horizontal","listLeft":280,"panelLeft":280,"stacked":true},"timestamp":1783605222067}
|
||||
{"sessionId":"c10bcb","runId":"post-fix","hypothesisId":"H1","location":"badge-tabs.tsx:useEffect","message":"BadgeTabs layout metrics","data":{"flexDirection":"column","orientation":"horizontal","listLeft":280,"panelLeft":280,"stacked":true},"timestamp":1783605819320}
|
||||
{"sessionId":"c10bcb","runId":"post-fix","hypothesisId":"H1","location":"badge-tabs.tsx:useEffect","message":"BadgeTabs layout metrics","data":{"flexDirection":"column","orientation":"horizontal","listLeft":280,"panelLeft":280,"stacked":true},"timestamp":1783605827145}
|
||||
{"sessionId":"c10bcb","runId":"post-fix","hypothesisId":"H1","location":"badge-tabs.tsx:useEffect","message":"BadgeTabs layout metrics","data":{"flexDirection":"column","orientation":"horizontal","listLeft":280,"panelLeft":280,"stacked":true},"timestamp":1783605864391}
|
||||
{"sessionId":"c10bcb","runId":"post-fix","hypothesisId":"H1","location":"badge-tabs.tsx:useEffect","message":"BadgeTabs layout metrics","data":{"flexDirection":"column","orientation":"horizontal","listLeft":280,"panelLeft":280,"stacked":true},"timestamp":1783606471768}
|
||||
{"sessionId":"c10bcb","runId":"post-fix","hypothesisId":"H1","location":"badge-tabs.tsx:useEffect","message":"BadgeTabs layout metrics","data":{"flexDirection":"column","orientation":"horizontal","listLeft":280,"panelLeft":280,"stacked":true},"timestamp":1783606472923}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import type { ComponentProps, ReactNode } from 'react'
|
||||
import { useEffect, useRef } from 'react'
|
||||
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@evobgp/ui/components/tabs'
|
||||
import { cn } from '@evobgp/ui/lib/utils'
|
||||
@@ -36,67 +35,34 @@ export function BadgeTabs({
|
||||
listClassName,
|
||||
contentClassName,
|
||||
}: BadgeTabsProps) {
|
||||
const tabsRef = useRef<HTMLDivElement>(null)
|
||||
|
||||
useEffect(() => {
|
||||
const el = tabsRef.current?.querySelector('[data-slot=tabs]')
|
||||
if (!el || !(el instanceof HTMLElement)) return
|
||||
const flexDirection = getComputedStyle(el).flexDirection
|
||||
const listRect = el.querySelector('[data-slot=tabs-list]')?.getBoundingClientRect()
|
||||
const panelRect = el.querySelector('[data-slot=tabs-content]')?.getBoundingClientRect()
|
||||
// #region agent log
|
||||
fetch('http://127.0.0.1:7311/ingest/6b35c3ae-1bcd-4c9c-81eb-f157c9347393', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json', 'X-Debug-Session-Id': 'c10bcb' },
|
||||
body: JSON.stringify({
|
||||
sessionId: 'c10bcb',
|
||||
runId: 'post-fix',
|
||||
hypothesisId: 'H1',
|
||||
location: 'badge-tabs.tsx:useEffect',
|
||||
message: 'BadgeTabs layout metrics',
|
||||
data: {
|
||||
flexDirection,
|
||||
orientation: el.getAttribute('data-orientation'),
|
||||
listLeft: listRect?.left,
|
||||
panelLeft: panelRect?.left,
|
||||
stacked: flexDirection === 'column',
|
||||
},
|
||||
timestamp: Date.now(),
|
||||
}),
|
||||
}).catch(() => {})
|
||||
// #endregion
|
||||
}, [value, defaultValue])
|
||||
|
||||
return (
|
||||
<div ref={tabsRef} className={cn('w-full', className)}>
|
||||
<Tabs
|
||||
value={value}
|
||||
defaultValue={defaultValue}
|
||||
onValueChange={onValueChange}
|
||||
className="w-full"
|
||||
<Tabs
|
||||
value={value}
|
||||
defaultValue={defaultValue}
|
||||
onValueChange={onValueChange}
|
||||
className={cn('w-full', className)}
|
||||
>
|
||||
<TabsList
|
||||
variant="line"
|
||||
className={cn(
|
||||
'mb-4 w-full justify-start gap-6',
|
||||
listClassName,
|
||||
)}
|
||||
>
|
||||
<TabsList
|
||||
variant="line"
|
||||
className={cn(
|
||||
'mb-4 w-full justify-start gap-6',
|
||||
listClassName,
|
||||
)}
|
||||
>
|
||||
{items.map((item) => (
|
||||
<TabsTrigger key={item.value} value={item.value} className="gap-2">
|
||||
{item.icon}
|
||||
{item.label}
|
||||
{item.count !== undefined ? (
|
||||
<Badge variant={item.badgeVariant ?? 'primary-light'} size="sm">
|
||||
{item.count}
|
||||
</Badge>
|
||||
) : null}
|
||||
</TabsTrigger>
|
||||
))}
|
||||
</TabsList>
|
||||
<div className={cn('w-full min-w-0', contentClassName)}>{children}</div>
|
||||
</Tabs>
|
||||
</div>
|
||||
{items.map((item) => (
|
||||
<TabsTrigger key={item.value} value={item.value} className="gap-2">
|
||||
{item.icon}
|
||||
{item.label}
|
||||
{item.count !== undefined ? (
|
||||
<Badge variant={item.badgeVariant ?? 'primary-light'} size="sm">
|
||||
{item.count}
|
||||
</Badge>
|
||||
) : null}
|
||||
</TabsTrigger>
|
||||
))}
|
||||
</TabsList>
|
||||
<div className={cn('w-full min-w-0', contentClassName)}>{children}</div>
|
||||
</Tabs>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@ import { Link } from '@tanstack/react-router'
|
||||
import { ChevronRight } from 'lucide-react'
|
||||
import type { ReactNode } from 'react'
|
||||
|
||||
import { Frame, FramePanel } from '@/components/reui/frame'
|
||||
import { CardDotField } from '@/components/dashboard/card-dot-field'
|
||||
import { FramePanel } from '@/components/reui/frame'
|
||||
import { cn } from '@evobgp/ui/lib/utils'
|
||||
import { Item, ItemMedia } from '@evobgp/ui/components/item'
|
||||
|
||||
@@ -25,46 +25,44 @@ export function DashboardQuickLinkCard({
|
||||
iconClass,
|
||||
}: QuickLinkCardProps) {
|
||||
return (
|
||||
<Frame className="h-full">
|
||||
<Link
|
||||
to={to}
|
||||
search={search}
|
||||
className="group block h-full rounded-[inherit] focus-visible:outline-none"
|
||||
aria-label={`${label}: ${description}`}
|
||||
<Link
|
||||
to={to}
|
||||
search={search}
|
||||
className="group block h-full rounded-[inherit] focus-visible:outline-none"
|
||||
aria-label={`${label}: ${description}`}
|
||||
>
|
||||
<FramePanel
|
||||
className={cn(
|
||||
'relative isolate h-full overflow-hidden transition-colors',
|
||||
'hover:border-foreground/20',
|
||||
'group-focus-visible:ring-2 group-focus-visible:ring-ring group-focus-visible:ring-offset-2 group-focus-visible:ring-offset-background',
|
||||
)}
|
||||
>
|
||||
<FramePanel
|
||||
className={cn(
|
||||
'relative isolate h-full overflow-hidden transition-colors',
|
||||
'hover:border-foreground/20',
|
||||
'group-focus-visible:ring-2 group-focus-visible:ring-ring group-focus-visible:ring-offset-2 group-focus-visible:ring-offset-background',
|
||||
)}
|
||||
>
|
||||
<CardDotField className="text-muted-foreground [mask-image:linear-gradient(to_bottom_left,black,transparent_60%)]" />
|
||||
<div className="relative z-10 flex h-full flex-col gap-7.5">
|
||||
<Item
|
||||
className={cn(
|
||||
'border-background flex size-11 shrink-0 items-center justify-center border-2 p-0 shadow-[0_1px_3px_0_rgba(0,0,0,0.14)] dark:border [&_svg]:size-5',
|
||||
iconClass,
|
||||
)}
|
||||
>
|
||||
<ItemMedia variant="icon" className="size-auto">
|
||||
{icon}
|
||||
</ItemMedia>
|
||||
</Item>
|
||||
<div className="mt-auto flex flex-col gap-3">
|
||||
<span className="text-foreground block text-sm leading-tight font-medium">{label}</span>
|
||||
<p className="text-muted-foreground text-xs leading-relaxed">{description}</p>
|
||||
<span className="text-primary inline-flex items-center gap-1 text-xs font-medium underline-offset-2 group-hover:underline">
|
||||
Перейти
|
||||
<ChevronRight
|
||||
aria-hidden
|
||||
className="size-2.5 shrink-0 transition-transform group-hover:translate-x-0.5"
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
<CardDotField className="text-muted-foreground [mask-image:linear-gradient(to_bottom_left,black,transparent_60%)]" />
|
||||
<div className="relative z-10 flex h-full flex-col gap-7.5">
|
||||
<Item
|
||||
className={cn(
|
||||
'border-background flex size-11 shrink-0 items-center justify-center border-2 p-0 shadow-[0_1px_3px_0_rgba(0,0,0,0.14)] dark:border [&_svg]:size-5',
|
||||
iconClass,
|
||||
)}
|
||||
>
|
||||
<ItemMedia variant="icon" className="size-auto">
|
||||
{icon}
|
||||
</ItemMedia>
|
||||
</Item>
|
||||
<div className="mt-auto flex flex-col gap-3">
|
||||
<span className="text-foreground block text-sm leading-tight font-medium">{label}</span>
|
||||
<p className="text-muted-foreground text-xs leading-relaxed">{description}</p>
|
||||
<span className="text-primary inline-flex items-center gap-1 text-xs font-medium underline-offset-2 group-hover:underline">
|
||||
Перейти
|
||||
<ChevronRight
|
||||
aria-hidden
|
||||
className="size-2.5 shrink-0 transition-transform group-hover:translate-x-0.5"
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
</FramePanel>
|
||||
</Link>
|
||||
</Frame>
|
||||
</div>
|
||||
</FramePanel>
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -2,6 +2,12 @@ import { Gauge, Network, Play, Plus, Share2, Tags } from 'lucide-react'
|
||||
import type { ReactNode } from 'react'
|
||||
|
||||
import { DashboardQuickLinkCard } from '@/components/dashboard/dashboard-quick-link-card'
|
||||
import {
|
||||
Frame,
|
||||
FrameDescription,
|
||||
FrameHeader,
|
||||
FrameTitle,
|
||||
} from '@/components/reui/frame'
|
||||
|
||||
type QuickLink = {
|
||||
icon: ReactNode
|
||||
@@ -63,12 +69,16 @@ const LINKS: QuickLink[] = [
|
||||
|
||||
export function DashboardQuickLinks() {
|
||||
return (
|
||||
<div className="@container w-full">
|
||||
<div className="grid gap-3 sm:grid-cols-2 @2xl:grid-cols-3">
|
||||
<Frame className="@container w-full">
|
||||
<FrameHeader>
|
||||
<FrameTitle>Быстрые действия</FrameTitle>
|
||||
<FrameDescription>Частые переходы к настройке и деплою</FrameDescription>
|
||||
</FrameHeader>
|
||||
<div className="grid gap-1 sm:grid-cols-2 xl:grid-cols-3">
|
||||
{LINKS.map((link) => (
|
||||
<DashboardQuickLinkCard key={link.label} {...link} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</Frame>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -85,6 +85,8 @@ function DashboardComponent() {
|
||||
}
|
||||
/>
|
||||
|
||||
<DashboardQuickLinks />
|
||||
|
||||
{initialLoading ? (
|
||||
<AnalyticsDashboardSkeleton />
|
||||
) : (
|
||||
@@ -142,14 +144,6 @@ function DashboardComponent() {
|
||||
)}
|
||||
</DashboardFramePanel>
|
||||
</div>
|
||||
|
||||
<DashboardFramePanel
|
||||
title="Быстрые действия"
|
||||
description="Частые переходы к настройке и деплою"
|
||||
contentClassName="p-4"
|
||||
>
|
||||
<DashboardQuickLinks />
|
||||
</DashboardFramePanel>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user