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; /** Force full width instead of auto-fit when collapsed * (Khusus justify-between dan justify-end) */ fullWidth?: 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, fullWidth, 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', !fullWidth && !open && 'w-fit', className ); const titleNode = useMemo(() => { if (subtitle) { return (