Changelog
atroui/changelogFreeBlock
Filterable month-grouped changelog list.
Install it
npx shadcn@latest add https://atroui.com/r/changelog.jsonSource
1"use client"23import * as React from "react"45export type ChangelogEntry = {6 date: string7 content: string8 tags?: string[]9}1011const ENTRIES: ChangelogEntry[] = [12 {13 date: "2026-08-01",14 content: "Shipped a narrow personal site kit for the registry.",15 tags: ["ship", "registry"],16 },17 {18 date: "2026-07-18",19 content: "Tightened token mapping for hairline lists and mono stamps.",20 tags: ["design"],21 },22 {23 date: "2026-07-04",24 content: "Added command menu and countdown building blocks.",25 tags: ["build"],26 },27 {28 date: "2026-06-20",29 content: "Documented Open-Meteo weather chip and local clock.",30 tags: ["docs"],31 },32]3334function formatDate(iso: string) {35 const d = new Date(iso.length <= 10 ? `${iso}T00:00:00Z` : iso)36 return new Intl.DateTimeFormat("en-US", {37 month: "short",38 day: "2-digit",39 timeZone: "UTC",40 }).format(d)41}4243function monthKey(iso: string) {44 const d = new Date(iso.length <= 10 ? `${iso}T00:00:00Z` : iso)45 return new Intl.DateTimeFormat("en-US", {46 year: "numeric",47 month: "long",48 timeZone: "UTC",49 }).format(d)50}5152function defaultTags(entries: ChangelogEntry[]) {53 const set = new Set<string>()54 for (const e of entries) {55 for (const t of e.tags ?? []) set.add(t)56 }57 return Array.from(set).sort()58}5960export function Changelog({61 entries = ENTRIES,62 tags,63 className,64}: {65 entries?: ChangelogEntry[]66 tags?: string[]67 className?: string68} = {}) {69 const allTags = tags ?? defaultTags(entries)70 const [activeTag, setActiveTag] = React.useState("")7172 const filtered = React.useMemo(() => {73 if (!activeTag) return entries74 return entries.filter((e) => e.tags?.includes(activeTag))75 }, [entries, activeTag])7677 const grouped = React.useMemo(() => {78 const map = new Map<string, ChangelogEntry[]>()79 for (const e of filtered) {80 const k = monthKey(e.date)81 const arr = map.get(k) ?? []82 arr.push(e)83 map.set(k, arr)84 }85 return Array.from(map.entries())86 }, [filtered])8788 const toggleTag = (tag: string) => {89 setActiveTag((prev) => (prev === tag ? "" : tag))90 }9192 return (93 <div className={className ?? "mx-auto max-w-[640px]"}>94 {allTags.length > 0 ? (95 <div className="mb-10 flex flex-wrap items-center gap-1.5">96 <span className="mr-1.5 font-mono text-[10.5px] tracking-[0.12em] text-muted-foreground uppercase">97 Filter98 </span>99 <TagButton100 label="all"101// … truncated
Files it writes
More from atroui
All 82 items| Component | Registry | Kind | Access | Installs | Command |
|---|---|---|---|---|---|
| Analytics Provideranalytics-provider | atroui | Blocks | Free | 1 dep | |
| AR Portfolioar-portfolio | atroui | Blocks | Free | 1 dep | |
| Before / Afterbefore-after-slider | atroui | Blocks | Free | no deps | |
| Calendly Embedcalendly-embed | atroui | Blocks | Free | 1 dep | |
| Command Menucommand-menu | atroui | Blocks | Free | 4 deps | |
| Contact Formcontact-form | atroui | Blocks | Free | 2 deps | |
| Contextual CTAcontextual-cta | atroui | Blocks | Free | 2 deps | |
| Count Upcount-up | atroui | Blocks | Free | no deps |
