104 lines
2.9 KiB
TypeScript
104 lines
2.9 KiB
TypeScript
import React, { useState, useEffect, useRef } from 'react';
|
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
|
import { IconDefinition } from '@fortawesome/fontawesome-svg-core';
|
|
import './TabView.css';
|
|
|
|
interface TabBarProps {
|
|
tabs: {
|
|
id: string;
|
|
label: string;
|
|
icon?: React.ReactNode | IconDefinition;
|
|
component: React.ComponentType<any>;
|
|
}[];
|
|
activeTab?: string;
|
|
onTabChange?: (tabId: string) => void;
|
|
}
|
|
|
|
export const TabView: React.FC<TabBarProps> = ({
|
|
tabs,
|
|
activeTab,
|
|
onTabChange
|
|
}) => {
|
|
const [active, setActive] = useState(activeTab || tabs[0]?.id || '');
|
|
const [ActiveComponent, setActiveComponent] = useState<React.ComponentType<any> | null>(null);
|
|
const [isTransitioning, setIsTransitioning] = useState(false);
|
|
const contentRef = useRef<HTMLDivElement>(null);
|
|
|
|
useEffect(() => {
|
|
if (activeTab && activeTab !== active) {
|
|
setActive(activeTab);
|
|
}
|
|
}, [activeTab, active]);
|
|
|
|
useEffect(() => {
|
|
const activeTabData = tabs.find(tab => tab.id === active);
|
|
if (activeTabData) {
|
|
// Add transitioning effect
|
|
setIsTransitioning(true);
|
|
|
|
// Fade out current content
|
|
if (contentRef.current) {
|
|
contentRef.current.style.opacity = '0';
|
|
contentRef.current.style.transform = 'translateX(-10px)';
|
|
}
|
|
|
|
// Set new component after brief transition
|
|
setTimeout(() => {
|
|
setActiveComponent(() => activeTabData.component);
|
|
|
|
// Fade in new content
|
|
if (contentRef.current) {
|
|
contentRef.current.style.opacity = '1';
|
|
contentRef.current.style.transform = 'translateX(0)';
|
|
}
|
|
|
|
setIsTransitioning(false);
|
|
}, 100);
|
|
}
|
|
}, [active, tabs]);
|
|
|
|
const handleTabClick = (tabId: string) => {
|
|
if (tabId !== active && !isTransitioning) {
|
|
setActive(tabId);
|
|
onTabChange?.(tabId);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div className="tab-container">
|
|
<div
|
|
className="tab-content"
|
|
ref={contentRef}
|
|
style={{
|
|
transition: 'opacity 0.3s ease, transform 0.3s ease',
|
|
}}
|
|
>
|
|
{ActiveComponent && <ActiveComponent />}
|
|
</div>
|
|
<div className="tab-bar">
|
|
{tabs.map(tab => (
|
|
<button
|
|
key={tab.id}
|
|
className={`tab-bar-item ${active === tab.id ? 'active' : ''}`}
|
|
onClick={() => handleTabClick(tab.id)}
|
|
aria-selected={active === tab.id}
|
|
role="tab"
|
|
>
|
|
{tab.icon && (
|
|
<span className="tab-icon">
|
|
{React.isValidElement(tab.icon) ? (
|
|
tab.icon
|
|
) : (
|
|
<FontAwesomeIcon icon={tab.icon as IconDefinition} />
|
|
)}
|
|
</span>
|
|
)}
|
|
<span className="tab-label">{tab.label}</span>
|
|
</button>
|
|
))}
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default TabView;
|