{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "author": "Lopifi (https://lopifi.com)",
  "categories": [
    "payments",
    "finance",
    "accounting"
  ],
  "dependencies": [],
  "description": "Frequency, end condition, preview of next 3.",
  "files": [
    {
      "content": "\"use client\";\n\nimport * as React from \"react\";\nimport { cn } from \"@/lib/ledger/cn\";\nimport { formatDate } from \"@/lib/ledger/fiscal-period\";\n\nexport type Frequency = \"weekly\" | \"biweekly\" | \"monthly\" | \"quarterly\" | \"annual\";\n\n/**\n * \"Monthly on the 31st\" is ambiguous in February. Resolving the next three\n * occurrences makes that visible before it surprises anyone.\n */\nexport function nextOccurrences(start: Date, frequency: Frequency, count = 3): Date[] {\n  const out: Date[] = [];\n  const dayOfMonth = start.getDate();\n  for (let i = 1; i <= count; i++) {\n    if (frequency === \"weekly\") out.push(addDays(start, 7 * i));\n    else if (frequency === \"biweekly\") out.push(addDays(start, 14 * i));\n    else {\n      const step = frequency === \"monthly\" ? 1 : frequency === \"quarterly\" ? 3 : 12;\n      const target = new Date(start.getFullYear(), start.getMonth() + step * i, 1);\n      const lastDay = new Date(target.getFullYear(), target.getMonth() + 1, 0).getDate();\n      out.push(new Date(target.getFullYear(), target.getMonth(), Math.min(dayOfMonth, lastDay)));\n    }\n  }\n  return out;\n}\n\nfunction addDays(d: Date, n: number) {\n  return new Date(d.getFullYear(), d.getMonth(), d.getDate() + n);\n}\n\nexport interface RecurringScheduleProps {\n  className?: string;\n  defaultFrequency?: Frequency;\n}\n\nexport function RecurringSchedule({ className, defaultFrequency = \"monthly\" }: RecurringScheduleProps) {\n  const [frequency, setFrequency] = React.useState<Frequency>(defaultFrequency);\n  const [start, setStart] = React.useState(() => new Date(2026, 0, 31));\n  const [endKind, setEndKind] = React.useState<\"never\" | \"on\" | \"after\">(\"never\");\n  const [occurrences, setOccurrences] = React.useState(12);\n\n  const preview = nextOccurrences(start, frequency);\n\n  return (\n    <div className={cn(\"space-y-4\", className)}>\n      <label className=\"block\">\n        <span className=\"mb-2 block text-label font-medium text-text-secondary\">Frequency</span>\n        <select\n          className=\"h-10 w-full rounded-sm border border-border bg-surface px-3 text-body outline-none focus-visible:border-accent\"\n          onChange={(e) => setFrequency(e.target.value as Frequency)}\n          value={frequency}\n        >\n          <option value=\"weekly\">Weekly</option>\n          <option value=\"biweekly\">Every two weeks</option>\n          <option value=\"monthly\">Monthly</option>\n          <option value=\"quarterly\">Quarterly</option>\n          <option value=\"annual\">Annually</option>\n        </select>\n      </label>\n\n      <label className=\"block\">\n        <span className=\"mb-2 block text-label font-medium text-text-secondary\">Starting</span>\n        <input\n          className=\"numeric h-10 w-full rounded-sm border border-border bg-surface px-3 text-body outline-none focus-visible:border-accent\"\n          onChange={(e) => setStart(new Date(`${e.target.value}T00:00:00`))}\n          type=\"date\"\n          value={start.toISOString().slice(0, 10)}\n        />\n      </label>\n\n      <fieldset>\n        <legend className=\"mb-2 text-label font-medium text-text-secondary\">Ends</legend>\n        <div className=\"flex flex-wrap items-center gap-4\">\n          {([\"never\", \"on\", \"after\"] as const).map((k) => (\n            <label className=\"flex items-center gap-2 text-body\" key={k}>\n              <input\n                checked={endKind === k}\n                className=\"accent-[var(--accent)]\"\n                name=\"end\"\n                onChange={() => setEndKind(k)}\n                type=\"radio\"\n              />\n              {k === \"never\" ? \"Never\" : k === \"on\" ? \"On a date\" : \"After N payments\"}\n            </label>\n          ))}\n          {endKind === \"after\" ? (\n            <input\n              aria-label=\"Number of payments\"\n              className=\"numeric h-8 w-16 rounded-sm border border-border bg-surface px-2 text-body outline-none focus-visible:border-accent focus-visible:ring-2 focus-visible:ring-accent-subtle\"\n              inputMode=\"numeric\"\n              onChange={(e) => setOccurrences(Number(e.target.value.replace(/\\D/g, \"\")) || 1)}\n              type=\"text\"\n              value={occurrences}\n            />\n          ) : null}\n        </div>\n      </fieldset>\n\n      <div className=\"rounded-md bg-surface-sunken p-3\">\n        <p className=\"text-caption text-text-secondary\">Next three payments</p>\n        <ul className=\"mt-1 space-y-0.5\">\n          {preview.map((d) => (\n            <li className=\"numeric text-body\" key={d.toISOString()}>\n              {formatDate(d)}\n            </li>\n          ))}\n        </ul>\n      </div>\n    </div>\n  );\n}\n",
      "path": "components/ledger/recurring-schedule.tsx",
      "target": "components/ledger/recurring-schedule.tsx",
      "type": "registry:component"
    }
  ],
  "name": "recurring-schedule",
  "registryDependencies": [
    "@lopifi/theme",
    "@lopifi/cn",
    "@lopifi/fiscal-period"
  ],
  "title": "RecurringSchedule",
  "type": "registry:component"
}
