{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "banner",
  "title": "Banner",
  "description": "Banner: a borderless status note — a flat wash of the tone at 7–8%, a tone-coloured icon, and body copy inked toward the hue instead of muted grey. Four tones (info, warning, success, danger), each driven by three custom properties. No runtime dependencies.",
  "registryDependencies": [
    "https://sc1m.vercel.app/r/tokens.json",
    "https://sc1m.vercel.app/r/cn.json"
  ],
  "files": [
    {
      "path": "src/components/ui/banner/banner.tsx",
      "content": "import * as React from \"react\";\nimport { cn } from \"@/lib/cn\";\n\ntype Tone = \"info\" | \"warning\" | \"success\" | \"danger\";\n\n/**\n * Flat wash: a borderless tint, a tone-coloured icon, and body copy in the\n * tone's own ink rather than muted grey. The whole block is the signal, so it\n * survives being skimmed at speed.\n *\n * Three values drive every tone, so a tone is a row in this table and nothing\n * else:\n *   --banner-tone  the hue\n *   --banner-wash  how much of it lands in the fill\n *   --banner-ink   how far the body copy is pulled from --fg toward the hue\n *\n * The wash and ink percentages differ per tone on purpose — they are not a\n * single number applied four times. Yellow carries far less weight than blue\n * at the same percentage, so warning washes harder (8%) and inks lighter\n * (45%) to land at the same perceived strength. Normalising these would make\n * the warning banner read as the weakest of the set.\n */\nconst tones: Record<Tone, string> = {\n  info: \"[--banner-tone:var(--color-info)] [--banner-wash:7%] [--banner-ink:55%]\",\n  warning:\n    \"[--banner-tone:var(--color-warning)] [--banner-wash:8%] [--banner-ink:45%]\",\n  success:\n    \"[--banner-tone:var(--color-success)] [--banner-wash:7%] [--banner-ink:50%]\",\n  danger:\n    \"[--banner-tone:var(--color-danger)] [--banner-wash:7%] [--banner-ink:50%]\",\n};\n\n/** Spoken prefix for the icon, which is decorative to a screen reader. */\nconst toneLabels: Record<Tone, string> = {\n  info: \"Note\",\n  warning: \"Warning\",\n  success: \"Success\",\n  danger: \"Error\",\n};\n\n/**\n * Phosphor `fill` weight, path data verbatim from @phosphor-icons/core — see\n * /docs/foundations/icons. Inlined rather than imported: the icon package\n * ships its React icons as client components, and Banner is deliberately\n * server-safe, so importing one would force the whole banner into the client\n * bundle for a static glyph.\n *\n * The counters (the dot of the info \"i\", the bar of the warning) are real\n * holes in Phosphor's path rather than shapes painted in the background\n * colour, so they show the banner's own wash at any tint, in either theme,\n * under any brand — nothing to keep in sync.\n */\nconst icons: Record<Tone, string> = {\n  /* phosphor: info fill */\n  info: \"M128,24A104,104,0,1,0,232,128,104.11,104.11,0,0,0,128,24Zm-4,48a12,12,0,1,1-12,12A12,12,0,0,1,124,72Zm12,112a16,16,0,0,1-16-16V128a8,8,0,0,1,0-16,16,16,0,0,1,16,16v40a8,8,0,0,1,0,16Z\",\n  /* phosphor: warning fill */\n  warning:\n    \"M236.8,188.09,149.35,36.22h0a24.76,24.76,0,0,0-42.7,0L19.2,188.09a23.51,23.51,0,0,0,0,23.72A24.35,24.35,0,0,0,40.55,224h174.9a24.35,24.35,0,0,0,21.33-12.19A23.51,23.51,0,0,0,236.8,188.09ZM120,104a8,8,0,0,1,16,0v40a8,8,0,0,1-16,0Zm8,88a12,12,0,1,1,12-12A12,12,0,0,1,128,192Z\",\n  /* phosphor: check-circle fill */\n  success:\n    \"M128,24A104,104,0,1,0,232,128,104.11,104.11,0,0,0,128,24Zm45.66,85.66-56,56a8,8,0,0,1-11.32,0l-24-24a8,8,0,0,1,11.32-11.32L112,148.69l50.34-50.35a8,8,0,0,1,11.32,11.32Z\",\n  /* phosphor: x-circle fill */\n  danger:\n    \"M128,24A104,104,0,1,0,232,128,104.11,104.11,0,0,0,128,24Zm37.66,130.34a8,8,0,0,1-11.32,11.32L128,139.31l-26.34,26.35a8,8,0,0,1-11.32-11.32L116.69,128,90.34,101.66a8,8,0,0,1,11.32-11.32L128,116.69l26.34-26.35a8,8,0,0,1,11.32,11.32L139.31,128Z\",\n};\n\nexport interface BannerProps extends React.ComponentPropsWithRef<\"div\"> {\n  tone?: Tone;\n}\n\nexport function Banner({\n  tone = \"info\",\n  className,\n  children,\n  role,\n  ...props\n}: BannerProps) {\n  return (\n    <div\n      // danger is the one tone that interrupts: an error the reader must hear\n      // about now. The rest announce politely. Pass `role` to override.\n      role={role ?? (tone === \"danger\" ? \"alert\" : \"status\")}\n      className={cn(\n        \"flex gap-[11px] rounded-lg px-4 py-[13px]\",\n        \"[--banner-bg:color-mix(in_oklab,var(--banner-tone)_var(--banner-wash),var(--color-bg))]\",\n        \"bg-(--banner-bg)\",\n        \"text-[13.5px] leading-[1.6] text-pretty\",\n        \"text-[color-mix(in_oklab,var(--banner-tone)_var(--banner-ink),var(--color-fg))]\",\n        tones[tone],\n        className,\n      )}\n      {...props}\n    >\n      <svg\n        aria-hidden\n        viewBox=\"0 0 256 256\"\n        fill=\"currentColor\"\n        className=\"mt-[3px] size-[15px] shrink-0 text-(--banner-tone)\"\n      >\n        <path d={icons[tone]} />\n      </svg>\n      {/* a div, never a p: MDX wraps multi-line JSX children in a paragraph of\n          its own, and a <p> inside a <p> is unwrappable HTML — it fragments\n          the subtree and fails hydration. */}\n      <div>\n        <span className=\"sr-only\">{toneLabels[tone]}: </span>\n        {children}\n      </div>\n    </div>\n  );\n}\n",
      "type": "registry:ui"
    }
  ],
  "type": "registry:ui"
}