{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"decision-inbox-clean","type":"registry:ui","title":"Decision Inbox (Clean)","description":"Open decisions with two quick answers each – answered ones confirm and fold away","dependencies":[],"files":[{"path":"registry/decision-inbox-clean/decision-inbox-clean.tsx","type":"registry:ui","content":"\"use client\";\n\nimport { useState } from \"react\";\n\n/*\n * Decision Inbox (clean style) – open decisions with two quick answers each, in one white card, in a single self-contained file.\n * A heading row with the count sits at the top of the card; every decision is a row under a hairline.\n * Answering a decision confirms it briefly, then folds it away and the count goes down.\n * The styles are injected below, so no extra files are needed.\n */\n\nexport type Decision = {\n  id: string;\n  title: string;\n  /** Short context under the title */\n  detail?: string;\n  /** Main answer, shown as the dark button */\n  primary: string;\n  /** Other answer, shown as the light button */\n  secondary: string;\n  /** Starts folded to one muted line – click to open */\n  collapsed?: boolean;\n};\n\nexport type DecisionInboxProps = {\n  decisions: Decision[];\n  /** Called with the decision and the chosen answer */\n  onDecide?: (decision: Decision, answer: \"primary\" | \"secondary\") => void;\n  className?: string;\n};\n\ntype State = { open: boolean; answered?: string; gone?: boolean };\n\nexport function DecisionInbox({ decisions, onDecide, className }: DecisionInboxProps) {\n  const [state, setState] = useState<Record<string, State>>(() =>\n    Object.fromEntries(decisions.map((d) => [d.id, { open: !d.collapsed }])),\n  );\n  const patch = (id: string, next: Partial<State>) => setState((s) => ({ ...s, [id]: { ...s[id], ...next } }));\n\n  const open = decisions.filter((d) => !state[d.id]?.answered).length;\n  // 1284 → \"1,284\"\n  const count = new Intl.NumberFormat(\"en\").format(open);\n\n  const decide = (d: Decision, answer: \"primary\" | \"secondary\") => {\n    patch(d.id, { answered: answer === \"primary\" ? d.primary : d.secondary });\n    onDecide?.(d, answer);\n    // Show the confirmation for a moment, then fold the row away and open the next folded one\n    window.setTimeout(() => {\n      setState((s) => {\n        const next = { ...s, [d.id]: { ...s[d.id], gone: true } };\n        const folded = decisions.find((x) => x.id !== d.id && !next[x.id]?.open && !next[x.id]?.answered);\n        if (folded) next[folded.id] = { ...next[folded.id], open: true };\n        return next;\n      });\n    }, 1100);\n  };\n\n  return (\n    <>\n      <style href=\"ui-decision-inbox-clean\" precedence=\"default\">\n        {css}\n      </style>\n      <section className={[\"ui-dic\", className].filter(Boolean).join(\" \")} data-clear={open === 0 || undefined}>\n        <header className=\"ui-dic__head\" aria-live=\"polite\">\n          {open > 0 ? (\n            <>\n              <AlertIcon />\n              <span>\n                {count} {open === 1 ? \"needs\" : \"need\"} your input\n              </span>\n            </>\n          ) : (\n            <>\n              <DoneIcon />\n              <span>All caught up</span>\n            </>\n          )}\n        </header>\n\n        <div className=\"ui-dic__card\">\n          {decisions.map((d) => {\n            const s = state[d.id] ?? { open: true };\n            return (\n              <div key={d.id} className=\"ui-dic__row\" data-gone={s.gone || undefined} inert={s.gone}>\n                <div className=\"ui-dic__row-inner\">\n                  <div className=\"ui-dic__item\" data-open={s.open || undefined} data-answered={s.answered ? true : undefined}>\n                    {s.open ? (\n                      <p className=\"ui-dic__title\" dir=\"auto\">\n                        {d.title}\n                      </p>\n                    ) : (\n                      <button type=\"button\" className=\"ui-dic__title ui-dic__title--folded\" dir=\"auto\" title={d.title} onClick={() => patch(d.id, { open: true })}>\n                        {d.title}\n                      </button>\n                    )}\n                    <div className=\"ui-dic__body\" data-open={s.open || undefined}>\n                      <div className=\"ui-dic__body-inner\">\n                        {d.detail && (\n                          <p className=\"ui-dic__detail\" dir=\"auto\">\n                            {d.detail}\n                          </p>\n                        )}\n                        <div className=\"ui-dic__actions\">\n                          {s.answered ? (\n                            <span className=\"ui-dic__done\" role=\"status\">\n                              <DoneIcon />\n                              <span dir=\"auto\">{s.answered}</span>\n                            </span>\n                          ) : (\n                            <>\n                              <button type=\"button\" className=\"ui-dic__btn ui-dic__btn--primary\" dir=\"auto\" onClick={() => decide(d, \"primary\")}>\n                                {d.primary}\n                              </button>\n                              <button type=\"button\" className=\"ui-dic__btn\" dir=\"auto\" onClick={() => decide(d, \"secondary\")}>\n                                {d.secondary}\n                              </button>\n                            </>\n                          )}\n                        </div>\n                      </div>\n                    </div>\n                  </div>\n                </div>\n              </div>\n            );\n          })}\n          <p className=\"ui-dic__empty\">Nothing waiting on you – new decisions show up here.</p>\n        </div>\n      </section>\n    </>\n  );\n}\n\n/* ---------- Icons ---------- */\n\n// Solid glyphs – the cut-out mark takes the card color, so it works in dark mode too\nconst AlertIcon = () => (\n  <svg className=\"ui-dic__glyph\" viewBox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\">\n    <path d=\"M6.7 2.3a1.5 1.5 0 0 1 2.6 0l5.4 9.4A1.5 1.5 0 0 1 13.4 14H2.6a1.5 1.5 0 0 1-1.3-2.3Z\" fill=\"currentColor\" />\n    <path className=\"ui-dic__cut\" d=\"M8 5.8v3.2\" strokeWidth=\"1.4\" strokeLinecap=\"round\" fill=\"none\" />\n    <circle className=\"ui-dic__cut-dot\" cx=\"8\" cy=\"11.3\" r=\"0.8\" />\n  </svg>\n);\n\nconst DoneIcon = () => (\n  <svg className=\"ui-dic__glyph\" viewBox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\">\n    <circle cx=\"8\" cy=\"8\" r=\"6.5\" fill=\"currentColor\" />\n    <path className=\"ui-dic__cut\" d=\"m5.3 8.2 1.8 1.8 3.6-3.8\" fill=\"none\" strokeWidth=\"1.4\" strokeLinecap=\"round\" strokeLinejoin=\"round\" />\n  </svg>\n);\n\n/* ---------- Styles ---------- */\n\nconst css = /* css */ `\n/* Colors and motion as variables – override them on .ui-dic */\n.ui-dic {\n  --ui-dic-card: var(--aui-surface, #ffffff);\n  --ui-dic-strong: var(--aui-ink, #000000);\n  --ui-dic-ink: var(--aui-ink, rgba(0, 0, 0, 0.88));\n  --ui-dic-text: var(--aui-muted, rgba(0, 0, 0, 0.61));\n  --ui-dic-muted: var(--aui-muted, rgba(0, 0, 0, 0.45));\n  --ui-dic-faint: var(--aui-placeholder, rgba(0, 0, 0, 0.27));\n  --ui-dic-line: var(--aui-border, rgba(0, 0, 0, 0.06));\n  --ui-dic-wash: var(--aui-subtle, rgba(0, 0, 0, 0.03));\n  --ui-dic-alert: var(--aui-amber, #e8b000);\n  --ui-dic-ok: var(--aui-success, #1e8a4f);\n  --ui-dic-ok-bg: color-mix(in srgb, var(--aui-green, #5fc88a) 8%, transparent);\n  --ui-dic-solid: var(--aui-primary, rgba(0, 0, 0, 0.88));\n  --ui-dic-solid-hover: rgba(0, 0, 0, 0.8);\n  --ui-dic-on-solid: var(--aui-on-primary, #fcfcfc);\n  /* Strokes are shadows: a 0.5px hairline plus a soft lift */\n  --ui-dic-shadow: 0 0 0 0.5px rgba(0, 0, 0, 0.06), 0 1px 1px -1px rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.05);\n  --ui-dic-shadow-hover: 0 0 0 0.5px rgba(0, 0, 0, 0.1), 0 1px 1px -1px rgba(0, 0, 0, 0.12), 0 1px 3px 0 rgba(0, 0, 0, 0.07);\n  --ui-dic-focus: var(--aui-focus, rgba(0, 96, 226, 0.7));\n  --ui-dic-ease: var(--aui-ease, cubic-bezier(0.22, 1, 0.36, 1));\n  box-sizing: border-box;\n  container-type: inline-size;\n  width: 100%;\n  max-width: 440px;\n  overflow: hidden;\n  /* One white card – the edge is a shadow, not a border */\n  border-radius: var(--aui-radius-lg, 10px);\n  background: var(--ui-dic-card);\n  box-shadow: var(--aui-shadow-card, var(--ui-dic-shadow));\n  font-family: var(--aui-font, \"Inter\", \"InterVariable\", ui-sans-serif, system-ui, -apple-system, \"Segoe UI\", Roboto, sans-serif);\n  font-size: 13px;\n  font-weight: 400;\n  line-height: 20px;\n  color: var(--ui-dic-ink);\n  text-align: left;\n  -webkit-font-smoothing: antialiased;\n}\n.ui-dic *, .ui-dic *::before, .ui-dic *::after { box-sizing: border-box; }\n:where(.ui-dic) button { margin: 0; padding: 0; border: 0; background: none; font: inherit; color: inherit; cursor: pointer; -webkit-tap-highlight-color: transparent; }\n:where(.ui-dic) svg { display: block; flex: none; }\n.ui-dic button:focus-visible { outline: 2px solid var(--ui-dic-focus); outline-offset: 2px; }\n\n.ui-dic__glyph { width: 16px; height: 16px; }\n.ui-dic__cut { stroke: var(--ui-dic-card); }\n.ui-dic__cut-dot { fill: var(--ui-dic-card); }\n\n/* The heading row at the top of the card: an amber mark while something waits, a green one when all is done */\n.ui-dic__head {\n  display: flex;\n  align-items: center;\n  gap: 8px;\n  height: 44px;\n  padding: 0 16px;\n  border-bottom: 0.5px solid var(--ui-dic-line);\n  font-size: 13px;\n  font-weight: 500;\n  line-height: 20px;\n  color: var(--ui-dic-ink);\n}\n.ui-dic__head .ui-dic__glyph { color: var(--ui-dic-alert); animation: ui-dic-pop 360ms var(--ui-dic-ease) both; }\n.ui-dic[data-clear] .ui-dic__head .ui-dic__glyph { color: var(--ui-dic-ok); }\n.ui-dic__head span { animation: ui-dic-in 320ms var(--ui-dic-ease) both; }\n\n/* Rows fold away with a height transition once answered */\n.ui-dic__row {\n  display: grid;\n  grid-template-rows: 1fr;\n  transition: grid-template-rows 460ms var(--ui-dic-ease), opacity 300ms ease;\n}\n.ui-dic__row[data-gone] { grid-template-rows: 0fr; opacity: 0; }\n.ui-dic__row-inner { min-height: 0; overflow: hidden; }\n.ui-dic__row + .ui-dic__row .ui-dic__item { border-top: 0.5px solid var(--ui-dic-line); }\n.ui-dic__row[data-gone] + .ui-dic__row .ui-dic__item { border-top-color: transparent; }\n\n.ui-dic__item { padding: 14px 16px 16px; }\n.ui-dic__item:not([data-open]) { padding: 0; }\n.ui-dic__title {\n  display: block;\n  width: 100%;\n  margin: 0;\n  font-size: 13px;\n  font-weight: 500;\n  line-height: 20px;\n  text-align: start;\n  color: var(--ui-dic-strong);\n  overflow-wrap: anywhere;\n}\n/* A folded decision: one muted line that opens on click */\n:where(.ui-dic) .ui-dic__title--folded {\n  /* Still one line however long the title – it opens in full on click */\n  overflow: hidden;\n  white-space: nowrap;\n  text-overflow: ellipsis;\n  padding: 14px 16px;\n  font-weight: 400;\n  color: var(--ui-dic-muted);\n  transition: color 140ms ease, background-color 140ms ease;\n}\n.ui-dic__title--folded:hover { background: var(--ui-dic-wash); color: var(--ui-dic-ink); }\n.ui-dic button.ui-dic__title--folded:focus-visible { outline-offset: -2px; }\n\n.ui-dic__body {\n  display: grid;\n  grid-template-rows: 0fr;\n  transition: grid-template-rows 420ms var(--ui-dic-ease);\n}\n.ui-dic__body[data-open] { grid-template-rows: 1fr; }\n/* Room around the buttons so the clipping for the fold never cuts their focus ring and shadow */\n.ui-dic__body-inner {\n  min-height: 0;\n  overflow: hidden;\n  margin: 0 -4px -4px;\n  padding: 0 4px 4px;\n}\n.ui-dic__detail {\n  margin: 2px 0 0;\n  font-size: 13px;\n  line-height: 20px;\n  color: var(--ui-dic-text);\n  white-space: pre-line;\n  overflow-wrap: anywhere;\n}\n.ui-dic__actions {\n  display: flex;\n  align-items: center;\n  /* Long or translated answers go on a second line instead of out of the card */\n  flex-wrap: wrap;\n  gap: 8px;\n  min-height: 28px;\n  margin-top: 12px;\n}\n\n/* Secondary: white with the card's hairline shadow */\n.ui-dic__btn {\n  display: inline-flex;\n  align-items: center;\n  justify-content: center;\n  max-width: 100%;\n  min-height: 28px;\n  padding: 4px 10px;\n  border-radius: calc(var(--aui-radius, 7px) - 1px);\n  text-align: center;\n  overflow-wrap: anywhere;\n  font-size: 13px;\n  font-weight: 500;\n  line-height: 20px;\n  transition: background-color 140ms ease, box-shadow 140ms ease, transform 160ms var(--ui-dic-ease);\n}\n:where(.ui-dic) .ui-dic__btn { background: var(--ui-dic-card); box-shadow: var(--ui-dic-shadow); color: var(--ui-dic-strong); }\n.ui-dic__btn:hover { box-shadow: var(--ui-dic-shadow-hover); }\n.ui-dic__btn:active { transform: scale(0.97); }\n/* Primary: a dark chip */\n:where(.ui-dic) .ui-dic__btn--primary { background: var(--ui-dic-solid); box-shadow: none; color: var(--ui-dic-on-solid); }\n.ui-dic__btn--primary:hover { background: var(--ui-dic-solid-hover); box-shadow: none; }\n\n/* The answer: a minimal green fill in the place of the buttons */\n.ui-dic__done {\n  display: inline-flex;\n  align-items: center;\n  gap: 6px;\n  max-width: 100%;\n  min-height: 28px;\n  padding: 4px 10px 4px 8px;\n  border-radius: calc(var(--aui-radius-sm, 5px) + 1px);\n  background: var(--ui-dic-ok-bg);\n  font-size: 13px;\n  font-weight: 500;\n  line-height: 20px;\n  color: var(--ui-dic-ok);\n  overflow-wrap: anywhere;\n  animation: ui-dic-in 360ms var(--ui-dic-ease);\n}\n.ui-dic__done > span { min-width: 0; }\n\n.ui-dic__empty {\n  display: none;\n  margin: 0;\n  padding: 14px 16px;\n  font-size: 13px;\n  line-height: 20px;\n  color: var(--ui-dic-muted);\n}\n.ui-dic[data-clear] .ui-dic__empty { display: block; animation: ui-dic-in 420ms var(--ui-dic-ease) 300ms both; }\n\n@keyframes ui-dic-in {\n  from { opacity: 0; transform: translateY(3px); }\n}\n@keyframes ui-dic-pop {\n  from { opacity: 0; transform: scale(0.6); }\n}\n\n/* Narrow cards (phones) */\n@container (max-width: 340px) {\n  .ui-dic__head { padding: 0 14px; }\n  .ui-dic__item { padding: 12px 14px 14px; }\n  :where(.ui-dic) .ui-dic__title--folded { padding: 14px 14px; }\n  .ui-dic__empty { padding: 14px; }\n}\n/* Touch screens: bigger tap targets */\n@media (pointer: coarse) {\n  .ui-dic__btn, .ui-dic__done { min-height: 36px; padding: 8px 14px; }\n  .ui-dic__done { padding: 8px 14px 8px 12px; }\n  .ui-dic__actions { min-height: 36px; }\n  :where(.ui-dic) .ui-dic__title--folded { padding-top: 14px; padding-bottom: 14px; }\n}\n\n@media (prefers-reduced-motion: reduce) {\n  .ui-dic__row, .ui-dic__body { transition: none; }\n  .ui-dic__done, .ui-dic__empty, .ui-dic__head .ui-dic__glyph, .ui-dic__head span { animation: none !important; }\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-dic {\n  --ui-dic-card: var(--aui-surface, #1c1c1e);\n  --ui-dic-strong: var(--aui-ink, #ffffff);\n  --ui-dic-ink: var(--aui-ink, rgba(255, 255, 255, 0.9));\n  --ui-dic-text: var(--aui-muted, rgba(255, 255, 255, 0.62));\n  --ui-dic-muted: var(--aui-muted, rgba(255, 255, 255, 0.46));\n  --ui-dic-faint: var(--aui-placeholder, rgba(255, 255, 255, 0.3));\n  --ui-dic-line: var(--aui-border, rgba(255, 255, 255, 0.08));\n  --ui-dic-wash: var(--aui-subtle, rgba(255, 255, 255, 0.04));\n  --ui-dic-alert: var(--aui-amber, #e6c24d);\n  --ui-dic-ok: var(--aui-green, #5fc88a);\n  --ui-dic-ok-bg: color-mix(in srgb, var(--aui-green, #7dcf9f) 14%, transparent);\n  /* The primary inverts: a light chip with dark text */\n  --ui-dic-solid: var(--aui-primary, rgba(255, 255, 255, 0.9));\n  --ui-dic-solid-hover: rgba(255, 255, 255, 0.82);\n  --ui-dic-on-solid: var(--aui-on-primary, #161618);\n  --ui-dic-shadow: 0 0 0 0.5px rgba(255, 255, 255, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.4);\n  --ui-dic-shadow-hover: 0 0 0 0.5px rgba(255, 255, 255, 0.16), 0 1px 2px 0 rgba(0, 0, 0, 0.4);\n  --ui-dic-focus: var(--aui-focus, rgba(77, 141, 255, 0.8));\n}\n`;\n"}]}