Tab Components
The Tab Components provide comprehensive tabbed navigation functionality for the WorkPayCore Frontend application. These components handle multi-view interfaces, page navigation, and content organization with consistent styling and behavior patterns.
Overview
This document covers tab-related components that enable organized content presentation, navigation between different views, and complex page layouts with tabbed interfaces.
Components Overview
Core Tab Components
- TabsWrapper - Basic flexible tab component with routing support
- AdminTabsWrapper - Admin interface tabs with progress indicators
- WPTabbedPage - Complete page template with integrated tabs
- WPTabPage - Advanced tab page wrapper with complex layouts
Navigation Tab Components
- NavTabs - Routed tabs with React Router integration
- RoutedTabs - Router-aware tab container
Specialized Tab Patterns
- Manual Tab Control - Manual tab switching patterns
- Nested Tab Layouts - Complex nested tab structures
- Tab with Filters - Tabs integrated with filtering systems
TabsWrapper
Basic flexible tab component supporting horizontal/vertical orientation with custom styling and routing.
Component Location
import TabsWrapper from 'components/Tabs/TabsWrapper';
Props
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
| tabData | array | ✓ | - | Tab configuration array |
| orientation | string | - | 'horizontal' | Tab orientation |
| variant | string | - | - | Custom styling variant |
| defaultIndex | number | - | 0 | Default active tab |
| index | number | - | - | Controlled active tab |
| onTabChange | function | - | - | Tab change handler |
| width | string | - | 'max-content' | Tab width |
TypeScript Interface
interface TabData {
id: string | number;
title: string;
link?: string;
children?: ReactNode;
}
interface TabsWrapperProps {
tabData: TabData[];
orientation?: 'horizontal' | 'vertical';
variant?: 'custom' | string;
defaultIndex?: number;
index?: number;
onTabChange?: (index: number) => void;
width?: string;
}
Usage Examples
Basic Horizontal Tabs
import TabsWrapper from 'components/Tabs/TabsWrapper';
function BasicTabs() {
const tabData = [
{
id: 1,
title: 'Overview',
link: '/dashboard/overview',
},
{
id: 2,
title: 'Reports',
link: '/dashboard/reports',
},
{
id: 3,
title: 'Settings',
link: '/dashboard/settings',
},
];
return (
<TabsWrapper
tabData={tabData}
orientation='horizontal'
variant='custom'
onTabChange={index => console.log('Tab changed:', index)}
/>
);
}
Vertical Navigation Tabs
function VerticalTabs() {
const tabData = [
{
id: 1,
title: 'Employee Management',
link: '/hr/employees',
},
{
id: 2,
title: 'Leave Management',
link: '/hr/leaves',
},
{
id: 3,
title: 'Payroll',
link: '/hr/payroll',
},
];
return <TabsWrapper tabData={tabData} orientation='vertical' width='200px' />;
}
Controlled Tabs with State
function ControlledTabs() {
const [activeTab, setActiveTab] = useState(0);
const tabData = [
{ id: 1, title: 'Active', link: '/employees?status=active' },
{ id: 2, title: 'Inactive', link: '/employees?status=inactive' },
{ id: 3, title: 'Pending', link: '/employees?status=pending' },
];
return (
<TabsWrapper
tabData={tabData}
index={activeTab}
onTabChange={setActiveTab}
variant='custom'
/>
);
}
AdminTabsWrapper
Specialized tab component for admin interfaces with progress indicators and step navigation.
Component Location
import AdminTabsWrapper from 'components/Tabs/AdminTabsWrapper';
Props
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
| tabData | array | ✓ | - | Tab configuration array |
| orientation | string | - | - | Tab orientation |
| variant | string | - | - | Styling variant |
| tabIsDisabled | boolean | - | false | Disable tab interaction |
| iconIsDisabled | boolean | - | false | Disable progress icons |
Features
- Progress Indicators: Visual step progress with numbered icons
- Completion Status: Shows completed vs. pending steps
- Onboarding Integration: Works with employee onboarding workflows
- URL State Management: Manages tab state via URL parameters
Usage Examples
Onboarding Workflow Tabs
import AdminTabsWrapper from 'components/Tabs/AdminTabsWrapper';
function OnboardingTabs() {
const onboardingSteps = [
{
id: 1,
title: 'Personal Information',
link: '/onboarding/personal',
completed: true,
},
{
id: 2,
title: 'Employment Details',
link: '/onboarding/employment',
completed: true,
},
{
id: 3,
title: 'Documents Upload',
link: '/onboarding/documents',
completed: false,
},
{
id: 4,
title: 'Review & Submit',
link: '/onboarding/review',
completed: false,
},
];
return (
<AdminTabsWrapper
tabData={onboardingSteps}
orientation='vertical'
variant='custom'
/>
);
}
Multi-Step Process with Disabled States
function MultiStepProcess() {
const [currentStep, setCurrentStep] = useState(0);
const [completedSteps, setCompletedSteps] = useState([]);
const processSteps = [
{ id: 1, title: 'Step 1: Setup', link: '/process/setup' },
{ id: 2, title: 'Step 2: Configuration', link: '/process/config' },
{ id: 3, title: 'Step 3: Verification', link: '/process/verify' },
{ id: 4, title: 'Step 4: Completion', link: '/process/complete' },
];
return (
<AdminTabsWrapper
tabData={processSteps}
orientation='vertical'
tabIsDisabled={false}
iconIsDisabled={false}
/>
);
}
WPTabbedPage
Complete page template component with integrated tabs, filters, and table functionality.
Component Location
import { WPTabbedPage } from 'components/WPagesTemplates/WPTabbedPage';
Sub-components
- WPTabbedPage.BreadCrumb - Navigation breadcrumb
- WPTabbedPage.Title - Page title with tooltip support
- WPTabbedPage.Body - Main tab container
- WPTabbedPage.HeaderSection - Header layout section
- WPTabbedPage.TabsNFilters - Tabs with integrated filters
- WPTabbedPage.Table - Table component with tab integration
Props for Main Component
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
| pageTitle | string | - | - | Main page title |
| noFooter | boolean | - | false | Hide page footer |
| overflow | string | - | - | Overflow behavior |
| children | ReactNode | ✓ | - | Page content |
Title Component Props
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
| title | string | ✓ | - | Title text |
| description | string | - | - | Tooltip description |
| hasToolTip | boolean | - | false | Show tooltip |
| fontSize | number | - | 26 | Title font size |
| color | string | - | 'charcoal' | Title color |
TabsNFilters Component Props
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
| tabData | array | ✓ | - | Tab configuration |
| children | ReactNode | - | - | Filter components |
| childrenWidth | string | - | 'full' | Filter section width |
Usage Examples
Complete Page with Tabs and Filters
import { WPTabbedPage } from 'components/WPagesTemplates/WPTabbedPage';
import { Tab, TabList, TabPanels, TabPanel } from '@chakra-ui/react';
function EmployeePage() {
const tabData = [
{ title: 'Active Employees', id: 'active' },
{ title: 'Inactive Employees', id: 'inactive' },
{ title: 'Pending Approval', id: 'pending' },
];
return (
<WPTabbedPage pageTitle='Employee Management'>
<WPTabbedPage.BreadCrumb>
<HStack as={Link} to='/dashboard'>
<ChevronLeftOutline />
<Text>Back to Dashboard</Text>
</HStack>
</WPTabbedPage.BreadCrumb>
<WPTabbedPage.Title
title='Employee Management'
description='Manage all employee records, statuses, and related information'
hasToolTip
/>
<WPTabbedPage.Body defaultIndex={0}>
<WPTabbedPage.HeaderSection>
<WPTabbedPage.TabsNFilters tabData={tabData}>
<EmployeeFilters />
</WPTabbedPage.TabsNFilters>
</WPTabbedPage.HeaderSection>
<TabPanels>
<TabPanel>
<ActiveEmployeesTable />
</TabPanel>
<TabPanel>
<InactiveEmployeesTable />
</TabPanel>
<TabPanel>
<PendingEmployeesTable />
</TabPanel>
</TabPanels>
</WPTabbedPage.Body>
</WPTabbedPage>
);
}
Page with Integrated Table
function TransactionsPage() {
const tabData = [
{
id: 'pending',
title: 'Pending',
tableData: pendingTransactions,
children: <PendingTransactionsView />,
},
{
id: 'completed',
title: 'Completed',
tableData: completedTransactions,
children: <CompletedTransactionsView />,
},
];
return (
<WPTabbedPage pageTitle='Transactions'>
<WPTabbedPage.Title title='Payment Transactions' />
<WPTabbedPage.Body>
<WPTabbedPage.Table
tabData={tabData}
customColumn={transactionColumns}
isLoading={isLoading}
paginatedData={paginationData}
/>
</WPTabbedPage.Body>
</WPTabbedPage>
);
}
Page with Right Section Actions
function DashboardPage() {
return (
<WPTabbedPage pageTitle='Analytics Dashboard'>
<WPTabbedPage.Title title='Analytics & Reports' />
<WPTabbedPage.Body>
<WPTabbedPage.HeaderSection>
<WPTabbedPage.TabsNFilters tabData={tabData}>
<DateFilter />
<StatusFilter />
</WPTabbedPage.TabsNFilters>
<WPTabbedPage.RightSection>
<StatisticsCard />
<ExportButton />
</WPTabbedPage.RightSection>
</WPTabbedPage.HeaderSection>
<TabPanels>
{tabData.map((tab, index) => (
<TabPanel key={index}>{tab.children}</TabPanel>
))}
</TabPanels>
</WPTabbedPage.Body>
</WPTabbedPage>
);
}
WPTabPage
Advanced tab page wrapper with complex layout support and header sections.
Component Location
import { WPTabPage } from 'components/Tabs/AdvancedTabsWrapper';
Sub-components
- WPTabPage.BreadCrumb - Breadcrumb navigation
- WPTabPage.Title - Page title section
- WPTabPage.Body - Main tab container
- WPTabPage.HeaderSection - Header layout
- WPTabPage.TabsNFilters - Tabs with filters
- WPTabPage.Filters - Filter section wrapper
- WPTabPage.RightSection - Right-aligned content
- WPTabPage.Content - Tab panels content
Usage Examples
Advanced Layout with Multiple Sections
import { WPTabPage } from 'components/Tabs/AdvancedTabsWrapper';
function AdvancedDashboard() {
const tabData = [
{ title: 'Overview', children: <OverviewPanel /> },
{ title: 'Analytics', children: <AnalyticsPanel /> },
{ title: 'Reports', children: <ReportsPanel /> },
];
return (
<WPTabPage>
<WPTabPage.BreadCrumb>
<BreadcrumbNavigation />
</WPTabPage.BreadCrumb>
<WPTabPage.Title title='Advanced Dashboard' />
<WPTabPage.Body>
<WPTabPage.HeaderSection>
<WPTabPage.TabsNFilters tabData={tabData}>
<WPTabPage.Filters>
<DateRangeFilter />
<DepartmentFilter />
</WPTabPage.Filters>
</WPTabPage.TabsNFilters>
<WPTabPage.RightSection>
<QuickStats />
<ActionButtons />
</WPTabPage.RightSection>
</WPTabPage.HeaderSection>
<WPTabPage.Content tabData={tabData} />
</WPTabPage.Body>
</WPTabPage>
);
}
NavTabs
Router-integrated tab component that works with React Router for navigation.
Component Location
import { NavTab } from 'components/RoutedTabs/NavTabs';
Props
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
| to | string | ✓ | - | Navigation path |
| exact | boolean | - | false | Exact path matching |
| strict | boolean | - | false | Strict path matching |
| activeClassName | string | - | - | CSS class for active tab |
| disabled | boolean | - | false | Disable tab interaction |
| allowClickOnActive | boolean | - | false | Allow clicking active tab |
Usage Examples
Router-Based Navigation
import { NavTab } from 'components/RoutedTabs/NavTabs';
function NavigationTabs() {
return (
<div className='tab-container'>
<NavTab to='/employees' exact activeClassName='active-tab'>
Employees
</NavTab>
<NavTab to='/departments' activeClassName='active-tab'>
Departments
</NavTab>
<NavTab to='/payroll' activeClassName='active-tab'>
Payroll
</NavTab>
</div>
);
}
Nested Route Tabs
function NestedRouteTabs() {
return (
<RoutedTabs startPathWith='/hr'>
<NavTab to='/employees' exact>
Employee Management
</NavTab>
<NavTab to='/leaves'>Leave Management</NavTab>
<NavTab to='/attendance'>Attendance</NavTab>
</RoutedTabs>
);
}
Tab Patterns
Manual Tab Control Pattern
import { Tabs, TabList, Tab, TabPanels, TabPanel } from '@chakra-ui/react';
import { NavLink, useLocation } from 'react-router-dom';
function ManualTabControl() {
const location = useLocation();
const [tab, setTab] = useState('overview');
const tabs = [
{ tabName: 'overview', tab: 'Overview', path: '/dashboard?tab=overview' },
{ tabName: 'reports', tab: 'Reports', path: '/dashboard?tab=reports' },
{ tabName: 'settings', tab: 'Settings', path: '/dashboard?tab=settings' },
];
useEffect(() => {
const urlParams = new URLSearchParams(location.search);
const currentTab = urlParams.get('tab') || 'overview';
setTab(currentTab);
}, [location.search]);
return (
<Tabs
isLazy
width='full'
isManual
index={tabs.findIndex(({ tabName }) => tabName === tab)}
>
<TabList borderBottom='1px solid' borderColor='hue-navy.50'>
{tabs.map((tab, idx) => (
<Tab
key={idx}
textStyle='body-regular'
color='slategrey'
cursor='pointer'
as={NavLink}
to={tab.path}
>
{tab.tab}
</Tab>
))}
</TabList>
<TabPanels>
{tabs.map((tab, idx) => (
<TabPanel p={0} key={idx}>
{tab.tabPanel}
</TabPanel>
))}
</TabPanels>
</Tabs>
);
}
Tab with Filters Integration Pattern
function TabsWithFilters() {
const [selectedFilters, setSelectedFilters] = useState({});
const [activeTab, setActiveTab] = useState(0);
const tabData = [
{ title: 'All Items', filter: {} },
{ title: 'Active', filter: { status: 'active' } },
{ title: 'Pending', filter: { status: 'pending' } },
];
const handleTabChange = index => {
setActiveTab(index);
setSelectedFilters(tabData[index].filter);
};
return (
<WPTabbedPage pageTitle='Items Management'>
<WPTabbedPage.Body defaultIndex={0} onTabChange={handleTabChange}>
<WPTabbedPage.HeaderSection>
<WPTabbedPage.TabsNFilters tabData={tabData}>
<FilterComponent
filters={selectedFilters}
onFilterChange={setSelectedFilters}
/>
</WPTabbedPage.TabsNFilters>
</WPTabbedPage.HeaderSection>
<TabPanels>
{tabData.map((tab, index) => (
<TabPanel key={index}>
<DataTable filters={{ ...selectedFilters, ...tab.filter }} />
</TabPanel>
))}
</TabPanels>
</WPTabbedPage.Body>
</WPTabbedPage>
);
}
Nested Tab Layout Pattern
function NestedTabLayout() {
return (
<WPTabbedPage pageTitle='Complex Dashboard'>
<WPTabbedPage.Body>
<TabList>
<Tab>Analytics</Tab>
<Tab>Reports</Tab>
<Tab>Settings</Tab>
</TabList>
<TabPanels>
<TabPanel>
{/* Nested tabs within Analytics */}
<Tabs variant='enclosed'>
<TabList>
<Tab>Revenue</Tab>
<Tab>Users</Tab>
<Tab>Performance</Tab>
</TabList>
<TabPanels>
<TabPanel>
<RevenueAnalytics />
</TabPanel>
<TabPanel>
<UserAnalytics />
</TabPanel>
<TabPanel>
<PerformanceAnalytics />
</TabPanel>
</TabPanels>
</Tabs>
</TabPanel>
<TabPanel>
<ReportsSection />
</TabPanel>
<TabPanel>
<SettingsSection />
</TabPanel>
</TabPanels>
</WPTabbedPage.Body>
</WPTabbedPage>
);
}
Dynamic Tab Generation Pattern
function DynamicTabs() {
const [tabsConfig, setTabsConfig] = useState([]);
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
fetchTabConfiguration().then(config => {
setTabsConfig(config);
setIsLoading(false);
});
}, []);
if (isLoading) {
return <SkeletonLoader />;
}
return (
<TabsWrapper
tabData={tabsConfig.map(config => ({
id: config.id,
title: config.label,
link: config.path,
disabled: !config.enabled,
}))}
orientation='horizontal'
variant='custom'
/>
);
}
Best Practices
Tab Design
-
Clear Navigation
- Use descriptive tab labels
- Maintain consistent tab ordering
- Provide visual feedback for active states
-
Content Organization
- Group related content logically
- Keep tab content focused and concise
- Use progressive disclosure for complex data
-
Responsive Behavior
- Handle mobile tab layouts appropriately
- Use scrollable tabs for many items
- Consider accordion alternatives on small screens
State Management
-
URL Synchronization
- Keep tab state in URL parameters
- Enable deep linking to specific tabs
- Handle browser back/forward navigation
-
Data Loading
- Implement lazy loading for tab content
- Cache data between tab switches
- Show loading states appropriately
-
Filter Integration
- Reset filters when changing tabs
- Maintain filter state within tabs
- Provide clear filter indicators
Performance Optimization
-
Lazy Loading
<Tabs isLazy>
<TabPanels>
<TabPanel>
<Suspense fallback={<Loading />}>
<HeavyComponent />
</Suspense>
</TabPanel>
</TabPanels>
</Tabs> -
Memoization
const MemoizedTabContent = memo(({ data }) => {
return <ExpensiveComponent data={data} />;
}); -
Virtual Scrolling
// For tabs with large datasets
<TabPanel>
<VirtualizedTable data={largeDataset} />
</TabPanel>
Testing
Unit Tests
import { render, screen, fireEvent } from '@testing-library/react';
import { BrowserRouter } from 'react-router-dom';
import TabsWrapper from 'components/Tabs/TabsWrapper';
describe('TabsWrapper', () => {
const mockTabData = [
{ id: 1, title: 'Tab 1', link: '/tab1' },
{ id: 2, title: 'Tab 2', link: '/tab2' },
];
it('renders all tabs', () => {
render(
<BrowserRouter>
<TabsWrapper tabData={mockTabData} />
</BrowserRouter>,
);
expect(screen.getByText('Tab 1')).toBeInTheDocument();
expect(screen.getByText('Tab 2')).toBeInTheDocument();
});
it('calls onTabChange when tab is clicked', async () => {
const mockOnTabChange = jest.fn();
const { user } = render(
<BrowserRouter>
<TabsWrapper tabData={mockTabData} onTabChange={mockOnTabChange} />
</BrowserRouter>,
);
await user.click(screen.getByText('Tab 2'));
expect(mockOnTabChange).toHaveBeenCalledWith(1);
});
it('applies custom variant styling', () => {
render(
<BrowserRouter>
<TabsWrapper tabData={mockTabData} variant='custom' />
</BrowserRouter>,
);
const tabList = screen.getByRole('tablist');
expect(tabList).toHaveStyle({ backgroundColor: 'white' });
});
});
Integration Tests
describe('WPTabbedPage Integration', () => {
it('integrates tabs with filters correctly', async () => {
const { user } = render(<EmployeeManagementPage />);
// Switch to inactive tab
await user.click(screen.getByText('Inactive Employees'));
// Apply filter
await user.click(screen.getByText('Department'));
await user.click(screen.getByText('Engineering'));
// Verify API call with correct parameters
expect(mockApiCall).toHaveBeenCalledWith({
status: 'inactive',
department: 'engineering',
});
});
it('maintains filter state across tab switches', async () => {
const { user } = render(<EmployeeManagementPage />);
// Apply filter on first tab
await user.click(screen.getByText('Department'));
await user.click(screen.getByText('Engineering'));
// Switch to second tab
await user.click(screen.getByText('Inactive Employees'));
// Switch back to first tab
await user.click(screen.getByText('Active Employees'));
// Verify filter is still applied
expect(screen.getByText('Engineering')).toBeInTheDocument();
});
});
Migration Guide
From Legacy Tab Components
-
Update Import Paths
// Old
import TabComponent from 'components/OldTabs';
// New
import TabsWrapper from 'components/Tabs/TabsWrapper'; -
Update Tab Data Structure
// Old
const tabs = ['Tab 1', 'Tab 2', 'Tab 3'];
// New
const tabData = [
{ id: 1, title: 'Tab 1', link: '/tab1' },
{ id: 2, title: 'Tab 2', link: '/tab2' },
{ id: 3, title: 'Tab 3', link: '/tab3' },
]; -
Update Event Handlers
// Old
<TabComponent onChange={handleTabChange} />
// New
<TabsWrapper
tabData={tabData}
onTabChange={handleTabChange}
/>
CSS to Chakra UI Migration
// Old CSS-based tabs
.tab-container {
display: flex;
border-bottom: 1px solid #ccc;
}
.tab {
padding: 12px 16px;
cursor: pointer;
}
.tab.active {
border-bottom: 2px solid #green;
background: #f0f0f0;
}
// New Chakra UI tabs
<Tabs variant="enclosed">
<TabList>
<Tab
_selected={{
borderBottom: '2px solid',
borderColor: 'green',
bg: 'gray.50'
}}
>
Tab Content
</Tab>
</TabList>
</Tabs>
Advanced Usage
Custom Tab Animations
import { motion, AnimatePresence } from 'framer-motion';
function AnimatedTabs() {
const [activeTab, setActiveTab] = useState(0);
return (
<Tabs index={activeTab} onChange={setActiveTab}>
<TabList>
{tabData.map((tab, index) => (
<Tab key={index}>{tab.title}</Tab>
))}
</TabList>
<TabPanels>
<AnimatePresence mode='wait'>
{tabData.map(
(tab, index) =>
activeTab === index && (
<motion.div
key={index}
initial={{ opacity: 0, x: 20 }}
animate={{ opacity: 1, x: 0 }}
exit={{ opacity: 0, x: -20 }}
transition={{ duration: 0.2 }}
>
<TabPanel>{tab.content}</TabPanel>
</motion.div>
),
)}
</AnimatePresence>
</TabPanels>
</Tabs>
);
}
Tab State Persistence
function PersistentTabs() {
const [activeTab, setActiveTab] = useLocalStorage('activeTab', 0);
const handleTabChange = index => {
setActiveTab(index);
// Also update URL if needed
history.pushState({}, '', `?tab=${index}`);
};
return (
<TabsWrapper
tabData={tabData}
index={activeTab}
onTabChange={handleTabChange}
/>
);
}
Conditional Tab Rendering
function ConditionalTabs({ userPermissions }) {
const allTabs = [
{ id: 'overview', title: 'Overview', permission: 'view_overview' },
{ id: 'admin', title: 'Admin', permission: 'admin_access' },
{ id: 'reports', title: 'Reports', permission: 'view_reports' },
];
const visibleTabs = allTabs.filter(tab =>
userPermissions.includes(tab.permission),
);
return <TabsWrapper tabData={visibleTabs} orientation='horizontal' />;
}