{
  "schemaVersion": 1,
  "target": "react",
  "name": "expandable-panel",
  "version": "0.2.0",
  "category": "core",
  "title": "Expandable Panel",
  "description": "A compact disclosure with a leading chevron that reveals a region below it, with a CSS-only height animation and content fade.",
  "tags": [
    "disclosure",
    "collapsible",
    "expand",
    "details"
  ],
  "requires": {
    "projectTypes": [
      "next",
      "vite-react"
    ],
    "styling": "tailwind-v4"
  },
  "npmDependencies": {},
  "registryDependencies": [],
  "docsPath": "/components/expandable-panel",
  "props": [
    {
      "name": "title",
      "type": "ReactNode",
      "description": "Text of the button that shows and hides the content."
    },
    {
      "name": "open",
      "type": "boolean",
      "description": "Controlled open state. Pair with onOpenChange."
    },
    {
      "name": "defaultOpen",
      "type": "boolean",
      "default": "false",
      "description": "Initial open state when uncontrolled."
    },
    {
      "name": "onOpenChange",
      "type": "(open: boolean) => void",
      "description": "Called with the next open state when the button is pressed."
    },
    {
      "name": "disabled",
      "type": "boolean",
      "default": "false",
      "description": "Disables the button so the panel cannot be toggled."
    },
    {
      "name": "triggerClassName",
      "type": "string",
      "description": "Classes for the toggle button. It is a compact, content-width button with a hover tint, offset 6px left so the chevron lines up with the content edge."
    },
    {
      "name": "contentClassName",
      "type": "string",
      "description": "Classes for the padded content wrapper (indented to the title text). className styles the outer element."
    },
    {
      "name": "...props",
      "type": "ComponentProps<\"div\">",
      "description": "Any native div attribute for the outer element."
    }
  ],
  "accessibility": [
    "The toggle is a native <button> with aria-expanded and aria-controls pointing at the content region.",
    "The content has role=region and is labelled by the button.",
    "Enter and Space toggle the panel.",
    "Closed content is hidden with visibility: hidden after the animation ends, so it is neither focusable nor announced.",
    "The chevron is decorative and rotates from right to down; the open state is conveyed by aria-expanded.",
    "Opens in 220ms and closes in 180ms; with reduced motion the panel opens and closes instantly and the chevron turns without rotating."
  ],
  "states": [
    "closed",
    "open",
    "disabled"
  ],
  "files": [
    {
      "path": "ui/expandable-panel.tsx",
      "content": "\"use client\";\n\nimport { type ComponentProps, type ReactNode, useId, useState } from \"react\";\n\nfunction cx(...classes: Array<string | false | null | undefined>) {\n  return classes.filter(Boolean).join(\" \");\n}\n\nexport interface ExpandablePanelProps extends Omit<ComponentProps<\"div\">, \"title\"> {\n  /** Text of the button that shows and hides the content. */\n  title: ReactNode;\n  /** Controlled open state. Pair with `onOpenChange`. */\n  open?: boolean;\n  /** Initial open state when uncontrolled. */\n  defaultOpen?: boolean;\n  /** Called with the next open state when the button is pressed. */\n  onOpenChange?: (open: boolean) => void;\n  /** Prevents opening and closing. */\n  disabled?: boolean;\n  /** Classes for the toggle button. */\n  triggerClassName?: string;\n  /** Classes for the padded content wrapper. `className` styles the outer element. */\n  contentClassName?: string;\n}\n\n/**\n * A disclosure: a compact button with a leading chevron that shows and hides a region below it.\n * The height animates with CSS grid rows (220ms quint ease-out, 180ms to close) while the content\n * fades; both are instant with reduced motion.\n */\nexport function ExpandablePanel({\n  title,\n  open: openProp,\n  defaultOpen = false,\n  onOpenChange,\n  disabled = false,\n  triggerClassName,\n  contentClassName,\n  className,\n  children,\n  ...props\n}: ExpandablePanelProps) {\n  const [uncontrolledOpen, setUncontrolledOpen] = useState(defaultOpen);\n  const controlled = openProp !== undefined;\n  const open = controlled ? openProp : uncontrolledOpen;\n\n  const id = useId();\n  const triggerId = `${id}-trigger`;\n  const regionId = `${id}-region`;\n\n  function toggle() {\n    const next = !open;\n    if (!controlled) setUncontrolledOpen(next);\n    onOpenChange?.(next);\n  }\n\n  return (\n    <div data-open={open ? \"\" : undefined} className={cx(\"w-full\", className)} {...props}>\n      <button\n        type=\"button\"\n        id={triggerId}\n        aria-expanded={open}\n        aria-controls={regionId}\n        disabled={disabled}\n        onClick={toggle}\n        className={cx(\n          \"group -ml-1.5 inline-flex min-h-8 max-w-[calc(100%+0.375rem)] cursor-pointer items-center gap-1.5 rounded-lg py-1.5 pr-2.5 pl-1.5 text-left text-sm font-medium\",\n          \"text-zinc-600 transition-[background-color,color] duration-150 ease-out motion-reduce:transition-none\",\n          \"hover:bg-zinc-950/[0.04] hover:text-zinc-950 aria-expanded:text-zinc-950\",\n          \"dark:text-zinc-400 dark:hover:bg-white/[0.06] dark:hover:text-white dark:aria-expanded:text-white\",\n          \"focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-sky-500\",\n          \"disabled:cursor-not-allowed disabled:opacity-45 disabled:hover:bg-transparent\",\n          triggerClassName,\n        )}\n      >\n        <svg\n          aria-hidden=\"true\"\n          viewBox=\"0 0 16 16\"\n          fill=\"none\"\n          className={cx(\n            \"size-4 shrink-0 text-zinc-400 dark:text-zinc-500\",\n            \"transition-[rotate,color] duration-220 ease-[cubic-bezier(0.22,1,0.36,1)] motion-reduce:transition-none\",\n            \"group-hover:text-current group-aria-expanded:rotate-90 group-aria-expanded:text-current\",\n          )}\n        >\n          <path d=\"m6.25 4.5 3.5 3.5-3.5 3.5\" stroke=\"currentColor\" strokeWidth=\"1.5\" strokeLinecap=\"round\" strokeLinejoin=\"round\" />\n        </svg>\n        <span className=\"min-w-0\">{title}</span>\n      </button>\n      {/*\n        Rows animate between 0fr and 1fr. `visibility` is transitioned too, so closed content\n        leaves the tab order and accessibility tree only after the closing animation ends.\n      */}\n      <div\n        id={regionId}\n        role=\"region\"\n        aria-labelledby={triggerId}\n        className={cx(\n          \"grid transition-[grid-template-rows,visibility] ease-[cubic-bezier(0.22,1,0.36,1)] motion-reduce:transition-none\",\n          open ? \"visible grid-rows-[1fr] duration-220\" : \"invisible grid-rows-[0fr] duration-180\",\n        )}\n      >\n        <div className=\"min-h-0 overflow-hidden\">\n          <div\n            className={cx(\n              \"pt-1.5 pb-2 pl-5.5 text-sm leading-relaxed text-zinc-600 dark:text-zinc-400\",\n              \"transition-opacity ease-out motion-reduce:transition-none\",\n              open ? \"opacity-100 duration-220\" : \"opacity-0 duration-120\",\n              contentClassName,\n            )}\n          >\n            {children}\n          </div>\n        </div>\n      </div>\n    </div>\n  );\n}\n"
    }
  ],
  "example": {
    "content": "import { ExpandablePanel } from \"@/components/seamless/ui/expandable-panel\";\n\nconst rows = [\n  { term: \"Method\", value: \"POST\" },\n  { term: \"Path\", value: \"/v1/messages\" },\n  { term: \"Status\", value: \"200 OK\" },\n  { term: \"Duration\", value: \"412 ms\" },\n];\n\nexport default function ExpandablePanelExample() {\n  return (\n    <ExpandablePanel title=\"Show request details\" className=\"max-w-md\">\n      <dl className=\"grid grid-cols-[auto_1fr] gap-x-6 gap-y-1.5 rounded-lg border border-zinc-950/[0.07] bg-zinc-950/[0.02] p-3 font-mono text-xs leading-relaxed dark:border-white/[0.08] dark:bg-transparent\">\n        {rows.map((row) => (\n          <div key={row.term} className=\"contents\">\n            <dt className=\"text-zinc-500\">{row.term}</dt>\n            <dd className=\"text-zinc-950 tabular-nums dark:text-white\">{row.value}</dd>\n          </div>\n        ))}\n      </dl>\n    </ExpandablePanel>\n  );\n}\n"
  }
}
