Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 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 110 111 112 113 114 115 116 | 44x 44x 6x 1359x 1359x 3x | import { useState, type ReactNode } from "react";
import { ChevronDown } from "lucide-react";
type CollapsibleProps = {
title: ReactNode;
description?: ReactNode;
icon?: ReactNode;
defaultOpen?: boolean;
children: ReactNode;
className?: string;
headerClassName?: string;
};
export function Collapsible({
title,
description,
icon,
defaultOpen = false,
children,
className = "",
headerClassName = "",
}: CollapsibleProps) {
const [isOpen, setIsOpen] = useState(defaultOpen);
return (
<div className={className}>
<button
type="button"
onClick={() => setIsOpen(!isOpen)}
className={`w-full flex items-center justify-between text-left ${headerClassName}`}
>
<div className="flex items-center gap-2">
{icon}
<div>
<div className="font-semibold text-foreground-default">{title}</div>
{description && (
<div className="text-sm text-foreground-muted">{description}</div>
)}
</div>
</div>
<ChevronDown
className={`h-5 w-5 text-foreground-muted transition-transform duration-200 ${
isOpen ? "rotate-180" : ""
}`}
/>
</button>
<div
className={`overflow-hidden transition-all duration-200 ${
isOpen ? "max-h-[2000px] opacity-100" : "max-h-0 opacity-0"
}`}
>
{children}
</div>
</div>
);
}
type CollapsibleCardProps = {
title: ReactNode;
description?: ReactNode;
icon?: ReactNode;
defaultOpen?: boolean;
children: ReactNode;
badge?: ReactNode;
};
export function CollapsibleCard({
title,
description,
icon,
defaultOpen = false,
children,
badge,
}: CollapsibleCardProps) {
const [isOpen, setIsOpen] = useState(defaultOpen);
return (
<div className="rounded-lg border border-border-default bg-background-elevated shadow-card">
<button
type="button"
onClick={() => setIsOpen(!isOpen)}
className="w-full flex items-center justify-between p-6 text-left hover:bg-background-subtle transition-colors rounded-t-lg"
>
<div className="flex items-center gap-3">
{icon && <span className="text-foreground-muted">{icon}</span>}
<div>
<div className="flex items-center gap-2">
<span className="text-lg font-semibold text-foreground-default">
{title}
</span>
{badge}
</div>
{description && (
<p className="text-sm text-foreground-muted">{description}</p>
)}
</div>
</div>
<ChevronDown
className={`h-5 w-5 text-foreground-subtle transition-transform duration-200 flex-shrink-0 ${
isOpen ? "rotate-180" : ""
}`}
/>
</button>
<div
className={`overflow-hidden transition-all duration-200 ${
isOpen ? "max-h-[2000px] opacity-100" : "max-h-0 opacity-0"
}`}
>
<div className="p-6 pt-0 border-t border-border-default">
{children}
</div>
</div>
</div>
);
}
|