/** * SubTabContainer - 二级导航容器组件 * * 深空 FUI 设计风格(Glassmorphism + Ash Thorp + James Turrell) * - 玻璃态导航栏,漂浮感 * - 选中态发光效果,科幻数据终端感 * - 流畅的过渡动画 * * @example * ```tsx * console.log('切换到', key)} * /> * ``` */ import React, { useState, useCallback, memo } from 'react'; import { Box, Tabs, TabList, TabPanels, Tab, TabPanel, Icon, HStack, Text, Spacer, } from '@chakra-ui/react'; import type { ComponentType } from 'react'; import type { IconType } from 'react-icons'; /** * Tab 配置项 */ export interface SubTabConfig { key: string; name: string; icon?: IconType | ComponentType; component?: ComponentType; } /** * 深空 FUI 主题配置 */ const DEEP_SPACE = { // 背景 bgGlass: 'rgba(12, 14, 28, 0.6)', bgGlassHover: 'rgba(18, 22, 42, 0.7)', // 边框 borderGold: 'rgba(212, 175, 55, 0.2)', borderGoldHover: 'rgba(212, 175, 55, 0.5)', borderGlass: 'rgba(255, 255, 255, 0.06)', // 发光 glowGold: '0 0 30px rgba(212, 175, 55, 0.25), 0 4px 20px rgba(0, 0, 0, 0.3)', innerGlow: 'inset 0 1px 0 rgba(255, 255, 255, 0.08)', // 文字 textWhite: 'rgba(255, 255, 255, 0.95)', textMuted: 'rgba(255, 255, 255, 0.6)', textGold: '#F4D03F', textDark: '#0A0A14', // 选中态 selectedBg: 'linear-gradient(135deg, rgba(212, 175, 55, 0.95) 0%, rgba(184, 150, 12, 0.95) 100%)', // 圆角 radius: '12px', radiusLG: '16px', // 动画 transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)', }; /** * 主题配置 */ export interface SubTabTheme { bg: string; borderColor: string; tabSelectedBg: string; tabSelectedColor: string; tabUnselectedColor: string; tabHoverBg: string; } /** * 预设主题 - 深空 FUI 风格 */ const THEME_PRESETS: Record = { blackGold: { bg: DEEP_SPACE.bgGlass, borderColor: DEEP_SPACE.borderGold, tabSelectedBg: DEEP_SPACE.selectedBg, tabSelectedColor: DEEP_SPACE.textDark, tabUnselectedColor: DEEP_SPACE.textWhite, tabHoverBg: DEEP_SPACE.bgGlassHover, }, default: { bg: 'white', borderColor: 'gray.200', tabSelectedBg: 'blue.500', tabSelectedColor: 'white', tabUnselectedColor: 'gray.600', tabHoverBg: 'gray.100', }, }; export interface SubTabContainerProps { /** Tab 配置数组 */ tabs: SubTabConfig[]; /** 传递给 Tab 内容组件的 props */ componentProps?: Record; /** 默认选中的 Tab 索引 */ defaultIndex?: number; /** 受控模式下的当前索引 */ index?: number; /** Tab 变更回调 */ onTabChange?: (index: number, tabKey: string) => void; /** 主题预设 */ themePreset?: 'blackGold' | 'default'; /** 自定义主题(优先级高于预设) */ theme?: Partial; /** 内容区内边距 */ contentPadding?: number; /** 是否懒加载 */ isLazy?: boolean; /** TabList 右侧自定义内容 */ rightElement?: React.ReactNode; /** 紧凑模式 - 移除 TabList 的外边距 */ compact?: boolean; } const SubTabContainer: React.FC = memo(({ tabs, componentProps = {}, defaultIndex = 0, index: controlledIndex, onTabChange, themePreset = 'blackGold', theme: customTheme, contentPadding = 4, isLazy = true, rightElement, compact = false, }) => { // 内部状态(非受控模式) const [internalIndex, setInternalIndex] = useState(defaultIndex); // 当前索引 const currentIndex = controlledIndex ?? internalIndex; // 记录已访问的 Tab 索引(用于真正的懒加载) const [visitedTabs, setVisitedTabs] = useState>( () => new Set([controlledIndex ?? defaultIndex]) ); // 合并主题 const theme: SubTabTheme = { ...THEME_PRESETS[themePreset], ...customTheme, }; /** * 处理 Tab 切换 */ const handleTabChange = useCallback( (newIndex: number) => { const tabKey = tabs[newIndex]?.key || ''; onTabChange?.(newIndex, tabKey); // 记录已访问的 Tab(用于懒加载) setVisitedTabs(prev => { if (prev.has(newIndex)) return prev; return new Set(prev).add(newIndex); }); if (controlledIndex === undefined) { setInternalIndex(newIndex); } }, [tabs, onTabChange, controlledIndex] ); return ( {/* TabList - 玻璃态导航栏 */} {/* 顶部金色光条 */} {tabs.map((tab, idx) => { const isSelected = idx === currentIndex; return ( {tab.icon && ( )} {tab.name} ); })} {rightElement && ( <> {rightElement} )} {tabs.map((tab, idx) => { const Component = tab.component; // 懒加载:只渲染已访问过的 Tab const shouldRender = !isLazy || visitedTabs.has(idx); return ( {shouldRender && Component ? ( ) : null} ); })} ); }); SubTabContainer.displayName = 'SubTabContainer'; export default SubTabContainer;