CD / update-wiki (push) Successful in 6s
quality / commitlint (push) Skipped
quality / changes (push) Successful in 11s
quality / api (push) Skipped
quality / docker-check (push) Skipped
quality / web (push) Failing after 52s
CD / quality (push) Failing after 1m5s
CD / publish (push) Skipped
Подключить stats-12, card-12, settings-3 и empty-state-12 к оболочке, спискам, карточкам и настройкам, чтобы метрики и пустые состояния шли из одних блоков. Co-authored-by: Cursor <[email protected]>
81 lines
2.6 KiB
TypeScript
81 lines
2.6 KiB
TypeScript
import type { ReactNode } from 'react'
|
|
import { Link } from '@tanstack/react-router'
|
|
import {
|
|
Frame,
|
|
FrameDescription,
|
|
FrameHeader,
|
|
FramePanel,
|
|
FrameTitle,
|
|
} from '@/components/reui/frame'
|
|
import { Badge } from '@/components/reui/badge'
|
|
import { cn } from '@cfdm/ui/lib/utils'
|
|
|
|
export interface QuickActionItem {
|
|
id: string
|
|
title: string
|
|
description: string
|
|
to: string
|
|
search?: Record<string, unknown>
|
|
icon?: ReactNode
|
|
iconClassName?: string
|
|
badgeVariant?: 'info-light' | 'warning-light'
|
|
}
|
|
|
|
interface QuickActionGridProps {
|
|
actions: QuickActionItem[]
|
|
title?: string
|
|
description?: string
|
|
className?: string
|
|
}
|
|
|
|
/**
|
|
* Quick links from card-12, with the artboard title, description and «Перейти».
|
|
* Preview: https://reui.io/preview/base/card-12
|
|
* Docs: https://reui.io/blocks/application/card/card-12
|
|
*/
|
|
export function QuickActionGrid({
|
|
actions,
|
|
title = 'Быстрые действия',
|
|
description,
|
|
className,
|
|
}: QuickActionGridProps) {
|
|
if (actions.length === 0) return null
|
|
|
|
return (
|
|
<Frame className={cn('w-full', className)}>
|
|
{title || description ? (
|
|
<FrameHeader>
|
|
{title ? <FrameTitle>{title}</FrameTitle> : null}
|
|
{description ? <FrameDescription>{description}</FrameDescription> : null}
|
|
</FrameHeader>
|
|
) : null}
|
|
<div className="grid gap-1 sm:grid-cols-2 lg:grid-cols-3">
|
|
{actions.map((action) => (
|
|
<Link
|
|
key={action.id}
|
|
to={action.to}
|
|
search={action.search}
|
|
aria-label={`${action.title}: ${action.description}`}
|
|
className="group/card focus-visible:ring-ring/50 hover:[&_svg]:text-primary block h-full min-w-0 cursor-pointer rounded-(--frame-radius) outline-none focus-visible:ring-[3px] [&_svg]:size-5 [&_svg]:opacity-60 hover:[&_svg]:opacity-100"
|
|
>
|
|
<FramePanel className="flex h-full flex-col gap-3 p-3.5">
|
|
<span className={cn('[&_svg]:size-5', action.iconClassName)} aria-hidden="true">
|
|
{action.icon}
|
|
</span>
|
|
<div className="flex items-start justify-between gap-2">
|
|
<h3 className="group-hover/card:text-primary text-sm leading-relaxed transition-colors">
|
|
{action.title}
|
|
</h3>
|
|
<Badge variant={action.badgeVariant ?? 'info-light'} size="sm">
|
|
Перейти
|
|
</Badge>
|
|
</div>
|
|
<p className="text-muted-foreground text-xs leading-relaxed">{action.description}</p>
|
|
</FramePanel>
|
|
</Link>
|
|
))}
|
|
</div>
|
|
</Frame>
|
|
)
|
|
}
|