import * as React from 'react'; import { ChevronDown, ChevronUp } from 'lucide-react'; import { cn } from '@/lib/utils'; import { Card, CardHeader, CardTitle, CardContent } from './card'; export interface CollapsibleProps { /** * Content to display when collapsed (header/trigger) */ trigger: React.ReactNode; /** * Content to display when expanded */ children: React.ReactNode; /** * Whether the collapsible is open by default * @default false */ defaultOpen?: boolean; /** * Controlled open state */ open?: boolean; /** * Callback when open state changes */ onOpenChange?: (open: boolean) => void; /** * Additional CSS classes for the container */ className?: string; /** * Additional CSS classes for the trigger button */ triggerClassName?: string; /** * Additional CSS classes for the content */ contentClassName?: string; /** * Whether to show the chevron icon * @default true */ showChevron?: boolean; } /** * Collapsible - Reusable collapsible component * * A component that can be expanded/collapsed to show/hide content. * Supports both controlled and uncontrolled modes. * * @example * ```tsx * Click to expand}> *

This content is hidden by default

*
* ``` * * @example * ```tsx * Activity Feed} * defaultOpen={false} * onOpenChange={(open) => console.log('State:', open)} * > *
Activity content here
*
* ``` */ export function Collapsible({ trigger, children, defaultOpen = false, open: controlledOpen, onOpenChange, className, triggerClassName, contentClassName, showChevron = true, }: CollapsibleProps) { const [uncontrolledOpen, setUncontrolledOpen] = React.useState(defaultOpen); // Use controlled state if provided, otherwise use uncontrolled const isOpen = controlledOpen !== undefined ? controlledOpen : uncontrolledOpen; const handleToggle = () => { const newOpen = !isOpen; if (controlledOpen === undefined) { setUncontrolledOpen(newOpen); } onOpenChange?.(newOpen); }; const ChevronIcon = isOpen ? ChevronUp : ChevronDown; return (
{children}
); } /** * CollapsibleCard - Collapsible component with Card styling * * A collapsible component that wraps content in a Card for consistent styling. * * @example * ```tsx * *
Activity content
*
* ``` */ export interface CollapsibleCardProps extends Omit { /** * Title/header text for the collapsible card */ title: React.ReactNode; /** * Optional icon to display next to the title */ icon?: React.ReactNode; } export function CollapsibleCard({ title, icon, children, className, triggerClassName, contentClassName, ...collapsibleProps }: CollapsibleCardProps) { return ( {icon} {title} } triggerClassName={cn('p-0 hover:bg-transparent', triggerClassName)} contentClassName={cn('pt-0', contentClassName)} {...collapsibleProps} > {children} ); }