59 lines
1.5 KiB
TypeScript
59 lines
1.5 KiB
TypeScript
import React, { useState } from 'react';
|
|
import styles from './Tabs.module.css';
|
|
|
|
export interface Tab {
|
|
id: string;
|
|
label: string;
|
|
content: React.ReactNode;
|
|
}
|
|
|
|
export interface TabsProps {
|
|
tabs: Tab[];
|
|
defaultTabId?: string;
|
|
/** Controlled active tab. When provided, internal state is ignored. */
|
|
activeTabId?: string;
|
|
onTabChange?: (tabId: string) => void;
|
|
className?: string;
|
|
}
|
|
|
|
export function Tabs({ tabs, defaultTabId, activeTabId: controlledTabId, onTabChange, className = '' }: TabsProps) {
|
|
const [internalTabId, setInternalTabId] = useState<string>(
|
|
defaultTabId || tabs[0]?.id || ''
|
|
);
|
|
|
|
const activeTabId = controlledTabId ?? internalTabId;
|
|
|
|
const handleTabClick = (tabId: string) => {
|
|
if (!controlledTabId) setInternalTabId(tabId);
|
|
onTabChange?.(tabId);
|
|
};
|
|
|
|
const activeTab = tabs.find(tab => tab.id === activeTabId) || tabs[0];
|
|
|
|
if (!tabs || tabs.length === 0) {
|
|
return null;
|
|
}
|
|
|
|
return (
|
|
<div className={`${styles.tabsContainer} ${className}`}>
|
|
<div className={styles.tabsHeader}>
|
|
{tabs.map(tab => (
|
|
<button
|
|
key={tab.id}
|
|
className={`${styles.tabButton} ${activeTabId === tab.id ? styles.tabButtonActive : ''}`}
|
|
onClick={() => handleTabClick(tab.id)}
|
|
type="button"
|
|
>
|
|
{tab.label}
|
|
</button>
|
|
))}
|
|
</div>
|
|
<div className={styles.tabsContent}>
|
|
{activeTab && activeTab.content}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export default Tabs;
|
|
|