main
tsx 119 lines 2.38 KB
Raw
1 import type { ReactNode } from "react";
2
3 type Tone = "neutral" | "positive" | "negative" | "warning" | "info";
4
5 export function Card({
6 children,
7 className = "",
8 as: Component = "section"
9 }: {
10 children: ReactNode;
11 className?: string;
12 as?: "section" | "article" | "div";
13 }) {
14 return <Component className={`card ${className}`}>{children}</Component>;
15 }
16
17 export function Badge({ children, tone = "neutral" }: { children: ReactNode; tone?: Tone }) {
18 return <span className={`badge badge-${tone}`}>{children}</span>;
19 }
20
21 export function Button({
22 children,
23 variant = "primary",
24 className = "",
25 ...props
26 }: React.ButtonHTMLAttributes<HTMLButtonElement> & {
27 variant?: "primary" | "secondary" | "ghost" | "danger";
28 }) {
29 return (
30 <button className={`button button-${variant} ${className}`} {...props}>
31 {children}
32 </button>
33 );
34 }
35
36 export function Field({
37 label,
38 children,
39 hint
40 }: {
41 label: string;
42 children: ReactNode;
43 hint?: string;
44 }) {
45 return (
46 <label className="field">
47 <span>{label}</span>
48 {children}
49 {hint ? <small>{hint}</small> : null}
50 </label>
51 );
52 }
53
54 export function MetricCard({
55 label,
56 value,
57 meta,
58 tone = "neutral"
59 }: {
60 label: string;
61 value: string;
62 meta?: string;
63 tone?: Tone;
64 }) {
65 return (
66 <Card className="metric-card" as="article">
67 <span className="metric-label">{label}</span>
68 <strong className={`metric-value metric-${tone}`}>{value}</strong>
69 {meta ? <span className="metric-meta">{meta}</span> : null}
70 </Card>
71 );
72 }
73
74 export function Skeleton({ rows = 1 }: { rows?: number }) {
75 return (
76 <div className="skeleton-stack" aria-label="Loading">
77 {Array.from({ length: rows }, (_, index) => (
78 <div className="skeleton-line" key={index} />
79 ))}
80 </div>
81 );
82 }
83
84 export function EmptyState({
85 title,
86 message,
87 action
88 }: {
89 title: string;
90 message: string;
91 action?: ReactNode;
92 }) {
93 return (
94 <div className="empty-state">
95 <h3>{title}</h3>
96 <p>{message}</p>
97 {action}
98 </div>
99 );
100 }
101
102 export function ErrorState({
103 message,
104 correlationId,
105 action
106 }: {
107 message: string;
108 correlationId?: string;
109 action?: ReactNode;
110 }) {
111 return (
112 <div className="error-state" role="alert">
113 <h3>Unable to load data</h3>
114 <p>{message}</p>
115 {correlationId ? <small>Correlation ID: {correlationId}</small> : null}
116 {action}
117 </div>
118 );
119 }