{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"change-summary","type":"registry:ui","title":"Change Summary","description":"What moved since last time – one metric per row with a colored trend","dependencies":[],"files":[{"path":"registry/change-summary/change-summary.tsx","type":"registry:ui","content":"\"use client\";\n\nimport type { ReactNode } from \"react\";\n\n/*\n * Change Summary – what moved since last time, one metric per row, in a gray tray with a white card, in a single self-contained file.\n * The styles are injected below, so no extra files are needed.\n */\n\nexport type MetricChange = {\n  label: string;\n  /** What happened, in words, e.g. \"Up 6.1% to 18.2K\" */\n  change: string;\n  /** good: green arrow · bad: red arrow · neutral: gray dash */\n  tone?: \"good\" | \"bad\" | \"neutral\";\n  /** Direction of the arrow – defaults to up for good, down for bad */\n  direction?: \"up\" | \"down\";\n};\n\nexport type ChangeSummaryProps = {\n  title?: string;\n  /** Small text on the right of the heading, e.g. \"This week\" */\n  period?: string;\n  icon?: ReactNode;\n  changes: MetricChange[];\n  className?: string;\n};\n\nexport function ChangeSummary({ title = \"What's changed\", period, icon, changes, className }: ChangeSummaryProps) {\n  return (\n    <>\n      <style href=\"ui-change-summary\" precedence=\"default\">\n        {css}\n      </style>\n      <section className={[\"ui-cs\", className].filter(Boolean).join(\" \")} aria-label={title}>\n        <header className=\"ui-cs__head\">\n          <span className=\"ui-cs__title\">\n            <span className=\"ui-cs__icon\">{icon ?? <ChangesIcon />}</span>\n            <span className=\"ui-cs__title-text\" dir=\"auto\" title={title}>\n              {title}\n            </span>\n          </span>\n          {period && (\n            <span className=\"ui-cs__period\" dir=\"auto\" title={period}>\n              {period}\n            </span>\n          )}\n        </header>\n        {changes.length > 0 && (\n        <dl className=\"ui-cs__card\">\n          {changes.map((c, i) => {\n            // Anything but good or bad reads as neutral, so the mark always has its tint\n            const tone = c.tone === \"good\" || c.tone === \"bad\" ? c.tone : \"neutral\";\n            const direction = c.direction ?? (tone === \"bad\" ? \"down\" : \"up\");\n            return (\n              // The stagger stops after twelve rows, so a long list is all there at once\n              <div key={`${i}-${c.label}`} className=\"ui-cs__row\" style={{ animationDelay: `${120 + Math.min(i, 12) * 70}ms` }}>\n                <dt className=\"ui-cs__label\" dir=\"auto\" title={c.label}>\n                  {c.label}\n                </dt>\n                <dd className=\"ui-cs__change\">\n                  <span className=\"ui-cs__trend\" data-tone={tone} aria-hidden=\"true\">\n                    {tone === \"neutral\" ? <DashIcon /> : <ArrowIcon down={direction === \"down\"} />}\n                  </span>\n                  <span className=\"ui-cs__text\" dir=\"auto\" title={c.change}>\n                    {c.change}\n                  </span>\n                </dd>\n              </div>\n            );\n          })}\n        </dl>\n        )}\n      </section>\n    </>\n  );\n}\n\n/* ---------- Icons ---------- */\n\nconst ChangesIcon = () => (\n  <svg viewBox=\"0 0 16 16\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.5\" strokeLinecap=\"round\" aria-hidden=\"true\">\n    <path d=\"M2.5 4h4M2.5 8h8M6.5 12h7M9.5 4h1\" />\n  </svg>\n);\n\nconst ArrowIcon = ({ down }: { down?: boolean }) => (\n  <svg viewBox=\"0 0 12 12\" width=\"12\" height=\"12\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.6\" strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden=\"true\">\n    {down ? <path d=\"M6 2.5v7M3 6.5l3 3 3-3\" /> : <path d=\"M6 9.5v-7M3 5.5l3-3 3 3\" />}\n  </svg>\n);\n\nconst DashIcon = () => (\n  <svg viewBox=\"0 0 12 12\" width=\"12\" height=\"12\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.6\" strokeLinecap=\"round\" aria-hidden=\"true\">\n    <path d=\"M3 6h6\" />\n  </svg>\n);\n\n/* ---------- Styles ---------- */\n\nconst css = /* css */ `\n.ui-cs {\n  --ui-cs-tray: var(--aui-subtle, #f0f0f0);\n  --ui-cs-card: var(--aui-surface, #ffffff);\n  --ui-cs-line: var(--aui-border, #f0f0f0);\n  --ui-cs-ink: var(--aui-ink, #171717);\n  --ui-cs-muted: var(--aui-muted, #737373);\n  --ui-cs-accent: var(--aui-blue, #4fb0fe);\n  --ui-cs-ease: var(--aui-ease, cubic-bezier(0.22, 1, 0.36, 1));\n  box-sizing: border-box;\n  width: 100%;\n  max-width: 480px;\n  container-type: inline-size;\n  /* Like the Chat Composer: a gray tray with the heading, the white card sits on top of it */\n  background: var(--ui-cs-tray);\n  /* The gray tray frames the white card on every side */\n  padding: 6px;\n  border-radius: calc(var(--aui-radius-lg, 16px) + 6px);\n  font-family: var(--aui-font, \"Inter\", ui-sans-serif, system-ui, -apple-system, \"Segoe UI\", Roboto, sans-serif);\n  color: var(--ui-cs-ink);\n  -webkit-font-smoothing: antialiased;\n}\n.ui-cs *, .ui-cs *::before, .ui-cs *::after { box-sizing: border-box; }\n\n.ui-cs__head {\n  display: flex;\n  align-items: center;\n  justify-content: space-between;\n  gap: 12px;\n  padding: 11px 18px 10px;\n  font-size: 14px;\n  line-height: 20px;\n}\n.ui-cs__title {\n  display: inline-flex;\n  align-items: center;\n  gap: 8px;\n  min-width: 0;\n  font-weight: 500;\n  color: var(--ui-cs-accent);\n}\n.ui-cs__icon { display: grid; place-items: center; flex: none; width: 16px; height: 16px; overflow: hidden; }\n/* A custom icon – an image too – fits the 16px tile */\n.ui-cs__icon > :is(img, svg) { display: block; width: 16px; height: 16px; object-fit: contain; }\n.ui-cs__title-text { min-width: 0; overflow-wrap: anywhere; }\n/* The period shortens before it squeezes the title */\n.ui-cs__period { flex: 0 1 auto; min-width: 0; max-width: 50%; overflow: hidden; font-size: 13px; white-space: nowrap; text-overflow: ellipsis; color: var(--ui-cs-muted); }\n\n.ui-cs__card {\n  margin: 0;\n  overflow: hidden;\n  background: var(--ui-cs-card);\n  border: 1px solid var(--aui-border, #e5e5e5);\n  border-radius: var(--aui-radius-lg, 16px);\n  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.06);\n}\n.ui-cs__row {\n  display: grid;\n  grid-template-columns: minmax(0, 0.8fr) minmax(0, 1.2fr);\n  align-items: center;\n  gap: 16px;\n  min-height: 52px;\n  padding: 0 20px;\n  animation: ui-cs-in 520ms var(--ui-cs-ease) both;\n  transition: background-color 140ms ease;\n}\n.ui-cs__row + .ui-cs__row { border-top: 1px solid var(--ui-cs-line); }\n.ui-cs__row:hover { background: var(--aui-subtle, #fafafa); }\n.ui-cs__label {\n  overflow: hidden;\n  font-size: 14px;\n  font-weight: 500;\n  line-height: 20px;\n  white-space: nowrap;\n  text-overflow: ellipsis;\n  overflow-wrap: anywhere;\n  text-align: start;\n}\n.ui-cs__change {\n  display: flex;\n  align-items: center;\n  gap: 8px;\n  min-width: 0;\n  margin: 0;\n  font-size: 14px;\n  line-height: 20px;\n  color: var(--ui-cs-muted);\n}\n/* The change keeps its numbers: it wraps up to three lines instead of being cut off */\n.ui-cs__text { display: -webkit-box; overflow: hidden; white-space: pre-line; overflow-wrap: anywhere; text-align: start; -webkit-line-clamp: 3; -webkit-box-orient: vertical; }\n/* Narrow: each metric stacks above its change, so the text wraps instead of being cut off */\n@container (max-width: 400px) {\n  .ui-cs__row { grid-template-columns: minmax(0, 1fr); gap: 4px; padding: 11px 16px; }\n  .ui-cs__label { white-space: normal; }\n  .ui-cs__change { align-items: flex-start; }\n}\n.ui-cs__trend {\n  display: grid;\n  place-items: center;\n  flex: none;\n  width: 20px;\n  height: 20px;\n  border-radius: 6px;\n}\n.ui-cs__trend[data-tone=\"good\"] { background: #eef9f3; color: var(--aui-success, #1e8a4f); }\n.ui-cs__trend[data-tone=\"bad\"] { background: #fff0f0; color: var(--aui-red, #f04438); }\n.ui-cs__trend[data-tone=\"neutral\"] { background: var(--aui-subtle, #f5f5f5); color: var(--aui-muted, #8f8f8f); }\n\n@keyframes ui-cs-in {\n  from { opacity: 0; transform: translateY(4px); }\n}\n@media (prefers-reduced-motion: reduce) {\n  .ui-cs__row { animation: none; }\n}\n\n/* Dark mode: follows a .dark class or data-theme=\"dark\" on any ancestor, e.g. <html> */\n:where(.dark, [data-theme=\"dark\"]) .ui-cs {\n  --ui-cs-tray: var(--aui-subtle, #161618);\n  --ui-cs-card: var(--aui-surface, #232326);\n  --ui-cs-line: var(--aui-border, rgba(255, 255, 255, 0.08));\n  --ui-cs-ink: var(--aui-ink, #ededed);\n  --ui-cs-muted: var(--aui-muted, #8f8f8f);\n  --ui-cs-accent: var(--aui-blue, #4fb0fe);\n}\n:where(.dark, [data-theme=\"dark\"]) .ui-cs__card {\n  border-color: var(--aui-border, rgba(255, 255, 255, 0.08));\n  box-shadow: var(--aui-shadow-card, 0 1px 2px rgba(0, 0, 0, 0.4));\n}\n:where(.dark, [data-theme=\"dark\"]) .ui-cs__row:hover { background: var(--aui-subtle, #2a2a2d); }\n:where(.dark, [data-theme=\"dark\"]) .ui-cs__trend[data-tone=\"good\"] { background: color-mix(in srgb, var(--aui-green, #7dcf9f) 18%, transparent); color: #41bd74; }\n:where(.dark, [data-theme=\"dark\"]) .ui-cs__trend[data-tone=\"bad\"] { background: color-mix(in srgb, var(--aui-red, #ff6b5f) 16%, transparent); color: var(--aui-red, #ff6b5f); }\n:where(.dark, [data-theme=\"dark\"]) .ui-cs__trend[data-tone=\"neutral\"] { background: var(--aui-subtle, #2a2a2d); color: var(--aui-muted, #8f8f8f); }\n`;\n"}]}