{
  "$schema": "https://uipkge.dev/schema/registry-item.json",
  "name": "changelog",
  "title": "Changelog",
  "type": "registry:block",
  "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"
  ],
  "files": [
    {
      "path": "packages/elements-lit/src/blocks/changelog/changelog.ts",
      "content": "import { LitElement, css, html, nothing } from 'lit'\nimport { Bell } from 'lucide'\nimport { cn } from '../../lib/utils'\nimport { tailwind } from '../../lib/styles'\nimport { ThemeController } from '../../lib/theme'\nimport { icon } from '../../lib/icon'\n// Side-effect imports: register the composed elements for standalone use\n// (consumers who install only this block still get working primitives).\nimport '../../components/badge/badge'\nimport '../../components/button/button'\nimport '../../components/section-card/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\nconst entryTone: Record<ChangelogEntryType, 'success' | 'info' | 'warning' | 'destructive'> = {\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\n/**\n * <uip-changelog> — the registry Changelog block as a web component.\n *\n * Class strings are React's Changelog verbatim. `heading` is React's `title`\n * (`title` is taken by HTMLElement). `releases` is a property (arrays don't\n * cross attributes); the remaining props are kebab-case attributes. The\n * SectionCard chrome is a <uip-section-card> with the Subscribe action in\n * its header-action slot.\n */\nexport class UipChangelog extends LitElement {\n  static shadowRootOptions = { ...LitElement.shadowRootOptions, delegatesFocus: true }\n  // :host display can't be a utility class (the host has no template of its own).\n  static styles = [tailwind, css`:host { display: block; }`]\n\n  static properties = {\n    releases: {},\n    heading: {},\n    description: {},\n    subscribe: { type: Boolean },\n    subscribeVariant: { attribute: 'subscribe-variant' },\n    scrollable: { type: Boolean },\n  }\n\n  releases?: Release[]\n  heading = 'Changelog'\n  description = 'New features, improvements, and fixes — newest first.'\n  subscribe = true\n  subscribeVariant: 'default' | 'outline' = 'outline'\n  scrollable = false\n\n  constructor() {\n    super()\n    new ThemeController(this)\n  }\n\n  connectedCallback() {\n    super.connectedCallback()\n    this.setAttribute('data-uipkge', '')\n    this.setAttribute('data-slot', 'changelog')\n  }\n\n  protected updated() {\n    // React passes data-slot=\"changelog\" through SectionCard; <uip-section-card>\n    // labels itself \"section-card\" on connect, so re-label it for DOM parity.\n    this.renderRoot.querySelector('uip-section-card')?.setAttribute('data-slot', 'changelog')\n  }\n\n  render() {\n    const source = this.releases ?? stubReleases\n    return html`\n      <uip-section-card data-slot=\"changelog\" heading=${this.heading} description=${this.description}>\n        ${this.subscribe\n          ? html`<span slot=\"header-action\"\n              ><uip-button variant=${this.subscribeVariant} size=\"sm\"\n                >${icon(Bell, 'bell')} Subscribe</uip-button\n              ></span\n            >`\n          : nothing}\n        ${source.length === 0\n          ? html`<p class=\"text-muted-foreground py-6 text-center text-sm\">No releases yet.</p>`\n          : html`<ol class=${cn('space-y-8', this.scrollable && 'max-h-96 overflow-y-auto pr-1')}>\n              ${source.map(\n                (release, ri) => html`<li>\n                  <div class=\"flex items-center gap-2.5\">\n                    <h3 class=\"font-mono text-sm font-semibold tracking-tight break-all\">${release.version}</h3>\n                    ${(release.latest ?? ri === 0) ? html`<uip-badge>Latest</uip-badge>` : nothing}\n                    <time dateTime=${release.date} class=\"text-muted-foreground ml-auto text-xs\">\n                      ${formatDate(release.date)}\n                    </time>\n                  </div>\n\n                  <ol class=\"mt-3\">\n                    ${release.entries.map(\n                      (entry) => html`<li\n                        class=\"border-border border-l pb-5 pl-6 last:pb-0\"\n                      >\n                        <div class=\"flex items-start gap-3\">\n                          <span\n                            aria-hidden=\"true\"\n                            class=\"bg-muted-foreground/40 ring-background mt-1.5 -ml-[30px] size-2.5 shrink-0 rounded-full ring-4\"\n                          ></span>\n                          <div class=\"min-w-0\">\n                            <div class=\"flex flex-wrap items-center gap-x-2 gap-y-1\">\n                              <uip-badge variant=${entryTone[entry.type]}>${entryLabel[entry.type]}</uip-badge>\n                              <p class=\"text-sm font-medium\">${entry.title}</p>\n                            </div>\n                            ${entry.description\n                              ? html`<p class=\"text-muted-foreground mt-1 text-sm leading-relaxed\">\n                                  ${entry.description}\n                                </p>`\n                              : nothing}\n                          </div>\n                        </div>\n                      </li>`,\n                    )}\n                  </ol>\n                </li>`,\n              )}\n            </ol>`}\n      </uip-section-card>\n    `\n  }\n}\n\ncustomElements.get('uip-changelog') || customElements.define('uip-changelog', UipChangelog)\n\ndeclare global {\n  interface HTMLElementTagNameMap {\n    'uip-changelog': UipChangelog\n  }\n}\n",
      "type": "registry:file",
      "target": "src/uip/blocks/changelog/changelog.ts"
    }
  ],
  "dependencies": [
    "lit",
    "lucide"
  ],
  "registryDependencies": [
    "https://uipkge.dev/r/lit/badge.json",
    "https://uipkge.dev/r/lit/button.json",
    "https://uipkge.dev/r/lit/lit-core.json",
    "https://uipkge.dev/r/lit/section-card.json"
  ]
}
