# V sheet 6

> Notification Center

A shadcn/ui block from the [@cnippet](https://ui.cnippet.dev/) registry, listed on [shadcn.exchange](https://shadcn.exchange/r/cnippet/v-sheet-6).

- Page, with a live preview: https://shadcn.exchange/r/cnippet/v-sheet-6
- Screenshot: https://abzjhwgwpdpz7ua5.public.blob.vercel-storage.com/shots/cnippet/v-sheet-6-light-00e8fc9b67410a77.png
- Registry item: https://ui.cnippet.dev/r/v-sheet-6.json
- Categories: sheet
- Listed: 2026-10-13, source last changed 2026-10-13

## Install

```bash
npx shadcn@latest add https://ui.cnippet.dev/r/v-sheet-6.json
```

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

## Dependencies

- Registry items: `@cnippet/sheet`

## Files

### `registry/default/variants/v-sheet-6.tsx`

```tsx
"use client";

import {
  AtSignIcon,
  BellIcon,
  CheckCheckIcon,
  GitPullRequestIcon,
  MessageSquareIcon,
} from "lucide-react";
import { useState } from "react";
import { Badge } from "@/registry/default/ui/badge";
import { Button } from "@/registry/default/ui/button";
import {
  Sheet,
  SheetClose,
  SheetDescription,
  SheetFooter,
  SheetHeader,
  SheetPanel,
  SheetPopup,
  SheetTitle,
  SheetTrigger,
} from "@/registry/default/ui/sheet";

type Notification = {
  id: number;
  icon: React.ElementType;
  iconBg: string;
  title: string;
  body: string;
  time: string;
  read: boolean;
};

const initial: Notification[] = [
  {
    body: "Left a comment on your pull request #42.",
    icon: MessageSquareIcon,
    iconBg: "bg-blue-100 text-blue-600 dark:bg-blue-950 dark:text-blue-400",
    id: 1,
    read: false,
    time: "2 min ago",
    title: "Sarah commented on your PR",
  },
  {
    body: "Mentioned you in the #design-review channel.",
    icon: AtSignIcon,
    iconBg:
      "bg-violet-100 text-violet-600 dark:bg-violet-950 dark:text-violet-400",
    id: 2,
    read: false,
    time: "14 min ago",
    title: "Marcus mentioned you",
  },
  {
    body: "Your PR #38 was approved and merged into main.",
    icon: GitPullRequestIcon,
    iconBg:
      "bg-emerald-100 text-emerald-600 dark:bg-emerald-950 dark:text-emerald-400",
    id: 3,
    read: false,
    time: "1 hr ago",
    title: "PR merged successfully",
  },
  {
    body: "You've been assigned to issue #117: Dashboard redesign.",
    icon: BellIcon,
    iconBg: "bg-amber-100 text-amber-600 dark:bg-amber-950 dark:text-amber-400",
    id: 4,
    read: true,
    time: "3 hr ago",
    title: "New issue assigned to you",
  },
];

export function Pattern() {
  const [notifications, setNotifications] = useState(initial);
  const unread = notifications.filter((n) => !n.read).length;

  const markAllRead = () =>
    setNotifications((prev) => prev.map((n) => ({ ...n, read: true })));

  const markRead = (id: number) =>
    setNotifications((prev) =>
      prev.map((n) => (n.id === id ? { ...n, read: true } : n)),
    );

  return (
    <div className="flex items-center justify-center">
      <Sheet>
        <SheetTrigger render={<Button variant="outline" />}>
          <BellIcon aria-hidden="true" />
          Notifications
          {unread > 0 && (
            <Badge className="ml-0.5 h-4 px-1 text-[10px]">{unread}</Badge>
          )}
        </SheetTrigger>
        <SheetPopup>
          <SheetHeader>
            <div className="flex items-center justify-between">
              <SheetTitle>Notifications</SheetTitle>
              {unread > 0 && (
                <Button
                  className="h-auto p-0 text-xs"
                  onClick={markAllRead}
                  variant="link"
                >
                  <CheckCheckIcon className="mr-1 size-3.5" />
                  Mark all read
                </Button>
              )}
            </div>
            <SheetDescription>
              {unread > 0
                ? `You have ${unread} unread notification${unread > 1 ? "s" : ""}.`
                : "You're all caught up."}
            </SheetDescription>
          </SheetHeader>
          <SheetPanel className="space-y-1 px-3">
            {notifications.map((n) => {
              const Icon = n.icon;
              return (
                <button
                  className={`flex w-full items-start gap-3 rounded-lg p-3 text-left transition-colors hover:bg-muted ${n.read ? "opacity-60" : ""}`}
                  key={n.id}
                  onClick={() => markRead(n.id)}
                >
                  <span
                    className={`mt-0.5 flex size-8 shrink-0 items-center justify-center rounded-full ${n.iconBg}`}
                  >
                    <Icon aria-hidden="true" className="size-4" />
                  </span>
                  <div className="min-w-0 flex-1">
                    <div className="flex items-center justify-between gap-2">
                      <p className="truncate font-medium text-sm">{n.title}</p>
                      {!n.read && (
                        <span className="size-1.5 shrink-0 rounded-full bg-primary" />
                      )}
                    </div>
                    <p className="mt-0.5 text-muted-foreground text-xs">
                      {n.body}
                    </p>
                    <p className="mt-1 text-muted-foreground text-xs">
                      {n.time}
                    </p>
                  </div>
                </button>
              );
            })}
          </SheetPanel>
          <SheetFooter variant="bare">
            <SheetClose
              render={<Button className="w-full" variant="outline" />}
            >
              Close
            </SheetClose>
          </SheetFooter>
        </SheetPopup>
      </Sheet>
    </div>
  );
}
```
