{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "team-prompt-library",
  "title": "Team Prompt Library",
  "description": "Shared prompt library for the team with favorites and categories.",
  "registryDependencies": [
    "avatar",
    "badge",
    "button",
    "card",
    "dialog",
    "dropdown-menu",
    "empty",
    "field",
    "input-group",
    "select",
    "separator",
    "textarea"
  ],
  "files": [
    {
      "path": "registry/new-york/blocks/team/team-prompt-library.tsx",
      "content": "\"use client\";\n\nimport {\n  BookOpen,\n  Heart,\n  Loader2,\n  MoreVertical,\n  Plus,\n  Search,\n  Sparkles,\n  Star,\n  Trash2,\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  Dialog,\n  DialogContent,\n  DialogDescription,\n  DialogFooter,\n  DialogHeader,\n  DialogTitle,\n  DialogTrigger,\n} from \"@/registry/new-york/ui/dialog\";\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 { Field, FieldContent, FieldLabel } from \"@/registry/new-york/ui/field\";\nimport {\n  InputGroup,\n  InputGroupAddon,\n  InputGroupInput,\n} from \"@/registry/new-york/ui/input-group\";\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\";\nimport { Textarea } from \"@/registry/new-york/ui/textarea\";\n\nexport interface TeamPrompt {\n  id: string;\n  title: string;\n  prompt: string;\n  description?: string;\n  category?: string;\n  tags?: string[];\n  author: {\n    id: string;\n    name: string;\n    avatar?: string;\n  };\n  isFavorite?: boolean;\n  rating?: number;\n  usageCount?: number;\n  bestModel?: string;\n  tone?: string;\n  createdAt: Date;\n  updatedAt: Date;\n}\n\nexport interface TeamPromptLibraryProps {\n  prompts?: TeamPrompt[];\n  currentUserId?: string;\n  onCreate?: (data: {\n    title: string;\n    prompt: string;\n    description?: string;\n    category?: string;\n    tags?: string[];\n    bestModel?: string;\n    tone?: string;\n  }) => Promise<TeamPrompt>;\n  onUpdate?: (promptId: string, data: Partial<TeamPrompt>) => Promise<void>;\n  onDelete?: (promptId: string) => Promise<void>;\n  onFavorite?: (promptId: string, isFavorite: boolean) => Promise<void>;\n  onUse?: (prompt: TeamPrompt) => void;\n  className?: string;\n  showSearch?: boolean;\n  showCategories?: boolean;\n}\n\nconst defaultCategories = [\n  \"All\",\n  \"Writing\",\n  \"Code\",\n  \"Analysis\",\n  \"Creative\",\n  \"Research\",\n  \"Business\",\n];\n\nfunction formatDate(date: Date): string {\n  return new Intl.DateTimeFormat(\"en-US\", {\n    month: \"short\",\n    day: \"numeric\",\n    year: \"numeric\",\n  }).format(date);\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 TeamPromptLibrary({\n  prompts = [],\n  currentUserId,\n  onCreate,\n  onUpdate,\n  onDelete,\n  onFavorite,\n  onUse,\n  className,\n  showSearch = true,\n  showCategories = true,\n}: TeamPromptLibraryProps) {\n  const [searchQuery, setSearchQuery] = useState(\"\");\n  const [selectedCategory, setSelectedCategory] = useState<string>(\"All\");\n  const [createDialogOpen, setCreateDialogOpen] = useState(false);\n  const [isCreating, setIsCreating] = useState(false);\n  const [actionLoading, setActionLoading] = useState<string | null>(null);\n  const [promptData, setPromptData] = useState({\n    title: \"\",\n    prompt: \"\",\n    description: \"\",\n    category: \"\",\n    tags: [] as string[],\n    bestModel: \"\",\n    tone: \"\",\n  });\n\n  const filteredPrompts = prompts.filter((prompt) => {\n    const matchesCategory =\n      selectedCategory === \"All\" || prompt.category === selectedCategory;\n    const matchesSearch =\n      !searchQuery.trim() ||\n      prompt.title.toLowerCase().includes(searchQuery.toLowerCase()) ||\n      prompt.prompt.toLowerCase().includes(searchQuery.toLowerCase()) ||\n      prompt.description?.toLowerCase().includes(searchQuery.toLowerCase()) ||\n      prompt.tags?.some((tag) =>\n        tag.toLowerCase().includes(searchQuery.toLowerCase())\n      );\n    return matchesCategory && matchesSearch;\n  });\n\n  const handleCreate = async () => {\n    if (!(promptData.title.trim() && promptData.prompt.trim() && onCreate))\n      return;\n\n    setIsCreating(true);\n    try {\n      await onCreate(promptData);\n      setPromptData({\n        title: \"\",\n        prompt: \"\",\n        description: \"\",\n        category: \"\",\n        tags: [],\n        bestModel: \"\",\n        tone: \"\",\n      });\n      setCreateDialogOpen(false);\n    } finally {\n      setIsCreating(false);\n    }\n  };\n\n  const handleAction = async (\n    action: () => Promise<void>,\n    promptId: string\n  ) => {\n    setActionLoading(promptId);\n    try {\n      await action();\n    } finally {\n      setActionLoading(null);\n    }\n  };\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>Prompt Library</CardTitle>\n              <CardDescription>\n                {prompts.length} prompt{prompts.length !== 1 ? \"s\" : \"\"} shared\n                with your team\n              </CardDescription>\n            </div>\n            {onCreate && (\n              <Dialog\n                onOpenChange={setCreateDialogOpen}\n                open={createDialogOpen}\n              >\n                <DialogTrigger asChild>\n                  <Button className=\"w-full shrink-0 md:w-auto\" type=\"button\">\n                    <Plus className=\"size-4\" />\n                    Add Prompt\n                  </Button>\n                </DialogTrigger>\n                <DialogContent className=\"md:max-w-2xl\">\n                  <DialogHeader>\n                    <DialogTitle>Add Prompt to Library</DialogTitle>\n                    <DialogDescription>\n                      Share a prompt with your team\n                    </DialogDescription>\n                  </DialogHeader>\n                  <div className=\"flex flex-col gap-4\">\n                    <Field>\n                      <FieldLabel htmlFor=\"prompt-title\">Title</FieldLabel>\n                      <FieldContent>\n                        <InputGroup>\n                          <InputGroupInput\n                            id=\"prompt-title\"\n                            onChange={(\n                              e: React.ChangeEvent<HTMLInputElement>\n                            ) =>\n                              setPromptData((prev) => ({\n                                ...prev,\n                                title: e.target.value,\n                              }))\n                            }\n                            placeholder=\"Prompt title…\"\n                            type=\"text\"\n                            value={promptData.title}\n                          />\n                        </InputGroup>\n                      </FieldContent>\n                    </Field>\n                    <Field>\n                      <FieldLabel htmlFor=\"prompt-content\">Prompt</FieldLabel>\n                      <FieldContent>\n                        <Textarea\n                          id=\"prompt-content\"\n                          onChange={(\n                            e: React.ChangeEvent<HTMLTextAreaElement>\n                          ) =>\n                            setPromptData((prev) => ({\n                              ...prev,\n                              prompt: e.target.value,\n                            }))\n                          }\n                          placeholder=\"Enter the prompt text…\"\n                          rows={6}\n                          value={promptData.prompt}\n                        />\n                      </FieldContent>\n                    </Field>\n                    <Field>\n                      <FieldLabel htmlFor=\"prompt-description\">\n                        Description\n                      </FieldLabel>\n                      <FieldContent>\n                        <Textarea\n                          id=\"prompt-description\"\n                          onChange={(\n                            e: React.ChangeEvent<HTMLTextAreaElement>\n                          ) =>\n                            setPromptData((prev) => ({\n                              ...prev,\n                              description: e.target.value,\n                            }))\n                          }\n                          placeholder=\"What is this prompt best used for?\"\n                          rows={2}\n                          value={promptData.description}\n                        />\n                      </FieldContent>\n                    </Field>\n                    <div className=\"grid gap-4 md:grid-cols-2\">\n                      <Field>\n                        <FieldLabel htmlFor=\"prompt-category\">\n                          Category\n                        </FieldLabel>\n                        <FieldContent>\n                          <Select\n                            onValueChange={(value: string) =>\n                              setPromptData((prev) => ({\n                                ...prev,\n                                category: value,\n                              }))\n                            }\n                            value={promptData.category}\n                          >\n                            <SelectTrigger id=\"prompt-category\">\n                              <SelectValue placeholder=\"Select category\" />\n                            </SelectTrigger>\n                            <SelectContent>\n                              {defaultCategories\n                                .filter((c) => c !== \"All\")\n                                .map((category) => (\n                                  <SelectItem key={category} value={category}>\n                                    {category}\n                                  </SelectItem>\n                                ))}\n                            </SelectContent>\n                          </Select>\n                        </FieldContent>\n                      </Field>\n                      <Field>\n                        <FieldLabel htmlFor=\"prompt-model\">\n                          Best Model\n                        </FieldLabel>\n                        <FieldContent>\n                          <InputGroup>\n                            <InputGroupInput\n                              id=\"prompt-model\"\n                              onChange={(\n                                e: React.ChangeEvent<HTMLInputElement>\n                              ) =>\n                                setPromptData((prev) => ({\n                                  ...prev,\n                                  bestModel: e.target.value,\n                                }))\n                              }\n                              placeholder=\"gpt-4, claude-3, etc.\"\n                              type=\"text\"\n                              value={promptData.bestModel}\n                            />\n                          </InputGroup>\n                        </FieldContent>\n                      </Field>\n                    </div>\n                  </div>\n                  <DialogFooter>\n                    <Button\n                      onClick={() => setCreateDialogOpen(false)}\n                      type=\"button\"\n                      variant=\"outline\"\n                    >\n                      Cancel\n                    </Button>\n                    <Button\n                      aria-busy={isCreating}\n                      data-loading={isCreating}\n                      disabled={\n                        !(promptData.title.trim() && promptData.prompt.trim())\n                      }\n                      onClick={handleCreate}\n                      type=\"button\"\n                    >\n                      {isCreating ? (\n                        <>\n                          <Loader2 className=\"size-4 animate-spin\" />\n                          Adding…\n                        </>\n                      ) : (\n                        \"Add Prompt\"\n                      )}\n                    </Button>\n                  </DialogFooter>\n                </DialogContent>\n              </Dialog>\n            )}\n          </div>\n          {showSearch && (\n            <InputGroup>\n              <InputGroupAddon>\n                <Search className=\"size-4\" />\n              </InputGroupAddon>\n              <InputGroupInput\n                onChange={(e: React.ChangeEvent<HTMLInputElement>) =>\n                  setSearchQuery(e.target.value)\n                }\n                placeholder=\"Search prompts…\"\n                type=\"search\"\n                value={searchQuery}\n              />\n            </InputGroup>\n          )}\n          {showCategories && (\n            <div className=\"flex flex-wrap gap-2\">\n              {defaultCategories.map((category) => (\n                <Button\n                  className={cn(\n                    \"h-auto gap-2 px-3 py-1.5\",\n                    selectedCategory === category\n                      ? \"bg-primary text-primary-foreground\"\n                      : \"bg-muted text-muted-foreground hover:bg-muted/80\"\n                  )}\n                  key={category}\n                  onClick={() => setSelectedCategory(category)}\n                  type=\"button\"\n                  variant=\"ghost\"\n                >\n                  {category !== \"All\" && <Sparkles className=\"size-3.5\" />}\n                  <span className=\"text-xs\">{category}</span>\n                </Button>\n              ))}\n            </div>\n          )}\n        </div>\n      </CardHeader>\n      <CardContent>\n        {filteredPrompts.length === 0 ? (\n          <Empty>\n            <EmptyHeader>\n              <EmptyMedia variant=\"icon\">\n                <BookOpen className=\"size-6\" />\n              </EmptyMedia>\n              <EmptyTitle>\n                {searchQuery || selectedCategory !== \"All\"\n                  ? \"No prompts found\"\n                  : \"No prompts yet\"}\n              </EmptyTitle>\n            </EmptyHeader>\n          </Empty>\n        ) : (\n          <div className=\"grid gap-4 md:grid-cols-2\">\n            {filteredPrompts.map((prompt) => (\n              <div\n                className=\"group flex flex-col gap-3 rounded-lg border bg-card p-4 transition-colors hover:border-primary hover:shadow-sm\"\n                key={prompt.id}\n              >\n                <div className=\"flex items-start justify-between gap-2\">\n                  <div className=\"flex min-w-0 flex-1 flex-col gap-2\">\n                    <h3 className=\"wrap-break-word font-semibold text-base leading-tight\">\n                      {prompt.title}\n                    </h3>\n                    {prompt.description && (\n                      <p className=\"wrap-break-word line-clamp-2 text-muted-foreground text-sm\">\n                        {prompt.description}\n                      </p>\n                    )}\n                    <div className=\"flex flex-wrap items-center gap-2\">\n                      {prompt.category && (\n                        <Badge className=\"text-xs\" variant=\"outline\">\n                          {prompt.category}\n                        </Badge>\n                      )}\n                      {prompt.bestModel && (\n                        <Badge className=\"text-xs\" variant=\"secondary\">\n                          {prompt.bestModel}\n                        </Badge>\n                      )}\n                      {prompt.rating && (\n                        <div className=\"flex items-center gap-1\">\n                          <Star className=\"size-3 fill-yellow-500 text-yellow-500\" />\n                          <span className=\"text-muted-foreground text-xs\">\n                            {prompt.rating.toFixed(1)}\n                          </span>\n                        </div>\n                      )}\n                      {prompt.usageCount !== undefined && (\n                        <span className=\"text-muted-foreground text-xs\">\n                          {prompt.usageCount} use\n                          {prompt.usageCount !== 1 ? \"s\" : \"\"}\n                        </span>\n                      )}\n                    </div>\n                  </div>\n                  <DropdownMenu>\n                    <DropdownMenuTrigger asChild>\n                      <Button\n                        aria-label={`More options for ${prompt.title}`}\n                        size=\"icon-sm\"\n                        type=\"button\"\n                        variant=\"ghost\"\n                      >\n                        {actionLoading === prompt.id ? (\n                          <Loader2 className=\"size-4 animate-spin\" />\n                        ) : (\n                          <MoreVertical className=\"size-4\" />\n                        )}\n                      </Button>\n                    </DropdownMenuTrigger>\n                    <DropdownMenuContent\n                      align=\"end\"\n                      collisionPadding={8}\n                      sideOffset={4}\n                    >\n                      {onUse && (\n                        <DropdownMenuItem onSelect={() => onUse(prompt)}>\n                          <Sparkles className=\"size-4\" />\n                          Use Prompt\n                        </DropdownMenuItem>\n                      )}\n                      {onFavorite && (\n                        <DropdownMenuItem\n                          onSelect={() =>\n                            handleAction(\n                              () => onFavorite(prompt.id, !prompt.isFavorite),\n                              prompt.id\n                            )\n                          }\n                        >\n                          <Heart\n                            className={cn(\n                              \"size-4\",\n                              prompt.isFavorite && \"fill-red-500 text-red-500\"\n                            )}\n                          />\n                          {prompt.isFavorite\n                            ? \"Remove from favorites\"\n                            : \"Add to favorites\"}\n                        </DropdownMenuItem>\n                      )}\n                      {onDelete && (\n                        <>\n                          <DropdownMenuSeparator />\n                          <DropdownMenuItem\n                            onSelect={() =>\n                              handleAction(() => onDelete(prompt.id), prompt.id)\n                            }\n                            variant=\"destructive\"\n                          >\n                            <Trash2 className=\"size-4\" />\n                            Delete\n                          </DropdownMenuItem>\n                        </>\n                      )}\n                    </DropdownMenuContent>\n                  </DropdownMenu>\n                </div>\n                {prompt.tags && prompt.tags.length > 0 && (\n                  <div className=\"flex flex-wrap gap-1\">\n                    {prompt.tags.map((tag) => (\n                      <Badge className=\"text-xs\" key={tag} variant=\"outline\">\n                        {tag}\n                      </Badge>\n                    ))}\n                  </div>\n                )}\n                <Separator />\n                <div className=\"flex items-center justify-between\">\n                  <div className=\"flex items-center gap-2\">\n                    <Avatar className=\"size-6\">\n                      <AvatarImage\n                        alt={prompt.author.name}\n                        src={prompt.author.avatar}\n                      />\n                      <AvatarFallback className=\"text-xs\">\n                        {getInitials(prompt.author.name)}\n                      </AvatarFallback>\n                    </Avatar>\n                    <span className=\"text-muted-foreground text-xs\">\n                      {prompt.author.name}\n                    </span>\n                  </div>\n                  <span className=\"text-muted-foreground text-xs\">\n                    {formatDate(prompt.createdAt)}\n                  </span>\n                </div>\n              </div>\n            ))}\n          </div>\n        )}\n      </CardContent>\n    </Card>\n  );\n}\n",
      "type": "registry:ui"
    }
  ],
  "categories": [
    "block",
    "team"
  ],
  "type": "registry:ui"
}