{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "vercel-notification",
  "title": "Vercel Notification Popover",
  "description": "A Vercel-style notification center that adapts to the device. Renders as a popover on desktop and a bottom sheet on mobile, featuring animated tabs and hover actions.",
  "dependencies": [
    "lucide-react",
    "@base-ui/react"
  ],
  "registryDependencies": [
    "@lumi-ui/use-mobile",
    "@lumi-ui/dropdown-menu",
    "@lumi-ui/input",
    "@lumi-ui/popover",
    "@lumi-ui/sheet",
    "@lumi-ui/tabs",
    "@lumi-ui/button"
  ],
  "files": [
    {
      "path": "registry/components/vercel-notification.tsx",
      "content": "\"use client\";\n\nimport {\n  ArchiveIcon,\n  BellIcon,\n  CalendarIcon,\n  MessageCircleDashed,\n  MessageCircleIcon,\n  PlusIcon,\n  Settings,\n  TypeIcon,\n  UserIcon,\n} from \"lucide-react\";\nimport React from \"react\";\nimport { useIsMobile } from \"@/hooks/use-mobile\";\nimport { Button } from \"@/registry/ui/button\";\nimport {\n  DropdownMenu,\n  DropdownMenuContent,\n  DropdownMenuItem,\n  DropdownMenuTrigger,\n} from \"@/registry/ui/dropdown-menu\";\nimport { Input } from \"@/registry/ui/input\";\nimport { Popover, PopoverContent, PopoverTrigger } from \"@/registry/ui/popover\";\nimport { ScrollArea } from \"@/registry/ui/scroll-area\";\nimport { Sheet, SheetContent, SheetTrigger } from \"@/registry/ui/sheet\";\nimport {\n  TabIndicator,\n  Tabs,\n  TabsList,\n  TabsPanel,\n  TabsTab,\n} from \"@/registry/ui/tabs\";\n\nconst NotificationContext = React.createContext<\n  | {\n      open: boolean;\n      setOpen: (open: boolean) => void;\n      onClose: () => void;\n    }\n  | undefined\n>(undefined);\n\nconst useNotification = () => {\n  const context = React.useContext(NotificationContext);\n  if (!context) {\n    throw new Error(\n      \"useNotification must be used within a NotificationContext.Provider\",\n    );\n  }\n  return context;\n};\n\nexport const Notifications = () => {\n  const [open, setOpen] = React.useState(false);\n  const handleClose = () => setOpen(false);\n\n  return (\n    <NotificationContext.Provider\n      value={{\n        onClose: handleClose,\n        open,\n        setOpen,\n      }}\n    >\n      <NotificationContent />\n    </NotificationContext.Provider>\n  );\n};\n\nconst NotificationContent = () => {\n  const isMobile = useIsMobile();\n\n  if (isMobile) {\n    return <NotificationSheet />;\n  }\n\n  return <NotificationPopover />;\n};\n\nconst NotificationSheet = () => {\n  const { open, setOpen } = useNotification();\n  return (\n    <Sheet onOpenChange={setOpen} open={open}>\n      <SheetTrigger\n        className=\"relative\"\n        render={\n          <Button\n            aria-label=\"Notifications, you have unread messages\"\n            size=\"icon-sm\"\n            variant=\"outline\"\n          >\n            <span>\n              <BellIcon />\n            </span>\n            <span className=\"absolute -top-1 right-0 size-2.5 bg-green-600 rounded-full\" />\n            <span className=\"sr-only\">You have unread messages</span>\n          </Button>\n        }\n      />\n      <SheetContent\n        className=\"flex flex-col p-0 bg-background duration-300 h-[50dvh] max-h-[50dvh] overflow-hidden\"\n        side=\"bottom\"\n      >\n        <UnderLineTabs />\n      </SheetContent>\n    </Sheet>\n  );\n};\n\nfunction NotificationPopover() {\n  const { open, setOpen } = useNotification();\n  return (\n    <Popover onOpenChange={setOpen} open={open}>\n      <PopoverTrigger\n        render={\n          <Button\n            aria-label=\"Notifications, you have unread messages\"\n            size=\"icon-sm\"\n            variant=\"outline\"\n          >\n            <span>\n              <BellIcon />\n            </span>\n            <span className=\"absolute -top-1 -right-1 pointer-events-none\">\n              <span className=\"relative flex size-2\">\n                <span className=\"absolute inline-flex size-full animate-ping rounded-full bg-green-600 opacity-75\"></span>\n                <span className=\"relative inline-flex size-2 rounded-full bg-green-600\"></span>\n              </span>\n            </span>\n            <span className=\"sr-only\">You have unread messages</span>\n          </Button>\n        }\n      />\n      <PopoverContent\n        align=\"end\"\n        className=\"flex flex-col p-0 w-96 bg-background duration-300 h-[50dvh] max-h-[50dvh] overflow-hidden\"\n        matchAnchorWidth={false}\n        side=\"bottom\"\n      >\n        <UnderLineTabs />\n      </PopoverContent>\n    </Popover>\n  );\n}\n\nconst UnderLineTabs = () => {\n  const { onClose } = useNotification();\n  return (\n    <Tabs className=\"h-full\" defaultValue=\"all\">\n      <div className=\"flex justify-between h-12 items-center border-b\">\n        <TabsList className=\"gap-6 h-full\">\n          <TabsTab\n            className=\"w-16 cursor-pointer hover:text-foreground\"\n            value=\"all\"\n          >\n            All\n          </TabsTab>\n          <TabsTab\n            className=\"w-16 cursor-pointer hover:text-foreground\"\n            value=\"unread\"\n          >\n            Unread\n          </TabsTab>\n          <TabsTab\n            className=\"w-16 cursor-pointer hover:text-foreground\"\n            value=\"archived\"\n          >\n            Archived\n          </TabsTab>\n          <TabIndicator className=\"bg-foreground -bottom-0.5 left-px h-0.5 translate-x-(--active-tab-left) translate-y-0\" />\n        </TabsList>\n        <Button className=\"mr-2\" size=\"icon-sm\" variant=\"ghost\">\n          <Settings />\n        </Button>\n      </div>\n\n      <TabsPanel\n        className=\"h-full flex flex-col min-h-0\"\n        keepMounted\n        value=\"all\"\n      >\n        <ScrollArea\n          className=\"flex flex-col flex-1 min-h-0 h-full\"\n          gradientScrollFade\n          noScrollBar\n        >\n          <ol className=\"list-none group w-full\">\n            {Array.from({ length: 30 }).map((_, index) => (\n              <li\n                className=\"group/link border-b border-muted last:border-0 hover:bg-muted transition-colors\"\n                key={index}\n              >\n                <a\n                  className=\"outline-none flex justify-between items-center gap-4 p-4\"\n                  href=\"#\"\n                  onClick={onClose}\n                >\n                  <span>\n                    <MessageCircleDashed className=\"size-4\" />\n                  </span>\n\n                  <div className=\"flex flex-col justify-start gap-0.5 flex-1\">\n                    <span className=\"text-sm\">\n                      New notification {index + 1}\n                    </span>\n                    <span className=\"text-xs text-muted-foreground\">\n                      {index + 1} mins ago\n                    </span>\n                  </div>\n\n                  <div className=\"opacity-0 group-hover/link:opacity-100 transition-opacity w-8\">\n                    <Button\n                      className=\"cursor-pointer rounded-full text-muted-foreground hover:text-foreground\"\n                      onClick={(e) => {\n                        e.stopPropagation();\n                        e.preventDefault();\n                      }}\n                      size=\"icon-sm\"\n                      title=\"Archive\"\n                      variant=\"ghost\"\n                    >\n                      <ArchiveIcon />\n                    </Button>\n                  </div>\n                </a>\n              </li>\n            ))}\n          </ol>\n        </ScrollArea>\n        <div className=\"bg-background border-t border-muted flex-none\">\n          <Button\n            className=\"w-full rounded-none cursor-pointer hover:bg-muted shadow-none\"\n            onClick={onClose}\n            variant=\"unstyled\"\n          >\n            View All\n          </Button>\n        </div>\n      </TabsPanel>\n      <TabsPanel\n        className=\"h-full flex flex-col min-h-0\"\n        keepMounted\n        value=\"unread\"\n      >\n        <ScrollArea className=\"flex-1 min-h-0\" gradientScrollFade noScrollBar>\n          <ol className=\"list-none group\">\n            {Array.from({ length: 20 }).map((_, index) => (\n              <li\n                className=\"group/link border-b border-muted last:border-0 hover:bg-muted transition-colors\"\n                key={index}\n              >\n                <a\n                  className=\"outline-none flex justify-between items-center gap-4 p-4\"\n                  href=\"#\"\n                  onClick={onClose}\n                >\n                  <span>\n                    <MessageCircleIcon className=\"size-4\" />\n                  </span>\n                  <div className=\"flex flex-col justify-start gap-0.5 flex-1\">\n                    <span className=\"text-sm\">\n                      Unread notification {index + 1}\n                    </span>\n                    <span className=\"text-xs text-muted-foreground\">\n                      {index + 1} mins ago\n                    </span>\n                  </div>\n                  <div className=\"w-8 opacity-0\" />\n                </a>\n              </li>\n            ))}\n          </ol>\n        </ScrollArea>\n      </TabsPanel>\n      <TabsPanel className=\"flex flex-col h-full\" keepMounted value=\"archived\">\n        <ScrollArea\n          className=\"flex-1 min-h-0 flex flex-col relative\"\n          gradientScrollFade\n          noScrollBar\n        >\n          <div className=\"flex gap-2 py-2 px-4 items-center\">\n            <Input placeholder=\"Search\" variant=\"transparent\" />\n            <FilterDropdown />\n          </div>\n          <div className=\"flex justify-center items-center absolute inset-x-0 top-[calc(50%-3rem)]\">\n            <span className=\"text-muted-foreground flex flex-col items-center gap-2\">\n              <span className=\"bg-muted rounded-full p-3\">\n                <ArchiveIcon className=\"size-5\" />\n              </span>\n              No archived notifications\n            </span>\n          </div>\n        </ScrollArea>\n      </TabsPanel>\n    </Tabs>\n  );\n};\n\nconst FilterDropdown = () => {\n  return (\n    <DropdownMenu>\n      <DropdownMenuTrigger\n        render={<Button aria-label=\"Filter notifications\" variant=\"outline\" />}\n      >\n        <PlusIcon />\n        Filter\n      </DropdownMenuTrigger>\n      <DropdownMenuContent\n        align=\"end\"\n        className=\"w-36\"\n        matchAnchorWidth={false}\n      >\n        <DropdownMenuItem>\n          <UserIcon /> Author\n          <span className=\"sr-only\">Filter by author</span>\n        </DropdownMenuItem>\n        <DropdownMenuItem>\n          <CalendarIcon /> Date\n          <span className=\"sr-only\">Filter by date</span>\n        </DropdownMenuItem>\n        <DropdownMenuItem>\n          <TypeIcon /> Type\n          <span className=\"sr-only\">Filter by type</span>\n        </DropdownMenuItem>\n      </DropdownMenuContent>\n    </DropdownMenu>\n  );\n};\n",
      "type": "registry:component"
    }
  ],
  "meta": {
    "alignment": "top-right",
    "iframeHeight": 800
  },
  "categories": [
    "overlays-dialogs",
    "dashboard"
  ],
  "type": "registry:component"
}