{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "plan",
  "title": "Plan",
  "author": "Jamie Thompson <jamie@jamiethompson.design> (https://jamiethompson.design)",
  "description": "Card-shaped, collapsible plan an agent proposes before acting: a header with title, description and a toggle trigger, collapsible content for the steps, and a footer for approve or edit actions. isStreaming renders the title and description with shadcn's shimmer utility while the plan is still being generated. Composes shadcn card, collapsible and button.",
  "dependencies": [
    "cn",
    "lucide-react"
  ],
  "registryDependencies": [
    "button",
    "card",
    "collapsible"
  ],
  "files": [
    {
      "path": "registry/ai/plan.tsx",
      "content": "// Derived from Vercel AI Elements plan.tsx (Apache-2.0, Copyright 2023 Vercel, Inc.).\n// Modified for uifiles: ported from Radix UI to Base UI; dependencies point at @uifiles.\n\"use client\"\n\nimport { cn } from \"cn\"\nimport { ChevronsUpDownIcon } from \"lucide-react\"\nimport type { ComponentProps } from \"react\"\nimport { createContext, useContext, useMemo } from \"react\"\nimport { Button } from \"@/components/ui/button\"\nimport {\n  Card,\n  CardAction,\n  CardContent,\n  CardDescription,\n  CardFooter,\n  CardHeader,\n  CardTitle,\n} from \"@/components/ui/card\"\nimport {\n  Collapsible,\n  CollapsibleContent,\n  CollapsibleTrigger,\n} from \"@/components/ui/collapsible\"\n\ninterface PlanContextValue {\n  isStreaming: boolean\n}\n\nconst PlanContext = createContext<PlanContextValue | null>(null)\n\nconst usePlan = () => {\n  const context = useContext(PlanContext)\n  if (!context) {\n    throw new Error(\"Plan components must be used within Plan\")\n  }\n  return context\n}\n\nexport type PlanProps = ComponentProps<typeof Collapsible> & {\n  isStreaming?: boolean | undefined\n}\n\nexport const Plan = ({\n  className,\n  isStreaming = false,\n  children,\n  ...props\n}: PlanProps) => {\n  const contextValue = useMemo(() => ({ isStreaming }), [isStreaming])\n\n  return (\n    <PlanContext.Provider value={contextValue}>\n      <Collapsible\n        data-slot=\"plan\"\n        render={<Card className={cn(\"shadow-none\", className)} />}\n        {...props}\n      >\n        {children}\n      </Collapsible>\n    </PlanContext.Provider>\n  )\n}\n\nexport type PlanHeaderProps = ComponentProps<typeof CardHeader>\n\nexport const PlanHeader = ({ className, ...props }: PlanHeaderProps) => (\n  <CardHeader\n    className={cn(\"flex items-start justify-between\", className)}\n    data-slot=\"plan-header\"\n    {...props}\n  />\n)\n\nexport type PlanTitleProps = Omit<\n  ComponentProps<typeof CardTitle>,\n  \"children\"\n> & {\n  children: string\n}\n\nexport const PlanTitle = ({ children, ...props }: PlanTitleProps) => {\n  const { isStreaming } = usePlan()\n\n  return (\n    <CardTitle data-slot=\"plan-title\" {...props}>\n      {isStreaming ? <span className=\"shimmer\">{children}</span> : children}\n    </CardTitle>\n  )\n}\n\nexport type PlanDescriptionProps = Omit<\n  ComponentProps<typeof CardDescription>,\n  \"children\"\n> & {\n  children: string\n}\n\nexport const PlanDescription = ({\n  className,\n  children,\n  ...props\n}: PlanDescriptionProps) => {\n  const { isStreaming } = usePlan()\n\n  return (\n    <CardDescription\n      className={cn(\"text-balance\", className)}\n      data-slot=\"plan-description\"\n      {...props}\n    >\n      {isStreaming ? <span className=\"shimmer\">{children}</span> : children}\n    </CardDescription>\n  )\n}\n\nexport type PlanActionProps = ComponentProps<typeof CardAction>\n\nexport const PlanAction = (props: PlanActionProps) => (\n  <CardAction data-slot=\"plan-action\" {...props} />\n)\n\nexport type PlanContentProps = ComponentProps<typeof CardContent>\n\nexport const PlanContent = (props: PlanContentProps) => (\n  <CollapsibleContent\n    render={<CardContent data-slot=\"plan-content\" {...props} />}\n  />\n)\n\nexport type PlanFooterProps = ComponentProps<\"div\">\n\nexport const PlanFooter = (props: PlanFooterProps) => (\n  <CardFooter data-slot=\"plan-footer\" {...props} />\n)\n\nexport type PlanTriggerProps = ComponentProps<typeof CollapsibleTrigger>\n\nexport const PlanTrigger = ({ className, ...props }: PlanTriggerProps) => (\n  <CollapsibleTrigger\n    className={className}\n    data-slot=\"plan-trigger\"\n    render={<Button className=\"size-8\" size=\"icon\" variant=\"ghost\" />}\n    {...props}\n  >\n    <ChevronsUpDownIcon className=\"size-4\" />\n    <span className=\"sr-only\">Toggle plan</span>\n  </CollapsibleTrigger>\n)\n",
      "type": "registry:component",
      "target": "components/ai/plan.tsx"
    }
  ],
  "docs": "Derived from Vercel AI Elements (Apache-2.0, Copyright 2023 Vercel, Inc.); ported to Base UI and maintained by uifiles. Built for the Base UI styles (base-nova): it composes the Base UI wrappers from components/ui and relies on their render prop and event signatures, so it does not work on Radix or React Aria styles. Base UI port of AI Elements plan. Plan renders the Collapsible as the Card via render={<Card />}; PlanContent and PlanTrigger use render the same way (all three were asChild). PlanContent takes CardContent props as upstream, so Collapsible.Panel props such as keepMounted are not forwarded. Upstream's <Shimmer> component is replaced by shadcn's `shimmer` class on a <span>, so there is no shimmer item or motion dependency. PlanTrigger spreads its props onto the collapsible trigger; className is merged into the inner Button and re-run through cn, so overrides still win, and a `render` prop replaces the Button. PlanTitle and PlanDescription are <div>s (CardTitle/CardDescription), so a plan never affects the page's heading order. Plan is closed by default (as upstream); pass defaultOpen. onOpenChange receives (open, eventDetails).",
  "categories": [
    "ai",
    "chat",
    "agent"
  ],
  "type": "registry:component"
}