react-vite-themes
Version:
A test/experimental React theme system created for learning purposes. Features atomic design components, SCSS variables, and dark/light theme support. Not intended for production use.
90 lines (89 loc) • 4.7 kB
JavaScript
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
import React, { useState, createContext, useContext, useEffect, useRef } from 'react';
import { Icon } from '../../atoms/Icon';
import { cn } from '../../../utils/classNames';
import './Tabs.scss';
const TabsContext = createContext(undefined);
const useTabsContext = () => {
const context = useContext(TabsContext);
if (!context) {
throw new Error('Tabs components must be used within a Tabs component');
}
return context;
};
export const TabList = ({ children, className }) => {
const { variant, size, isVertical, isFullWidth } = useTabsContext();
const tabListClasses = cn('tabs-list', `tabs-list--${variant}`, `tabs-list--${size}`, isVertical && 'tabs-list--vertical', isFullWidth && 'tabs-list--full-width', className);
return (_jsx("div", { className: tabListClasses, role: "tablist", children: children }));
};
export const Tab = ({ tabId, children, className }) => {
const { activeTab, setActiveTab, variant, size } = useTabsContext();
const isActive = activeTab === tabId;
const tabClasses = cn('tab', `tab--${variant}`, `tab--${size}`, isActive && 'tab--active', className);
return (_jsx("button", { className: tabClasses, onClick: () => setActiveTab(tabId), role: "tab", "aria-selected": isActive, "aria-controls": `panel-${tabId}`, id: `tab-${tabId}`, children: children }));
};
export const TabPanel = ({ tabId, children, className }) => {
const { activeTab, useTargetId } = useTabsContext();
const isActive = activeTab === tabId;
// If using targetId approach, don't render this panel
if (useTargetId) {
return null;
}
const panelClasses = cn('tab-panel', isActive && 'tab-panel--active', className);
return (_jsx("div", { className: panelClasses, role: "tabpanel", id: `panel-${tabId}`, "aria-labelledby": `tab-${tabId}`, hidden: !isActive, children: children }));
};
export const TargetContent = ({ targetId }) => {
const { activeTab, useTargetId } = useTabsContext();
const targetRef = useRef(null);
useEffect(() => {
if (useTargetId) {
targetRef.current = document.getElementById(targetId);
}
}, [targetId, useTargetId]);
useEffect(() => {
if (useTargetId && targetRef.current) {
const isActive = activeTab === targetId.replace('-content', '');
if (isActive) {
targetRef.current.style.display = 'block';
targetRef.current.setAttribute('aria-hidden', 'false');
}
else {
targetRef.current.style.display = 'none';
targetRef.current.setAttribute('aria-hidden', 'true');
}
}
}, [activeTab, targetId, useTargetId]);
// This component doesn't render anything visible
// It just manages the target element's visibility
return null;
};
// Main Tabs Component
export const Tabs = ({ tabs, defaultActiveTab, variant = 'default', size = 'md', isFullWidth = false, isVertical = false, showIcons = true, className, onTabChange }) => {
const [activeTab, setActiveTab] = useState(defaultActiveTab || tabs[0]?.id || '');
// Auto-detect which approach is being used
const useTargetId = tabs.some(tab => tab.targetId) && !tabs.some(tab => tab.content);
const handleTabChange = (tabId) => {
setActiveTab(tabId);
onTabChange?.(tabId);
};
const contextValue = {
activeTab,
setActiveTab: handleTabChange,
variant,
size,
isVertical,
isFullWidth,
showIcons,
useTargetId
};
const tabsClasses = cn('tabs', `tabs--${variant}`, `tabs--${size}`, isVertical && 'tabs--vertical', className);
return (_jsx(TabsContext.Provider, { value: contextValue, children: _jsxs("div", { className: tabsClasses, children: [_jsx(TabList, { children: tabs.map((tab) => (_jsxs(Tab, { tabId: tab.id, children: [showIcons && tab.icon && (_jsx(Icon, { name: tab.icon, size: "sm" })), _jsx("span", { className: "tab-label", children: tab.label })] }, tab.id))) }), useTargetId ? (
// Target ID approach - manage external content
_jsx("div", { className: "tabs-content", children: tabs.map((tab) => (_jsx(TargetContent, { targetId: tab.targetId }, tab.id))) })) : (
// Content prop approach - render content inline
_jsx("div", { className: "tabs-content", children: tabs.map((tab) => (_jsx(TabPanel, { tabId: tab.id, children: tab.content }, tab.id))) }))] }) }));
};
// Simple Tabs Component (for backward compatibility)
export const SimpleTabs = (props) => {
return _jsx(Tabs, { ...props });
};