Tabs
The Tabs
component provides an interactive tab navigation system with smooth animations. It features a sliding indicator and supports multiple tab options for organizing content into different sections.
Import
Section titled “Import”import { Tabs, type Tab } from '@space-uy/pulsar-ui';
Basic usage
Section titled “Basic usage”const [selectedTab, setSelectedTab] = useState<Tab>({ value: 'tab1', label: 'Tab 1',});
<Tabs options={[ { value: 'tab1', label: 'Tab 1' }, { value: 'tab2', label: 'Tab 2' }, { value: 'tab3', label: 'Tab 3' }, ]} selected={selectedTab} onChange={setSelectedTab}/>;
Properties
Section titled “Properties”Property | Type | Required | Default value | Description |
---|---|---|---|---|
options | Tab[] | ✅ | - | Array of tab options to display |
selected | Tab | ✅ | - | Currently selected tab |
onChange | (tab: Tab) => void | ✅ | - | Callback when tab selection changes |
style | StyleProp<ViewStyle> | ❌ | - | Custom styles for the tabs container |
Tab Type
Section titled “Tab Type”The Tab
object has the following structure:
Property | Type | Required | Description |
---|---|---|---|
value | string | ✅ | Unique identifier for the tab |
label | string | ✅ | Display text for the tab |
Basic examples
Section titled “Basic examples”Simple tabs
Section titled “Simple tabs”const [activeTab, setActiveTab] = useState<Tab>({ value: 'home', label: 'Home',});
const tabs = [ { value: 'home', label: 'Home' }, { value: 'profile', label: 'Profile' }, { value: 'settings', label: 'Settings' },];
<Tabs options={tabs} selected={activeTab} onChange={setActiveTab} />;
Content switching
Section titled “Content switching”const [currentTab, setCurrentTab] = useState<Tab>({ value: 'overview', label: 'Overview',});
const tabs = [ { value: 'overview', label: 'Overview' }, { value: 'details', label: 'Details' }, { value: 'reviews', label: 'Reviews' },];
const renderContent = () => { switch (currentTab.value) { case 'overview': return <Text variant="pm">Overview content goes here...</Text>; case 'details': return <Text variant="pm">Detailed information...</Text>; case 'reviews': return <Text variant="pm">User reviews and ratings...</Text>; default: return null; }};
<View> <Tabs options={tabs} selected={currentTab} onChange={setCurrentTab} /> <View style={{ padding: 16 }}>{renderContent()}</View></View>;
Advanced examples
Section titled “Advanced examples”Dashboard tabs
Section titled “Dashboard tabs”const [dashboardTab, setDashboardTab] = useState<Tab>({ value: 'analytics', label: 'Analytics',});
const dashboardTabs = [ { value: 'analytics', label: 'Analytics' }, { value: 'users', label: 'Users' }, { value: 'revenue', label: 'Revenue' }, { value: 'reports', label: 'Reports' },];
<Card> <View style={{ marginBottom: 16 }}> <Text variant="h3">Dashboard</Text> <Text variant="ps" style={{ opacity: 0.7, marginTop: 4 }}> Monitor your app's performance </Text> </View>
<Tabs options={dashboardTabs} selected={dashboardTab} onChange={setDashboardTab} />
<View style={{ marginTop: 16 }}> {dashboardTab.value === 'analytics' && ( <View> <Text variant="h4">Analytics Overview</Text> <Text variant="pm" style={{ marginTop: 8 }}> Your app has been viewed 1,234 times this month. </Text> </View> )} {dashboardTab.value === 'users' && ( <View> <Text variant="h4">User Management</Text> <Text variant="pm" style={{ marginTop: 8 }}> Total active users: 456 </Text> </View> )} {dashboardTab.value === 'revenue' && ( <View> <Text variant="h4">Revenue Tracking</Text> <Text variant="pm" style={{ marginTop: 8 }}> Monthly revenue: $12,345 </Text> </View> )} {dashboardTab.value === 'reports' && ( <View> <Text variant="h4">Generated Reports</Text> <Text variant="pm" style={{ marginTop: 8 }}> Download your latest reports here. </Text> </View> )} </View></Card>;
Settings categories
Section titled “Settings categories”const [settingsCategory, setSettingsCategory] = useState<Tab>({ value: 'account', label: 'Account',});
const settingsTabs = [ { value: 'account', label: 'Account' }, { value: 'privacy', label: 'Privacy' }, { value: 'notifications', label: 'Notifications' }, { value: 'advanced', label: 'Advanced' },];
<View> <Header title="Settings" />
<View style={{ padding: 16 }}> <Tabs options={settingsTabs} selected={settingsCategory} onChange={setSettingsCategory} />
<View style={{ marginTop: 20 }}> {settingsCategory.value === 'account' && ( <View style={{ gap: 16 }}> <Input label="Display Name" value="John Doe" /> <Input label="Email" value="john@example.com" /> <Button text="Update Profile" /> </View> )}
{settingsCategory.value === 'privacy' && ( <View style={{ gap: 16 }}> <View style={{ flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', }} > <Text variant="pm">Public Profile</Text> <Switch value={false} onValueChange={() => {}} /> </View> <View style={{ flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', }} > <Text variant="pm">Show Online Status</Text> <Switch value={true} onValueChange={() => {}} /> </View> </View> )}
{settingsCategory.value === 'notifications' && ( <View style={{ gap: 16 }}> <View style={{ flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', }} > <Text variant="pm">Push Notifications</Text> <Switch value={true} onValueChange={() => {}} /> </View> <View style={{ flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', }} > <Text variant="pm">Email Notifications</Text> <Switch value={false} onValueChange={() => {}} /> </View> </View> )} </View> </View></View>;
Product catalog
Section titled “Product catalog”const [catalogTab, setCatalogTab] = useState<Tab>({ value: 'all', label: 'All',});
const catalogTabs = [ { value: 'all', label: 'All' }, { value: 'featured', label: 'Featured' }, { value: 'new', label: 'New' }, { value: 'sale', label: 'On Sale' },];
const products = { all: ['Product 1', 'Product 2', 'Product 3', 'Product 4'], featured: ['Product 1', 'Product 3'], new: ['Product 4'], sale: ['Product 2'],};
<View> <Header title="Catalog" />
<View style={{ padding: 16 }}> <Tabs options={catalogTabs} selected={catalogTab} onChange={setCatalogTab} />
<View style={{ marginTop: 16, gap: 8 }}> {products[catalogTab.value as keyof typeof products].map( (product, index) => ( <Card key={index}> <Text variant="pm">{product}</Text> </Card> ) )} </View> </View></View>;
Message categories
Section titled “Message categories”const [messageTab, setMessageTab] = useState<Tab>({ value: 'inbox', label: 'Inbox',});
const messageTabs = [ { value: 'inbox', label: 'Inbox' }, { value: 'sent', label: 'Sent' }, { value: 'drafts', label: 'Drafts' }, { value: 'trash', label: 'Trash' },];
const messageCount = { inbox: 12, sent: 0, drafts: 3, trash: 5,};
const TabWithBadge = ({ tab, isSelected, count,}: { tab: Tab; isSelected: boolean; count: number;}) => ( <View style={{ flexDirection: 'row', alignItems: 'center', gap: 6 }}> <Text variant="h4" style={{ color: isSelected ? colors.foreground : colors.foreground + '60', }} > {tab.label} </Text> {count > 0 && ( <View style={{ backgroundColor: colors.primary, borderRadius: 10, minWidth: 20, height: 20, alignItems: 'center', justifyContent: 'center', paddingHorizontal: 6, }} > <Text variant="caption" style={{ color: colors.background }}> {count > 99 ? '99+' : count} </Text> </View> )} </View>);
<View> <Header title="Messages" />
<View style={{ padding: 16 }}> <Tabs options={messageTabs} selected={messageTab} onChange={setMessageTab} />
<View style={{ marginTop: 16 }}> <Text variant="pm"> {messageTab.label}:{' '} {messageCount[messageTab.value as keyof typeof messageCount]} messages </Text> </View> </View></View>;
Custom styled tabs
Section titled “Custom styled tabs”<Tabs options={[ { value: 'design', label: 'Design' }, { value: 'development', label: 'Development' }, { value: 'marketing', label: 'Marketing' }, ]} selected={selectedTab} onChange={setSelectedTab} style={{ backgroundColor: colors.primary + '10', borderRadius: 12, padding: 4, }}/>
Tab navigation with icons
Section titled “Tab navigation with icons”// Note: This would require extending the Tab type to include iconsconst enhancedTabs = [ { value: 'home', label: 'Home', icon: 'Home' }, { value: 'search', label: 'Search', icon: 'Search' }, { value: 'profile', label: 'Profile', icon: 'User' },];
// Custom tab renderer with icons<View style={{ flexDirection: 'row', backgroundColor: colors.altBackground, borderRadius: 8, }}> {enhancedTabs.map((tab) => ( <Pressable key={tab.value} style={{ flex: 1, alignItems: 'center', padding: 12, backgroundColor: selectedTab.value === tab.value ? colors.background : 'transparent', borderRadius: 6, margin: 2, }} onPress={() => setSelectedTab(tab)} > <Icon name={tab.icon as IconName} size={20} color={ selectedTab.value === tab.value ? colors.primary : colors.foreground } /> <Text variant="ps" style={{ marginTop: 4, color: selectedTab.value === tab.value ? colors.primary : colors.foreground, }} > {tab.label} </Text> </Pressable> ))}</View>;
Implementation notes
Section titled “Implementation notes”- The tabs component uses a semi-transparent background with theme border color
- The active tab indicator is a sliding background that animates smoothly between positions
- Animation duration is 250ms with smooth easing for the sliding indicator
- Tab text automatically truncates with
numberOfLines={1}
to maintain layout - The component respects theme roundness settings for consistent appearance
- Each tab has equal width distribution across the available space
- The selected tab uses full foreground color while inactive tabs use 60% opacity
Animation details
Section titled “Animation details”- Sliding indicator: 250ms duration with smooth easing
- Width calculation: Automatically distributes tab width based on container size
- Position interpolation: Smooth translation based on selected tab index
- Border radius: Dynamic border radius that matches theme roundness minus 2px
Styling
Section titled “Styling”Theme integration
Section titled “Theme integration”The Tabs component automatically applies theme styling:
- Background: Semi-transparent border color (50% opacity)
- Active tab: Theme background color with border
- Text color: Theme foreground color with opacity variations
- Border radius: Uses theme roundness setting
Layout
Section titled “Layout”- Height: Fixed at 40px for consistent appearance
- Padding: 4px container padding with individual tab padding
- Tab height: 32px internal height for touch targets
- Distribution: Equal width tabs using flexbox
Accessibility
Section titled “Accessibility”- Tabs are fully keyboard accessible with proper focus management
- Screen readers properly announce tab selection and content changes
- Each tab maintains proper touch targets for mobile accessibility
- Color contrast meets accessibility guidelines when using theme colors
- Tab navigation follows expected interaction patterns
</rewritten_file>