{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "changelog",
  "title": "Changelog",
  "description": "Filterable month-grouped changelog list.",
  "files": [
    {
      "path": "registry/default/blocks/changelog.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\n\nexport type ChangelogEntry = {\n  date: string\n  content: string\n  tags?: string[]\n}\n\nconst ENTRIES: ChangelogEntry[] = [\n  {\n    date: \"2026-08-01\",\n    content: \"Shipped a narrow personal site kit for the registry.\",\n    tags: [\"ship\", \"registry\"],\n  },\n  {\n    date: \"2026-07-18\",\n    content: \"Tightened token mapping for hairline lists and mono stamps.\",\n    tags: [\"design\"],\n  },\n  {\n    date: \"2026-07-04\",\n    content: \"Added command menu and countdown building blocks.\",\n    tags: [\"build\"],\n  },\n  {\n    date: \"2026-06-20\",\n    content: \"Documented Open-Meteo weather chip and local clock.\",\n    tags: [\"docs\"],\n  },\n]\n\nfunction formatDate(iso: string) {\n  const d = new Date(iso.length <= 10 ? `${iso}T00:00:00Z` : iso)\n  return new Intl.DateTimeFormat(\"en-US\", {\n    month: \"short\",\n    day: \"2-digit\",\n    timeZone: \"UTC\",\n  }).format(d)\n}\n\nfunction monthKey(iso: string) {\n  const d = new Date(iso.length <= 10 ? `${iso}T00:00:00Z` : iso)\n  return new Intl.DateTimeFormat(\"en-US\", {\n    year: \"numeric\",\n    month: \"long\",\n    timeZone: \"UTC\",\n  }).format(d)\n}\n\nfunction defaultTags(entries: ChangelogEntry[]) {\n  const set = new Set<string>()\n  for (const e of entries) {\n    for (const t of e.tags ?? []) set.add(t)\n  }\n  return Array.from(set).sort()\n}\n\nexport function Changelog({\n  entries = ENTRIES,\n  tags,\n  className,\n}: {\n  entries?: ChangelogEntry[]\n  tags?: string[]\n  className?: string\n} = {}) {\n  const allTags = tags ?? defaultTags(entries)\n  const [activeTag, setActiveTag] = React.useState(\"\")\n\n  const filtered = React.useMemo(() => {\n    if (!activeTag) return entries\n    return entries.filter((e) => e.tags?.includes(activeTag))\n  }, [entries, activeTag])\n\n  const grouped = React.useMemo(() => {\n    const map = new Map<string, ChangelogEntry[]>()\n    for (const e of filtered) {\n      const k = monthKey(e.date)\n      const arr = map.get(k) ?? []\n      arr.push(e)\n      map.set(k, arr)\n    }\n    return Array.from(map.entries())\n  }, [filtered])\n\n  const toggleTag = (tag: string) => {\n    setActiveTag((prev) => (prev === tag ? \"\" : tag))\n  }\n\n  return (\n    <div className={className ?? \"mx-auto max-w-[640px]\"}>\n      {allTags.length > 0 ? (\n        <div className=\"mb-10 flex flex-wrap items-center gap-1.5\">\n          <span className=\"mr-1.5 font-mono text-[10.5px] tracking-[0.12em] text-muted-foreground uppercase\">\n            Filter\n          </span>\n          <TagButton\n            label=\"all\"\n            active={activeTag === \"\"}\n            onClick={() => setActiveTag(\"\")}\n          />\n          {allTags.map((t) => (\n            <TagButton\n              key={t}\n              label={t}\n              active={activeTag === t}\n              onClick={() => toggleTag(t)}\n            />\n          ))}\n        </div>\n      ) : null}\n\n      {grouped.length === 0 ? (\n        <p className=\"text-[14px] text-muted-foreground\">\n          Nothing tagged{\" \"}\n          <span className=\"font-mono\">{activeTag}</span>. Try another filter.\n        </p>\n      ) : null}\n\n      {grouped.map(([month, items]) => (\n        <section key={month} className=\"mb-12\">\n          <h2 className=\"mb-3 font-mono text-[10.5px] tracking-[0.12em] text-muted-foreground uppercase\">\n            {month}\n          </h2>\n          <ul className=\"divide-y divide-border-subtle border-y border-border-subtle\">\n            {items.map((entry, i) => (\n              <li\n                key={`${entry.date}-${i}`}\n                className=\"flex gap-4 py-[11px] sm:gap-5\"\n              >\n                <time\n                  dateTime={entry.date}\n                  className=\"w-[56px] shrink-0 pt-[3px] font-mono text-[11px] text-muted-foreground\"\n                >\n                  {formatDate(entry.date)}\n                </time>\n                <div className=\"min-w-0 flex-1\">\n                  <p className=\"text-[14.5px] leading-[1.6] text-foreground\">\n                    {entry.content}\n                  </p>\n                  {entry.tags && entry.tags.length > 0 ? (\n                    <div className=\"mt-1.5 flex flex-wrap gap-1\">\n                      {entry.tags.map((tag) => (\n                        <button\n                          key={tag}\n                          type=\"button\"\n                          onClick={() => toggleTag(tag)}\n                          className={\n                            \"inline-flex items-center rounded-[3px] px-1.5 py-[1px] font-mono text-[10.5px] transition-colors \" +\n                            (activeTag === tag\n                              ? \"bg-[color-mix(in_oklab,var(--color-brand,#0b7bff)_16%,transparent)] text-[var(--color-brand,#0b7bff)]\"\n                              : \"bg-muted text-muted-foreground hover:bg-muted/80 hover:text-foreground\")\n                          }\n                        >\n                          {tag}\n                        </button>\n                      ))}\n                    </div>\n                  ) : null}\n                </div>\n              </li>\n            ))}\n          </ul>\n        </section>\n      ))}\n    </div>\n  )\n}\n\nfunction TagButton({\n  label,\n  active,\n  onClick,\n}: {\n  label: string\n  active: boolean\n  onClick: () => void\n}) {\n  return (\n    <button\n      type=\"button\"\n      onClick={onClick}\n      className={\n        \"inline-flex items-center rounded-md px-2.5 py-1 font-mono text-[11px] transition-colors \" +\n        (active\n          ? \"bg-[color-mix(in_oklab,var(--color-brand,#0b7bff)_16%,transparent)] text-[var(--color-brand,#0b7bff)]\"\n          : \"bg-muted text-muted-foreground hover:bg-muted/80 hover:text-foreground\")\n      }\n    >\n      {label}\n    </button>\n  )\n}\n",
      "type": "registry:component",
      "target": "components/blocks/changelog.tsx"
    }
  ],
  "type": "registry:block"
}