{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "accordion",
  "type": "registry:ui",
  "title": "Accordion",
  "description": "Stacked sections that grow to fit, with answers find-in-page can still reach.",
  "categories": [
    "disclosure"
  ],
  "dependencies": [
    "@base-ui/react"
  ],
  "registryDependencies": [
    "https://www.stealth.pm/r/tokens.json",
    "https://www.stealth.pm/r/cn.json"
  ],
  "files": [
    {
      "path": "components/ui/accordion.tsx",
      "content": "\"use client\";\nimport { Accordion as BaseAccordion } from \"@base-ui/react/accordion\";\nimport { createContext, use } from \"react\";\nimport { cn } from \"@/lib/cn\";\n\ntype Variant = \"card\" | \"flush\";\ntype Indicator = \"chevron\" | \"plus\";\n\nconst AccordionContext = createContext<{ variant: Variant; indicator: Indicator }>({ variant: \"card\", indicator: \"chevron\" });\n\nexport type AccordionProps<Value = string> = Omit<BaseAccordion.Root.Props<Value>, \"className\"> & {\n  /** A bordered card with hairlines between items, or bare rows that sit in the page's own column. */\n  variant?: Variant;\n  /** A chevron that turns over, or a plus whose upright stroke lies down into a minus. */\n  indicator?: Indicator;\n  className?: string;\n};\n\n/**\n * Single by default; pass `multiple` to let several stay open.\n * Closed panels stay in the DOM as `hidden=\"until-found\"`, so the browser's\n * find-in-page can search them and opens the one that matches.\n */\nexport function Accordion<Value = string>({\n  variant = \"card\",\n  indicator = \"chevron\",\n  hiddenUntilFound = true,\n  className,\n  ...rest\n}: AccordionProps<Value>) {\n  return (\n    <AccordionContext value={{ variant, indicator }}>\n      <BaseAccordion.Root<Value>\n        data-variant={variant}\n        hiddenUntilFound={hiddenUntilFound}\n        className={cn(\n          \"flex w-full min-w-0 flex-col\",\n          variant === \"card\" && \"overflow-hidden rounded-xl border border-line-2 bg-raised shadow-[var(--shadow)]\",\n          className,\n        )}\n        {...rest}\n      />\n    </AccordionContext>\n  );\n}\n\nexport type AccordionItemProps = Omit<BaseAccordion.Item.Props, \"className\"> & { className?: string };\n\nexport function AccordionItem({ className, ...rest }: AccordionItemProps) {\n  const { variant } = use(AccordionContext);\n  return (\n    <BaseAccordion.Item\n      className={cn(\n        \"group/item relative min-w-0\",\n        variant === \"card\" ? \"border-t border-line first:border-t-0\" : \"border-b border-line\",\n        className,\n      )}\n      {...rest}\n    />\n  );\n}\n\nexport type AccordionTriggerProps = Omit<BaseAccordion.Trigger.Props, \"className\" | \"children\"> & {\n  children: React.ReactNode;\n  /** 16px icon before the title. */\n  icon?: React.ReactNode;\n  /** Short text or a count before the indicator: \"3 open\", \"Pro\". Read as part of the button's name. */\n  hint?: React.ReactNode;\n  /** The heading level wrapping the trigger, so the outline of the page stays correct. */\n  level?: 2 | 3 | 4 | 5 | 6;\n  className?: string;\n};\n\nexport function AccordionTrigger({ children, icon, hint, level = 3, className, ...rest }: AccordionTriggerProps) {\n  const { variant, indicator } = use(AccordionContext);\n  const Heading = `h${level}` as const;\n  return (\n    <BaseAccordion.Header render={<Heading />} className=\"m-0 flex\">\n      <BaseAccordion.Trigger\n        className={cn(\n          // Accessories align to the first line, so a title that wraps keeps its icon and chevron beside its opening words.\n          \"group/trigger relative flex min-h-11 min-w-0 flex-1 select-none items-start gap-3 text-left\",\n          \"touch-manipulation [-webkit-tap-highlight-color:transparent]\",\n          \"text-[13px] font-medium leading-[18px] tracking-[-0.005em] text-fg\",\n          \"outline-none focus-visible:outline-solid focus-visible:outline-1 focus-visible:outline-fg-3\",\n          \"transition-[background-color,color] duration-150 ease-out\",\n          \"data-disabled:cursor-not-allowed data-disabled:text-fg-4\",\n          variant === \"card\"\n            ? // Full-bleed rows: the wash fills the row, the ring sits just inside it.\n              \"px-4 py-[13px] focus-visible:-outline-offset-2 focus-visible:rounded-[10px] hover:not-data-disabled:bg-fg/[0.035] active:not-data-disabled:bg-fg/[0.06]\"\n            : // Bare rows: text aligns with the column; the wash reaches 8px past it on each side.\n              \"-mx-2 rounded-lg px-2 py-[13px] focus-visible:-outline-offset-1 hover:not-data-disabled:bg-hover active:not-data-disabled:bg-fg/[0.06]\",\n          className,\n        )}\n        {...rest}\n      >\n        {icon && (\n          <span data-accordion-icon=\"\" className=\"grid h-[18px] w-4 shrink-0 place-items-center text-fg-3 transition-colors duration-150 group-hover/trigger:text-fg-2 group-data-[panel-open]/trigger:text-fg group-data-disabled/trigger:text-fg-4\">\n            {icon}\n          </span>\n        )}\n        <span className=\"min-w-0 flex-1 text-balance\">{children}</span>\n        {hint != null && <span className=\"shrink-0 text-[12px] font-normal leading-[18px] tabular text-fg-3 group-data-disabled/trigger:text-fg-4\">{hint}</span>}\n        <span className=\"-mr-0.5 grid h-[18px] w-5 shrink-0 place-items-center text-fg-3 transition-colors duration-150 group-hover/trigger:text-fg group-data-disabled/trigger:text-fg-4\">\n          {indicator === \"plus\" ? <PlusMinus /> : <Chevron />}\n        </span>\n      </BaseAccordion.Trigger>\n    </BaseAccordion.Header>\n  );\n}\n\n/**\n * Turns over with the panel. On hover it leans 1px the way it will go,\n * so the row says which direction it opens before it is pressed.\n */\nfunction Chevron() {\n  return (\n    <svg\n      width=\"16\"\n      height=\"16\"\n      viewBox=\"0 0 16 16\"\n      fill=\"none\"\n      stroke=\"currentColor\"\n      strokeWidth={1.4}\n      strokeLinecap=\"round\"\n      strokeLinejoin=\"round\"\n      aria-hidden\n      className={cn(\n        \"transition-[rotate,translate] duration-[260ms] ease-in-out-quart motion-reduce:transition-none\",\n        \"group-hover/trigger:translate-y-px group-data-[panel-open]/trigger:rotate-180 group-hover/trigger:group-data-[panel-open]/trigger:-translate-y-px\",\n        \"group-data-disabled/trigger:translate-y-0!\",\n      )}\n    >\n      <path d=\"m4.5 6.25 3.5 3.5 3.5-3.5\" />\n    </svg>\n  );\n}\n\n/** The upright stroke turns a quarter and fades as it lies on the crossbar: plus becomes minus without a doubled stroke. */\nfunction PlusMinus() {\n  return (\n    <svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"currentColor\" strokeWidth={1.4} strokeLinecap=\"round\" aria-hidden>\n      <path d=\"M3.5 8h9\" />\n      <path\n        d=\"M8 3.5v9\"\n        className=\"origin-center transition-[rotate,opacity] duration-[260ms] ease-in-out-quart [transform-box:fill-box] motion-reduce:transition-none group-data-[panel-open]/trigger:rotate-90 group-data-[panel-open]/trigger:opacity-0\"\n      />\n    </svg>\n  );\n}\n\nexport type AccordionPanelProps = Omit<BaseAccordion.Panel.Props, \"className\"> & {\n  className?: string;\n  /** Classes for the padded inner box. */\n  contentClassName?: string;\n};\n\n/**\n * Height grows from the measured `--accordion-panel-height`; the words fade up\n * 60ms later, once there is room for them, so text never slides out from under a clip.\n */\nexport function AccordionPanel({ className, contentClassName, children, ...rest }: AccordionPanelProps) {\n  const { variant } = use(AccordionContext);\n  return (\n    <BaseAccordion.Panel\n      className={cn(\n        \"group/panel h-(--accordion-panel-height) overflow-hidden\",\n        \"transition-[height] duration-[260ms] ease-out-quart data-ending-style:duration-200 data-ending-style:ease-in-out-quart\",\n        \"data-starting-style:h-0 data-ending-style:h-0 motion-reduce:transition-none\",\n        className,\n      )}\n      {...rest}\n    >\n      <div\n        className={cn(\n          \"text-[13px] leading-[1.6] text-fg-2 text-pretty\",\n          \"transition-[opacity,translate] delay-[60ms] duration-[280ms] ease-out-expo\",\n          \"group-data-[starting-style]/panel:-translate-y-1 group-data-[starting-style]/panel:opacity-0\",\n          // Leaving: no delay and no travel, just out of the way before the height closes over it.\n          \"group-data-[ending-style]/panel:opacity-0 group-data-[ending-style]/panel:delay-0 group-data-[ending-style]/panel:duration-[140ms]\",\n          \"motion-reduce:translate-y-0 motion-reduce:transition-[opacity] motion-reduce:delay-0\",\n          // With a leading icon, the answer lines up under the title, not under the icon.\n          variant === \"card\" ? \"px-4 pb-4 group-has-[[data-accordion-icon]]/item:pl-11\" : \"pb-4 group-has-[[data-accordion-icon]]/item:pl-7\",\n          contentClassName,\n        )}\n      >\n        {children}\n      </div>\n    </BaseAccordion.Panel>\n  );\n}\n",
      "type": "registry:ui"
    }
  ],
  "docs": "https://www.stealth.pm/ui/accordion"
}
