Tabs Service
A flexible, responsive tabs service for managing tab state with both index-based and URL-based routing. Includes a responsive component that automatically switches between tabs (desktop) and select dropdown (mobile).
Overview
The Tabs Service provides:
- Index-based & URL Routing: Switch tabs by index or via URL query parameters
- Responsive Component: Automatically converts to select dropdown on mobile
- Tab State Management: Observable-based API with RxJS
- Badge Support: Display notifications/counts on tabs
- Icon Support: Optional icons for each tab
- Dynamic Tab Management: Add, remove, or update tabs at runtime
- Tab Navigation: Built-in next/previous methods
- Enable/Disable: Control which tabs are interactive
Architecture
Core Service
TabsService
Main service for managing tab state:
interface TabItem {
id: string;
label: string;
disabled?: boolean;
icon?: string;
badge?: string | number;
}
interface TabsConfig {
tabs: TabItem[];
defaultTabId?: string;
enableUrlRouting?: boolean;
urlParam?: string;
}
interface TabChangeEvent {
previousTabId: string | null;
currentTabId: string;
tab: TabItem;
}
Installation
npm install @codella-software/utils @codella-software/react
Setup
Core Service Usage
1. Initialize Service
import { TabsService } from '@codella-software/utils/tabs';
const tabsService = new TabsService({
tabs: [
{ id: 'overview', label: 'Overview', icon: '📊' },
{ id: 'details', label: 'Details', icon: '📝' },
{ id: 'settings', label: 'Settings', icon: '⚙️', badge: 3 }
],
defaultTabId: 'overview',
enableUrlRouting: true,
urlParam: 'tab'
});
2. Set Active Tab
// By ID
tabsService.setActiveTab('details');
// By index
tabsService.setActiveTabByIndex(1);
// Navigation
tabsService.nextTab();
tabsService.previousTab();
3. Listen to Changes
// Get active tab ID
tabsService.getActiveTabId$().subscribe(tabId => {
console.log('Active tab:', tabId);
});
// Get active tab object
tabsService.getActiveTab$().subscribe(tab => {
console.log('Active tab:', tab);
});
// Listen to tab changes
tabsService.onTabChange$().subscribe(event => {
console.log('Changed from', event.previousTabId, 'to', event.currentTabId);
});
4. Dynamic Tab Management
// Add tab
tabsService.addTab(
{ id: 'advanced', label: 'Advanced' },
2 // Insert at index 2
);
// Remove tab
tabsService.removeTab('settings');
// Update badge
tabsService.setTabBadge('settings', 5);
// Enable/disable
tabsService.setTabDisabled('details', true);
// Update multiple tabs
tabsService.updateTabs([
{ id: 'tab1', label: 'Tab 1' },
{ id: 'tab2', label: 'Tab 2' }
]);
React Integration
1. Wrap with Provider
import { TabsProvider } from '@codella-software/react/tabs';
function App() {
return (
<TabsProvider
config={{
tabs: [
{ id: 'overview', label: 'Overview', icon: '📊' },
{ id: 'details', label: 'Details', icon: '📝' },
{ id: 'settings', label: 'Settings', icon: '⚙️' }
],
defaultTabId: 'overview',
enableUrlRouting: true
}}
>
<TabContent />
</TabsProvider>
);
}
For responsive tab components, use the CLI template system to generate ready-made components.
2. Use Hooks
useSetActiveTab
import { useSetActiveTab } from '@codella-software/react/tabs';
function TabNavigation() {
const { setActiveTab, nextTab, previousTab } = useSetActiveTab();
return (
<div>
<button onClick={() => setActiveTab('details')}>
Go to Details
</button>
<button onClick={previousTab}>Previous</button>
<button onClick={nextTab}>Next</button>
</div>
);
}
useActiveTab
import { useActiveTab } from '@codella-software/react/tabs';
function CurrentTabDisplay() {
const activeTab = useActiveTab();
return activeTab ? (
<div>
{activeTab.icon && <span>{activeTab.icon}</span>}
<span>{activeTab.label}</span>
{activeTab.badge && <span className="badge">{activeTab.badge}</span>}
</div>
) : null;
}
useTabs
import { useTabs } from '@codella-software/react/tabs';
function TabsList() {
const tabs = useTabs();
return (
<ul>
{tabs.map(tab => (
<li key={tab.id} className={tab.disabled ? 'disabled' : ''}>
{tab.label}
{tab.badge && <span className="badge">{tab.badge}</span>}
</li>
))}
</ul>
);
}
useTabChange
import { useTabChange } from '@codella-software/react/tabs';
function TabChangeLogger() {
const changeEvent = useTabChange();
useEffect(() => {
if (changeEvent) {
console.log(`Changed from ${changeEvent.previousTabId} to ${changeEvent.currentTabId}`);
}
}, [changeEvent]);
return null;
}
useTabsService
import { useTabsService } from '@codella-software/react/tabs';
function CustomTabUI() {
const service = useTabsService();
const handleAddTab = () => {
service.addTab({
id: 'new-tab',
label: 'New Tab'
});
};
return (
<button onClick={handleAddTab}>
Add Tab
</button>
);
}
URL Routing
Enable URL-based Navigation
const config = {
tabs: [
{ id: 'products', label: 'Products' },
{ id: 'orders', label: 'Orders' },
{ id: 'analytics', label: 'Analytics' }
],
enableUrlRouting: true,
urlParam: 'section' // Customize query param name
};
URL Examples
https://example.com?tab=products // Active: products
https://example.com?tab=orders // Active: orders
https://example.com?section=orders // With custom urlParam
Component Props
ResponsiveTabs Props
- className (optional): CSS class for tab container
- tabClassName (optional): CSS class for individual tabs
- activeTabClassName (optional): CSS class for active tab
- selectClassName (optional): CSS class for select dropdown
- breakpoint (optional): Pixel width to switch to select (default: 768)
- showBadges (optional): Display tab badges (default: true)
- icon (optional): Display tab icons (default: true)
TabsConfig Options
- tabs (required): Array of TabItem definitions
- defaultTabId (optional): Initial active tab
- enableUrlRouting (optional): Enable URL-based routing
- urlParam (optional): Query parameter name (default: 'tab')
Examples
Basic Tabs with URL Routing
function Settings() {
return (
<TabsProvider
config={{
tabs: [
{ id: 'account', label: 'Account' },
{ id: 'privacy', label: 'Privacy' },
{ id: 'notifications', label: 'Notifications' }
],
enableUrlRouting: true,
defaultTabId: 'account'
}}
>
<ResponsiveTabs />
<div>
<AccountTab />
<PrivacyTab />
<NotificationsTab />
</div>
</TabsProvider>
);
}
Tabs with Badges
function NotificationCenter() {
const { service } = useTabsContext();
useEffect(() => {
// Update badge count
service.setTabBadge('messages', 5);
service.setTabBadge('alerts', 2);
}, [service]);
return (
<ResponsiveTabs
showBadges={true}
/>
);
}
Conditional Tab Disabling
function EditForm() {
const service = useTabsService();
const [isSaving, setIsSaving] = useState(false);
useEffect(() => {
// Disable tabs while saving
service.setTabDisabled('advanced', isSaving);
service.setTabDisabled('preview', isSaving);
}, [isSaving, service]);
return (
<ResponsiveTabs />
);
}
Dynamic Tab Creation
function DocumentEditor() {
const service = useTabsService();
const [documentCount, setDocumentCount] = useState(1);
const openDocument = (title: string) => {
const tabId = `doc-${Date.now()}`;
service.addTab({
id: tabId,
label: title,
icon: '📄'
});
service.setActiveTab(tabId);
setDocumentCount(c => c + 1);
};
const closeDocument = (tabId: string) => {
service.removeTab(tabId);
setDocumentCount(c => Math.max(0, c - 1));
};
return (
<div>
<ResponsiveTabs />
<button onClick={() => openDocument('Untitled')}>
New Document ({documentCount})
</button>
</div>
);
}
Custom Tab Component
function CustomTabs() {
const { service } = useTabsContext();
const tabs = useTabs();
const { setActiveTab } = useSetActiveTab();
return (
<div className="custom-tabs">
{tabs.map(tab => (
<CustomTabButton
key={tab.id}
tab={tab}
onClick={() => !tab.disabled && setActiveTab(tab.id)}
/>
))}
</div>
);
}
function CustomTabButton({ tab, onClick }) {
return (
<button
onClick={onClick}
disabled={tab.disabled}
className="custom-button"
>
{tab.icon && <Icon name={tab.icon} />}
{tab.label}
{tab.badge && <Badge count={tab.badge} />}
</button>
);
}
Best Practices
- Single Instance: Create one TabsService per tab group
- URL Routing: Use for user-navigable tab groups
- Responsive: ResponsiveTabs handles mobile automatically
- Dynamic Updates: Update badges/status via service methods
- Cleanup: Call
destroy()when done (Provider handles this) - Accessibility: ResponsiveTabs includes proper ARIA attributes
- Breakpoint: Adjust breakpoint based on your design
Responsive Behavior
- Desktop (> 768px): Shows horizontal tab buttons
- Mobile (≤ 768px): Converts to select dropdown
- Customizable: Change breakpoint via
breakpointprop - Smooth transition: Automatic on window resize
Accessibility
ResponsiveTabs includes:
- Proper ARIA labels (
role="tablist",role="tab") aria-selectedfor active statearia-controlsfor tab panels- Keyboard navigation support
- Disabled state management
API Reference
TabsService Methods
| Method | Description |
|---|---|
setActiveTab(tabId) | Set active tab by ID |
setActiveTabByIndex(index) | Set active tab by index |
getActiveTabId() | Get current active tab ID |
getActiveTab() | Get active tab object |
getActiveTabIndex() | Get index of active tab |
getTabs() | Get all tabs |
addTab(tab, index?) | Add new tab |
removeTab(tabId) | Remove tab |
setTabDisabled(tabId, disabled) | Enable/disable tab |
setTabBadge(tabId, badge) | Update tab badge |
nextTab() | Move to next tab |
previousTab() | Move to previous tab |
destroy() | Cleanup service |
Observables
| Observable | Returns |
|---|---|
getActiveTabId$() | Current active tab ID |
getActiveTab$() | Current active tab object |
onTabChange$() | Tab change events |
getTabs$() | All tabs list |