1.AI 协作文档体系重构(objections/worklines/contracts+matrix.md) 2.coord 仲裁文档(final-decisions/cross-review/final-rulings/orchestration) 3.各服务 01/02 文档补全 4.共享包初始化(shared-ts/shared-go/hooks/ui-components/ui-tokens) 5.Proto 契约补全 6.004 架构影响地图更新 7.端口分配表 8.设计规格文档
73 lines
1.8 KiB
TypeScript
73 lines
1.8 KiB
TypeScript
import type { ReactNode } from "react";
|
||
import { cn } from "./utils/cn.js";
|
||
|
||
/**
|
||
* Empty - 空态展示(插画占位 + 文案 + CTA)
|
||
*
|
||
* 用途:数据列表为空时展示友好提示,引导用户操作。
|
||
*
|
||
* @example
|
||
* <Empty title="暂无班级" description="点击下方按钮创建第一个班级" action={<Button>新建班级</Button>} />
|
||
*/
|
||
|
||
export interface EmptyProps {
|
||
/** 标题(必填) */
|
||
title: string;
|
||
/** 描述文案 */
|
||
description?: string;
|
||
/** 行动按钮(CTA) */
|
||
action?: ReactNode;
|
||
/** 自定义插画;未提供时使用默认占位 */
|
||
illustration?: ReactNode;
|
||
/** 自定义类名 */
|
||
className?: string;
|
||
}
|
||
|
||
export function Empty({
|
||
title,
|
||
description,
|
||
action,
|
||
illustration,
|
||
className,
|
||
}: EmptyProps): ReactNode {
|
||
return (
|
||
<div
|
||
role="status"
|
||
className={cn(
|
||
"flex flex-col items-center justify-center gap-4 p-8 text-center",
|
||
className,
|
||
)}
|
||
>
|
||
{illustration ?? <DefaultIllustration />}
|
||
<div className="flex flex-col gap-1">
|
||
<h3 className="text-base font-medium">{title}</h3>
|
||
{description ? (
|
||
<p className="text-sm text-muted-foreground">{description}</p>
|
||
) : null}
|
||
</div>
|
||
{action ?? null}
|
||
</div>
|
||
);
|
||
}
|
||
|
||
function DefaultIllustration(): ReactNode {
|
||
return (
|
||
<div
|
||
className="flex h-16 w-16 items-center justify-center rounded-full bg-muted"
|
||
aria-hidden="true"
|
||
>
|
||
<svg
|
||
xmlns="http://www.w3.org/2000/svg"
|
||
viewBox="0 0 24 24"
|
||
fill="none"
|
||
stroke="currentColor"
|
||
strokeWidth="1.5"
|
||
className="h-8 w-8 text-muted-foreground"
|
||
>
|
||
<path d="M9 17a2 2 0 002 2h2a2 2 0 002-2M5 9h14l-1 8H6L5 9z" />
|
||
<path d="M9 9V5a3 3 0 016 0v4" />
|
||
</svg>
|
||
</div>
|
||
);
|
||
}
|