{ "$schema": "https://ui.shadcn.com/schema/registry-item.json", "name": "accordion", "type": "registry:ui", "title": "Accordion", "description": "A customizable accordion component", "dependencies": [ "clsx", "tailwind-merge", "@base-ui/react", "motion" ], "registryDependencies": [ "classes" ], "files": [ { "path": "src/registry/pure-ui/ui/accordion/index.tsx", "content": "\"use client\";\r\n\r\nimport React, {\r\n createContext,\r\n useContext,\r\n useEffect,\r\n useMemo,\r\n useState,\r\n} from \"react\";\r\nimport { Accordion as AccordionPrimitive } from \"@base-ui/react/accordion\";\r\n\r\nimport { cn } from \"@/lib/classes\";\r\n\r\nconst cssAnimationPresets = {\r\n none: \"transition-none\",\r\n fade: [\r\n `[transition-property:opacity,height] [will-change:opacity,height]`,\r\n `data-starting-style:opacity-0 data-ending-style:opacity-0 data-starting-style:h-0 data-ending-style:h-0`,\r\n ],\r\n scale: [\r\n `[transition-property:scale,opacity,height] [will-change:scale,opacity,height] origin-left`,\r\n `data-starting-style:scale-85 data-starting-style:opacity-0 data-starting-style:h-0 data-ending-style:opacity-0 data-ending-style:h-0 data-ending-style:scale-85`,\r\n ],\r\n slide: [\r\n `[transition-property:translate,opacity,height] [will-change:translate,opacity,height]`,\r\n `data-starting-style:opacity-0 data-starting-style:translate-y-[10px] data-ending-style:translate-y-[10px] data-ending-style:opacity-0 data-ending-style:h-0 data-starting-style:h-0`,\r\n ],\r\n perspective: [\r\n `[transition-property:opacity,rotateX,rotateY,transform,height] [will-change:opacity,rotateX,rotateY,transform,height]`,\r\n `[transform:perspective(1000px)] origin-top`,\r\n `data-starting-style:h-0 data-ending-style:h-0`,\r\n `data-starting-style:opacity-0 data-ending-style:opacity-0`,\r\n `data-starting-style:-rotate-x-[90deg] data-ending-style:-rotate-x-[90deg]`,\r\n ],\r\n perspectiveBlur: [\r\n `[transition-property:opacity,rotateX,rotateY,transform,height,filter] [will-change:opacity,rotateX,rotateY,transform,height,filter]`,\r\n `[transform:perspective(1000px)] origin-top`,\r\n `data-starting-style:h-0 data-ending-style:h-0`,\r\n `data-starting-style:opacity-0 data-ending-style:opacity-0`,\r\n `data-starting-style:-rotate-x-[90deg] data-ending-style:-rotate-x-[90deg]`,\r\n `data-starting-style:blur-[9px] data-ending-style:blur-[9px]`,\r\n ],\r\n};\r\n\r\nconst cssTransitionPresets = {\r\n inExpo: `duration-[0.35s] ease-[cubic-bezier(0.95,0.05,0.795,0.035)]`,\r\n outExpo: `duration-[0.35s] ease-[cubic-bezier(0.19,1,0.22,1)]`,\r\n inOutExpo: `duration-[0.35s] ease-[cubic-bezier(1,0,0,1)]`,\r\n anticipate: `duration-[0.35s] ease-[cubic-bezier(1,-0.4,0.35,0.95)]`,\r\n quickOut: `duration-[0.35s] ease-out`,\r\n overshootOut: `duration-[0.35s] ease-[cubic-bezier(0.175,0.885,0.32,1.275)]`,\r\n swiftOut: `duration-[0.35s] ease-[cubic-bezier(0.175,0.885,0.32,1.1)]`,\r\n snappyOut: `duration-[0.35s] ease-[cubic-bezier(0.19,1,0.22,1)]`,\r\n in: `duration-[0.35s] ease-[cubic-bezier(0.42,0,1,1)]`,\r\n out: `duration-[0.35s] ease-[cubic-bezier(0,0,0.58,1)]`,\r\n inOut: `duration-[0.25s] ease-[cubic-bezier(0.42,0,0.58,1)]`,\r\n outIn: `duration-[0.35s] ease-[cubic-bezier(0.1,0.7,0.9,0.5)]`,\r\n inQuad: `duration-[0.35s] ease-[cubic-bezier(0.55,0.085,0.68,0.53)]`,\r\n outQuad: `duration-[0.25s] ease-[cubic-bezier(0.25,0.46,0.45,0.94)]`,\r\n inOutQuad: `duration-[0.32s] ease-[cubic-bezier(0.455,0.03,0.515,0.955)]`,\r\n inCubic: `duration-[0.35s] ease-[cubic-bezier(0.55,0.055,0.675,0.19)]`,\r\n outCubic: `duration-[0.35s] ease-[cubic-bezier(0.215,0.61,0.355,1)]`,\r\n inOutCubic: `duration-[0.35s] ease-[cubic-bezier(0.645,0.045,0.355,1)]`,\r\n inQuart: `duration-[0.35s] ease-[cubic-bezier(0.895,0.03,0.685,0.22)]`,\r\n outQuart: `duration-[0.35s] ease-[cubic-bezier(0.165,0.84,0.44,1)]`,\r\n inOutQuart: `duration-[0.35s] ease-[cubic-bezier(0.77,0,0.175,1)]`,\r\n inQuint: `duration-[0.35s] ease-[cubic-bezier(0.755,0.05,0.855,0.06)]`,\r\n outQuint: `duration-[0.35s] ease-[cubic-bezier(0.23,1,0.32,1)]`,\r\n inOutQuint: `duration-[0.35s] ease-[cubic-bezier(0.86,0,0.07,1)]`,\r\n inCirc: `duration-[0.35s] ease-[cubic-bezier(0.6,0.04,0.98,0.335)]`,\r\n outCirc: `duration-[0.35s] ease-[cubic-bezier(0.075,0.82,0.165,1)]`,\r\n inOutCirc: `duration-[0.35s] ease-[cubic-bezier(0.785,0.135,0.15,0.86)]`,\r\n inOutBase: `duration-[0.35s] ease-[cubic-bezier(0.25,0.1,0.25,1)]`,\r\n};\r\n\r\ntype CSSAnimationPreset = keyof typeof cssAnimationPresets;\r\ntype CSSTransitionPreset = keyof typeof cssTransitionPresets;\r\n\r\ntype AccordionVariant = \"default\" | \"card\" | \"swiss\";\r\n\r\ninterface AccordionContextType {\r\n animationPreset?: CSSAnimationPreset;\r\n transitionPreset?: CSSTransitionPreset;\r\n reduceMotion?: boolean;\r\n value: AccordionProps[\"value\"];\r\n onValueChange: AccordionProps[\"onValueChange\"];\r\n variant?: AccordionVariant;\r\n}\r\n\r\nconst AccordionContext = createContext(\r\n undefined\r\n);\r\n\r\nfunction useAccordion() {\r\n const context = useContext(AccordionContext);\r\n if (!context) {\r\n throw new Error(\"useAccordion must be used within a AccordionProvider\");\r\n }\r\n return context;\r\n}\r\n\r\ninterface AccordionProps extends AccordionPrimitive.Root.Props {\r\n animationPreset?: CSSAnimationPreset;\r\n transitionPreset?: CSSTransitionPreset;\r\n reduceMotion?: boolean;\r\n variant?: AccordionVariant;\r\n}\r\n\r\nfunction Accordion({\r\n value,\r\n defaultValue,\r\n onValueChange,\r\n animationPreset = \"fade\",\r\n transitionPreset = \"outQuad\",\r\n reduceMotion,\r\n variant = \"default\",\r\n className,\r\n multiple = false,\r\n ...props\r\n}: AccordionProps) {\r\n const [accordionValue, setAccordionValue] = useState(\r\n value ?? defaultValue ?? []\r\n );\r\n\r\n const handleValueChange: AccordionProps[\"onValueChange\"] = (\r\n value,\r\n eventDetails\r\n ) => {\r\n setAccordionValue(value);\r\n onValueChange?.(value, eventDetails);\r\n };\r\n\r\n return (\r\n \r\n \r\n \r\n );\r\n}\r\n\r\ninterface AccordionItemContextType {\r\n open: boolean;\r\n onOpenChange: AccordionItemProps[\"onOpenChange\"];\r\n variant?: AccordionVariant;\r\n}\r\n\r\nconst AccordionItemContext = createContext<\r\n AccordionItemContextType | undefined\r\n>(undefined);\r\n\r\nfunction useAccordionItem() {\r\n const context = useContext(AccordionItemContext);\r\n if (!context) {\r\n throw new Error(\r\n \"useAccordionItem must be used within a AccordionItemProvider\"\r\n );\r\n }\r\n return context;\r\n}\r\n\r\ninterface AccordionItemProps extends AccordionPrimitive.Item.Props {}\r\n\r\nfunction AccordionItem({\r\n value: itemValue,\r\n onOpenChange,\r\n className,\r\n ...rest\r\n}: AccordionItemProps) {\r\n const { value, variant = \"default\" } = useAccordion();\r\n\r\n const [isOpen, setIsOpen] = useState(value?.includes(itemValue) ?? false);\r\n\r\n useEffect(() => {\r\n setIsOpen(value?.includes(itemValue) ?? false);\r\n }, [value, itemValue]);\r\n\r\n const handleItemOpenChange: AccordionItemProps[\"onOpenChange\"] = (\r\n open,\r\n eventDetails\r\n ) => {\r\n setIsOpen(open);\r\n onOpenChange?.(open, eventDetails);\r\n };\r\n\r\n return (\r\n \r\n \r\n \r\n );\r\n}\r\n\r\ninterface AccordionHeaderProps extends AccordionPrimitive.Header.Props {}\r\n\r\nfunction AccordionHeader({ className, ...props }: AccordionHeaderProps) {\r\n return (\r\n \r\n );\r\n}\r\n\r\ninterface AccordionTriggerProps extends AccordionPrimitive.Trigger.Props {\r\n icon?: (props: { open: boolean }) => React.ReactNode;\r\n}\r\n\r\nfunction AccordionTrigger({\r\n className,\r\n icon,\r\n children,\r\n ...props\r\n}: AccordionTriggerProps) {\r\n const { open, variant = \"default\" } = useAccordionItem();\r\n\r\n return (\r\n \r\n \r\n {children}\r\n\r\n \r\n {icon ? (\r\n icon({ open })\r\n ) : (\r\n \r\n \r\n \r\n \r\n )}\r\n \r\n \r\n \r\n );\r\n}\r\n\r\ninterface AccordionPanelProps extends AccordionPrimitive.Panel.Props {}\r\n\r\nfunction AccordionPanel({\r\n className,\r\n children,\r\n style,\r\n ...props\r\n}: AccordionPanelProps) {\r\n const {\r\n variant = \"default\",\r\n animationPreset = \"fade\",\r\n transitionPreset = \"snappyOut\",\r\n reduceMotion = false,\r\n } = useAccordion();\r\n\r\n const cssAnimationConfig = useMemo(() => {\r\n if (reduceMotion) return cssAnimationPresets.none;\r\n\r\n if (animationPreset) {\r\n return cssAnimationPresets[animationPreset];\r\n }\r\n\r\n return cssAnimationPresets.fade;\r\n }, [animationPreset, reduceMotion]);\r\n\r\n const cssTransitionConfig = useMemo(() => {\r\n if (reduceMotion) return cssAnimationPresets.none;\r\n\r\n if (transitionPreset) {\r\n return cssTransitionPresets[transitionPreset];\r\n }\r\n\r\n return cssTransitionPresets.inOutExpo;\r\n }, [transitionPreset, reduceMotion]);\r\n\r\n return (\r\n \r\n \r\n {children}\r\n \r\n \r\n );\r\n}\r\n\r\nexport {\r\n Accordion,\r\n AccordionItem,\r\n AccordionHeader,\r\n AccordionTrigger,\r\n AccordionPanel,\r\n};\r\n", "type": "registry:ui", "target": "components/ui/accordion.tsx" } ] }