import { HTMLAttributes, ReactNode, useEffect, useState } from 'react'; import { cn } from '@/lib/helper'; export interface TabItem { id: string; label: ReactNode; content?: ReactNode; disabled?: boolean; } export interface TabsProps extends Omit, 'className'> { tabs: TabItem[]; variant?: 'bordered' | 'lifted' | 'boxed'; size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl'; placement?: 'top' | 'bottom'; /** Tab yang aktif secara default (uncontrolled mode) */ defaultActiveId?: string; /** Tab yang aktif (controlled mode, dikontrol parent) */ activeTabId?: string; className?: | string | { container?: string; wrapper?: string; tab?: string; content?: string; tabHeaderWrapper?: string; }; onTabChange?: (tabId: string) => void; sideContent?: ReactNode; } const Tabs = ({ tabs, variant, size = 'md', placement = 'top', defaultActiveId, activeTabId: controlledActiveId, className, onTabChange, sideContent, ...props }: TabsProps) => { // State internal hanya dipakai kalau `activeTabId` (controlled) tidak diset const [uncontrolledActiveId, setUncontrolledActiveId] = useState( defaultActiveId || tabs[0]?.id || '' ); const isControlled = controlledActiveId !== undefined; const activeTabId = isControlled ? controlledActiveId : uncontrolledActiveId; const handleTabChange = (tabId: string) => { if (tabId === activeTabId) return; if (!isControlled) setUncontrolledActiveId(tabId); onTabChange?.(tabId); }; const { container: containerClassName, wrapper: wrapperClassName, tab: tabClassName, content: contentClassName, tabHeaderWrapper: tabHeaderWrapperClassName, } = typeof className === 'object' ? className : { wrapper: className, tab: undefined }; const getTabsClasses = () => { const variantClasses: Record = { bordered: 'tabs-bordered', lifted: 'tabs-lift', boxed: 'tabs-box', }; const sizeClasses: Record = { xs: 'tabs-xs', sm: 'tabs-sm', md: '', lg: 'tabs-lg', xl: 'tabs-xl', }; const placementClasses: Record = { top: '', bottom: 'tabs-bottom', }; return cn( 'tabs', variant && variantClasses[variant], sizeClasses[size], placementClasses[placement], wrapperClassName ); }; const getTabClasses = (isActive: boolean, isDisabled?: boolean) => cn( 'tab', { 'tab-active': isActive, 'tab-disabled': isDisabled, }, tabClassName ); const getSideContentClasses = () => { return cn('flex flex-row', tabHeaderWrapperClassName); }; const activeContent = tabs.find((tab) => tab.id === activeTabId)?.content; return ( {tabs.map(({ id, label, disabled }) => ( !disabled && handleTabChange(id)} disabled={disabled} > {label} ))} {sideContent && sideContent} {activeContent && ( {activeContent} )} ); }; export default Tabs;