{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "callout",
  "type": "registry:ui",
  "title": "Callout",
  "description": "Inline note, tip, warning or danger block with details that unfold in place.",
  "categories": [
    "notifications"
  ],
  "dependencies": [
    "@base-ui/react"
  ],
  "registryDependencies": [
    "https://www.stealth.pm/r/tokens.json",
    "https://www.stealth.pm/r/cn.json",
    "https://www.stealth.pm/r/icons.json",
    "https://www.stealth.pm/r/use-controllable-state.json"
  ],
  "files": [
    {
      "path": "components/ui/callout.tsx",
      "content": "\"use client\";\nimport { Collapsible } from \"@base-ui/react/collapsible\";\nimport { cn } from \"@/lib/cn\";\nimport { Alert, ArrowRight, ChevronDown, CircleCheck, Info, Warning } from \"@/lib/icons\";\nimport { useControllableState } from \"@/lib/use-controllable-state\";\n\nexport type CalloutTone = \"note\" | \"info\" | \"tip\" | \"success\" | \"warning\" | \"danger\";\n\n// A lightbulb on the shared 16px grid, 1.4 stroke.\nconst Bulb = () => (\n  <svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"currentColor\" strokeWidth={1.4} strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden>\n    <path d=\"M6 11.25h4M6.5 13.5h3M8 2.25a4 4 0 0 0-2.4 7.2c.35.27.55.68.55 1.12v.18h3.7v-.18c0-.44.2-.85.55-1.12A4 4 0 0 0 8 2.25z\" />\n  </svg>\n);\n\nconst tones: Record<CalloutTone, { icon: React.ReactNode; surface: string; glyph: string; label: string }> = {\n  note: { icon: <Info />, surface: \"border-line bg-raised\", glyph: \"text-fg-3\", label: \"Note\" },\n  info: { icon: <Info />, surface: \"border-info/20 bg-info-soft\", glyph: \"text-info\", label: \"Info\" },\n  tip: { icon: <Bulb />, surface: \"border-success/20 bg-success-soft\", glyph: \"text-success\", label: \"Tip\" },\n  success: { icon: <CircleCheck />, surface: \"border-success/20 bg-success-soft\", glyph: \"text-success\", label: \"Success\" },\n  warning: { icon: <Warning />, surface: \"border-warning/25 bg-warning-soft\", glyph: \"text-warning\", label: \"Warning\" },\n  danger: { icon: <Alert />, surface: \"border-danger/25 bg-danger-soft\", glyph: \"text-danger\", label: \"Important\" },\n};\n\nexport type CalloutProps = Omit<React.ComponentProps<\"div\">, \"title\"> & {\n  tone?: CalloutTone;\n  size?: \"sm\" | \"md\";\n  title?: React.ReactNode;\n  /** Replace the tone's icon, or `false` for none. */\n  icon?: React.ReactNode | false;\n  /** One or two `CalloutAction`s, under the body. */\n  action?: React.ReactNode;\n  /** Longer supporting content behind a Show details toggle: a list, a log, the why. */\n  details?: React.ReactNode;\n  detailsLabel?: string;\n  hideDetailsLabel?: string;\n  /** Controlled details state. */\n  detailsOpen?: boolean;\n  defaultDetailsOpen?: boolean;\n  onDetailsOpenChange?: (open: boolean) => void;\n};\n\nexport function Callout({\n  tone = \"note\",\n  size = \"md\",\n  title,\n  icon,\n  action,\n  details,\n  detailsLabel = \"Show details\",\n  hideDetailsLabel = \"Hide details\",\n  detailsOpen,\n  defaultDetailsOpen,\n  onDetailsOpenChange,\n  className,\n  children,\n  ...rest\n}: CalloutProps) {\n  const t = tones[tone];\n  const glyph = icon === false ? null : (icon ?? t.icon);\n  const sm = size === \"sm\";\n  const [open, setOpen] = useControllableState({ value: detailsOpen, defaultValue: defaultDetailsOpen ?? false, onChange: onDetailsOpenChange });\n\n  return (\n    <div\n      role=\"note\"\n      data-tone={tone}\n      data-size={size}\n      className={cn(\n        \"grid grid-cols-[auto_minmax(0,1fr)] rounded-xl border\",\n        sm ? \"px-2.5 py-2 text-[12.5px] leading-[18px]\" : \"px-3 py-2.5 text-[13px] leading-5\",\n        t.surface,\n        className,\n      )}\n      {...rest}\n    >\n      {glyph && (\n        <span className={cn(\"col-start-1 grid place-items-center\", sm ? \"mr-2 mt-px size-4 [&_svg]:size-3.5\" : \"mr-2.5 mt-0.5 size-4\", t.glyph)}>{glyph}</span>\n      )}\n      <div className=\"col-start-2 min-w-0\">\n        <span className=\"sr-only\">{t.label}: </span>\n        {title != null && <p className=\"font-medium tracking-[-0.005em] text-fg text-pretty\">{title}</p>}\n        {children != null && <div className={cn(\"text-fg-2 text-pretty\", title != null && \"mt-0.5\")}>{children}</div>}\n\n        {details != null && (\n          <Collapsible.Root open={open} onOpenChange={setOpen}>\n            <Collapsible.Panel\n              className={cn(\n                \"h-(--collapsible-panel-height) overflow-hidden transition-[height] duration-240 ease-in-out-quart motion-reduce:transition-none\",\n                \"data-starting-style:h-0 data-ending-style:h-0 [&[hidden]:not([hidden='until-found'])]:hidden\",\n              )}\n              hiddenUntilFound\n            >\n              {/* The content lifts in as the panel opens and settles back as it closes. */}\n              <div\n                className={cn(\n                  \"pt-2 text-fg-2 transition-[opacity,translate] duration-240 ease-out-expo\",\n                  \"in-data-starting-style:-translate-y-1 in-data-starting-style:opacity-0 in-data-ending-style:-translate-y-1 in-data-ending-style:opacity-0\",\n                )}\n              >\n                {details}\n              </div>\n            </Collapsible.Panel>\n            <DetailsRow action={action} label={detailsLabel} hideLabel={hideDetailsLabel} open={open} sm={sm} />\n          </Collapsible.Root>\n        )}\n        {details == null && action != null && <div className={cn(\"-ml-2 flex flex-wrap items-center gap-1\", sm ? \"mt-1\" : \"mt-1.5\")}>{action}</div>}\n      </div>\n    </div>\n  );\n}\n\nfunction DetailsRow({ action, label, hideLabel, open, sm }: { action?: React.ReactNode; label: string; hideLabel: string; open: boolean; sm: boolean }) {\n  return (\n    <div className={cn(\"-ml-2 flex flex-wrap items-center gap-1\", sm ? \"mt-1\" : \"mt-1.5\")}>\n      {action}\n      <Collapsible.Trigger\n        className={cn(\n          \"group/details relative inline-flex h-7 items-center gap-1 rounded-md px-2 text-[12.5px] text-fg-2\",\n          \"outline-none focus-visible:outline-solid focus-visible:outline-1 focus-visible:outline-offset-0 focus-visible:outline-fg-3\",\n          \"transition-[background-color,color,scale] duration-150 ease-out hover:bg-fg/[0.06] hover:text-fg active:scale-[0.97] active:duration-75\",\n        )}\n      >\n        {/* Both labels share one cell, so the toggle never changes width. */}\n        <span className=\"grid\">\n          <span aria-hidden={open} className={cn(\"col-start-1 row-start-1 transition-opacity duration-150\", open && \"opacity-0\")}>\n            {label}\n          </span>\n          <span aria-hidden={!open} className={cn(\"col-start-1 row-start-1 transition-opacity duration-150\", !open && \"opacity-0\")}>\n            {hideLabel}\n          </span>\n        </span>\n        <ChevronDown size={14} className=\"transition-transform duration-240 ease-in-out-quart group-data-panel-open/details:rotate-180 motion-reduce:transition-none\" />\n      </Collapsible.Trigger>\n    </div>\n  );\n}\n\nexport type CalloutActionProps = (Omit<React.ComponentProps<\"button\">, \"type\"> & { href?: undefined }) | (React.ComponentProps<\"a\"> & { href: string });\n\n/** A text action with an arrow that leans forward on hover. Renders a link when given `href`. */\nexport function CalloutAction({ className, children, ...rest }: CalloutActionProps) {\n  const cls = cn(\n    \"group/action inline-flex h-7 shrink-0 items-center gap-1 rounded-md px-2 text-[12.5px] font-medium text-fg\",\n    \"outline-none focus-visible:outline-solid focus-visible:outline-1 focus-visible:outline-offset-0 focus-visible:outline-fg-3\",\n    \"transition-[background-color,scale] duration-150 ease-out hover:bg-fg/[0.06] active:scale-[0.97] active:duration-75\",\n    className,\n  );\n  const inner = (\n    <>\n      {children}\n      <ArrowRight size={14} className=\"text-fg-3 transition-[translate,color] duration-200 ease-out-expo group-hover/action:translate-x-0.5 group-hover/action:text-fg\" />\n    </>\n  );\n  if (rest.href !== undefined) {\n    return (\n      <a className={cls} {...(rest as React.ComponentProps<\"a\">)}>\n        {inner}\n      </a>\n    );\n  }\n  return (\n    <button type=\"button\" className={cls} {...(rest as React.ComponentProps<\"button\">)}>\n      {inner}\n    </button>\n  );\n}\n",
      "type": "registry:ui"
    }
  ],
  "docs": "https://www.stealth.pm/ui/callout"
}
