release: 1.1.0
TallyNote release / linux-x64 (push) Successful in 6m24s

This commit is contained in:
Qiufeng
2026-08-31 20:11:34 +08:00
parent cbe0d65320
commit 12495fb6a4
66 changed files with 4315 additions and 77 deletions
+52
View File
@@ -0,0 +1,52 @@
import type { ReactNode } from "react";
import { AlertCircle, Loader2 } from "lucide-react";
import { Alert, Button, Card, Loading, Space, Tag } from "tdesign-react";
export function Page({ eyebrow, title, subtitle, actions, children, className = "" }: {
eyebrow?: string;
title: string;
subtitle?: string;
actions?: ReactNode;
children: ReactNode;
className?: string;
}) {
return <section className={`tn-page ${className}`}>
<div className="tn-page-head">
<div>{eyebrow && <div className="tn-eyebrow">{eyebrow}</div>}<h1 className="tn-page-title">{title}</h1>{subtitle && <p className="tn-page-subtitle">{subtitle}</p>}</div>
{actions && <div className="tn-page-actions">{actions}</div>}
</div>
{children}
</section>;
}
export function AsyncState({ loading, error, empty, onRetry, children }: {
loading: boolean;
error?: string;
empty?: ReactNode;
onRetry?: () => void;
children: ReactNode;
}) {
if (loading && empty) return <div className="tn-empty" role="status" aria-live="polite"><Loading text="加载中…" /></div>;
if (error && empty) return <div className="tn-empty" role="alert"><AlertCircle size={28} /><p>{error}</p>{onRetry && <Button variant="outline" onClick={onRetry}>重试</Button>}</div>;
return <>
{loading && <div className="tn-inline-loading" role="status"><Loader2 size={15} className="tn-spin" aria-hidden="true" />正在更新…</div>}
{error && <ErrorBanner message={error} onRetry={onRetry} />}
{empty || children}
</>;
}
export function ErrorBanner({ message, onRetry }: { message: string; onRetry?: () => void }) {
return <div role="alert"><Alert theme="error" className="tn-alert" icon={<AlertCircle size={16} />} message={<Space size="small"><span>{message}</span>{onRetry && <Button variant="text" onClick={onRetry}>重试</Button>}</Space>} /></div>;
}
export function BusyIcon() { return <Loader2 size={16} className="tn-spin" aria-hidden="true" />; }
export function StatusTag({ status }: { status: "unreimbursed" | "reimbursed" | "active" | "disabled" }) {
const labels = { unreimbursed: "未报销", reimbursed: "已报销", active: "有效", disabled: "已停用" } as const;
const themes = { unreimbursed: "warning", reimbursed: "success", active: "success", disabled: "default" } as const;
return <Tag theme={themes[status]} variant="light-outline">{labels[status]}</Tag>;
}
export function Surface({ children, className = "" }: { children: ReactNode; className?: string }) {
return <Card bordered className={`tn-flat-section ${className}`}>{children}</Card>;
}