collapsible.tsx 1.4 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647
  1. import * as React from "react";
  2. import { ChevronRight } from "lucide-react";
  3. interface CollapsibleProps {
  4. title: string;
  5. count?: number;
  6. defaultOpen?: boolean;
  7. icon?: React.ReactNode;
  8. badge?: React.ReactNode;
  9. children: React.ReactNode;
  10. }
  11. export function Collapsible({
  12. title,
  13. count,
  14. defaultOpen = true,
  15. icon,
  16. badge,
  17. children,
  18. }: CollapsibleProps) {
  19. const [open, setOpen] = React.useState(defaultOpen);
  20. return (
  21. <div className="rounded-lg border border-border bg-muted/10">
  22. <button
  23. type="button"
  24. className="flex w-full items-center gap-3 px-4 py-3 text-left transition-colors hover:bg-muted/20"
  25. onClick={() => setOpen((prev) => !prev)}
  26. >
  27. <ChevronRight
  28. className={`h-4 w-4 shrink-0 text-muted-foreground transition-transform ${
  29. open ? "rotate-90" : ""
  30. }`}
  31. />
  32. {icon && <span className="shrink-0 text-muted-foreground">{icon}</span>}
  33. <span className="text-sm font-semibold">{title}</span>
  34. {count !== undefined && (
  35. <span className="rounded-full bg-muted px-2 py-0.5 text-xs font-medium text-muted-foreground">
  36. {count}
  37. </span>
  38. )}
  39. {badge && <span className="ml-auto">{badge}</span>}
  40. </button>
  41. {open && <div className="border-t border-border px-4 py-4">{children}</div>}
  42. </div>
  43. );
  44. }