fix(ui): центрировать EmptyState по empty-state-12
ResourcePage оборачивает пустой список в Frame; в панелях — centered=false. Co-authored-by: Cursor <[email protected]>
This commit is contained in:
@@ -87,6 +87,7 @@ export function DomainBindingsCard({ bindings }: DomainBindingsCardProps) {
|
|||||||
icon={Link2Icon}
|
icon={Link2Icon}
|
||||||
title="Нет привязок"
|
title="Нет привязок"
|
||||||
description="Привяжите сервис к поддомену"
|
description="Привяжите сервис к поддомену"
|
||||||
|
centered={false}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<ItemGroup className="gap-0">
|
<ItemGroup className="gap-0">
|
||||||
|
|||||||
@@ -243,6 +243,7 @@ export function DomainAvailabilityPanel({
|
|||||||
icon={ActivityIcon}
|
icon={ActivityIcon}
|
||||||
title="Мониторы не настроены"
|
title="Мониторы не настроены"
|
||||||
description="Добавьте hostname для проверки доступности"
|
description="Добавьте hostname для проверки доступности"
|
||||||
|
centered={false}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<ItemGroup className="gap-0">
|
<ItemGroup className="gap-0">
|
||||||
|
|||||||
@@ -18,8 +18,18 @@ interface EmptyStateProps {
|
|||||||
className?: string
|
className?: string
|
||||||
/** Use IconStack media (empty-state-12). Default true. */
|
/** Use IconStack media (empty-state-12). Default true. */
|
||||||
stackedIcon?: boolean
|
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 — adapted from empty-state-12.
|
||||||
|
* @see https://reui.io/preview/base/empty-state-12
|
||||||
|
*/
|
||||||
export function EmptyState({
|
export function EmptyState({
|
||||||
icon: Icon = InboxIcon,
|
icon: Icon = InboxIcon,
|
||||||
title,
|
title,
|
||||||
@@ -27,9 +37,15 @@ export function EmptyState({
|
|||||||
action,
|
action,
|
||||||
className,
|
className,
|
||||||
stackedIcon = true,
|
stackedIcon = true,
|
||||||
|
centered = true,
|
||||||
}: EmptyStateProps) {
|
}: EmptyStateProps) {
|
||||||
return (
|
const body = (
|
||||||
<Empty className={cn('max-w-md flex-none border-0 bg-transparent p-0', className)}>
|
<Empty
|
||||||
|
className={cn(
|
||||||
|
'max-w-md flex-none border-0 bg-transparent p-0',
|
||||||
|
!centered && className,
|
||||||
|
)}
|
||||||
|
>
|
||||||
<EmptyHeader className="gap-5 text-center">
|
<EmptyHeader className="gap-5 text-center">
|
||||||
<EmptyMedia className="mb-0">
|
<EmptyMedia className="mb-0">
|
||||||
{stackedIcon ? (
|
{stackedIcon ? (
|
||||||
@@ -53,7 +69,24 @@ export function EmptyState({
|
|||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
</EmptyHeader>
|
</EmptyHeader>
|
||||||
{action ? <EmptyContent>{action}</EmptyContent> : null}
|
{action ? (
|
||||||
|
<EmptyContent className="mt-1 items-center justify-center">
|
||||||
|
{action}
|
||||||
|
</EmptyContent>
|
||||||
|
) : null}
|
||||||
</Empty>
|
</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>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ export function HealthTimeline({ events }: HealthTimelineProps) {
|
|||||||
icon={Link2Icon}
|
icon={Link2Icon}
|
||||||
title="Нет событий"
|
title="Нет событий"
|
||||||
description="Результаты проверок появятся после первого прогона"
|
description="Результаты проверок появятся после первого прогона"
|
||||||
|
centered={false}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -242,11 +242,32 @@ export function ResourcePage<T extends object>({
|
|||||||
|
|
||||||
if (data.length === 0 && emptyState) {
|
if (data.length === 0 && emptyState) {
|
||||||
return (
|
return (
|
||||||
<EmptyState
|
<Frame dense variant="default" spacing="sm" className="w-full">
|
||||||
title={emptyState.title}
|
{!hideHeader ? (
|
||||||
description={emptyState.description}
|
<FrameHeader className="flex-row items-start justify-between gap-3">
|
||||||
action={emptyState.action}
|
<div className="flex min-w-0 flex-col gap-px">
|
||||||
/>
|
<FrameTitle className="text-balance">{title}</FrameTitle>
|
||||||
|
{description ? (
|
||||||
|
<FrameDescription className="text-xs text-pretty">
|
||||||
|
{description}
|
||||||
|
</FrameDescription>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
{primaryAction ? (
|
||||||
|
<div className="flex shrink-0 flex-wrap items-center justify-end gap-2">
|
||||||
|
{primaryAction}
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
</FrameHeader>
|
||||||
|
) : null}
|
||||||
|
<FramePanel className="flex min-h-[min(28rem,55svh)] w-full flex-col items-stretch justify-center p-0">
|
||||||
|
<EmptyState
|
||||||
|
title={emptyState.title}
|
||||||
|
description={emptyState.description}
|
||||||
|
action={emptyState.action}
|
||||||
|
/>
|
||||||
|
</FramePanel>
|
||||||
|
</Frame>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -509,6 +509,7 @@ export function ServiceEditSheet({
|
|||||||
icon={Link2Icon}
|
icon={Link2Icon}
|
||||||
title="Нет привязок"
|
title="Нет привязок"
|
||||||
description="Необязательно. Пример: newdom.ivx.su — зона ivx.su определится автоматически."
|
description="Необязательно. Пример: newdom.ivx.su — зона ivx.su определится автоматически."
|
||||||
|
centered={false}
|
||||||
action={
|
action={
|
||||||
<Button type="button" variant="outline" size="sm" onClick={handleAddBinding}>
|
<Button type="button" variant="outline" size="sm" onClick={handleAddBinding}>
|
||||||
<PlusIcon data-icon="inline-start" />
|
<PlusIcon data-icon="inline-start" />
|
||||||
|
|||||||
Reference in New Issue
Block a user