This commit is contained in:
ZacharyZhang-NY committed 2026-04-04 12:03:50 -04:00
1 parent 66158e222e
commit 67c757f481
314 files changed
+37945 -22962

No files matched your search

+161
View File
@@ -0,0 +1,161 @@
import { useEffect, type ReactNode } from "react";
import { AnimatePresence, motion } from "framer-motion";
import { ActionButton } from "./primitives";
import { useFocusTrap } from "./hooks";
const EASE = [0.16, 1, 0.3, 1] as const;
/* ------------------------------------------------------------------ */
/* StatusPill */
/* ------------------------------------------------------------------ */
export function StatusPill({ status }: { status: string }) {
const positive = ["ready", "active", "running", "succeeded", "approved", "achieved", "done"].includes(status);
const negative = ["error", "failed", "cancelled", "blocked", "rejected", "interrupted"].includes(status);
const warning = ["auth_required", "degraded", "pending", "in_review", "todo", "queued", "not_installed"].includes(status);
const checking = status === "detected";
const toneClass = positive
? "text-success"
: negative
? "text-destructive"
: warning
? "text-warning"
: "text-muted-foreground";
const dotClass = positive
? "bg-success"
: negative
? "bg-destructive"
: warning
? "bg-warning"
: "bg-[color:var(--muted-foreground)]";
const displayLabel = checking ? "checking" : status.replaceAll("_", " ");
return (
<span className={`inline-flex items-center gap-2 text-[11px] font-bold uppercase tracking-[0.12em] ${toneClass}`}>
{checking ? (
<span className="h-2 w-2 animate-pulse rounded-full bg-[color:var(--muted-foreground)]" aria-hidden="true" />
) : (
<span className={`h-2 w-2 rounded-full ${dotClass}`} aria-hidden="true" />
)}
{displayLabel}
</span>
);
}
/* ------------------------------------------------------------------ */
/* InlineNotice */
/* ------------------------------------------------------------------ */
export function InlineNotice({
message,
tone = "danger",
action,
}: {
message: string;
tone?: "danger" | "success" | "warn";
action?: ReactNode;
}) {
const toneClass = tone === "success"
? "border-[color:var(--primary)] bg-success-muted text-success"
: tone === "warn"
? "border-[color:var(--warning)] bg-warning-muted text-warning"
: "border-[color:var(--destructive)] bg-destructive-muted text-destructive";
return (
<div className={`rounded-lg border px-4 py-3 text-[13px] ${toneClass}`} role={tone === "danger" ? "alert" : "status"}>
{action ? (
<div className="flex items-start justify-between gap-3">
<span>{message}</span>
{action}
</div>
) : message}
</div>
);
}
/* ------------------------------------------------------------------ */
/* ConfirmDialog */
/* ------------------------------------------------------------------ */
export function ConfirmDialog({ open, title, message, errorMessage, onConfirm, onCancel }: {
open: boolean;
title: string;
message: string;
errorMessage?: string | null;
onConfirm: () => void;
onCancel: () => void;
}) {
const trapRef = useFocusTrap(open);
useEffect(() => {
if (!open) return;
function onKey(event: KeyboardEvent) {
if (event.key === "Escape") onCancel();
}
document.addEventListener("keydown", onKey);
return () => document.removeEventListener("keydown", onKey);
}, [open, onCancel]);
return (
<AnimatePresence>
{open ? (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: 0.15, ease: EASE }}
className="fixed inset-0 z-50 flex items-center justify-center bg-[color:var(--foreground)]/45"
onClick={onCancel}
>
<motion.div
ref={trapRef}
role="dialog"
aria-modal="true"
aria-labelledby="confirm-dialog-title"
initial={{ scale: 0.96, opacity: 0 }}
animate={{ scale: 1, opacity: 1 }}
exit={{ scale: 0.96, opacity: 0 }}
transition={{ duration: 0.18, ease: EASE }}
className="w-full max-w-[400px] rounded-lg bg-card border border-border p-6 shadow-lg"
onClick={e => e.stopPropagation()}
>
<div id="confirm-dialog-title" className="text-[16px] font-semibold text-foreground mb-2">{title}</div>
<div className="text-[13px] text-muted-foreground mb-6">{message}</div>
{errorMessage ? <div className="mb-4"><InlineNotice message={errorMessage} /></div> : null}
<div className="flex gap-3 justify-end">
<ActionButton label="Cancel" onClick={onCancel} />
<ActionButton label="Confirm" tone="danger" onClick={onConfirm} />
</div>
</motion.div>
</motion.div>
) : null}
</AnimatePresence>
);
}
/* ------------------------------------------------------------------ */
/* EmptyState */
/* ------------------------------------------------------------------ */
export function EmptyState({
title,
detail,
action,
}: {
title: string;
detail?: string;
action?: ReactNode;
}) {
return (
<motion.div
initial={{ opacity: 0, y: 6 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.22, ease: EASE }}
className="px-1 py-8 text-center"
>
<div className="mb-2 text-[15px] font-semibold text-foreground">{title}</div>
{detail ? <div className="mx-auto max-w-[48ch] text-[13px] leading-relaxed text-muted-foreground">{detail}</div> : null}
{action ? <div className="mt-5">{action}</div> : null}
</motion.div>
);
}