{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"recommendation-card","title":"Recommendation Card","description":"Agent suggestion with a confidence meter and actions.","dependencies":[],"registryDependencies":["https://ward.so/r/foundation.json","https://ward.so/r/button.json","https://ward.so/r/entity-chip.json","https://ward.so/r/value-pill.json"],"files":[{"path":"registry/ward/RecommendationCard.tsx","content":"\"use client\";\n/*\n * MIT License\n *\n * Copyright (c) 2026 Shane Levine\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all\n * copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n * SOFTWARE.\n */\n\nimport \"./styles/foundation.css\";\n\nimport { useState } from \"react\";\nimport { Button } from \"@/registry/ward/ui/button\";\nimport { EntityChip } from \"./EntityChip\";\nimport { ValuePill } from \"./ValuePill\";\n\n/* ─────────────────────────────────────────────────────────\n * RECOMMENDATION CARD\n * The card holds its shape. Pressing \"Alternatives\" opens a\n * new drawer listing the other options; picking one promotes\n * it to the recommendation. The primary action confirms.\n * ───────────────────────────────────────────────────────── */\n\nexport type RecommendationOption = {\n  key: string;\n  body: React.ReactNode;\n  short: string;\n  signal: number;\n  tone: string;\n  label: string;\n  cta: string;\n  ctaVariant: \"default\" | \"secondary\";\n};\n\nexport type RecommendationLabels = {\n  title: string;\n  alternatives: string;\n  otherOptions: string;\n  accepted: string;\n};\n\nconst DEFAULT_LABELS: RecommendationLabels = {\n  title: \"Want me to place this restock order?\",\n  alternatives: \"Alternatives\",\n  otherOptions: \"Other options\",\n  accepted: \"Accepted\",\n};\n\nconst OPTIONS: RecommendationOption[] = [\n  {\n    key: \"high\",\n    body: (\n      <>\n        Reorder design assets from{\" \"}\n        <EntityChip name=\"Asset King\" />{\" \"}\n        with lead time <ValuePill tone=\"green\">7 days</ValuePill>\n      </>\n    ),\n    short: \"Reorder from Asset King · 7-day lead\",\n    signal: 3,\n    tone: \"var(--green)\",\n    label: \"High confidence\",\n    cta: \"Accept\",\n    ctaVariant: \"default\",\n  },\n  {\n    key: \"review\",\n    body: (\n      <>\n        Switch vanilla to <ValuePill>Vanilla Madagascar</ValuePill> for peak season.\n      </>\n    ),\n    short: \"Switch to Vanilla Madagascar\",\n    signal: 2,\n    tone: \"var(--orange)\",\n    label: \"Needs review\",\n    cta: \"Configure\",\n    ctaVariant: \"default\",\n  },\n  {\n    key: \"none\",\n    body: (\n      <>\n        Fall back to a <span className=\"font-medium text-ink\">full restock</span> across every SKU.\n      </>\n    ),\n    short: \"Full restock across every SKU\",\n    signal: 0,\n    tone: \"var(--ink-3)\",\n    label: \"No signal\",\n    cta: \"Accept full restock\",\n    ctaVariant: \"default\",\n  },\n];\n\nfunction Meter({ signal, tone }: { signal: number; tone: string }) {\n  return (\n    <span className=\"flex items-end gap-0.5\">\n      {[0, 1, 2].map((bar) => (\n        <span\n          key={bar}\n          className=\"w-1 rounded-full transition-colors duration-300\"\n          style={{ height: 10, background: bar < signal ? tone : \"var(--line-strong)\" }}\n        />\n      ))}\n    </span>\n  );\n}\n\nexport default function RecommendationCard({\n  options = OPTIONS,\n  labels,\n}: {\n  options?: RecommendationOption[];\n  labels?: Partial<RecommendationLabels>;\n  variant?: string;\n} = {}) {\n  const t = { ...DEFAULT_LABELS, ...labels };\n  const [selected, setSelected] = useState(0);\n  const [open, setOpen] = useState(false);\n  const [accepted, setAccepted] = useState(false);\n\n  const active = options[selected];\n  const others = options.map((o, i) => ({ o, i })).filter(({ i }) => i !== selected);\n\n  return (\n    <div className=\"w-full max-w-95 overflow-hidden rounded-card bg-card shadow-card\">\n      <div className=\"primitive-card-pad\">\n        <span className=\"text-[14px] font-medium text-ink\">\n          {t.title}\n        </span>\n        <p\n          key={active.key}\n          className=\"mt-1.5 min-h-12 text-[13px] leading-relaxed text-ink-2\"\n          style={{ animation: \"fade-in 180ms ease-out both\" }}\n        >\n          {active.body}\n        </p>\n      </div>\n\n      {/* alternatives drawer — a distinctly new section of the card */}\n      <div\n        className=\"grid transition-[grid-template-rows,opacity] duration-300\"\n        style={{\n          gridTemplateRows: open ? \"1fr\" : \"0fr\",\n          opacity: open ? 1 : 0,\n          transitionTimingFunction: \"cubic-bezier(0.16, 1, 0.3, 1)\",\n        }}\n      >\n        <div className=\"overflow-hidden\">\n          <div className=\"border-t border-line bg-card px-2 py-2\">\n            <p className=\"px-1.5 pb-1 text-[11px] font-medium text-ink-3\">\n              {t.otherOptions}\n            </p>\n            {others.map(({ o, i }) => (\n              <button\n                key={o.key}\n                type=\"button\"\n                onClick={() => {\n                  setSelected(i);\n                  setAccepted(false);\n                }}\n                className=\"flex w-full items-center gap-2.5 rounded-control px-1.5 py-1.5\n                  text-left transition-colors duration-100 hover:bg-hover\"\n              >\n                <Meter signal={o.signal} tone={o.tone} />\n                <span className=\"min-w-0 flex-1 truncate text-[12.5px] text-ink\">{o.short}</span>\n                <span className=\"shrink-0 text-[11px] text-ink-3\">{o.label}</span>\n              </button>\n            ))}\n          </div>\n        </div>\n      </div>\n\n      <div className=\"primitive-card-footer flex items-center justify-between gap-3 bg-card\">\n        <span className=\"flex items-center gap-2\">\n          <Meter signal={active.signal} tone={active.tone} />\n          <span className=\"text-[12.5px] font-medium text-ink-2\">{active.label}</span>\n        </span>\n\n        <span className=\"-mr-0.5 flex items-center gap-2\">\n          <Button\n            variant=\"secondary\"\n            size=\"sm\"\n            aria-expanded={open}\n            onClick={() => setOpen((current) => !current)}\n            className=\"h-7 px-2.5 text-[12.5px]\"\n          >\n            {t.alternatives}\n          </Button>\n          <Button\n            variant={accepted ? \"secondary\" : active.ctaVariant}\n            size=\"sm\"\n            onClick={() => setAccepted(true)}\n            className={accepted ? \"h-7 bg-green text-[12.5px] text-white hover:bg-green\" : \"h-7 text-[12.5px]\"}\n          >\n            {accepted ? t.accepted : active.cta}\n          </Button>\n        </span>\n      </div>\n    </div>\n  );\n}\n","type":"registry:component","target":"components/ward/RecommendationCard.tsx"}],"meta":{"variants":["Default"],"version":"1.1.0","source":"https://github.com/slev12397/beautiful-ui/blob/44a274e598395ab61e7c96c26fda2758780253b7/components/primitives/RecommendationCard.tsx","access":"free"},"categories":["ai"],"type":"registry:component"}