Skip to main content

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

  1. Single Instance: Create one TabsService per tab group
  2. URL Routing: Use for user-navigable tab groups
  3. Responsive: ResponsiveTabs handles mobile automatically
  4. Dynamic Updates: Update badges/status via service methods
  5. Cleanup: Call destroy() when done (Provider handles this)
  6. Accessibility: ResponsiveTabs includes proper ARIA attributes
  7. 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 breakpoint prop
  • Smooth transition: Automatic on window resize

Accessibility

ResponsiveTabs includes:

  • Proper ARIA labels (role="tablist", role="tab")
  • aria-selected for active state
  • aria-controls for tab panels
  • Keyboard navigation support
  • Disabled state management

API Reference

TabsService Methods

MethodDescription
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

ObservableReturns
getActiveTabId$()Current active tab ID
getActiveTab$()Current active tab object
onTabChange$()Tab change events
getTabs$()All tabs list