Enhanced Modal Components: - Optimized DeleteConfirmDialog with beautiful gradients and improved layout - Enhanced MaterialEditDialog with modern design and better information hierarchy - Improved TemplateDetailModal with elegant header and refined tab navigation - Enhanced AiClassificationFormDialog with modern styling Unified Tab System: - Created reusable TabNavigation component with multiple variants (default, pills, underline) - Implemented consistent tab design across ProjectDetails and other pages - Added support for icons, counts, and disabled states in tabs - Improved accessibility and keyboard navigation Advanced Animations: - Added comprehensive modal animations (fade-in, scale-in, slide-in) - Enhanced backdrop blur effects and smooth transitions - Implemented proper enter/exit animations for better UX - Added reduced motion support for accessibility Responsive Design: - Optimized modal layouts for mobile, tablet, and desktop - Improved touch-friendly interactions for mobile devices - Enhanced modal sizing and positioning across screen sizes - Added proper scrolling and overflow handling Interaction Improvements: - Enhanced ESC key support for modal closing - Improved backdrop click handling - Better focus management and keyboard navigation - Consistent button styling and hover effects All modal and tab components now follow unified design language while maintaining full functionality.
140 lines
4.1 KiB
TypeScript
140 lines
4.1 KiB
TypeScript
import React from 'react';
|
|
import { LucideIcon } from 'lucide-react';
|
|
|
|
export interface TabItem {
|
|
id: string;
|
|
label: string;
|
|
icon?: LucideIcon;
|
|
count?: number;
|
|
disabled?: boolean;
|
|
}
|
|
|
|
interface TabNavigationProps {
|
|
tabs: TabItem[];
|
|
activeTab: string;
|
|
onTabChange: (tabId: string) => void;
|
|
variant?: 'default' | 'pills' | 'underline';
|
|
size?: 'sm' | 'md' | 'lg';
|
|
className?: string;
|
|
}
|
|
|
|
/**
|
|
* 统一的选项卡导航组件
|
|
* 支持多种样式变体和尺寸
|
|
*/
|
|
export const TabNavigation: React.FC<TabNavigationProps> = ({
|
|
tabs,
|
|
activeTab,
|
|
onTabChange,
|
|
variant = 'default',
|
|
size = 'md',
|
|
className = '',
|
|
}) => {
|
|
const getTabClasses = (tab: TabItem, isActive: boolean) => {
|
|
const baseClasses = 'inline-flex items-center gap-2 font-medium transition-all duration-200 focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-offset-2';
|
|
|
|
// 尺寸样式
|
|
const sizeClasses = {
|
|
sm: 'px-3 py-2 text-sm',
|
|
md: 'px-4 py-2.5 text-sm',
|
|
lg: 'px-5 py-3 text-base',
|
|
};
|
|
|
|
// 变体样式
|
|
const variantClasses = {
|
|
default: isActive
|
|
? 'text-primary-600 border-b-2 border-primary-500 bg-white rounded-t-lg'
|
|
: 'text-gray-500 hover:text-gray-700 hover:bg-gray-50 rounded-t-lg',
|
|
pills: isActive
|
|
? 'text-primary-600 bg-primary-100 border border-primary-200 rounded-lg'
|
|
: 'text-gray-500 hover:text-gray-700 hover:bg-gray-100 rounded-lg',
|
|
underline: isActive
|
|
? 'text-primary-600 border-b-2 border-primary-500'
|
|
: 'text-gray-500 hover:text-gray-700 border-b-2 border-transparent hover:border-gray-300',
|
|
};
|
|
|
|
const disabledClasses = tab.disabled ? 'opacity-50 cursor-not-allowed' : 'cursor-pointer';
|
|
|
|
return `${baseClasses} ${sizeClasses[size]} ${variantClasses[variant]} ${disabledClasses}`;
|
|
};
|
|
|
|
const getContainerClasses = () => {
|
|
const baseClasses = 'flex overflow-x-auto';
|
|
|
|
const variantContainerClasses = {
|
|
default: 'space-x-1 border-b border-gray-100 bg-gradient-to-r from-gray-50 to-white px-4',
|
|
pills: 'space-x-2 p-1 bg-gray-100 rounded-lg',
|
|
underline: 'space-x-6 border-b border-gray-200',
|
|
};
|
|
|
|
return `${baseClasses} ${variantContainerClasses[variant]} ${className}`;
|
|
};
|
|
|
|
return (
|
|
<nav className={getContainerClasses()}>
|
|
{tabs.map((tab) => {
|
|
const Icon = tab.icon;
|
|
const isActive = activeTab === tab.id;
|
|
|
|
return (
|
|
<button
|
|
key={tab.id}
|
|
onClick={() => !tab.disabled && onTabChange(tab.id)}
|
|
className={getTabClasses(tab, isActive)}
|
|
disabled={tab.disabled}
|
|
type="button"
|
|
>
|
|
{Icon && (
|
|
<Icon
|
|
className={`w-4 h-4 transition-all duration-200 ${
|
|
isActive ? 'text-primary-600' : 'text-gray-400'
|
|
}`}
|
|
/>
|
|
)}
|
|
<span className="whitespace-nowrap">{tab.label}</span>
|
|
{tab.count !== undefined && (
|
|
<span className={`inline-flex items-center justify-center px-2 py-0.5 rounded-full text-xs font-medium ${
|
|
isActive
|
|
? 'bg-primary-200 text-primary-800'
|
|
: 'bg-gray-200 text-gray-600'
|
|
}`}>
|
|
{tab.count}
|
|
</span>
|
|
)}
|
|
{isActive && variant === 'default' && (
|
|
<div className="absolute inset-0 bg-gradient-to-r from-primary-50/30 to-primary-100/30 rounded-t-lg -z-10"></div>
|
|
)}
|
|
</button>
|
|
);
|
|
})}
|
|
</nav>
|
|
);
|
|
};
|
|
|
|
// 预设的选项卡样式
|
|
export const TabNavigationPresets = {
|
|
// 默认样式 - 适用于页面主要导航
|
|
default: {
|
|
variant: 'default' as const,
|
|
size: 'md' as const,
|
|
},
|
|
|
|
// 药丸样式 - 适用于紧凑的选项切换
|
|
pills: {
|
|
variant: 'pills' as const,
|
|
size: 'sm' as const,
|
|
},
|
|
|
|
// 下划线样式 - 适用于简洁的导航
|
|
underline: {
|
|
variant: 'underline' as const,
|
|
size: 'md' as const,
|
|
},
|
|
|
|
// 大尺寸 - 适用于重要的页面导航
|
|
large: {
|
|
variant: 'default' as const,
|
|
size: 'lg' as const,
|
|
},
|
|
};
|