import React, { useCallback, useId, useMemo, useState } from 'react'; import { cn } from '@/lib/helper'; export type CollapseVariant = 'default' | 'arrow' | 'plus'; export type CollapseProps = { /** Unique name used when `asRadio` is true (Accordion single-open). */ name?: string; /** If provided, component is controlled. */ open?: boolean; /** Initial open state for uncontrolled usage. */ defaultOpen?: boolean; /** Callback when open state changes. */ onOpenChange?: (open: boolean) => void; /** Title row content. Accepts string or custom node. */ title?: React.ReactNode; /** Optional secondary text displayed under/next to title. */ subtitle?: React.ReactNode; /** Content of the panel. */ children?: React.ReactNode; /** Visual variant: default / arrow / plus */ variant?: CollapseVariant; /** Add a bordered look */ bordered?: boolean; /** Disable interactions */ disabled?: boolean; /** Allow only one open at a time by switching to radio input */ asRadio?: boolean; /** Extra classnames */ className?: string; titleClassName?: string; contentClassName?: string; }; export const Collapse = ({ name, open, defaultOpen, onOpenChange, title, subtitle, children, variant = 'default', bordered, disabled, asRadio = false, className, titleClassName, contentClassName, }: CollapseProps) => { const inputId = useId(); const isControlled = typeof open === 'boolean'; const [internalOpen, setInternalOpen] = useState(!!defaultOpen); const isOpen = isControlled ? !!open : internalOpen; // Manage change from checkbox/radio const handleChange = useCallback( (next: boolean) => { if (!isControlled) setInternalOpen(next); onOpenChange?.(next); }, [isControlled, onOpenChange] ); const inputType = asRadio ? 'radio' : 'checkbox'; const rootClass = cn( 'collapse', variant === 'arrow' && 'collapse-arrow', variant === 'plus' && 'collapse-plus', bordered && 'border base-content/20 border-opacity-20 rounded-box', disabled && 'opacity-60 pointer-events-none', !open && 'w-fit', className ); const titleNode = useMemo(() => { if (subtitle) { return (
{title} {subtitle}
); } return title; }, [title, subtitle]); return (
handleChange(e.currentTarget.checked)} aria-controls={`${inputId}-content`} disabled={disabled} />
{ if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); handleChange(!isOpen); } }} onClick={() => handleChange(!isOpen)} > {titleNode}
{children}
); }; export default Collapse;