Components
A minimal show/hide primitive built on Base UI's Collapsible. A trigger toggles a height-animated panel — perfect for read-more text, FAQ rows, nested groups and expandable log output. Controlled or uncontrolled via open / defaultOpen, with data-panel-open styling hooks. Ships Collapsible, CollapsibleTrigger and CollapsiblePanel (also exported as CollapsibleContent).
Loading preview...
import { ChevronDownIcon } from "lucide-react";
import {
Collapsible,
CollapsiblePanel,
CollapsibleTrigger,
} from "@/components/ui/cnippet-collapsible";
type StepStatus = "success" | "failed" | "running" | "pending";
const statusBadge: Record<StepStatus, string> = {
failed: "border-transparent bg-destructive/15 text-destructive",
pending: "border text-muted-foreground",
running: "border-transparent bg-blue-500/15 text-blue-600 dark:text-blue-400",
success:
"border-transparent bg-emerald-500/15 text-emerald-600 dark:text-emerald-400",
};
const steps: {
id: string;
name: string;
status: StepStatus;
duration: string;
log: string[];
}[] = [
{
duration: "3s",
id: "install",
log: [
"$ bun install",
"bun install v1.1.0",
"Resolving dependencies...",
"✓ 412 packages installed [3.14s]",
],
name: "Install dependencies",
status: "success",
},
{
duration: "12s",
id: "typecheck",
log: [
"$ tsc --noEmit",
"src/components/Button.tsx(14,7): error TS2322",
" Type 'string' is not assignable to type 'number'.",
"Found 1 error.",
],
name: "Type check",
status: "failed",
},
{
duration: "—",
id: "build",
log: [],
name: "Build",
status: "pending",
},
];
export default function CollapsiblePipeline() {
return (
<div className="w-full max-w-md space-y-2">
{steps.map((step) => (
<div
className="overflow-hidden rounded-xl border bg-card text-card-foreground shadow-sm"
key={step.id}
>
<Collapsible disabled={step.status === "pending"}>
<CollapsibleTrigger className="w-full">
<div className="px-4 py-3">
<div className="flex items-center justify-between gap-3">
<div className="flex items-center gap-2.5">
<span
className={`inline-flex items-center rounded-md px-2 py-0.5 font-medium text-xs ${statusBadge[step.status]}`}
>
{step.status}
</span>
<span className="font-medium text-sm">{step.name}</span>
</div>
<div className="flex items-center gap-2 text-muted-foreground">
<span className="text-xs">{step.duration}</span>
{step.status !== "pending" && (
<ChevronDownIcon className="size-3.5 in-data-panel-open:rotate-180 transition-transform duration-200" />
)}
</div>
</div>
</div>
</CollapsibleTrigger>
{step.log.length > 0 && (
<CollapsiblePanel>
<div className="border-t bg-muted/40 px-4 py-3">
<pre className="space-y-0.5 overflow-x-auto text-xs leading-relaxed">
{step.log.map((line) => (
<div
className={
line.startsWith("$")
? "font-semibold text-foreground"
: line.includes("error") || line.includes("Error")
? "text-destructive"
: "text-muted-foreground"
}
key={line}
>
{line}
</div>
))}
</pre>
</div>
</CollapsiblePanel>
)}
</Collapsible>
</div>
))}
</div>
);
}
Loading preview...
Loading preview...
Loading preview...
Loading preview...
Loading preview...