89 lines
3.0 KiB
TypeScript
89 lines
3.0 KiB
TypeScript
import type { ReactNode } from "react";
|
|
import { IconKitSvg } from "./IconKitSvg";
|
|
import "./loading.css";
|
|
|
|
export function SystemLoading({
|
|
variant = "page",
|
|
state = "loading",
|
|
title = "正在加载",
|
|
description = "正在同步最新数据,请稍候。",
|
|
icon = "activity",
|
|
reference,
|
|
actions,
|
|
}: {
|
|
variant?: "inline" | "page" | "fullscreen";
|
|
state?: "loading" | "error";
|
|
title?: string;
|
|
description?: string;
|
|
icon?: string;
|
|
reference?: string;
|
|
actions?: ReactNode;
|
|
}) {
|
|
return (
|
|
<div className={`system-loading system-loading--${variant}${state === "error" ? " is-error" : ""}`} role={state === "error" ? "alert" : "status"} aria-live="polite">
|
|
<div className="system-loading-card">
|
|
<span className="system-loading-corner corner-tl" aria-hidden="true">+</span>
|
|
<span className="system-loading-corner corner-tr" aria-hidden="true">+</span>
|
|
<span className="system-loading-corner corner-bl" aria-hidden="true">+</span>
|
|
<span className="system-loading-corner corner-br" aria-hidden="true">+</span>
|
|
|
|
<div className="system-loading-meta mono">
|
|
<span>[ AIRSHELF / SYSTEM ]</span>
|
|
<span>{state === "error" ? "[ CONNECTION ERROR ]" : "[ SYNCING ]"}</span>
|
|
</div>
|
|
|
|
<div className="system-loading-main">
|
|
<div className="system-loading-icon" aria-hidden="true">
|
|
<IconKitSvg name={icon} size={20} />
|
|
{state === "loading" && <span className="system-loading-orbit" />}
|
|
</div>
|
|
<div className="system-loading-copy">
|
|
<h2>{title}</h2>
|
|
<p>{description}</p>
|
|
</div>
|
|
</div>
|
|
|
|
{actions ? (
|
|
<div className="system-loading-actions">{actions}</div>
|
|
) : state === "loading" ? (
|
|
<div className="system-loading-progress" aria-hidden="true">
|
|
<span /><span /><span /><span /><span />
|
|
</div>
|
|
) : null}
|
|
|
|
<div className="system-loading-foot mono">
|
|
<span>{reference ? `// REF · ${reference}` : "// DATA SYNC"}</span>
|
|
<span>{state === "error" ? "RETRY AVAILABLE" : "DATA HYDRATION"}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
// 列表数据在途时的骨架屏(grid 卡片 / 行),替代「为空」状态,让用户看到「加载中」而非「没数据」。
|
|
export function SkeletonGrid({ count = 8 }: { count?: number }) {
|
|
return (
|
|
<div>
|
|
<div className="skeleton-loading-tag">// 加载中…</div>
|
|
<div className="skeleton-grid">
|
|
{Array.from({ length: count }).map((_, i) => (
|
|
<div className="skeleton-block skeleton-card" key={i} />
|
|
))}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export function SkeletonRows({ count = 6 }: { count?: number }) {
|
|
return (
|
|
<div>
|
|
<div className="skeleton-loading-tag">// 加载中…</div>
|
|
<div className="skeleton-rows">
|
|
{Array.from({ length: count }).map((_, i) => (
|
|
<div className="skeleton-block skeleton-line" key={i} />
|
|
))}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|