Changelog

Release notes timeline with New / Improved / Fixed tags.

  1. v2.4

    Nov 12

    Command palette

    New
    • • Press ⌘K anywhere to search
    • • Keyboard navigation for every result
  2. v2.3

    Oct 28

    Faster dashboards

    Improved
    • • Charts render 3× faster
    • • Lazy-loaded widgets
  3. v2.2

    Oct 09

    Export fixes

    Fixed
    • • CSV exports keep time zones
    • • PDF page breaks no longer cut tables

Installation

  1. Install the dependencies

    $ npm i motion lucide-react
  2. Copy the source into components/ui/changelog.tsx

    components/ui/changelog.tsx
    "use client";
    
    import { motion } from "motion/react";
    
    type Release = { version: string; date: string; title: string; tag: "New" | "Improved" | "Fixed"; items: string[] };
    
    const releases: Release[] = [
      { version: "v2.4", date: "Nov 12", title: "Command palette", tag: "New", items: ["Press ⌘K anywhere to search", "Keyboard navigation for every result"] },
      { version: "v2.3", date: "Oct 28", title: "Faster dashboards", tag: "Improved", items: ["Charts render 3× faster", "Lazy-loaded widgets"] },
      { version: "v2.2", date: "Oct 09", title: "Export fixes", tag: "Fixed", items: ["CSV exports keep time zones", "PDF page breaks no longer cut tables"] },
    ];
    
    const tagStyle = { New: "bg-blue-50 text-blue-700", Improved: "bg-emerald-50 text-emerald-700", Fixed: "bg-amber-50 text-amber-700" };
    
    export function Changelog({ items = releases }: { items?: Release[] }) {
      return (
        <ol className="space-y-10">
          {items.map((r, i) => (
            <motion.li
              key={r.version}
              initial={{ opacity: 0, y: 16 }}
              whileInView={{ opacity: 1, y: 0 }}
              viewport={{ once: true }}
              transition={{ delay: i * 0.1 }}
              className="grid gap-3 sm:grid-cols-[120px_1fr]"
            >
              <div className="text-sm">
                <p className="font-mono font-medium text-zinc-900">{r.version}</p>
                <p className="text-zinc-400">{r.date}</p>
              </div>
              <div className="border-l border-zinc-200 pl-6">
                <div className="flex items-center gap-2">
                  <h3 className="font-semibold text-zinc-900">{r.title}</h3>
                  <span className={`rounded-full px-2 py-0.5 text-xs font-medium ${tagStyle[r.tag]}`}>{r.tag}</span>
                </div>
                <ul className="mt-2 space-y-1 text-sm text-zinc-500">
                  {r.items.map((it) => (
                    <li key={it}>• {it}</li>
                  ))}
                </ul>
              </div>
            </motion.li>
          ))}
        </ol>
      );
    }
    
    export default function Demo() {
      return (
        <div className="w-full max-w-xl">
          <Changelog />
        </div>
      );
    }
    
  3. Use it

    The file exports the component and a Demo showing typical usage. Import whichever you need.