"use client" import * as React from "react" import { ChevronDownIcon } from "lucide-react" import { cn } from "@/lib/utils" export type CollapseProps = React.ComponentProps<"details"> & { defaultOpen?: boolean open?: boolean onOpenChange?: (open: boolean) => void variant?: "default" | "soft" | "ghost" size?: "sm" | "md" | "lg" } function Collapse({ open, defaultOpen, onOpenChange, onToggle, className, children, variant = "default", size = "md", ...props }: CollapseProps) { const isControlled = open !== undefined const [internalOpen, setInternalOpen] = React.useState(defaultOpen ?? false) const currentOpen = isControlled ? open : internalOpen return (
{ onToggle?.(event) const nextOpen = event.currentTarget.open if (!isControlled) { setInternalOpen(nextOpen) } else if (nextOpen !== currentOpen) { event.currentTarget.open = currentOpen } onOpenChange?.(nextOpen) }} open={currentOpen} {...props} > {children}
) } export type CollapseTriggerProps = React.ComponentProps<"summary"> & { icon?: React.ReactNode hideIcon?: boolean size?: "sm" | "md" | "lg" inset?: boolean indicatorPosition?: "start" | "end" } function CollapseTrigger({ icon, hideIcon = false, size = "md", inset = false, indicatorPosition = "end", className, children, onClick, ...props }: CollapseTriggerProps) { const indicator = !hideIcon ? ( {icon ?? } ) : null return ( { if (props["aria-disabled"]) { event.preventDefault() return } onClick?.(event) }} {...props} > {indicatorPosition === "start" ? indicator : null} {children} {indicatorPosition === "end" ? indicator : null} ) } function CollapseContent({ className, ...props }: React.ComponentProps<"div">) { return (
) } export type CollapseItem = { key: string title: React.ReactNode content: React.ReactNode description?: React.ReactNode meta?: React.ReactNode badge?: React.ReactNode disabled?: boolean disabledReason?: React.ReactNode icon?: React.ReactNode indicatorPosition?: CollapseTriggerProps["indicatorPosition"] triggerClassName?: string contentClassName?: string } export type CollapseGroupProps = React.ComponentProps<"div"> & { items: CollapseItem[] type?: "single" | "multiple" value?: string | string[] defaultValue?: string | string[] onValueChange?: (value: string | string[]) => void variant?: CollapseProps["variant"] size?: CollapseTriggerProps["size"] } function CollapseGroup({ items, type = "multiple", value, defaultValue, onValueChange, variant = "default", size = "md", className, ...props }: CollapseGroupProps) { const initialValue = React.useMemo(() => { if (value !== undefined) return value if (defaultValue !== undefined) return defaultValue return type === "single" ? "" : [] }, [defaultValue, type, value]) const [internalValue, setInternalValue] = React.useState(initialValue) const currentValue = value ?? internalValue const isOpen = (key: string) => Array.isArray(currentValue) ? currentValue.includes(key) : currentValue === key const updateValue = (key: string, nextOpen: boolean) => { const nextValue = type === "single" ? nextOpen ? key : "" : nextOpen ? Array.from(new Set([...(Array.isArray(currentValue) ? currentValue : []), key])) : (Array.isArray(currentValue) ? currentValue.filter((item) => item !== key) : []) if (value === undefined) setInternalValue(nextValue) onValueChange?.(nextValue) } return (
{items.map((item) => ( { if (item.disabled) return updateValue(item.key, open) }} variant={variant} size={size} className={cn(item.disabled && "pointer-events-none opacity-60")} > {item.title} {item.badge ? {item.badge} : null} {item.meta ? {item.meta} : null} {item.description && {item.description}} {item.disabled && item.disabledReason ? ( {item.disabledReason} ) : null} {item.content} ))}
) } export { Collapse, CollapseContent, CollapseGroup, CollapseTrigger }