Docker images / prepare-release (push) Successful in 10s
Docker images / backend-test (push) Successful in 2m13s
Docker images / frontend-image (push) Successful in 3m14s
Docker images / updater-image (push) Successful in 47s
Docker images / backend-image (push) Successful in 2m44s
Docker images / notify-webhook (push) Skipped
Docker images / publish-release (push) Successful in 14s
Co-authored-by: Cursor <[email protected]>
94 lines
3.0 KiB
TypeScript
94 lines
3.0 KiB
TypeScript
"use client"
|
|
|
|
import type { ReactNode } from "react"
|
|
import type { LucideIcon } from "lucide-react"
|
|
import { Badge } from "@/components/reui/badge"
|
|
import {
|
|
Frame,
|
|
FrameHeader,
|
|
FramePanel,
|
|
FrameTitle,
|
|
} from "@/components/reui/frame"
|
|
import { IconTile } from "@/components/reui/icon-tile"
|
|
import { cn } from "@/lib/utils"
|
|
|
|
/**
|
|
* Sibling Frame columns for dashboard attention queue.
|
|
* Preview: https://reui.io/preview/base/dashboard-1 · https://reui.io/preview/base/stats-12
|
|
* Docs: https://reui.io/docs/components/base/frame · https://reui.io/docs/components/base/icon-tile · https://reui.io/docs/components/base/badge
|
|
*/
|
|
export interface AttentionQueueColumn {
|
|
id: string
|
|
title: string
|
|
icon: LucideIcon
|
|
iconClassName?: string
|
|
count: number
|
|
countVariant?: "destructive" | "warning" | "secondary" | "destructive-light" | "warning-light"
|
|
emptyTitle: string
|
|
emptyDescription: string
|
|
emptyAction?: ReactNode
|
|
children: ReactNode
|
|
}
|
|
|
|
interface AttentionQueueProps {
|
|
columns: AttentionQueueColumn[]
|
|
className?: string
|
|
}
|
|
|
|
const DEFAULT_ICON_CLASS = "text-muted-foreground [&_svg]:text-current"
|
|
|
|
export function AttentionQueue({ columns, className }: AttentionQueueProps) {
|
|
return (
|
|
<div
|
|
className={cn(
|
|
"@container grid min-w-0 items-start gap-2 @3xl:grid-cols-3",
|
|
className,
|
|
)}
|
|
>
|
|
{columns.map((column) => {
|
|
const Icon = column.icon
|
|
const isEmpty = column.count === 0
|
|
return (
|
|
<Frame key={column.id} dense spacing="sm" className="min-w-0 w-full">
|
|
<FrameHeader>
|
|
<div className="flex min-w-0 items-center gap-2">
|
|
<IconTile
|
|
variant="elevated"
|
|
size="sm"
|
|
className={cn(DEFAULT_ICON_CLASS, column.iconClassName)}
|
|
aria-hidden="true"
|
|
>
|
|
<Icon />
|
|
</IconTile>
|
|
<FrameTitle className="min-w-0 truncate">{column.title}</FrameTitle>
|
|
{column.count > 0 ? (
|
|
<Badge
|
|
size="sm"
|
|
variant={column.countVariant ?? "secondary"}
|
|
className="tabular-nums"
|
|
>
|
|
{column.count}
|
|
</Badge>
|
|
) : null}
|
|
</div>
|
|
</FrameHeader>
|
|
<FramePanel className="min-w-0">
|
|
{isEmpty ? (
|
|
<div className="flex min-h-24 flex-col items-start justify-center gap-1 py-3">
|
|
<p className="text-sm font-medium">{column.emptyTitle}</p>
|
|
<p className="text-muted-foreground text-xs leading-relaxed">
|
|
{column.emptyDescription}
|
|
</p>
|
|
{column.emptyAction ? <div className="pt-1">{column.emptyAction}</div> : null}
|
|
</div>
|
|
) : (
|
|
column.children
|
|
)}
|
|
</FramePanel>
|
|
</Frame>
|
|
)
|
|
})}
|
|
</div>
|
|
)
|
|
}
|