{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "changelog",
  "title": "Changelog",
  "type": "registry:block",
  "files": [
    {
      "path": "packages/registry-react/blocks/changelog/Changelog.tsx",
      "content": "'use client'\n\nimport * as React from 'react'\nimport { Bell } from 'lucide-react'\nimport { cn } from '@/lib/utils'\nimport { Badge, type BadgeVariants } from '@/components/ui/badge'\nimport { Button } from '@/components/ui/button'\nimport { SectionCard } from '@/components/ui/section-card'\n\nexport type ChangelogEntryType = 'new' | 'improved' | 'fixed' | 'breaking'\n\nexport interface ChangelogEntry {\n  type: ChangelogEntryType\n  title: string\n  description?: string\n}\n\nexport interface Release {\n  version: string\n  /** ISO date string, e.g. '2026-08-14'. */\n  date: string\n  /** Overrides the default \"first release in the array is the latest\" rule. */\n  latest?: boolean\n  entries: ChangelogEntry[]\n}\n\nexport interface ChangelogProps {\n  releases?: Release[]\n  title?: string\n  description?: string\n  subscribe?: boolean\n  subscribeVariant?: 'default' | 'outline'\n  scrollable?: boolean\n  className?: string\n}\n\nconst entryTone: Record<ChangelogEntryType, NonNullable<BadgeVariants['variant']>> = {\n  new: 'success',\n  improved: 'info',\n  fixed: 'warning',\n  breaking: 'destructive',\n}\n\nconst entryLabel: Record<ChangelogEntryType, string> = {\n  new: 'New',\n  improved: 'Improved',\n  fixed: 'Fixed',\n  breaking: 'Breaking',\n}\n\nconst stubReleases: Release[] = [\n  {\n    version: 'v2.4.0',\n    date: '2026-08-14',\n    entries: [\n      {\n        type: 'new',\n        title: 'Command palette',\n        description: 'Global ⌘K palette with fuzzy search and full keyboard navigation.',\n      },\n      { type: 'improved', title: 'Table virtualization', description: 'Tables stay at 60fps past 10k rows.' },\n      { type: 'fixed', title: 'Dark mode contrast', description: 'Sidebar badges now meet AA contrast in dark mode.' },\n    ],\n  },\n  {\n    version: 'v2.3.0',\n    date: '2026-07-30',\n    entries: [\n      {\n        type: 'new',\n        title: 'Saved views',\n        description: 'Pin filtered table views to the sidebar and share them with your team.',\n      },\n      {\n        type: 'improved',\n        title: 'CSV import',\n        description: 'Merged cells and formula columns are flattened on import.',\n      },\n      { type: 'fixed', title: 'Scheduled exports', description: 'Exports no longer drift across DST boundaries.' },\n    ],\n  },\n  {\n    version: 'v2.2.1',\n    date: '2026-07-08',\n    entries: [\n      {\n        type: 'breaking',\n        title: 'Node 20+ required',\n        description: 'Node 18 reached end of life; the CLI now targets Node 20 and newer.',\n      },\n      {\n        type: 'fixed',\n        title: 'Safari login loop',\n        description: 'Session cookies set on a redirect were dropped by ITP.',\n      },\n      {\n        type: 'fixed',\n        title: 'Duplicate webhooks',\n        description: 'Retries after a timeout no longer double-deliver events.',\n      },\n    ],\n  },\n]\n\nconst dateFormatter = new Intl.DateTimeFormat('en-US', { month: 'short', day: 'numeric', year: 'numeric' })\n\nfunction formatDate(date: string) {\n  // Pin the time so a UTC-stored ISO date cannot shift a day in negative offsets.\n  return dateFormatter.format(new Date(`${date}T00:00:00`))\n}\n\nexport function Changelog({\n  releases,\n  title = 'Changelog',\n  description = 'New features, improvements, and fixes — newest first.',\n  subscribe = true,\n  subscribeVariant = 'outline',\n  scrollable = false,\n  className,\n}: ChangelogProps) {\n  const source = releases ?? stubReleases\n\n  return (\n    <SectionCard\n      data-slot=\"changelog\"\n      title={title}\n      description={description}\n      className={className}\n      headerAction={\n        subscribe ? (\n          <Button variant={subscribeVariant} size=\"sm\">\n            <Bell aria-hidden=\"true\" />\n            Subscribe\n          </Button>\n        ) : undefined\n      }\n    >\n      {/* Version groups. Each entry li carries its own border-l segment so the\n          rail stays continuous through row spacing but breaks at version headers. */}\n      {source.length === 0 ? (\n        <p className=\"text-muted-foreground py-6 text-center text-sm\">No releases yet.</p>\n      ) : (\n        <ol className={cn('space-y-8', scrollable && 'max-h-96 overflow-y-auto pr-1')}>\n          {source.map((release, ri) => (\n            <li key={release.version}>\n              <div className=\"flex items-center gap-2.5\">\n                <h3 className=\"font-mono text-sm font-semibold tracking-tight break-all\">{release.version}</h3>\n                {(release.latest ?? ri === 0) && <Badge>Latest</Badge>}\n                <time dateTime={release.date} className=\"text-muted-foreground ml-auto text-xs\">\n                  {formatDate(release.date)}\n                </time>\n              </div>\n\n              <ol className=\"mt-3\">\n                {release.entries.map((entry, ei) => (\n                  <li key={`${release.version}-${ei}`} className=\"border-border border-l pb-5 pl-6 last:pb-0\">\n                    <div className=\"flex items-start gap-3\">\n                      {/* In-flow dot pulled back over the rail; ring punches out the\n                          line behind it (same trick as TimelineMedia's connector). */}\n                      <span\n                        aria-hidden=\"true\"\n                        className=\"bg-muted-foreground/40 ring-background mt-1.5 -ml-[30px] size-2.5 shrink-0 rounded-full ring-4\"\n                      />\n                      <div className=\"min-w-0\">\n                        <div className=\"flex flex-wrap items-center gap-x-2 gap-y-1\">\n                          <Badge variant={entryTone[entry.type]}>{entryLabel[entry.type]}</Badge>\n                          <p className=\"text-sm font-medium\">{entry.title}</p>\n                        </div>\n                        {entry.description && (\n                          <p className=\"text-muted-foreground mt-1 text-sm leading-relaxed\">{entry.description}</p>\n                        )}\n                      </div>\n                    </div>\n                  </li>\n                ))}\n              </ol>\n            </li>\n          ))}\n        </ol>\n      )}\n    </SectionCard>\n  )\n}\n",
      "type": "registry:block",
      "target": "~/components/blocks/Changelog.tsx"
    }
  ],
  "dependencies": [
    "lucide-react"
  ],
  "devDependencies": [],
  "registryDependencies": [
    "https://uipkge.dev/r/react/badge.json",
    "https://uipkge.dev/r/react/button.json",
    "https://uipkge.dev/r/react/section-card.json"
  ],
  "description": "Release-notes timeline in a SectionCard: Subscribe header action, version groups with a monospace tag, Latest badge and right-aligned date, and entries pairing a typed badge (New / Improved / Fixed / Breaking) with a title and optional description along a continuous border-l rail with dot markers. Pass `releases` to replace the stub; first entry is treated as latest unless a release sets `latest`.",
  "categories": [
    "marketing"
  ]
}