"use client"; import * as React from "react"; import { ChevronDownIcon } from "lucide-react"; import { cn } from "@/lib/utils"; interface AccordionContextValue { type: "single" | "multiple"; collapsible?: boolean; value?: string | string[]; onValueChange?: (value: string | string[]) => void; } const AccordionContext = React.createContext(null); interface AccordionItemContextValue { value: string; } const AccordionItemContext = React.createContext(null); interface AccordionProps { type: "single" | "multiple"; collapsible?: boolean; value?: string | string[]; defaultValue?: string | string[]; onValueChange?: (value: string | string[]) => void; className?: string; children: React.ReactNode; onClick?: (e: React.MouseEvent) => void; } function Accordion({ type, collapsible = false, value: controlledValue, defaultValue, onValueChange, className, children, onClick, ...props }: AccordionProps) { const [uncontrolledValue, setUncontrolledValue] = React.useState( defaultValue || (type === "multiple" ? [] : ""), ); const value = controlledValue !== undefined ? controlledValue : uncontrolledValue; const handleValueChange = React.useCallback( (newValue: string | string[]) => { if (controlledValue === undefined) { setUncontrolledValue(newValue); } onValueChange?.(newValue); }, [controlledValue, onValueChange], ); const contextValue = React.useMemo( () => ({ type, collapsible, value, onValueChange: handleValueChange, }), [type, collapsible, value, handleValueChange], ); return (
{children}
); } interface AccordionItemProps { value: string; className?: string; children: React.ReactNode; } function AccordionItem({ value, className, children, ...props }: AccordionItemProps) { const context = React.useContext(AccordionContext); const handleClick = (e: React.MouseEvent) => { if (!context?.onValueChange) return; const target = e.target as HTMLElement; if (target.closest('button[type="button"]') && !target.closest(".accordion-trigger")) { return; } const isOpen = context.type === "multiple" ? Array.isArray(context.value) && context.value.includes(value) : context.value === value; if (context.type === "multiple") { const currentValue = Array.isArray(context.value) ? context.value : []; const newValue = isOpen ? currentValue.filter((v) => v !== value) : [...currentValue, value]; context.onValueChange(newValue); } else { const newValue = isOpen && context.collapsible ? "" : value; context.onValueChange(newValue); } }; return (
{children}
); } interface AccordionTriggerProps { className?: string; children: React.ReactNode; } function AccordionTrigger({ className, children, ...props }: AccordionTriggerProps) { const context = React.useContext(AccordionContext); const parent = React.useContext(AccordionItemContext); if (!context || !parent) { throw new Error("AccordionTrigger must be used within AccordionItem"); } const isOpen = context.type === "multiple" ? Array.isArray(context.value) && context.value.includes(parent.value) : context.value === parent.value; const handleClick = () => { if (!context.onValueChange) return; if (context.type === "multiple") { const currentValue = Array.isArray(context.value) ? context.value : []; const newValue = isOpen ? currentValue.filter((v) => v !== parent.value) : [...currentValue, parent.value]; context.onValueChange(newValue); } else { const newValue = isOpen && context.collapsible ? "" : parent.value; context.onValueChange(newValue); } }; return ( ); } interface AccordionContentProps { className?: string; children: React.ReactNode; } function AccordionContent({ className, children, ...props }: AccordionContentProps) { const context = React.useContext(AccordionContext); const parent = React.useContext(AccordionItemContext); const contentRef = React.useRef(null); if (!context || !parent) { throw new Error("AccordionContent must be used within AccordionItem"); } const isOpen = context.type === "multiple" ? Array.isArray(context.value) && context.value.includes(parent.value) : context.value === parent.value; return (
e.stopPropagation()} {...props} >
{children}
); } // AccordionItem을 래핑하여 컨텍스트 제공 const AccordionItemWithContext = React.forwardRef( ({ value, children, ...props }, ref) => { return ( {children} ); }, ); AccordionItemWithContext.displayName = "AccordionItem"; export { Accordion, AccordionItemWithContext as AccordionItem, AccordionTrigger, AccordionContent };