{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "accordion",
  "type": "registry:ui",
  "title": "Accordion",
  "description": "Progressively reveal supporting information in place.",
  "categories": [
    "disclosure"
  ],
  "dependencies": [
    "@radix-ui/react-accordion",
    "lucide-react",
    "motion"
  ],
  "registryDependencies": [
    "https://irisui.dev/r/foundation.json",
    "https://irisui.dev/r/motion-tokens.json",
    "https://irisui.dev/r/utils.json"
  ],
  "files": [
    {
      "path": "src/registry/components/accordion/accordion.tsx",
      "content": "\"use client\";\n\nimport { useState } from \"react\";\nimport type { ReactNode } from \"react\";\nimport * as AccordionPrimitive from \"@radix-ui/react-accordion\";\nimport { ChevronDown } from \"lucide-react\";\nimport { motion, useReducedMotion } from \"motion/react\";\nimport type { TargetAndTransition, Variants } from \"motion/react\";\nimport { motionTokens } from \"../lib/motion-tokens\";\nimport { cn } from \"../lib/utils\";\n\nexport interface AccordionItem {\n  title: string;\n  content: ReactNode;\n}\nexport interface AccordionProps {\n  items: AccordionItem[];\n  defaultOpen?: number;\n  /** \"lg\" suits page-level FAQs: questions at the large text size, answers at body size. */\n  size?: \"md\" | \"lg\";\n}\n\n/** Height follows the content on a spring that never overshoots; closed panels leave the accessibility tree once they finish collapsing. */\nconst panelOpen: TargetAndTransition = { height: \"auto\", opacity: 1, visibility: \"visible\" };\nconst panelClosed: TargetAndTransition = { height: 0, opacity: 0, transitionEnd: { visibility: \"hidden\" } };\n/** The answer settles down into place with a brief focus pull as the panel opens. */\nconst contentOpen: TargetAndTransition = { y: 0, filter: \"blur(0px)\", transitionEnd: { filter: \"none\" } };\nconst contentClosed: TargetAndTransition = { y: -6, filter: `blur(${motionTokens.blur.subtle}px)` };\nconst panelMotion: Variants = {\n  open: {\n    ...panelOpen,\n    transition: {\n      height: motionTokens.spring.smooth,\n      opacity: { duration: motionTokens.duration.standard, ease: [...motionTokens.ease.enter] },\n    },\n  },\n  closed: {\n    ...panelClosed,\n    transition: {\n      height: motionTokens.spring.smooth,\n      opacity: { duration: motionTokens.duration.fast, ease: [...motionTokens.ease.standard] },\n    },\n  },\n};\nconst contentMotion: Variants = {\n  open: {\n    ...contentOpen,\n    transition: {\n      y: motionTokens.spring.smooth,\n      filter: { duration: motionTokens.duration.standard, ease: [...motionTokens.ease.enter] },\n    },\n  },\n  closed: {\n    ...contentClosed,\n    transition: { duration: motionTokens.duration.fast, ease: [...motionTokens.ease.standard] },\n  },\n};\n/** Reduced motion: same end states in one step, so server and client render identical styles. */\nconst panelStill: Variants = {\n  open: { ...panelOpen, transition: { duration: 0 } },\n  closed: { ...panelClosed, transition: { duration: 0 } },\n};\nconst contentStill: Variants = {\n  open: { ...contentOpen, transition: { duration: 0 } },\n  closed: { ...contentClosed, transition: { duration: 0 } },\n};\n\n/** Colour changes on the trigger and its chevron share one quick ease. */\nconst fade = \"transition-[color] duration-fast ease-standard motion-reduce:transition-none\";\n/** \"md\" sits in forms and side panels; \"lg\" sets page-level FAQs, shrinking a step on narrow screens. */\nconst sizes = {\n  md: {\n    trigger: \"min-h-control-lg gap-5 py-2 text-sm leading-body\",\n    panel: \"text-sm leading-body\",\n    inner: \"pr-8 pb-5\",\n  },\n  lg: {\n    trigger:\n      \"min-h-[76px] gap-8 py-5 text-lg leading-body tracking-body max-[520px]:min-h-[68px] max-[520px]:text-base\",\n    panel: \"text-base leading-[1.65] max-[520px]:text-sm\",\n    inner: \"max-w-[62ch] pr-12 pb-6 max-[520px]:pr-6\",\n  },\n} as const;\n\nexport function Accordion({ items, defaultOpen = 0, size = \"md\" }: AccordionProps) {\n  const initialValue = defaultOpen >= 0 && defaultOpen < items.length ? String(defaultOpen) : \"\";\n  const [openValue, setOpenValue] = useState(initialValue);\n  const reduced = useReducedMotion();\n  const sized = sizes[size];\n  return (\n    <AccordionPrimitive.Root\n      className=\"w-full border-t border-line\"\n      type=\"single\"\n      collapsible\n      value={openValue}\n      onValueChange={setOpenValue}\n    >\n      {items.map((item, index) => {\n        const open = openValue === String(index);\n        return (\n          <AccordionPrimitive.Item\n            className=\"border-b border-line\"\n            value={String(index)}\n            key={`${item.title}-${index}`}\n          >\n            <AccordionPrimitive.Header className=\"m-0\">\n              <AccordionPrimitive.Trigger\n                className={cn(\n                  \"group/trigger flex w-full cursor-pointer items-center justify-between border-0 bg-transparent px-0 text-left font-medium text-ink [-webkit-tap-highlight-color:transparent] hover:text-ink-2\",\n                  fade,\n                  sized.trigger,\n                )}\n              >\n                <span>{item.title}</span>\n                <motion.span\n                  className={cn(\n                    \"inline-flex flex-none text-ink-3 group-hover/trigger:text-ink group-data-[state=open]/trigger:text-ink\",\n                    fade,\n                  )}\n                  initial={false}\n                  animate={{ rotate: open ? 180 : 0 }}\n                  transition={reduced ? { duration: 0 } : motionTokens.spring.snappy}\n                >\n                  <ChevronDown width={17} height={17} aria-hidden=\"true\" />\n                </motion.span>\n              </AccordionPrimitive.Trigger>\n            </AccordionPrimitive.Header>\n            {/* Radix keeps semantics and ids; motion owns the height so a toggle mid-flight retargets instead of restarting. */}\n            <AccordionPrimitive.Content forceMount asChild>\n              <motion.div\n                className={cn(\"overflow-hidden text-ink-2\", sized.panel)}\n                initial={false}\n                animate={open ? \"open\" : \"closed\"}\n                variants={reduced ? panelStill : panelMotion}\n              >\n                <motion.div\n                  className={cn(\"wrap-anywhere\", sized.inner)}\n                  variants={reduced ? contentStill : contentMotion}\n                >\n                  {item.content}\n                </motion.div>\n              </motion.div>\n            </AccordionPrimitive.Content>\n          </AccordionPrimitive.Item>\n        );\n      })}\n    </AccordionPrimitive.Root>\n  );\n}\n\nexport default Accordion;\n",
      "type": "registry:component",
      "target": "@components/iris/accordion/accordion.tsx"
    }
  ],
  "docs": "Docs and live preview: https://irisui.dev/components/accordion",
  "meta": {
    "kind": "component",
    "group": "Overlays & disclosure",
    "export": "Accordion",
    "tags": [
      "disclosure",
      "layout"
    ],
    "docs": "https://irisui.dev/components/accordion"
  }
}
