quality / changes (push) Successful in 8s
quality / api (push) Skipped
quality / commitlint (push) Skipped
quality / docker-check (push) Skipped
CD / update-wiki (push) Successful in 5s
quality / web (push) Successful in 1m6s
CD / quality (push) Successful in 1m21s
CD / publish (push) Successful in 2m36s
Соседние Frame-колонки как в EvoBGP вместо вложенной оболочки; compact empty через IconTile elevated; счётчики ReUI Badge. Co-authored-by: Cursor <cursoragent@cursor.com>
103 lines
2.6 KiB
TypeScript
103 lines
2.6 KiB
TypeScript
import { InboxIcon, type LucideIcon } from 'lucide-react'
|
|
import { IconStack } from '@/components/reui/icon-stack'
|
|
import { IconTile } from '@/components/reui/icon-tile'
|
|
import {
|
|
Empty,
|
|
EmptyContent,
|
|
EmptyDescription,
|
|
EmptyHeader,
|
|
EmptyMedia,
|
|
EmptyTitle,
|
|
} from '@cfdm/ui/components/empty'
|
|
import { cn } from '@cfdm/ui/lib/utils'
|
|
|
|
interface EmptyStateProps {
|
|
icon?: LucideIcon
|
|
title: string
|
|
description?: string
|
|
action?: React.ReactNode
|
|
className?: string
|
|
/** Use IconStack media (empty-state-12). Default true. */
|
|
stackedIcon?: boolean
|
|
/**
|
|
* Center in available width/height (empty-state-12).
|
|
* Preview: https://reui.io/preview/base/empty-state-12
|
|
* Set false for tight panels/sheets.
|
|
*/
|
|
centered?: boolean
|
|
}
|
|
|
|
/**
|
|
* ReUI Empty + IconStack — Frame-friendly empty-state-14 / empty-state-12.
|
|
* Preview: https://reui.io/preview/base/empty-state-14 · https://reui.io/preview/base/empty-state-12
|
|
*/
|
|
export function EmptyState({
|
|
icon: Icon = InboxIcon,
|
|
title,
|
|
description,
|
|
action,
|
|
className,
|
|
stackedIcon = true,
|
|
centered = true,
|
|
}: EmptyStateProps) {
|
|
const body = (
|
|
<Empty
|
|
className={cn(
|
|
'max-w-md flex-none border-0 bg-transparent p-0',
|
|
!centered && className,
|
|
)}
|
|
>
|
|
<EmptyHeader className={cn('text-center', stackedIcon ? 'gap-5' : 'gap-3')}>
|
|
<EmptyMedia className="mb-0">
|
|
{stackedIcon ? (
|
|
<IconStack aria-hidden="true" className="h-14 w-12">
|
|
<Icon strokeWidth={1.9} aria-hidden="true" className="size-5" />
|
|
</IconStack>
|
|
) : (
|
|
<IconTile
|
|
variant="elevated"
|
|
className="size-10.5 text-muted-foreground"
|
|
aria-hidden="true"
|
|
>
|
|
<Icon />
|
|
</IconTile>
|
|
)}
|
|
</EmptyMedia>
|
|
<div className="flex flex-col items-center gap-2">
|
|
<EmptyTitle
|
|
className={cn(
|
|
'font-semibold tracking-tight',
|
|
stackedIcon ? 'text-base' : 'text-sm',
|
|
)}
|
|
>
|
|
{title}
|
|
</EmptyTitle>
|
|
{description ? (
|
|
<EmptyDescription className="max-w-sm text-sm/relaxed">
|
|
{description}
|
|
</EmptyDescription>
|
|
) : null}
|
|
</div>
|
|
</EmptyHeader>
|
|
{action ? (
|
|
<EmptyContent className="mt-1 items-center justify-center">
|
|
{action}
|
|
</EmptyContent>
|
|
) : null}
|
|
</Empty>
|
|
)
|
|
|
|
if (!centered) return body
|
|
|
|
return (
|
|
<div
|
|
className={cn(
|
|
'flex w-full flex-1 items-center justify-center py-14 sm:py-16',
|
|
className,
|
|
)}
|
|
>
|
|
{body}
|
|
</div>
|
|
)
|
|
}
|