-
Notifications
You must be signed in to change notification settings - Fork 388
Expand file tree
/
Copy pathstate-screen.tsx
More file actions
109 lines (102 loc) · 3.07 KB
/
Copy pathstate-screen.tsx
File metadata and controls
109 lines (102 loc) · 3.07 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
import type React from "react";
import type { LucideIcon } from "lucide-react";
import { cn } from "@App/pkg/utils/cn";
import { Surface } from "./surface";
type StateScreenTone = "primary" | "muted" | "success" | "warning" | "error";
type StateScreenVariant = "plain" | "card";
const toneClass: Record<StateScreenTone, { ring: string; icon: string; detail?: string }> = {
primary: { ring: "bg-primary-light", icon: "text-primary" },
muted: { ring: "bg-muted", icon: "text-muted-foreground" },
success: { ring: "bg-success-bg", icon: "text-success-fg" },
warning: { ring: "bg-warning-bg", icon: "text-warning-fg" },
error: {
ring: "bg-destructive/10",
icon: "text-destructive",
detail: "border-destructive/30 bg-destructive/5 text-destructive",
},
};
type StateScreenProps = React.HTMLAttributes<HTMLDivElement> & {
icon?: LucideIcon;
title: React.ReactNode;
description?: React.ReactNode;
action?: React.ReactNode;
detail?: React.ReactNode;
detailTestId?: string;
detailClassName?: string;
progress?: React.ReactNode;
tone?: StateScreenTone;
variant?: StateScreenVariant;
compact?: boolean;
iconClassName?: string;
};
function StateScreen({
icon: Icon,
title,
description,
action,
detail,
detailTestId,
detailClassName,
progress,
tone = "muted",
variant = "plain",
compact = false,
iconClassName,
className,
...props
}: StateScreenProps) {
const toneStyle = toneClass[tone];
const body = (
<div
role="status"
aria-label={typeof title === "string" ? title : undefined}
data-slot="state-screen"
className={cn(
"flex min-h-0 flex-col items-center justify-center text-center",
compact ? "gap-3 py-12" : "gap-4 py-20",
className
)}
{...props}
>
{Icon && (
<span
className={cn(
"flex items-center justify-center rounded-full",
compact ? "size-14" : "size-[72px]",
toneStyle.ring
)}
>
<Icon className={cn(compact ? "size-7" : "size-10", toneStyle.icon, iconClassName)} aria-hidden="true" />
</span>
)}
<div className="flex max-w-[460px] flex-col items-center gap-1.5">
<p className={cn(compact ? "text-base" : "text-2xl", "font-semibold text-foreground")}>{title}</p>
{description && <p className="text-[13px] leading-relaxed text-muted-foreground">{description}</p>}
</div>
{detail && (
<pre
data-testid={detailTestId}
className={cn(
"max-w-[460px] overflow-auto rounded-lg border px-3.5 py-2.5 text-left font-mono text-xs whitespace-pre-wrap",
toneStyle.detail ?? "border-border bg-muted/40 text-fg-secondary",
detailClassName
)}
>
{detail}
</pre>
)}
{progress}
{action}
</div>
);
if (variant === "card") {
return (
<Surface padding="none" className="bg-background">
{body}
</Surface>
);
}
return body;
}
export { StateScreen };
export type { StateScreenProps, StateScreenTone, StateScreenVariant };