From 7e50afed5146716ef358cdb8c3703632292218fc Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Thu, 23 Apr 2026 13:33:52 +0000 Subject: [PATCH] feat(ui): AIHub/ClaudeCodeMarketplaceTab to shadcn - antd Input/@ant-design SearchOutlined + @tremor Card/Tab/TabGroup/ TabList/TabPanel/TabPanels/Text \u2192 shadcn Card/Input/Tabs + lucide Search. Category tabs now use the category name as the tab value (instead of numeric index) \u2014 simpler API. Gates: TS \u2713 | Build \u2713. Co-authored-by: yuneng-jiang --- .../AIHub/ClaudeCodeMarketplaceTab.tsx | 130 ++++++++---------- 1 file changed, 58 insertions(+), 72 deletions(-) diff --git a/ui/litellm-dashboard/src/components/AIHub/ClaudeCodeMarketplaceTab.tsx b/ui/litellm-dashboard/src/components/AIHub/ClaudeCodeMarketplaceTab.tsx index 043077c0210..089fb0cc49c 100644 --- a/ui/litellm-dashboard/src/components/AIHub/ClaudeCodeMarketplaceTab.tsx +++ b/ui/litellm-dashboard/src/components/AIHub/ClaudeCodeMarketplaceTab.tsx @@ -1,15 +1,19 @@ -import { SearchOutlined } from "@ant-design/icons"; -import { Card, Tab, TabGroup, TabList, TabPanel, TabPanels, Text } from "@tremor/react"; -import { Input } from "antd"; +import { Card } from "@/components/ui/card"; +import { Input } from "@/components/ui/input"; +import { + Tabs, + TabsContent, + TabsList, + TabsTrigger, +} from "@/components/ui/tabs"; +import { Search } from "lucide-react"; import React, { useEffect, useMemo, useState } from "react"; import { extractCategories, filterPluginsByCategory, filterPluginsBySearch, } from "../claude_code_plugins/helpers"; -import { - MarketplaceResponse -} from "../claude_code_plugins/types"; +import { MarketplaceResponse } from "../claude_code_plugins/types"; import { ModelDataTable } from "../model_dashboard/table"; import NotificationsManager from "../molecules/notifications_manager"; import { getClaudeCodeMarketplace } from "../networking"; @@ -26,49 +30,39 @@ const ClaudeCodeMarketplaceTab: React.FC = ({ useState(null); const [isLoading, setIsLoading] = useState(true); const [searchTerm, setSearchTerm] = useState(""); - const [selectedCategoryIndex, setSelectedCategoryIndex] = useState(0); + const [selectedCategory, setSelectedCategory] = useState("All"); useEffect(() => { + const fetchMarketplace = async () => { + setIsLoading(true); + try { + const data: MarketplaceResponse = await getClaudeCodeMarketplace(); + console.log("Claude Code marketplace:", data); + setMarketplaceData(data); + } catch (error) { + console.error("Error fetching marketplace:", error); + } finally { + setIsLoading(false); + } + }; fetchMarketplace(); }, []); - const fetchMarketplace = async () => { - setIsLoading(true); - try { - const data: MarketplaceResponse = await getClaudeCodeMarketplace(); - console.log("Claude Code marketplace:", data); - setMarketplaceData(data); - } catch (error) { - console.error("Error fetching marketplace:", error); - } finally { - setIsLoading(false); - } - }; - const copyToClipboard = (text: string) => { navigator.clipboard.writeText(text); NotificationsManager.success("Copied to clipboard!"); }; - // Extract unique categories from plugins const categories = useMemo(() => { if (!marketplaceData) return ["All"]; return extractCategories(marketplaceData.plugins); }, [marketplaceData]); - // Get selected category name - const selectedCategory = categories[selectedCategoryIndex] || "All"; - - // Filter plugins by search and category const filteredPlugins = useMemo(() => { if (!marketplaceData) return []; let plugins = marketplaceData.plugins; - - // Apply category filter plugins = filterPluginsByCategory(plugins, selectedCategory); - - // Apply search filter plugins = filterPluginsBySearch(plugins, searchTerm); return plugins; @@ -76,16 +70,16 @@ const ClaudeCodeMarketplaceTab: React.FC = ({ const columns = useMemo( () => getMarketplaceTableColumns(copyToClipboard, publicPage), - [publicPage] + [publicPage], ); if (!marketplaceData && !isLoading) { return (
- + Failed to load marketplace. Please try again later. - +
); @@ -93,67 +87,59 @@ const ClaudeCodeMarketplaceTab: React.FC = ({ return (
- {/* Search Bar */} -
+
+ } value={searchTerm} onChange={(e) => setSearchTerm(e.target.value)} - allowClear - size="large" + className="pl-9 h-11" />
- {/* Category Tabs */} - - + + {categories.map((category) => { - // Count plugins in this category const categoryPlugins = filterPluginsByCategory( marketplaceData?.plugins || [], - category + category, ); const count = filterPluginsBySearch( categoryPlugins, - searchTerm + searchTerm, ).length; return ( - + {category} {count > 0 && `(${count})`} - + ); })} - + - - {categories.map((category) => ( - - - {/* Plugin Table */} - - + {categories.map((category) => ( + + + + - {/* Footer Info */} -
- - Showing {filteredPlugins.length} of{" "} - {marketplaceData?.plugins.length || 0} plugin - {marketplaceData?.plugins.length !== 1 ? "s" : ""} - {searchTerm && ` matching "${searchTerm}"`} - {selectedCategory !== "All" && ` in ${selectedCategory}`} - -
-
- ))} -
-
+
+ + Showing {filteredPlugins.length} of{" "} + {marketplaceData?.plugins.length || 0} plugin + {marketplaceData?.plugins.length !== 1 ? "s" : ""} + {searchTerm && ` matching "${searchTerm}"`} + {selectedCategory !== "All" && ` in ${selectedCategory}`} + +
+ + ))} +
); };