# Tabbed Change Filter

> Changelog with tabs filtering release entries by All, Added, Improved, or Fixed, each showing version, date, badge, title, and description.

A shadcn/ui block from the [@7ovr](https://7ovr.com) registry, listed on [shadcn.exchange](https://shadcn.exchange/r/7ovr/changelog-2).

- Page, with a live preview: https://shadcn.exchange/r/7ovr/changelog-2
- Screenshot: https://abzjhwgwpdpz7ua5.public.blob.vercel-storage.com/shots/7ovr/changelog-2-light-98e40468d4d30d81.png
- Registry item: https://7ovr.com/r/changelog-2.json
- Categories: changelog
- Listed: 2026-10-13, source last changed 2026-10-13

## Install

```bash
npx shadcn@latest add https://7ovr.com/r/changelog-2.json
```

Run it in a project that has shadcn/ui set up. It adds the files below and installs what they depend on.

## Dependencies

- npm packages: `@base-ui/react`
- Registry items: `badge`, `separator`, `tabs`

## Files

### `registry/blocks/changelog/2/changelog-block.tsx`

```tsx
import { Badge } from "@/components/ui/badge"
import { Separator } from "@/components/ui/separator"
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"

type ChangeType = "Added" | "Improved" | "Fixed"

type Entry = {
  version: string
  date: string
  type: ChangeType
  title: string
  description: string
}

const entries: Entry[] = [
  {
    version: "2.4.0",
    date: "Jun 18, 2026",
    type: "Added",
    title: "Global command palette",
    description:
      "Press Command K anywhere to jump to projects, members, or files.",
  },
  {
    version: "2.4.0",
    date: "Jun 18, 2026",
    type: "Improved",
    title: "Faster billing page",
    description:
      "Large workspaces now load the billing view about twice as fast.",
  },
  {
    version: "2.3.2",
    date: "Jun 06, 2026",
    type: "Fixed",
    title: "Workspace rename sync",
    description: "Resolved a rare error when renaming a busy workspace.",
  },
  {
    version: "2.3.0",
    date: "May 30, 2026",
    type: "Added",
    title: "Role-based access",
    description: "Invite teammates as Admin, Editor, or Viewer.",
  },
  {
    version: "2.3.0",
    date: "May 30, 2026",
    type: "Improved",
    title: "Keyboard navigation",
    description: "Dialogs and menus are now fully keyboard accessible.",
  },
  {
    version: "2.2.1",
    date: "May 12, 2026",
    type: "Fixed",
    title: "Sidebar layout shift",
    description: "Toggling the sidebar no longer nudges page content.",
  },
]

const typeVariant: Record<ChangeType, "default" | "secondary" | "outline"> = {
  Added: "default",
  Improved: "secondary",
  Fixed: "outline",
}

const tabs: { value: string; label: string; match?: ChangeType }[] = [
  { value: "all", label: "All" },
  { value: "added", label: "Added", match: "Added" },
  { value: "improved", label: "Improved", match: "Improved" },
  { value: "fixed", label: "Fixed", match: "Fixed" },
]

function EntryRow({ entry }: { entry: Entry }) {
  return (
    <div className="flex flex-col gap-2 sm:flex-row sm:items-start sm:gap-5">
      <div className="flex shrink-0 items-center gap-2.5 sm:w-32 sm:flex-col sm:items-start sm:gap-1">
        <span className="font-mono text-xs font-semibold tabular-nums">
          {entry.version}
        </span>
        <span className="text-xs text-muted-foreground tabular-nums">
          {entry.date}
        </span>
      </div>
      <div className="flex flex-1 flex-col gap-1.5">
        <div className="flex items-center gap-2.5">
          <Badge variant={typeVariant[entry.type]}>{entry.type}</Badge>
          <h3 className="font-heading text-sm font-semibold">{entry.title}</h3>
        </div>
        <p className="text-sm/relaxed text-muted-foreground">
          {entry.description}
        </p>
      </div>
    </div>
  )
}

export default function ChangelogBlock() {
  return (
    <section className="flex w-full items-center justify-center bg-muted/30 px-6 py-16 text-foreground">
      <div className="mx-auto w-full max-w-2xl">
        <div className="mb-8">
          <h2 className="font-heading text-2xl font-bold tracking-tight">
            Changelog
          </h2>
          <p className="mt-2 text-sm text-muted-foreground">
            Everything we have shipped, filterable by type.
          </p>
        </div>

        <Tabs defaultValue="all" className="gap-6">
          <TabsList>
            {tabs.map((tab) => (
              <TabsTrigger key={tab.value} value={tab.value}>
                {tab.label}
              </TabsTrigger>
            ))}
          </TabsList>

          {tabs.map((tab) => {
            const list = tab.match
              ? entries.filter((e) => e.type === tab.match)
              : entries
            return (
              <TabsContent key={tab.value} value={tab.value}>
                <div className="flex flex-col">
                  {list.map((entry, i) => (
                    <div key={`${entry.version}-${entry.title}`}>
                      {i > 0 && <Separator className="my-5" />}
                      <EntryRow entry={entry} />
                    </div>
                  ))}
                </div>
              </TabsContent>
            )
          })}
        </Tabs>
      </div>
    </section>
  )
}
```
