Fortura/apps/www/public/r/base-accordion.json
2025-08-20 04:12:49 -06:00

19 lines
5.7 KiB
JSON

{
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
"name": "base-accordion",
"type": "registry:ui",
"title": "Base Accordion",
"description": "An easily stylable accordion component.",
"dependencies": [
"motion",
"@base-ui-components/react"
],
"files": [
{
"path": "registry/base/accordion/index.tsx",
"content": "'use client';\n\nimport * as React from 'react';\nimport { Accordion as AccordionPrimitive } from '@base-ui-components/react/accordion';\nimport {\n AnimatePresence,\n motion,\n type HTMLMotionProps,\n type Transition,\n} from 'motion/react';\n\nimport { cn } from '@/lib/utils';\nimport { ChevronDown } from 'lucide-react';\n\ntype AccordionItemContextType = {\n isOpen: boolean;\n setIsOpen: (open: boolean) => void;\n};\n\nconst AccordionItemContext = React.createContext<\n AccordionItemContextType | undefined\n>(undefined);\n\nconst useAccordionItem = (): AccordionItemContextType => {\n const context = React.useContext(AccordionItemContext);\n if (!context) {\n throw new Error('useAccordionItem must be used within an AccordionItem');\n }\n return context;\n};\n\ntype AccordionProps = React.ComponentProps<typeof AccordionPrimitive.Root>;\n\nfunction Accordion(props: AccordionProps) {\n return <AccordionPrimitive.Root data-slot=\"accordion\" {...props} />;\n}\n\ntype AccordionItemProps = React.ComponentProps<\n typeof AccordionPrimitive.Item\n> & {\n children: React.ReactNode;\n};\n\nfunction AccordionItem({ className, children, ...props }: AccordionItemProps) {\n const [isOpen, setIsOpen] = React.useState(false);\n\n return (\n <AccordionItemContext.Provider value={{ isOpen, setIsOpen }}>\n <AccordionPrimitive.Item\n data-slot=\"accordion-item\"\n className={cn('border-b', className)}\n {...props}\n >\n {children}\n </AccordionPrimitive.Item>\n </AccordionItemContext.Provider>\n );\n}\n\ntype AccordionTriggerProps = React.ComponentProps<\n typeof AccordionPrimitive.Trigger\n> & {\n transition?: Transition;\n chevron?: boolean;\n};\n\nfunction AccordionTrigger({\n ref,\n className,\n children,\n transition = { type: 'spring', stiffness: 150, damping: 22 },\n chevron = true,\n ...props\n}: AccordionTriggerProps) {\n const triggerRef = React.useRef<HTMLButtonElement | null>(null);\n React.useImperativeHandle(ref, () => triggerRef.current as HTMLButtonElement);\n const { isOpen, setIsOpen } = useAccordionItem();\n\n React.useEffect(() => {\n const node = triggerRef.current;\n if (!node) return;\n const observer = new MutationObserver((mutationsList) => {\n mutationsList.forEach((mutation) => {\n if (mutation.attributeName === 'data-panel-open') {\n const currentState = node.getAttribute('data-panel-open');\n setIsOpen(currentState === '');\n }\n });\n });\n observer.observe(node, {\n attributes: true,\n attributeFilter: ['data-panel-open'],\n });\n const initialState = node.getAttribute('data-panel-open');\n setIsOpen(initialState === '');\n return () => observer.disconnect();\n }, [setIsOpen]);\n\n return (\n <AccordionPrimitive.Header data-slot=\"accordion-header\" className=\"flex\">\n <AccordionPrimitive.Trigger\n ref={triggerRef}\n data-slot=\"accordion-trigger\"\n className={cn(\n 'flex flex-1 text-start items-center justify-between py-4 font-medium hover:underline',\n className,\n )}\n {...props}\n >\n {children}\n\n {chevron && (\n <motion.div\n data-slot=\"accordion-trigger-chevron\"\n animate={{ rotate: isOpen ? 180 : 0 }}\n transition={transition}\n >\n <ChevronDown className=\"size-5 shrink-0\" />\n </motion.div>\n )}\n </AccordionPrimitive.Trigger>\n </AccordionPrimitive.Header>\n );\n}\n\ntype AccordionPanelProps = React.ComponentProps<\n typeof AccordionPrimitive.Panel\n> & {\n motionProps?: HTMLMotionProps<'div'>;\n transition?: Transition;\n};\n\nfunction AccordionPanel({\n className,\n children,\n transition = { type: 'spring', stiffness: 150, damping: 22 },\n motionProps,\n ...props\n}: AccordionPanelProps) {\n const { isOpen } = useAccordionItem();\n\n return (\n <AnimatePresence>\n {isOpen && (\n <AccordionPrimitive.Panel\n hidden={false}\n keepMounted\n render={\n <motion.div\n key=\"accordion-panel\"\n data-slot=\"accordion-panel\"\n initial={{ height: 0, opacity: 0, '--mask-stop': '0%' }}\n animate={{ height: 'auto', opacity: 1, '--mask-stop': '100%' }}\n exit={{ height: 0, opacity: 0, '--mask-stop': '0%' }}\n transition={transition}\n style={{\n maskImage:\n 'linear-gradient(black var(--mask-stop), transparent var(--mask-stop))',\n WebkitMaskImage:\n 'linear-gradient(black var(--mask-stop), transparent var(--mask-stop))',\n }}\n className=\"overflow-hidden\"\n {...motionProps}\n >\n <div className={cn('pb-4 pt-0 text-sm', className)}>\n {children}\n </div>\n </motion.div>\n }\n {...props}\n />\n )}\n </AnimatePresence>\n );\n}\n\nexport {\n Accordion,\n AccordionItem,\n AccordionTrigger,\n AccordionPanel,\n useAccordionItem,\n type AccordionItemContextType,\n type AccordionProps,\n type AccordionItemProps,\n type AccordionTriggerProps,\n type AccordionPanelProps,\n};\n",
"type": "registry:ui",
"target": "components/animate-ui/base/accordion.tsx"
}
]
}