{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "accordion-dependencies",
  "title": "Accordion Dependencies",
  "description": "component for the Accordion Dependencies",
  "dependencies": [
    "@radix-ui/react-accordion",
    "lucide-react"
  ],
  "files": [
    {
      "path": "app/registry/spectrumui/accordion.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\nimport * as AccordionPrimitive from \"@radix-ui/react-accordion\";\nimport { ChevronDown } from \"lucide-react\";\n\nimport { cn } from \"@/lib/utils\";\n\ntype AccordionVariant = \"default\" | \"card\" | \"filled\" | \"ghost\";\ntype AccordionIndicator = \"chevron\" | \"plus\" | \"none\";\n\nconst AccordionStyleContext = React.createContext<{\n  variant: AccordionVariant;\n  indicator: AccordionIndicator;\n}>({ variant: \"default\", indicator: \"chevron\" });\n\nconst rootStyles: Record<AccordionVariant, string> = {\n  default: \"divide-y divide-black/[0.07] dark:divide-white/10\",\n  card: \"space-y-3\",\n  filled: \"space-y-2\",\n  ghost: \"space-y-1\",\n};\n\nconst itemStyles: Record<AccordionVariant, string> = {\n  default: \"\",\n  card: \"overflow-hidden rounded-2xl border border-black/[0.08] bg-white transition-colors data-[state=open]:border-black/[0.14] dark:border-white/10 dark:bg-white/[0.02] dark:data-[state=open]:border-white/20\",\n  filled:\n    \"overflow-hidden rounded-xl bg-black/[0.03] transition-colors data-[state=open]:bg-black/[0.05] dark:bg-white/[0.04] dark:data-[state=open]:bg-white/[0.07]\",\n  ghost: \"\",\n};\n\nconst triggerStyles: Record<AccordionVariant, string> = {\n  default:\n    \"gap-4 py-4 text-[15px] hover:text-black dark:hover:text-white [&[data-state=open]]:text-black dark:[&[data-state=open]]:text-white\",\n  card: \"gap-4 px-5 py-4 text-[15px] hover:text-black dark:hover:text-white\",\n  filled:\n    \"gap-4 px-4 py-3.5 text-[15px] hover:text-black dark:hover:text-white\",\n  ghost:\n    \"gap-3 rounded-lg py-3 text-[15px] hover:text-black dark:hover:text-white\",\n};\n\nconst contentStyles: Record<AccordionVariant, string> = {\n  default: \"pb-5 pr-8\",\n  card: \"px-5 pb-5 pr-10\",\n  filled: \"px-4 pb-4 pr-10\",\n  ghost: \"pb-4 pr-8\",\n};\n\nconst Accordion = ({\n  className,\n  variant = \"default\",\n  indicator = \"chevron\",\n  ...props\n}: React.ComponentProps<typeof AccordionPrimitive.Root> & {\n  variant?: AccordionVariant;\n  indicator?: AccordionIndicator;\n}) => (\n  <AccordionStyleContext.Provider value={{ variant, indicator }}>\n    <AccordionPrimitive.Root\n      className={cn(rootStyles[variant], className)}\n      {...props}\n    />\n  </AccordionStyleContext.Provider>\n);\nAccordion.displayName = \"Accordion\";\n\nconst AccordionItem = React.forwardRef<\n  React.ElementRef<typeof AccordionPrimitive.Item>,\n  React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Item>\n>(({ className, ...props }, ref) => {\n  const { variant } = React.useContext(AccordionStyleContext);\n  return (\n    <AccordionPrimitive.Item\n      ref={ref}\n      className={cn(itemStyles[variant], className)}\n      {...props}\n    />\n  );\n});\nAccordionItem.displayName = \"AccordionItem\";\n\nconst PlusIndicator = () => (\n  <span\n    aria-hidden=\"true\"\n    className=\"relative size-4 shrink-0 transition-transform duration-300 ease-out group-data-[state=open]:rotate-180\"\n  >\n    <span className=\"absolute left-0 top-1/2 h-[1.5px] w-4 -translate-y-1/2 rounded-full bg-current\" />\n    <span className=\"absolute left-1/2 top-0 h-4 w-[1.5px] -translate-x-1/2 rounded-full bg-current transition-transform duration-300 ease-out group-data-[state=open]:scale-y-0\" />\n  </span>\n);\n\nconst AccordionTrigger = React.forwardRef<\n  React.ElementRef<typeof AccordionPrimitive.Trigger>,\n  React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Trigger>\n>(({ className, children, ...props }, ref) => {\n  const { variant, indicator } = React.useContext(AccordionStyleContext);\n  return (\n    <AccordionPrimitive.Header className=\"flex\">\n      <AccordionPrimitive.Trigger\n        ref={ref}\n        className={cn(\n          \"group flex flex-1 items-center justify-between text-left font-medium text-neutral-700 outline-none transition-colors focus-visible:ring-2 focus-visible:ring-neutral-900/15 dark:text-neutral-200 dark:focus-visible:ring-white/25\",\n          triggerStyles[variant],\n          className,\n        )}\n        {...props}\n      >\n        {children}\n        {indicator === \"chevron\" && (\n          <ChevronDown\n            aria-hidden=\"true\"\n            className=\"size-4 shrink-0 text-neutral-400 transition-transform duration-300 ease-out group-data-[state=open]:rotate-180 dark:text-neutral-500\"\n            strokeWidth={2}\n          />\n        )}\n        {indicator === \"plus\" && <PlusIndicator />}\n      </AccordionPrimitive.Trigger>\n    </AccordionPrimitive.Header>\n  );\n});\nAccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName;\n\nconst AccordionContent = React.forwardRef<\n  React.ElementRef<typeof AccordionPrimitive.Content>,\n  React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Content>\n>(({ className, children, ...props }, ref) => {\n  const { variant } = React.useContext(AccordionStyleContext);\n  return (\n    <AccordionPrimitive.Content\n      ref={ref}\n      className=\"overflow-hidden text-sm leading-relaxed text-neutral-500 data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down dark:text-neutral-400\"\n      {...props}\n    >\n      <div className={cn(contentStyles[variant], className)}>{children}</div>\n    </AccordionPrimitive.Content>\n  );\n});\nAccordionContent.displayName = AccordionPrimitive.Content.displayName;\n\nconst usePrefersReducedMotion = () =>\n  React.useSyncExternalStore(\n    (onChange) => {\n      const query = window.matchMedia(\"(prefers-reduced-motion: reduce)\");\n      query.addEventListener(\"change\", onChange);\n      return () => query.removeEventListener(\"change\", onChange);\n    },\n    () => window.matchMedia(\"(prefers-reduced-motion: reduce)\").matches,\n    () => false,\n  );\n\nconst useIsOpen = (ref: React.RefObject<HTMLElement | null>) => {\n  const subscribe = React.useCallback(\n    (onChange: () => void) => {\n      const node = ref.current;\n      if (!node) return () => {};\n      const observer = new MutationObserver(onChange);\n      observer.observe(node, {\n        attributes: true,\n        attributeFilter: [\"data-state\"],\n      });\n      return () => observer.disconnect();\n    },\n    [ref],\n  );\n  const getSnapshot = React.useCallback(\n    () => ref.current?.getAttribute(\"data-state\") === \"open\",\n    [ref],\n  );\n  return React.useSyncExternalStore(subscribe, getSnapshot, () => false);\n};\n\nconst StreamShell = ({\n  text,\n  label,\n  icon,\n  streaming,\n  children,\n}: {\n  text: string;\n  label: string;\n  icon?: React.ReactNode;\n  streaming?: boolean;\n  children?: React.ReactNode;\n}) => (\n  <>\n    <div className=\"mb-2 flex items-center gap-1.5 text-[11px] font-medium uppercase tracking-[0.08em] text-neutral-400 dark:text-neutral-500\">\n      {icon}\n      <span>{label}</span>\n      {streaming && (\n        <span aria-hidden=\"true\" className=\"flex items-center gap-0.5\">\n          <span className=\"size-1 animate-pulse rounded-full bg-current [animation-duration:1s]\" />\n          <span className=\"size-1 animate-pulse rounded-full bg-current [animation-delay:200ms] [animation-duration:1s]\" />\n          <span className=\"size-1 animate-pulse rounded-full bg-current [animation-delay:400ms] [animation-duration:1s]\" />\n        </span>\n      )}\n    </div>\n    <div className=\"relative\">\n      <p aria-hidden=\"true\" className=\"invisible whitespace-pre-wrap\">\n        {text}\n        <span className=\"inline-block w-1\" />\n      </p>\n      {children}\n      <span className=\"sr-only\">{text}</span>\n    </div>\n  </>\n);\n\nconst StreamedAnswer = ({\n  text,\n  speed,\n  startDelay,\n  streamingLabel,\n  doneLabel,\n  icon,\n}: {\n  text: string;\n  speed: number;\n  startDelay: number;\n  streamingLabel: string;\n  doneLabel: string;\n  icon?: React.ReactNode;\n}) => {\n  const reducedMotion = usePrefersReducedMotion();\n  const [revealed, setRevealed] = React.useState(\n    reducedMotion ? text.length : 0,\n  );\n  const [thinking, setThinking] = React.useState(!reducedMotion);\n\n  React.useEffect(() => {\n    if (reducedMotion) return;\n\n    let index = 0;\n    let timer: ReturnType<typeof setTimeout>;\n\n    const tick = () => {\n      const chunk = 1 + (index % 3);\n      index = Math.min(text.length, index + chunk);\n      setRevealed(index);\n      if (index >= text.length) return;\n      const pause = /[.,!?;:]\\s?$/.test(text.slice(0, index)) ? 5 : 1;\n      timer = setTimeout(tick, speed * chunk * pause);\n    };\n\n    timer = setTimeout(() => {\n      setThinking(false);\n      tick();\n    }, startDelay);\n\n    return () => clearTimeout(timer);\n  }, [text, speed, startDelay, reducedMotion]);\n\n  const done = revealed >= text.length;\n\n  return (\n    <StreamShell\n      text={text}\n      icon={icon}\n      label={done ? doneLabel : streamingLabel}\n      streaming={!done}\n    >\n      <p aria-hidden=\"true\" className=\"absolute inset-0 whitespace-pre-wrap\">\n        {thinking ? (\n          <span className=\"inline-block h-[1em] w-24 animate-pulse rounded bg-current align-middle opacity-20 [animation-duration:1.2s]\" />\n        ) : (\n          text.slice(0, revealed)\n        )}\n        {!done && !thinking && (\n          <span className=\"ml-0.5 inline-block h-[0.95em] w-[3px] translate-y-[1px] animate-pulse rounded-full bg-neutral-900 align-middle [animation-duration:1s] dark:bg-neutral-100\" />\n        )}\n      </p>\n    </StreamShell>\n  );\n};\n\nconst AccordionStreamingContent = React.forwardRef<\n  React.ElementRef<typeof AccordionPrimitive.Content>,\n  Omit<\n    React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Content>,\n    \"children\"\n  > & {\n    text: string;\n    speed?: number;\n    startDelay?: number;\n    streamingLabel?: string;\n    doneLabel?: string;\n    icon?: React.ReactNode;\n  }\n>(\n  (\n    {\n      className,\n      text,\n      speed = 14,\n      startDelay = 420,\n      streamingLabel = \"Generating\",\n      doneLabel = \"Generated answer\",\n      icon,\n      ...props\n    },\n    ref,\n  ) => {\n    const { variant } = React.useContext(AccordionStyleContext);\n    const contentRef = React.useRef<HTMLDivElement | null>(null);\n    const open = useIsOpen(contentRef);\n\n    const assignRef = (node: HTMLDivElement | null) => {\n      contentRef.current = node;\n      if (typeof ref === \"function\") ref(node);\n      else if (ref) ref.current = node;\n    };\n\n    return (\n      <AccordionPrimitive.Content\n        ref={assignRef}\n        className=\"overflow-hidden text-sm leading-relaxed text-neutral-500 data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down dark:text-neutral-400\"\n        {...props}\n      >\n        <div className={cn(contentStyles[variant], className)}>\n          {open ? (\n            <StreamedAnswer\n              text={text}\n              speed={speed}\n              startDelay={startDelay}\n              streamingLabel={streamingLabel}\n              doneLabel={doneLabel}\n              icon={icon}\n            />\n          ) : (\n            <StreamShell text={text} label={doneLabel} icon={icon} />\n          )}\n        </div>\n      </AccordionPrimitive.Content>\n    );\n  },\n);\nAccordionStreamingContent.displayName = \"AccordionStreamingContent\";\n\nexport {\n  Accordion,\n  AccordionItem,\n  AccordionTrigger,\n  AccordionContent,\n  AccordionStreamingContent,\n};\n",
      "type": "registry:component",
      "target": "components/ui/accordion.tsx"
    }
  ],
  "type": "registry:component"
}
