{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "notifications-3",
  "title": "Notification Center Tabs",
  "description": "Notification center card with All, Unread, and Mentions tabs, per-row read toggling, a mark all read action, and a caught up empty state.",
  "dependencies": [
    "@base-ui/react"
  ],
  "registryDependencies": [
    "avatar",
    "badge",
    "button",
    "card",
    "scroll-area",
    "separator",
    "tabs"
  ],
  "files": [
    {
      "path": "registry/blocks/notifications/3/notifications-block.tsx",
      "content": "\"use client\"\n\nimport { useMemo, useState } from \"react\"\nimport { cn } from \"@/lib/utils\"\nimport { Avatar, AvatarFallback, AvatarImage } from \"@/components/ui/avatar\"\nimport { Badge } from \"@/components/ui/badge\"\nimport { Button } from \"@/components/ui/button\"\nimport {\n  Card,\n  CardAction,\n  CardContent,\n  CardDescription,\n  CardHeader,\n  CardTitle,\n} from \"@/components/ui/card\"\nimport { ScrollArea } from \"@/components/ui/scroll-area\"\nimport { Separator } from \"@/components/ui/separator\"\nimport { Tabs, TabsContent, TabsList, TabsTrigger } from \"@/components/ui/tabs\"\nimport { IconPlaceholder } from \"@/components/icons/icon-placeholder\"\n\n/** Props a call site may pass through to an icon. */\ntype IconProps = { className?: string; size?: number | string }\n\ntype NotificationKind =\n  | \"mention\"\n  | \"comment\"\n  | \"follow\"\n  | \"review\"\n  | \"star\"\n  | \"like\"\n  | \"security\"\n\ntype Notification = {\n  id: string\n  kind: NotificationKind\n  actor: string\n  initials: string\n  avatar?: string\n  title: string\n  snippet: string\n  timestamp: string\n  unread: boolean\n  mention: boolean\n}\n\nconst iconByKind: Record<\n  NotificationKind,\n  React.ComponentType<{ className?: string }>\n> = {\n  mention: (p: IconProps) => (\n    <IconPlaceholder\n      lucide=\"AtSign\"\n      tabler=\"IconAt\"\n      hugeicons=\"AtIcon\"\n      phosphor=\"At\"\n      remixicon=\"RiAtLine\"\n      {...p}\n    />\n  ),\n  comment: (p: IconProps) => (\n    <IconPlaceholder\n      lucide=\"MessageCircle\"\n      tabler=\"IconMessageCircle\"\n      hugeicons=\"Message01Icon\"\n      phosphor=\"ChatCircle\"\n      remixicon=\"RiChat3Line\"\n      {...p}\n    />\n  ),\n  follow: (p: IconProps) => (\n    <IconPlaceholder\n      lucide=\"UserPlus\"\n      tabler=\"IconUserPlus\"\n      hugeicons=\"UserAddIcon\"\n      phosphor=\"UserPlus\"\n      remixicon=\"RiUserAddLine\"\n      {...p}\n    />\n  ),\n  review: (p: IconProps) => (\n    <IconPlaceholder\n      lucide=\"GitPullRequest\"\n      tabler=\"IconGitPullRequest\"\n      hugeicons=\"GitPullRequestIcon\"\n      phosphor=\"GitPullRequest\"\n      remixicon=\"RiGitPullRequestLine\"\n      {...p}\n    />\n  ),\n  star: (p: IconProps) => (\n    <IconPlaceholder\n      lucide=\"Star\"\n      tabler=\"IconStar\"\n      hugeicons=\"StarIcon\"\n      phosphor=\"Star\"\n      remixicon=\"RiStarLine\"\n      {...p}\n    />\n  ),\n  like: (p: IconProps) => (\n    <IconPlaceholder\n      lucide=\"Heart\"\n      tabler=\"IconHeart\"\n      hugeicons=\"HeartIcon\"\n      phosphor=\"Heart\"\n      remixicon=\"RiHeart3Line\"\n      {...p}\n    />\n  ),\n  security: (p: IconProps) => (\n    <IconPlaceholder\n      lucide=\"ShieldCheck\"\n      tabler=\"IconShieldCheck\"\n      hugeicons=\"Shield01Icon\"\n      phosphor=\"ShieldCheck\"\n      remixicon=\"RiShieldCheckLine\"\n      {...p}\n    />\n  ),\n}\n\nconst accentByKind: Record<NotificationKind, string> = {\n  mention: \"text-primary\",\n  comment: \"text-foreground\",\n  follow: \"text-foreground\",\n  review: \"text-primary\",\n  star: \"text-foreground\",\n  like: \"text-foreground\",\n  security: \"text-destructive\",\n}\n\nconst initialNotifications: Notification[] = [\n  {\n    id: \"n1\",\n    kind: \"mention\",\n    actor: \"Mara Ellis\",\n    initials: \"ME\",\n    avatar: \"https://i.pravatar.cc/150?img=44\",\n    title: \"Mara Ellis mentioned you\",\n    snippet:\n      \"@you can you take a look at the new pricing copy before we ship the Acme launch page?\",\n    timestamp: \"2 Min Ago\",\n    unread: true,\n    mention: true,\n  },\n  {\n    id: \"n2\",\n    kind: \"review\",\n    actor: \"Devon Park\",\n    initials: \"DP\",\n    avatar: \"https://i.pravatar.cc/150?img=33\",\n    title: \"Review requested on acme/web#482\",\n    snippet:\n      \"Refactor checkout flow to use the new payment intents API. 14 files changed.\",\n    timestamp: \"18 Min Ago\",\n    unread: true,\n    mention: false,\n  },\n  {\n    id: \"n3\",\n    kind: \"comment\",\n    actor: \"Priya Nair\",\n    initials: \"PN\",\n    avatar: \"https://i.pravatar.cc/150?img=45\",\n    title: \"New comment on “Q3 Roadmap”\",\n    snippet:\n      \"Left a few notes on the onboarding milestone. Think we can pull it forward a sprint.\",\n    timestamp: \"1 Hour Ago\",\n    unread: true,\n    mention: false,\n  },\n  {\n    id: \"n4\",\n    kind: \"mention\",\n    actor: \"Leo Tanaka\",\n    initials: \"LT\",\n    avatar: \"https://i.pravatar.cc/150?img=51\",\n    title: \"Leo Tanaka mentioned you in #design\",\n    snippet:\n      \"thanks @you, pushed the updated tokens, dark mode contrast is fixed now.\",\n    timestamp: \"3 Hours Ago\",\n    unread: false,\n    mention: true,\n  },\n  {\n    id: \"n5\",\n    kind: \"follow\",\n    actor: \"Sasha Kim\",\n    initials: \"SK\",\n    avatar: \"https://i.pravatar.cc/150?img=32\",\n    title: \"Sasha Kim started following you\",\n    snippet: \"You now share 12 workspaces across the Acme organization.\",\n    timestamp: \"5 Hours Ago\",\n    unread: false,\n    mention: false,\n  },\n  {\n    id: \"n6\",\n    kind: \"security\",\n    actor: \"Acme Security\",\n    initials: \"AS\",\n    title: \"New sign-in from a new device\",\n    snippet:\n      \"MacBook Pro • San Francisco, CA. If this wasn’t you, review your active sessions.\",\n    timestamp: \"Yesterday\",\n    unread: false,\n    mention: false,\n  },\n  {\n    id: \"n7\",\n    kind: \"star\",\n    actor: \"Community\",\n    initials: \"AC\",\n    title: \"Your project hit 1,000 stars\",\n    snippet: \"acme/ui-kit just crossed 1k stars on the registry. Nice work.\",\n    timestamp: \"2 Days Ago\",\n    unread: false,\n    mention: false,\n  },\n]\n\ntype Filter = \"all\" | \"unread\" | \"mentions\"\n\nexport default function NotificationsBlock() {\n  const [items, setItems] = useState<Notification[]>(initialNotifications)\n  const [filter, setFilter] = useState<Filter>(\"all\")\n\n  const unreadCount = useMemo(\n    () => items.filter((item) => item.unread).length,\n    [items]\n  )\n  const mentionsCount = useMemo(\n    () => items.filter((item) => item.mention).length,\n    [items]\n  )\n\n  const visible = useMemo(() => {\n    if (filter === \"unread\") return items.filter((item) => item.unread)\n    if (filter === \"mentions\") return items.filter((item) => item.mention)\n    return items\n  }, [items, filter])\n\n  function markRead(id: string) {\n    setItems((prev) =>\n      prev.map((item) => (item.id === id ? { ...item, unread: false } : item))\n    )\n  }\n\n  function markAllRead() {\n    setItems((prev) => prev.map((item) => ({ ...item, unread: false })))\n  }\n\n  return (\n    <section className=\"flex w-full items-center justify-center bg-muted/30 px-6 py-16 text-foreground\">\n      <Card className=\"mx-auto w-full max-w-xl\">\n        <CardHeader>\n          <CardTitle className=\"flex items-center gap-2 text-lg\">\n            Notifications\n            {unreadCount > 0 ? (\n              <Badge variant=\"secondary\" className=\"tabular-nums\">\n                {unreadCount} New\n              </Badge>\n            ) : null}\n          </CardTitle>\n          <CardDescription>Activity across your Acme workspace</CardDescription>\n          <CardAction>\n            <Button\n              variant=\"link\"\n              size=\"sm\"\n              className=\"h-auto p-0\"\n              onClick={markAllRead}\n              disabled={unreadCount === 0}\n            >\n              <IconPlaceholder\n                lucide=\"CheckCheck\"\n                tabler=\"IconChecks\"\n                hugeicons=\"TickDouble01Icon\"\n                phosphor=\"Checks\"\n                remixicon=\"RiCheckDoubleLine\"\n                data-icon=\"inline-start\"\n              />\n              Mark All Read\n            </Button>\n          </CardAction>\n        </CardHeader>\n\n        <CardContent>\n          <Tabs\n            value={filter}\n            onValueChange={(value) => setFilter(value as Filter)}\n          >\n            <TabsList className=\"w-full\">\n              <TabsTrigger value=\"all\" className=\"flex-1\">\n                All\n              </TabsTrigger>\n              <TabsTrigger value=\"unread\" className=\"flex-1 gap-1.5\">\n                Unread\n                {unreadCount > 0 ? (\n                  <span className=\"rounded-full bg-primary/10 px-1.5 text-xs font-medium text-primary tabular-nums\">\n                    {unreadCount}\n                  </span>\n                ) : null}\n              </TabsTrigger>\n              <TabsTrigger value=\"mentions\" className=\"flex-1 gap-1.5\">\n                Mentions\n                {mentionsCount > 0 ? (\n                  <span className=\"rounded-full bg-muted px-1.5 text-xs font-medium text-muted-foreground tabular-nums\">\n                    {mentionsCount}\n                  </span>\n                ) : null}\n              </TabsTrigger>\n            </TabsList>\n\n            <TabsContent value={filter} className=\"mt-4\">\n              {visible.length === 0 ? (\n                <div className=\"flex flex-col items-center justify-center gap-2 rounded-lg border border-dashed border-border px-6 py-14 text-center\">\n                  <IconPlaceholder\n                    lucide=\"CheckCheck\"\n                    tabler=\"IconChecks\"\n                    hugeicons=\"TickDouble01Icon\"\n                    phosphor=\"Checks\"\n                    remixicon=\"RiCheckDoubleLine\"\n                    className=\"size-6 text-muted-foreground\"\n                  />\n                  <p className=\"text-sm font-medium\">You’re all caught up</p>\n                  <p className=\"text-sm text-muted-foreground\">\n                    No notifications in this view.\n                  </p>\n                </div>\n              ) : (\n                <ScrollArea className=\"h-96 [&_[data-slot=scroll-area-viewport]]:scroll-fade-y\">\n                  <ul className=\"-mx-2 pr-2\">\n                    {visible.map((item, index) => {\n                      const Icon = iconByKind[item.kind]\n                      return (\n                        <li key={item.id}>\n                          {index > 0 ? (\n                            <Separator className=\"opacity-60\" />\n                          ) : null}\n                          <button\n                            type=\"button\"\n                            onClick={() => markRead(item.id)}\n                            className={cn(\n                              \"group flex w-full items-start gap-3 rounded-md px-2 py-3 text-left transition-colors hover:bg-muted/60 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring\",\n                              item.unread && \"bg-muted/30\"\n                            )}\n                          >\n                            <div className=\"relative shrink-0\">\n                              <Avatar className=\"size-9\">\n                                {item.avatar ? (\n                                  <AvatarImage\n                                    src={item.avatar}\n                                    alt={item.actor}\n                                    className=\"grayscale\"\n                                  />\n                                ) : null}\n                                <AvatarFallback\n                                  className={cn(\n                                    \"text-xs font-medium\",\n                                    !item.avatar && accentByKind[item.kind]\n                                  )}\n                                >\n                                  {item.avatar ? (\n                                    item.initials\n                                  ) : (\n                                    <Icon\n                                      className=\"size-4\"\n                                      aria-hidden=\"true\"\n                                    />\n                                  )}\n                                </AvatarFallback>\n                              </Avatar>\n                              {item.avatar ? (\n                                <span\n                                  className={cn(\n                                    \"absolute -right-1 -bottom-1 flex size-5 items-center justify-center rounded-full bg-muted ring-2 ring-card\",\n                                    accentByKind[item.kind]\n                                  )}\n                                >\n                                  <Icon className=\"size-3\" />\n                                </span>\n                              ) : null}\n                            </div>\n\n                            <div className=\"min-w-0 flex-1\">\n                              <div className=\"flex items-center gap-2\">\n                                <p\n                                  className={cn(\n                                    \"truncate text-sm\",\n                                    item.unread\n                                      ? \"font-semibold text-foreground\"\n                                      : \"font-medium text-foreground\"\n                                  )}\n                                >\n                                  {item.title}\n                                </p>\n                                <span className=\"ml-auto shrink-0 text-xs text-muted-foreground\">\n                                  {item.timestamp}\n                                </span>\n                              </div>\n                              <p className=\"mt-0.5 line-clamp-2 text-sm text-muted-foreground\">\n                                {item.snippet}\n                              </p>\n                            </div>\n\n                            <span\n                              className={cn(\n                                \"mt-1.5 size-2 shrink-0 rounded-full\",\n                                item.unread ? \"bg-primary\" : \"bg-transparent\"\n                              )}\n                              aria-hidden\n                            />\n                          </button>\n                        </li>\n                      )\n                    })}\n                  </ul>\n                </ScrollArea>\n              )}\n            </TabsContent>\n          </Tabs>\n        </CardContent>\n      </Card>\n    </section>\n  )\n}\n",
      "type": "registry:component",
      "target": "components/blocks/notifications-3.tsx"
    }
  ],
  "meta": {
    "height": "676px",
    "tier": "free"
  },
  "categories": [
    "notifications"
  ],
  "type": "registry:block"
}