85 lines
2.2 KiB
TypeScript
85 lines
2.2 KiB
TypeScript
import type { ReactNode } from "react";
|
|
|
|
interface HousekeepingPageStateBaseProps {
|
|
title: string;
|
|
description: string;
|
|
headingLevel?: "h1" | "h2";
|
|
retryAction?: ReactNode;
|
|
}
|
|
|
|
type HousekeepingPageStateProps = HousekeepingPageStateBaseProps &
|
|
(
|
|
| { state: "partial"; partialLabel: string }
|
|
| {
|
|
state:
|
|
| "loading"
|
|
| "empty"
|
|
| "conflict"
|
|
| "dependency"
|
|
| "forbidden"
|
|
| "error"
|
|
| "not-found"
|
|
| "success";
|
|
partialLabel?: never;
|
|
}
|
|
);
|
|
|
|
const ALERT_STATES = new Set(["conflict", "dependency", "forbidden", "error"]);
|
|
|
|
export function HousekeepingPageState({
|
|
state,
|
|
partialLabel,
|
|
title,
|
|
description,
|
|
headingLevel = "h2",
|
|
retryAction,
|
|
}: HousekeepingPageStateProps) {
|
|
const isAlert = ALERT_STATES.has(state);
|
|
const isPartial = state === "partial";
|
|
const isWarning = isPartial || state === "conflict" || state === "dependency";
|
|
const isError = state === "error" || state === "forbidden";
|
|
const isSuccess = state === "success";
|
|
const Heading = headingLevel;
|
|
|
|
return (
|
|
<section
|
|
role={isAlert ? "alert" : "status"}
|
|
data-state-tone={
|
|
isError
|
|
? "error"
|
|
: isWarning
|
|
? "warning"
|
|
: isSuccess
|
|
? "success"
|
|
: "neutral"
|
|
}
|
|
aria-live={state === "loading" ? "polite" : undefined}
|
|
className={`rounded-lg border p-4 ${
|
|
isError
|
|
? "border-[var(--admin-error-border)] bg-[var(--admin-error-subtle)]"
|
|
: isWarning
|
|
? "border-[var(--admin-warning-border)] bg-[var(--admin-warning-subtle)]"
|
|
: isSuccess
|
|
? "border-[var(--admin-success)] bg-[var(--admin-success-subtle)]"
|
|
: "border-[var(--admin-border)] bg-[var(--admin-surface)]"
|
|
}`}
|
|
>
|
|
{isPartial ? (
|
|
<p
|
|
data-state-tone="warning"
|
|
className="mb-2 inline-flex rounded-full border border-[var(--admin-warning-border)] bg-[var(--admin-surface)] px-2 py-0.5 text-xs font-medium text-[var(--admin-warning)]"
|
|
>
|
|
{partialLabel}
|
|
</p>
|
|
) : null}
|
|
<Heading className="font-medium text-[var(--admin-text)]">
|
|
{title}
|
|
</Heading>
|
|
<p className="mt-1 text-sm text-[var(--admin-text-muted)]">
|
|
{description}
|
|
</p>
|
|
{retryAction ? <div className="mt-3">{retryAction}</div> : null}
|
|
</section>
|
|
);
|
|
}
|