{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "collapsible",
  "title": "Collapsible",
  "description": "A section that opens and closes, growing to its height.",
  "dependencies": [
    "@base-ui/react",
    "@stylexjs/stylex"
  ],
  "registryDependencies": [
    "https://constella.aayush.cv/r/base.json"
  ],
  "files": [
    {
      "path": "src/components/primitives/collapsible.tsx",
      "content": "\"use client\";\n\nimport { Collapsible as CollapsiblePrimitive } from \"@base-ui/react/collapsible\";\nimport * as stylex from \"@stylexjs/stylex\";\nimport { createContext, use, useState } from \"react\";\n\nimport { useSkipMotion } from \"@/lib/constella/motion\";\nimport type { Sx } from \"@/lib/constella/styles\";\nimport { durations, easings, media } from \"@/lib/constella/tokens.stylex\";\n\ntype Styled<T> = Omit<T, \"className\" | \"style\"> & {\n  sx?: Sx;\n};\n\nconst offstage = \":is([data-starting-style], [data-ending-style])\";\n\nconst styles = stylex.create({\n  // Content below has to make room, so the panel grows to Base UI's measured height.\n  content: {\n    height: {\n      default: \"var(--collapsible-panel-height)\",\n      [offstage]: 0,\n    },\n    opacity: { default: 1, [offstage]: 0 },\n    overflow: \"hidden\",\n    // Shutting is quicker and drops the gentle settle, whose long tail reads as stuck.\n    transitionDuration: {\n      default: durations.layout,\n      \":is([data-ending-style]):not([data-collapsible-instant])\": {\n        default: durations.layoutExit,\n        [media.reducedMotion]: \"0s\",\n      },\n      [media.reducedMotion]: \"0s\",\n      \":is([data-collapsible-instant])\": \"0s\",\n    },\n    transitionProperty: \"height, opacity\",\n    transitionTimingFunction: {\n      default: easings.layout,\n      \":is([data-ending-style])\": easings.out,\n    },\n  },\n});\n\n// Per root, so a nested collapsible opened by keyboard doesn't snap its parent.\nconst Instant = createContext(false);\n\nfunction Collapsible({\n  onOpenChange,\n  open,\n  sx,\n  ...props\n}: Styled<CollapsiblePrimitive.Root.Props>) {\n  const [skip, handleOpenChange] = useSkipMotion(onOpenChange);\n  const [toggledTo, setToggledTo] = useState<boolean>();\n  // A later change from the app's `open` isn't the keyboard's, so it animates.\n  const instant = skip && (open === undefined || open === toggledTo);\n  return (\n    <Instant value={instant}>\n      <CollapsiblePrimitive.Root\n        data-slot=\"collapsible\"\n        onOpenChange={(next, details) => {\n          setToggledTo(next);\n          handleOpenChange(next, details);\n        }}\n        open={open}\n        {...props}\n        {...stylex.props(sx)}\n      />\n    </Instant>\n  );\n}\n\n/** Carries `data-collapsible-instant` after a keyboard toggle, so its own icons can snap too. */\nfunction CollapsibleTrigger({\n  sx,\n  ...props\n}: Styled<CollapsiblePrimitive.Trigger.Props>) {\n  return (\n    <CollapsiblePrimitive.Trigger\n      data-collapsible-instant={use(Instant) || undefined}\n      data-slot=\"collapsible-trigger\"\n      {...props}\n      {...stylex.props(sx)}\n    />\n  );\n}\n\nfunction CollapsibleContent({\n  sx,\n  ...props\n}: Styled<CollapsiblePrimitive.Panel.Props>) {\n  return (\n    <CollapsiblePrimitive.Panel\n      data-collapsible-instant={use(Instant) || undefined}\n      data-slot=\"collapsible-content\"\n      {...props}\n      {...stylex.props(styles.content, sx)}\n    />\n  );\n}\n\nexport { Collapsible, CollapsibleContent, CollapsibleTrigger };\n",
      "type": "registry:component"
    }
  ],
  "docs": "Usage: https://constella.aayush.cv/docs/components/collapsible/index.md",
  "type": "registry:ui"
}