import React from 'react'; import type t from 'librechat-data-provider'; import useLocalize from '~/hooks/useLocalize'; import { SmartLoader } from './SmartLoader'; import { cn } from '~/utils'; /** * Props for the CategoryTabs component */ interface CategoryTabsProps { /** Array of agent categories to display as tabs */ categories: t.TMarketplaceCategory[]; /** Currently selected tab value */ activeTab: string; /** Whether categories are currently loading */ isLoading: boolean; /** Callback fired when a tab is selected */ onChange: (value: string) => void; } /** * CategoryTabs - Component for displaying category tabs with counts * * Renders a tabbed navigation interface showing agent categories. * Includes loading states, empty state handling, and displays counts for each category. * Uses database-driven category labels with no hardcoded values. */ const CategoryTabs: React.FC = ({ categories, activeTab, isLoading, onChange, }) => { const localize = useLocalize(); // Helper function to get category display name from database data const getCategoryDisplayName = (category: t.TCategory) => { // Special cases for system categories if (category.value === 'promoted') { return localize('com_agents_top_picks'); } if (category.value === 'all') { return 'All'; } // Use database label or fallback to capitalized value return category.label || category.value.charAt(0).toUpperCase() + category.value.slice(1); }; // Loading skeleton component const loadingSkeleton = (
{[...Array(6)].map((_, i) => (
))}
); // Handle keyboard navigation between tabs const handleKeyDown = (e: React.KeyboardEvent, currentCategory: string) => { const currentIndex = categories.findIndex((cat) => cat.value === currentCategory); let newIndex = currentIndex; switch (e.key) { case 'ArrowLeft': case 'ArrowUp': e.preventDefault(); newIndex = currentIndex > 0 ? currentIndex - 1 : categories.length - 1; break; case 'ArrowRight': case 'ArrowDown': e.preventDefault(); newIndex = currentIndex < categories.length - 1 ? currentIndex + 1 : 0; break; case 'Home': e.preventDefault(); newIndex = 0; break; case 'End': e.preventDefault(); newIndex = categories.length - 1; break; default: return; } const newCategory = categories[newIndex]; if (newCategory) { onChange(newCategory.value); // Focus the new tab setTimeout(() => { const newTab = document.getElementById(`category-tab-${newCategory.value}`); newTab?.focus(); }, 0); } }; // Early return if no categories available if (!isLoading && (!categories || categories.length === 0)) { return (
{localize('com_ui_no_categories')}
); } // Main tabs content const tabsContent = (
{categories.map((category, index) => ( ))}
); // Use SmartLoader to prevent category loading flashes return ( 0} delay={100} // Very short delay since categories should load quickly loadingComponent={loadingSkeleton} > {tabsContent} ); }; export default CategoryTabs;