{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "accordion",
  "title": "Accordion",
  "description": "An animated FAQ accordion with expandable panels, powered by Motion.",
  "dependencies": ["motion", "class-variance-authority"],
  "registryDependencies": ["utils", "@soralabs/hooks-use-auto-height"],
  "files": [
    {
      "path": "registry/primitives/disclosure/accordion/index.tsx",
      "content": "\"use client\";\n\nimport { cn } from \"@/lib/utils\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport {\n  type AnimationSequence,\n  stagger,\n  useAnimate,\n  useReducedMotion,\n} from \"motion/react\";\nimport {\n  type ComponentPropsWithoutRef,\n  createContext,\n  type ReactNode,\n  type RefObject,\n  useCallback,\n  useContext,\n  useEffect,\n  useId,\n  useRef,\n  useState,\n} from \"react\";\n\nimport { useAutoHeight } from \"@/hooks/use-auto-height\";\n\nconst EXPO_IN_OUT = [0.87, 0, 0.13, 1] as const;\nconst EXPO_OUT = [0.16, 1, 0.3, 1] as const;\nconst POWER2_IN_OUT = [0.45, 0, 0.55, 1] as const;\n\ntype IconMode = \"rotate\" | \"fade\" | \"both\";\ntype DisabledBreakpoint = \"mobile\" | \"tablet\" | \"desktop\";\ntype AccordionEase = readonly [number, number, number, number];\n\nconst DISABLE_MEDIA: Record<DisabledBreakpoint, string> = {\n  mobile: \"(max-width: 479px)\",\n  tablet: \"(max-width: 991px)\",\n  desktop: \"(min-width: 992px)\",\n};\n\ninterface AccordionContextValue {\n  allowMultiple: boolean;\n  animationsPaused: boolean;\n  defaultDuration: number;\n  defaultEase: AccordionEase;\n  defaultIconRotation: number;\n  disabledOn?: DisabledBreakpoint[];\n  iconMode: IconMode;\n  notifyItemOpen: (id: string) => void;\n  registerItem: (id: string, close: () => void) => void;\n  unregisterItem: (id: string) => void;\n}\n\ninterface AccordionItemContextValue {\n  contentId: string;\n  contentPanelRef: RefObject<HTMLElement | null>;\n  iconRef: RefObject<SVGSVGElement | null>;\n  isDisabled: boolean;\n  isOpen: boolean;\n  setPanelHeight: (height: number) => void;\n  toggle: () => void;\n  triggerId: string;\n  verticalBarRef: RefObject<SVGPathElement | null>;\n}\n\nconst AccordionContext = createContext<AccordionContextValue | null>(null);\nconst AccordionItemContext = createContext<AccordionItemContextValue | null>(\n  null\n);\n\nfunction useAccordionContext() {\n  const context = useContext(AccordionContext);\n  if (!context) {\n    throw new Error(\"Accordion components must be used within <Accordion>.\");\n  }\n  return context;\n}\n\nfunction useAccordionItemContext() {\n  const context = useContext(AccordionItemContext);\n  if (!context) {\n    throw new Error(\n      \"Accordion subcomponents must be used within <AccordionItem>.\"\n    );\n  }\n  return context;\n}\n\nfunction useDisabledOn(disabledOn?: DisabledBreakpoint[]) {\n  const [disabled, setDisabled] = useState(false);\n\n  useEffect(() => {\n    if (!disabledOn?.length) {\n      setDisabled(false);\n      return;\n    }\n\n    const mediaQueries = disabledOn.map((breakpoint) =>\n      window.matchMedia(DISABLE_MEDIA[breakpoint])\n    );\n\n    const update = () => {\n      setDisabled(mediaQueries.some((mediaQuery) => mediaQuery.matches));\n    };\n\n    update();\n\n    for (const mediaQuery of mediaQueries) {\n      mediaQuery.addEventListener(\"change\", update);\n    }\n\n    return () => {\n      for (const mediaQuery of mediaQueries) {\n        mediaQuery.removeEventListener(\"change\", update);\n      }\n    };\n  }, [disabledOn]);\n\n  return disabled;\n}\n\nconst accordionVariants = cva(\"flex w-full flex-col\");\n\nconst accordionItemVariants = cva(\"border-border border-b last:border-b-0\");\n\nconst accordionTriggerVariants = cva([\n  \"flex w-full items-center justify-between gap-4 border-0 bg-transparent py-5 text-left\",\n  \"cursor-pointer text-muted-foreground transition-colors duration-[400ms] ease-[cubic-bezier(0.16,1,0.3,1)]\",\n  \"will-change-transform [transform:translateZ(0)] hover:text-foreground\",\n  \"focus:outline-none focus-visible:rounded-sm focus-visible:outline-2 focus-visible:outline-[#fd551d] focus-visible:outline-offset-4\",\n  \"data-[state=open]:text-foreground\",\n]);\n\nconst accordionTitleVariants = cva(\n  \"m-0 font-medium text-base text-inherit leading-[1.4]\"\n);\n\nconst accordionContentVariants = cva(\n  \"overflow-hidden will-change-[height] [transform:translateZ(0)]\"\n);\n\nconst accordionTextVariants = cva(\n  \"m-0 pb-5 text-muted-foreground text-sm leading-[1.6]\"\n);\n\nexport interface AccordionItemData {\n  content: ReactNode;\n  defaultOpen?: boolean;\n  /** Reveal panel text line by line when this item opens. */\n  enableStagger?: boolean;\n  id?: string;\n  title: string;\n}\n\nexport interface AccordionProps\n  extends Omit<ComponentPropsWithoutRef<\"div\">, \"children\">,\n    VariantProps<typeof accordionVariants> {\n  allowMultiple?: boolean;\n  children?: ReactNode;\n  /**\n   * Disable accordion animations on matching breakpoints.\n   * Mirrors `data-anm-disable` on the sandbox section.\n   */\n  disabledOn?: DisabledBreakpoint[];\n  duration?: number;\n  ease?: AccordionEase;\n  iconMode?: IconMode;\n  iconRotation?: number;\n  items?: AccordionItemData[];\n}\n\nexport interface AccordionItemProps\n  extends Omit<ComponentPropsWithoutRef<\"div\">, \"children\"> {\n  children: ReactNode;\n  defaultOpen?: boolean;\n  delay?: number;\n  disabledOn?: DisabledBreakpoint[];\n  duration?: number;\n  ease?: AccordionEase;\n  /**\n   * Reveal panel text line by line when the item opens.\n   * @default false\n   */\n  enableStagger?: boolean;\n  iconRotation?: number;\n  onClose?: () => void;\n  onOpen?: () => void;\n  /**\n   * Delay between each staggered line, in seconds.\n   * @default 0.15\n   */\n  staggerDelay?: number;\n  /**\n   * Duration of each line reveal, in seconds.\n   * @default 0.6\n   */\n  staggerDuration?: number;\n  /**\n   * Easing curve for the line reveal.\n   * @default expo.out\n   */\n  staggerEase?: AccordionEase;\n  /**\n   * Wait before the line reveal starts after opening, in milliseconds.\n   * @default 200\n   */\n  staggerStartDelay?: number;\n  /**\n   * Initial vertical offset of each line, as a percentage of its height.\n   * @default 110\n   */\n  staggerYPercent?: number;\n}\n\nexport interface AccordionTriggerProps\n  extends ComponentPropsWithoutRef<\"button\"> {\n  children: ReactNode;\n  hideIcon?: boolean;\n}\n\nexport interface AccordionContentProps\n  extends ComponentPropsWithoutRef<\"section\"> {\n  children: ReactNode;\n}\n\nexport interface AccordionTextProps\n  extends Omit<ComponentPropsWithoutRef<\"p\">, \"children\"> {\n  children: ReactNode;\n}\n\nfunction renderAccordionItemContent(content: ReactNode) {\n  if (typeof content === \"string\") {\n    return <AccordionText>{content}</AccordionText>;\n  }\n\n  return content;\n}\n\ninterface SplitLinesResult {\n  lines: HTMLElement[];\n  revert: () => void;\n}\n\nconst WHITESPACE_REGEX = /\\s+/;\n\n/**\n * Lightweight SplitText alternative: wraps each word to measure natural line\n * breaks, then rebuilds the element as masked line blocks ready to animate.\n * Nested markup is flattened to plain text while the split is active.\n */\nfunction splitElementIntoLines(element: HTMLElement): SplitLinesResult {\n  const originalNodes = Array.from(element.childNodes);\n  const words = (element.textContent ?? \"\")\n    .split(WHITESPACE_REGEX)\n    .filter(Boolean);\n\n  element.replaceChildren();\n  const wordSpans: HTMLSpanElement[] = [];\n  for (const word of words) {\n    const span = document.createElement(\"span\");\n    span.style.display = \"inline-block\";\n    span.textContent = word;\n    element.append(span, document.createTextNode(\" \"));\n    wordSpans.push(span);\n  }\n\n  const lineGroups: string[][] = [];\n  let lastTop: number | null = null;\n  for (const span of wordSpans) {\n    const top = span.offsetTop;\n    if (lastTop === null || Math.abs(top - lastTop) > 1) {\n      lineGroups.push([]);\n      lastTop = top;\n    }\n    lineGroups.at(-1)?.push(span.textContent ?? \"\");\n  }\n\n  element.replaceChildren();\n  const lines: HTMLElement[] = [];\n  for (const group of lineGroups) {\n    const mask = document.createElement(\"div\");\n    mask.style.display = \"block\";\n    mask.style.overflow = \"hidden\";\n\n    const line = document.createElement(\"div\");\n    line.style.display = \"block\";\n    line.style.willChange = \"transform\";\n    line.textContent = group.join(\" \");\n\n    mask.appendChild(line);\n    element.appendChild(mask);\n    lines.push(line);\n  }\n\n  return {\n    lines,\n    revert: () => {\n      element.replaceChildren(...originalNodes);\n    },\n  };\n}\n\nfunction Accordion({\n  allowMultiple = false,\n  children,\n  className,\n  disabledOn,\n  duration = 0.8,\n  ease = EXPO_IN_OUT,\n  iconMode = \"both\",\n  iconRotation = -180,\n  items,\n  ...props\n}: AccordionProps) {\n  const itemsRef = useRef(new Map<string, () => void>());\n  const [animationsPaused, setAnimationsPaused] = useState(false);\n\n  useEffect(() => {\n    const handleVisibilityChange = () => {\n      setAnimationsPaused(document.hidden);\n    };\n\n    document.addEventListener(\"visibilitychange\", handleVisibilityChange);\n    return () => {\n      document.removeEventListener(\"visibilitychange\", handleVisibilityChange);\n    };\n  }, []);\n\n  const registerItem = useCallback((id: string, close: () => void) => {\n    itemsRef.current.set(id, close);\n  }, []);\n\n  const unregisterItem = useCallback((id: string) => {\n    itemsRef.current.delete(id);\n  }, []);\n\n  const notifyItemOpen = useCallback(\n    (id: string) => {\n      if (allowMultiple) {\n        return;\n      }\n\n      for (const [itemId, close] of itemsRef.current.entries()) {\n        if (itemId !== id) {\n          close();\n        }\n      }\n    },\n    [allowMultiple]\n  );\n\n  return (\n    <AccordionContext.Provider\n      value={{\n        allowMultiple,\n        animationsPaused,\n        defaultDuration: duration,\n        defaultEase: ease,\n        defaultIconRotation: iconRotation,\n        disabledOn,\n        iconMode,\n        notifyItemOpen,\n        registerItem,\n        unregisterItem,\n      }}\n    >\n      <div\n        className={cn(accordionVariants(), className)}\n        data-anm-accordion=\"\"\n        data-anm-allow-multiple={allowMultiple ? \"true\" : \"false\"}\n        {...props}\n      >\n        {items?.map((item, index) => (\n          <AccordionItem\n            defaultOpen={item.defaultOpen}\n            enableStagger={item.enableStagger}\n            key={item.id ?? item.title ?? index}\n          >\n            <AccordionTrigger>{item.title}</AccordionTrigger>\n            <AccordionContent>\n              {renderAccordionItemContent(item.content)}\n            </AccordionContent>\n          </AccordionItem>\n        ))}\n        {children}\n      </div>\n    </AccordionContext.Provider>\n  );\n}\n\nfunction AccordionItem({\n  children,\n  className,\n  defaultOpen = false,\n  delay = 0,\n  disabledOn: itemDisabledOn,\n  duration: itemDuration,\n  ease: itemEase,\n  enableStagger = false,\n  iconRotation: itemIconRotation,\n  onClose,\n  onOpen,\n  staggerDelay = 0.15,\n  staggerDuration = 0.6,\n  staggerEase = EXPO_OUT,\n  staggerStartDelay = 200,\n  staggerYPercent = 110,\n  ...props\n}: AccordionItemProps) {\n  const {\n    animationsPaused,\n    defaultDuration,\n    defaultEase,\n    defaultIconRotation,\n    disabledOn: sectionDisabledOn,\n    iconMode,\n    notifyItemOpen,\n    registerItem,\n    unregisterItem,\n  } = useAccordionContext();\n\n  const itemId = useId();\n  const triggerId = `${itemId}-trigger`;\n  const contentId = `${itemId}-content`;\n  const itemRef = useRef<HTMLDivElement>(null);\n  const contentPanelRef = useRef<HTMLElement>(null);\n  const iconRef = useRef<SVGSVGElement>(null);\n  const verticalBarRef = useRef<SVGPathElement>(null);\n  const [, animate] = useAnimate();\n  const animationRef = useRef<{ stop: () => void } | null>(null);\n  const hasAppliedInitialOpen = useRef(false);\n  const shouldReduceMotion = useReducedMotion();\n  const splitRevertsRef = useRef<(() => void)[]>([]);\n  const staggerAnimationRef = useRef<{ stop: () => void } | null>(null);\n  const staggerTimeoutRef = useRef<number | null>(null);\n\n  const [isOpen, setIsOpen] = useState(defaultOpen);\n  const [panelHeight, setPanelHeight] = useState(0);\n  const isOpenRef = useRef(isOpen);\n\n  useEffect(() => {\n    isOpenRef.current = isOpen;\n  }, [isOpen]);\n\n  const sectionAnimationsDisabled = useDisabledOn(sectionDisabledOn);\n  const itemAnimationsDisabled = useDisabledOn(itemDisabledOn);\n  const animationsDisabled =\n    sectionAnimationsDisabled || itemAnimationsDisabled;\n\n  const duration = itemDuration ?? defaultDuration;\n  const ease = itemEase ?? defaultEase;\n  const iconRotation = itemIconRotation ?? defaultIconRotation;\n\n  const close = useCallback(() => {\n    isOpenRef.current = false;\n    setIsOpen(false);\n  }, []);\n\n  useEffect(() => {\n    registerItem(itemId, close);\n    return () => unregisterItem(itemId);\n  }, [close, itemId, registerItem, unregisterItem]);\n\n  const dispatchItemEvent = useCallback(\n    (type: \"anm-accordion-open\" | \"anm-accordion-close\") => {\n      const node = itemRef.current;\n      const content = contentPanelRef.current;\n      if (!node) {\n        return;\n      }\n\n      node.dispatchEvent(\n        new CustomEvent(type, {\n          bubbles: true,\n          detail: { item: node, content },\n        })\n      );\n    },\n    []\n  );\n\n  const toggle = useCallback(() => {\n    if (isOpenRef.current) {\n      onClose?.();\n      dispatchItemEvent(\"anm-accordion-close\");\n      close();\n      return;\n    }\n\n    notifyItemOpen(itemId);\n    onOpen?.();\n    dispatchItemEvent(\"anm-accordion-open\");\n    isOpenRef.current = true;\n    setIsOpen(true);\n  }, [close, dispatchItemEvent, itemId, notifyItemOpen, onClose, onOpen]);\n\n  const clearIconInlineStyles = useCallback(() => {\n    iconRef.current?.style.removeProperty(\"transform\");\n    verticalBarRef.current?.style.removeProperty(\"opacity\");\n  }, []);\n\n  const applyStaticPanelState = useCallback(\n    (open: boolean, height: number) => {\n      const panel = contentPanelRef.current;\n      const icon = iconRef.current;\n      const vertical = verticalBarRef.current;\n      if (!panel) {\n        return;\n      }\n\n      panel.style.height = open ? `${height}px` : \"0px\";\n\n      if (icon) {\n        const rotateIcon = iconMode === \"rotate\" || iconMode === \"both\";\n        icon.style.transform =\n          rotateIcon && open ? `rotate(${iconRotation}deg)` : \"rotate(0deg)\";\n      }\n\n      if (vertical && (iconMode === \"fade\" || iconMode === \"both\")) {\n        vertical.style.opacity = open ? \"0\" : \"1\";\n      }\n    },\n    [iconMode, iconRotation]\n  );\n\n  const toInstantSequence = useCallback(\n    (sequence: AnimationSequence): AnimationSequence =>\n      sequence.map((segment) => {\n        const [element, keyframes, options] = segment as [\n          Element,\n          Record<string, unknown>,\n          { at?: number; duration?: number; ease?: AccordionEase },\n        ];\n\n        return [element, keyframes, { ...options, duration: 0, at: 0 }];\n      }) as AnimationSequence,\n    []\n  );\n\n  const buildPanelSequence = useCallback(\n    (open: boolean): AnimationSequence | null => {\n      const panel = contentPanelRef.current;\n      const icon = iconRef.current;\n      const vertical = verticalBarRef.current;\n      if (!panel) {\n        return null;\n      }\n\n      const rotateIcon = iconMode === \"rotate\" || iconMode === \"both\";\n      const fadeVertical =\n        (iconMode === \"fade\" || iconMode === \"both\") && vertical;\n\n      if (open) {\n        const openSequence: AnimationSequence = [\n          [\n            panel,\n            { height: `${panelHeight}px` },\n            { duration, ease, at: delay },\n          ],\n        ];\n\n        if (rotateIcon && icon) {\n          openSequence.push([\n            icon,\n            { rotate: iconRotation },\n            { duration, ease, at: delay },\n          ]);\n        }\n\n        if (fadeVertical) {\n          openSequence.push([\n            vertical,\n            { opacity: 0 },\n            {\n              duration: duration * 0.5,\n              ease: POWER2_IN_OUT,\n              at: delay + duration * 0.25,\n            },\n          ]);\n        }\n\n        return openSequence;\n      }\n\n      const closeSequence: AnimationSequence = [];\n\n      if (fadeVertical) {\n        closeSequence.push([\n          vertical,\n          { opacity: 1 },\n          { duration: duration * 0.5, ease: POWER2_IN_OUT, at: delay },\n        ]);\n      }\n\n      if (rotateIcon && icon) {\n        closeSequence.push([\n          icon,\n          { rotate: 0 },\n          { duration, ease, at: delay },\n        ]);\n      }\n\n      closeSequence.push([panel, { height: 0 }, { duration, ease, at: delay }]);\n      return closeSequence;\n    },\n    [delay, duration, ease, iconMode, iconRotation, panelHeight]\n  );\n\n  useEffect(() => {\n    animationRef.current?.stop();\n\n    if (animationsDisabled || shouldReduceMotion) {\n      applyStaticPanelState(isOpen, panelHeight);\n      return;\n    }\n\n    if (animationsPaused) {\n      return;\n    }\n\n    if (defaultOpen && isOpen && !hasAppliedInitialOpen.current) {\n      if (panelHeight > 0) {\n        hasAppliedInitialOpen.current = true;\n        const openSequence = buildPanelSequence(true);\n        if (openSequence) {\n          clearIconInlineStyles();\n          animationRef.current = animate(toInstantSequence(openSequence));\n        }\n      }\n      return;\n    }\n\n    if (isOpen && panelHeight === 0) {\n      return;\n    }\n\n    const sequence = buildPanelSequence(isOpen);\n    if (!sequence) {\n      return;\n    }\n\n    clearIconInlineStyles();\n    animationRef.current = animate(sequence);\n  }, [\n    animate,\n    animationsPaused,\n    applyStaticPanelState,\n    buildPanelSequence,\n    clearIconInlineStyles,\n    defaultOpen,\n    animationsDisabled,\n    isOpen,\n    panelHeight,\n    shouldReduceMotion,\n    toInstantSequence,\n  ]);\n\n  const resetTextStagger = useCallback(() => {\n    if (staggerTimeoutRef.current !== null) {\n      window.clearTimeout(staggerTimeoutRef.current);\n      staggerTimeoutRef.current = null;\n    }\n    staggerAnimationRef.current?.stop();\n    staggerAnimationRef.current = null;\n    for (const revert of splitRevertsRef.current) {\n      revert();\n    }\n    splitRevertsRef.current = [];\n  }, []);\n\n  const animateTextStagger = useCallback(() => {\n    const panel = contentPanelRef.current;\n    if (!panel || splitRevertsRef.current.length > 0) {\n      return;\n    }\n\n    const targets = panel.querySelectorAll<HTMLElement>(\"p, .accordion_text\");\n    const lines: HTMLElement[] = [];\n    for (const target of targets) {\n      const split = splitElementIntoLines(target);\n      splitRevertsRef.current.push(split.revert);\n      lines.push(...split.lines);\n    }\n\n    if (lines.length === 0) {\n      return;\n    }\n\n    for (const line of lines) {\n      line.style.transform = `translateY(${staggerYPercent}%)`;\n    }\n\n    staggerAnimationRef.current = animate(\n      lines,\n      { transform: \"translateY(0%)\" },\n      {\n        delay: stagger(staggerDelay),\n        duration: staggerDuration,\n        ease: staggerEase,\n      }\n    );\n  }, [animate, staggerDelay, staggerDuration, staggerEase, staggerYPercent]);\n\n  useEffect(() => {\n    if (!enableStagger) {\n      return;\n    }\n\n    if (!isOpen || animationsDisabled || shouldReduceMotion) {\n      resetTextStagger();\n      return;\n    }\n\n    staggerTimeoutRef.current = window.setTimeout(() => {\n      staggerTimeoutRef.current = null;\n      document.fonts.ready.then(() => {\n        if (isOpenRef.current) {\n          animateTextStagger();\n        }\n      });\n    }, staggerStartDelay);\n\n    return () => {\n      if (staggerTimeoutRef.current !== null) {\n        window.clearTimeout(staggerTimeoutRef.current);\n        staggerTimeoutRef.current = null;\n      }\n    };\n  }, [\n    animateTextStagger,\n    animationsDisabled,\n    enableStagger,\n    isOpen,\n    resetTextStagger,\n    shouldReduceMotion,\n    staggerStartDelay,\n  ]);\n\n  useEffect(() => resetTextStagger, [resetTextStagger]);\n\n  return (\n    <AccordionItemContext.Provider\n      value={{\n        contentId,\n        contentPanelRef,\n        iconRef,\n        isDisabled: animationsDisabled,\n        isOpen,\n        setPanelHeight,\n        toggle,\n        triggerId,\n        verticalBarRef,\n      }}\n    >\n      <div\n        className={cn(accordionItemVariants(), className)}\n        data-anm-accordion-item\n        data-state={isOpen ? \"open\" : \"closed\"}\n        ref={itemRef}\n        {...props}\n      >\n        {children}\n      </div>\n    </AccordionItemContext.Provider>\n  );\n}\n\nfunction AccordionTrigger({\n  children,\n  className,\n  disabled,\n  hideIcon = false,\n  onClick,\n  ...props\n}: AccordionTriggerProps) {\n  const {\n    contentId,\n    iconRef,\n    isDisabled,\n    isOpen,\n    toggle,\n    triggerId,\n    verticalBarRef,\n  } = useAccordionItemContext();\n  const isTriggerDisabled = Boolean(disabled || isDisabled);\n\n  return (\n    <button\n      {...props}\n      aria-controls={contentId}\n      aria-expanded={isOpen}\n      className={cn(accordionTriggerVariants(), className)}\n      data-anm-accordion-trigger\n      data-state={isOpen ? \"open\" : \"closed\"}\n      disabled={isTriggerDisabled}\n      id={triggerId}\n      onClick={(event) => {\n        onClick?.(event);\n        if (!(event.defaultPrevented || isTriggerDisabled)) {\n          toggle();\n        }\n      }}\n      type=\"button\"\n    >\n      <span className={accordionTitleVariants()}>{children}</span>\n      {hideIcon ? null : (\n        <svg\n          aria-hidden=\"true\"\n          className=\"size-4 shrink-0 text-current will-change-transform [transform:translateZ(0)]\"\n          data-anm-accordion-icon\n          fill=\"none\"\n          ref={iconRef}\n          viewBox=\"0 0 16 16\"\n        >\n          <path\n            d=\"M8 1V15\"\n            data-anm-accordion-icon-vertical\n            ref={verticalBarRef}\n            stroke=\"currentColor\"\n            strokeLinecap=\"round\"\n            strokeWidth={2}\n          />\n          <path\n            d=\"M1 8H15\"\n            data-anm-accordion-icon-horizontal\n            stroke=\"currentColor\"\n            strokeLinecap=\"round\"\n            strokeWidth={2}\n          />\n        </svg>\n      )}\n    </button>\n  );\n}\n\nfunction AccordionContent({\n  children,\n  className,\n  style,\n  ...props\n}: AccordionContentProps) {\n  const { contentId, contentPanelRef, isOpen, setPanelHeight, triggerId } =\n    useAccordionItemContext();\n  const { ref, height } = useAutoHeight<HTMLDivElement>([children, isOpen]);\n\n  useEffect(() => {\n    setPanelHeight(height);\n  }, [height, setPanelHeight]);\n\n  return (\n    <section\n      {...props}\n      aria-hidden={!isOpen}\n      aria-labelledby={triggerId}\n      className={cn(accordionContentVariants(), className)}\n      data-anm-accordion-content=\"\"\n      id={contentId}\n      ref={contentPanelRef}\n      style={{ ...style, height: 0 }}\n    >\n      <div ref={ref}>{children}</div>\n    </section>\n  );\n}\n\nfunction AccordionText({ children, className, ...props }: AccordionTextProps) {\n  return (\n    <p\n      className={cn(accordionTextVariants(), \"accordion_text\", className)}\n      {...props}\n    >\n      {children}\n    </p>\n  );\n}\n\nexport type { AccordionEase, DisabledBreakpoint, IconMode };\nexport {\n  Accordion,\n  AccordionContent,\n  AccordionItem,\n  AccordionText,\n  AccordionTrigger,\n  accordionContentVariants,\n  accordionItemVariants,\n  accordionTextVariants,\n  accordionTitleVariants,\n  accordionTriggerVariants,\n  accordionVariants,\n};\n",
      "type": "registry:ui",
      "target": "components/sora-ui/disclosure/accordion.tsx"
    }
  ],
  "meta": {
    "inspiration": {
      "type": "reimplemented",
      "label": "Annnimate",
      "url": "https://www.annnimate.com",
      "stack": "Motion and React"
    }
  },
  "type": "registry:ui"
}
