{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "team-notifications",
  "title": "Team Notifications",
  "description": "Team notifications and alerts with filtering and management.",
  "registryDependencies": [
    "avatar",
    "badge",
    "button",
    "card",
    "dropdown-menu",
    "empty",
    "select",
    "separator"
  ],
  "files": [
    {
      "path": "registry/new-york/blocks/team/team-notifications.tsx",
      "content": "\"use client\";\n\nimport {\n  Bell,\n  Check,\n  CheckCheck,\n  MessageSquare,\n  MoreVertical,\n  X,\n} from \"lucide-react\";\nimport { useState } from \"react\";\nimport { cn } from \"@/lib/utils\";\nimport {\n  Avatar,\n  AvatarFallback,\n  AvatarImage,\n} from \"@/registry/new-york/ui/avatar\";\nimport { Badge } from \"@/registry/new-york/ui/badge\";\nimport { Button } from \"@/registry/new-york/ui/button\";\nimport {\n  Card,\n  CardContent,\n  CardDescription,\n  CardHeader,\n  CardTitle,\n} from \"@/registry/new-york/ui/card\";\nimport {\n  DropdownMenu,\n  DropdownMenuContent,\n  DropdownMenuItem,\n  DropdownMenuSeparator,\n  DropdownMenuTrigger,\n} from \"@/registry/new-york/ui/dropdown-menu\";\nimport {\n  Empty,\n  EmptyHeader,\n  EmptyMedia,\n  EmptyTitle,\n} from \"@/registry/new-york/ui/empty\";\nimport {\n  Select,\n  SelectContent,\n  SelectItem,\n  SelectTrigger,\n  SelectValue,\n} from \"@/registry/new-york/ui/select\";\nimport { Separator } from \"@/registry/new-york/ui/separator\";\n\nexport type NotificationType =\n  | \"mention\"\n  | \"ai_event\"\n  | \"member_joined\"\n  | \"file_shared\"\n  | \"note_updated\"\n  | \"project_updated\"\n  | \"system\";\n\nexport interface TeamNotification {\n  id: string;\n  type: NotificationType;\n  title: string;\n  message: string;\n  user?: {\n    id: string;\n    name: string;\n    avatar?: string;\n  };\n  link?: string;\n  read: boolean;\n  timestamp: Date;\n  metadata?: Record<string, unknown>;\n}\n\nexport interface TeamNotificationsProps {\n  notifications?: TeamNotification[];\n  onMarkAsRead?: (notificationId: string) => Promise<void>;\n  onMarkAllAsRead?: () => Promise<void>;\n  onDelete?: (notificationId: string) => Promise<void>;\n  onClearAll?: () => Promise<void>;\n  className?: string;\n  showFilters?: boolean;\n  unreadCount?: number;\n}\n\nfunction formatRelativeTime(date: Date): string {\n  const now = Date.now();\n  const diff = now - date.getTime();\n  const minutes = Math.floor(diff / 60_000);\n  const hours = Math.floor(minutes / 60);\n  const days = Math.floor(hours / 24);\n\n  if (minutes < 1) return \"Just now\";\n  if (minutes < 60) return `${minutes}m ago`;\n  if (hours < 24) return `${hours}h ago`;\n  if (days < 7) return `${days}d ago`;\n  return new Intl.DateTimeFormat(\"en-US\", {\n    month: \"short\",\n    day: \"numeric\",\n  }).format(date);\n}\n\nfunction getNotificationIcon(type: NotificationType) {\n  switch (type) {\n    case \"mention\":\n      return MessageSquare;\n    case \"ai_event\":\n      return Bell;\n    default:\n      return Bell;\n  }\n}\n\nfunction getInitials(name: string): string {\n  return name\n    .split(\" \")\n    .map((n) => n[0])\n    .join(\"\")\n    .toUpperCase()\n    .slice(0, 2);\n}\n\nexport default function TeamNotifications({\n  notifications = [],\n  onMarkAsRead,\n  onMarkAllAsRead,\n  onDelete,\n  onClearAll,\n  className,\n  showFilters = true,\n  unreadCount,\n}: TeamNotificationsProps) {\n  const [typeFilter, setTypeFilter] = useState<string>(\"all\");\n  const [statusFilter, setStatusFilter] = useState<string>(\"all\");\n\n  const filteredNotifications = notifications.filter((notification) => {\n    const matchesType =\n      typeFilter === \"all\" || notification.type === typeFilter;\n    const matchesStatus =\n      statusFilter === \"all\" ||\n      (statusFilter === \"unread\" && !notification.read) ||\n      (statusFilter === \"read\" && notification.read);\n    return matchesType && matchesStatus;\n  });\n\n  const unreadNotifications = filteredNotifications.filter((n) => !n.read);\n  const displayUnreadCount = unreadCount ?? unreadNotifications.length;\n\n  return (\n    <Card className={cn(\"w-full shadow-xs\", className)}>\n      <CardHeader>\n        <div className=\"flex flex-col gap-4\">\n          <div className=\"flex flex-col flex-wrap gap-3 md:flex-row md:items-start md:justify-between\">\n            <div className=\"flex min-w-0 flex-1 flex-col gap-2\">\n              <CardTitle>Notifications</CardTitle>\n              <CardDescription>\n                {displayUnreadCount > 0 && (\n                  <span className=\"text-primary\">\n                    {displayUnreadCount} unread\n                  </span>\n                )}\n                {displayUnreadCount === 0 && \"All caught up!\"}\n              </CardDescription>\n            </div>\n            <div className=\"flex gap-2\">\n              {onMarkAllAsRead && displayUnreadCount > 0 && (\n                <Button\n                  onClick={onMarkAllAsRead}\n                  size=\"sm\"\n                  type=\"button\"\n                  variant=\"outline\"\n                >\n                  <CheckCheck className=\"size-4\" />\n                  Mark all read\n                </Button>\n              )}\n              {onClearAll && (\n                <Button\n                  onClick={onClearAll}\n                  size=\"sm\"\n                  type=\"button\"\n                  variant=\"outline\"\n                >\n                  <X className=\"size-4\" />\n                  Clear all\n                </Button>\n              )}\n            </div>\n          </div>\n          {showFilters && (\n            <div className=\"flex flex-wrap gap-2\">\n              <Select onValueChange={setTypeFilter} value={typeFilter}>\n                <SelectTrigger className=\"w-full md:w-[140px]\">\n                  <SelectValue placeholder=\"All types\" />\n                </SelectTrigger>\n                <SelectContent>\n                  <SelectItem value=\"all\">All types</SelectItem>\n                  <SelectItem value=\"mention\">Mentions</SelectItem>\n                  <SelectItem value=\"ai_event\">AI Events</SelectItem>\n                  <SelectItem value=\"member_joined\">Members</SelectItem>\n                  <SelectItem value=\"file_shared\">Files</SelectItem>\n                  <SelectItem value=\"note_updated\">Notes</SelectItem>\n                  <SelectItem value=\"project_updated\">Projects</SelectItem>\n                  <SelectItem value=\"system\">System</SelectItem>\n                </SelectContent>\n              </Select>\n              <Select onValueChange={setStatusFilter} value={statusFilter}>\n                <SelectTrigger className=\"w-full md:w-[140px]\">\n                  <SelectValue placeholder=\"All statuses\" />\n                </SelectTrigger>\n                <SelectContent>\n                  <SelectItem value=\"all\">All</SelectItem>\n                  <SelectItem value=\"unread\">Unread</SelectItem>\n                  <SelectItem value=\"read\">Read</SelectItem>\n                </SelectContent>\n              </Select>\n            </div>\n          )}\n        </div>\n      </CardHeader>\n      <CardContent>\n        {filteredNotifications.length === 0 ? (\n          <Empty>\n            <EmptyHeader>\n              <EmptyMedia variant=\"icon\">\n                <Bell className=\"size-6\" />\n              </EmptyMedia>\n              <EmptyTitle>\n                {typeFilter !== \"all\" || statusFilter !== \"all\"\n                  ? \"No notifications match your filters\"\n                  : \"No notifications yet\"}\n              </EmptyTitle>\n            </EmptyHeader>\n          </Empty>\n        ) : (\n          <div className=\"flex flex-col gap-0\">\n            {filteredNotifications.map((notification, idx) => {\n              const Icon = getNotificationIcon(notification.type);\n              const isFirst = idx === 0;\n              const isLast = idx === filteredNotifications.length - 1;\n              return (\n                <div key={notification.id}>\n                  <div\n                    className={cn(\n                      \"flex items-start gap-4 p-4 transition-colors\",\n                      !notification.read && \"bg-primary/5\",\n                      \"hover:bg-muted/50\",\n                      isFirst && \"rounded-t-lg\",\n                      isLast && \"rounded-b-lg\"\n                    )}\n                  >\n                    <div\n                      className={cn(\n                        \"flex size-10 shrink-0 items-center justify-center rounded-full\",\n                        notification.read\n                          ? \"bg-muted text-muted-foreground\"\n                          : \"bg-primary/10 text-primary\"\n                      )}\n                    >\n                      {notification.user ? (\n                        <Avatar className=\"size-10\">\n                          <AvatarImage\n                            alt={notification.user.name}\n                            src={notification.user.avatar}\n                          />\n                          <AvatarFallback>\n                            {getInitials(notification.user.name)}\n                          </AvatarFallback>\n                        </Avatar>\n                      ) : (\n                        <Icon className=\"size-5\" />\n                      )}\n                    </div>\n                    <div className=\"flex min-w-0 flex-1 flex-col gap-2\">\n                      <div className=\"flex flex-wrap items-center gap-1\">\n                        <span className=\"font-medium text-sm\">\n                          {notification.title}\n                        </span>\n                        {!notification.read && (\n                          <div className=\"size-2 shrink-0 rounded-full bg-primary\" />\n                        )}\n                        <Badge className=\"text-xs\" variant=\"outline\">\n                          {notification.type}\n                        </Badge>\n                      </div>\n                      <p className=\"wrap-break-word text-muted-foreground text-sm\">\n                        {notification.message}\n                      </p>\n                      <span className=\"text-muted-foreground text-xs\">\n                        {formatRelativeTime(notification.timestamp)}\n                      </span>\n                    </div>\n                    <DropdownMenu>\n                      <DropdownMenuTrigger asChild>\n                        <Button\n                          aria-label=\"More options\"\n                          size=\"icon-sm\"\n                          type=\"button\"\n                          variant=\"ghost\"\n                        >\n                          <MoreVertical className=\"size-4\" />\n                        </Button>\n                      </DropdownMenuTrigger>\n                      <DropdownMenuContent align=\"end\">\n                        {!notification.read && onMarkAsRead && (\n                          <DropdownMenuItem\n                            onClick={() => onMarkAsRead(notification.id)}\n                          >\n                            <Check className=\"size-4\" />\n                            Mark as read\n                          </DropdownMenuItem>\n                        )}\n                        {notification.link && (\n                          <DropdownMenuItem asChild>\n                            <a href={notification.link}>\n                              <MessageSquare className=\"size-4\" />\n                              View\n                            </a>\n                          </DropdownMenuItem>\n                        )}\n                        {onDelete && (\n                          <>\n                            <DropdownMenuSeparator />\n                            <DropdownMenuItem\n                              onSelect={() => onDelete(notification.id)}\n                              variant=\"destructive\"\n                            >\n                              <X className=\"size-4\" />\n                              Delete\n                            </DropdownMenuItem>\n                          </>\n                        )}\n                      </DropdownMenuContent>\n                    </DropdownMenu>\n                  </div>\n                  {idx < filteredNotifications.length - 1 && <Separator />}\n                </div>\n              );\n            })}\n          </div>\n        )}\n      </CardContent>\n    </Card>\n  );\n}\n",
      "type": "registry:ui"
    }
  ],
  "categories": [
    "block",
    "team"
  ],
  "type": "registry:ui"
}