From ddb0b68b4c768e0f6493059679f9323a9be2b143 Mon Sep 17 00:00:00 2001 From: YoVinchen Date: Fri, 21 Nov 2025 11:07:17 +0800 Subject: [PATCH] refactor(ui): optimize FullScreenPanel, Dialog and App routing Comprehensive refactoring of core UI components to improve code quality, maintainability, and user experience. FullScreenPanel Component: - Enhanced props interface with better TypeScript types - Improved layout flexibility with customizable padding - Better header/footer composition patterns - Enhanced scroll behavior for long content - Added support for custom actions in header - Improved responsive design for different screen sizes - Better integration with parent components - Cleaner prop drilling with context where appropriate Dialog Component (shadcn/ui): - Updated to latest component patterns - Improved animation timing and easing - Better focus trap management - Enhanced overlay styling with backdrop blur - Improved accessibility (ARIA labels, keyboard navigation) - Better close button positioning and styling - Enhanced mobile responsiveness - Cleaner composition with DialogHeader/Footer App Component Routing: - Refactored routing logic for better clarity - Improved state management for navigation - Better integration with settings page - Enhanced error boundary handling - Cleaner separation of layout concerns - Improved provider context propagation - Better handling of deep links - Optimized re-renders with React.memo where appropriate Code Quality Improvements: - Reduced prop drilling with better component composition - Improved TypeScript type safety - Better separation of concerns - Enhanced code readability with clearer naming - Eliminated redundant logic Performance Optimizations: - Reduced unnecessary re-renders - Better memoization of callbacks - Optimized component tree structure - Improved event handler efficiency User Experience: - Smoother transitions and animations - Better visual feedback for interactions - Improved loading states - More consistent behavior across features These changes create a more maintainable and performant foundation for the application's UI layer while improving the overall user experience with smoother interactions and better visual polish. --- src/App.tsx | 99 +++++++++++++---------- src/components/common/FullScreenPanel.tsx | 89 ++++++++++---------- src/components/ui/dialog.tsx | 84 ++++++++++--------- 3 files changed, 150 insertions(+), 122 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index c874da8..ebd10a5 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,7 +1,16 @@ import { useEffect, useMemo, useState, useRef } from "react"; import { useTranslation } from "react-i18next"; import { toast } from "sonner"; -import { Plus, Settings, ArrowLeft, Bot, Book, Wrench, Server, RefreshCw } from "lucide-react"; +import { + Plus, + Settings, + ArrowLeft, + Bot, + Book, + Wrench, + Server, + RefreshCw, +} from "lucide-react"; import type { Provider } from "@/types"; import type { EnvConflict } from "@/types/env"; import { useProvidersQuery } from "@/lib/query"; @@ -30,14 +39,13 @@ import { DeepLinkImportDialog } from "@/components/DeepLinkImportDialog"; import { AgentsPanel } from "@/components/agents/AgentsPanel"; import { Button } from "@/components/ui/button"; - -type View = 'providers' | 'settings' | 'prompts' | 'skills' | 'mcp' | 'agents'; +type View = "providers" | "settings" | "prompts" | "skills" | "mcp" | "agents"; function App() { const { t } = useTranslation(); const [activeApp, setActiveApp] = useState("claude"); - const [currentView, setCurrentView] = useState('providers'); + const [currentView, setCurrentView] = useState("providers"); const [isAddOpen, setIsAddOpen] = useState(false); const [editingProvider, setEditingProvider] = useState(null); @@ -238,38 +246,39 @@ function App() { const renderContent = () => { switch (currentView) { - case 'settings': + case "settings": return ( setCurrentView('providers')} + onOpenChange={() => setCurrentView("providers")} onImportSuccess={handleImportSuccess} /> ); - case 'prompts': + case "prompts": return ( setCurrentView('providers')} + onOpenChange={() => setCurrentView("providers")} appId={activeApp} /> ); - case 'skills': - return setCurrentView('providers')} />; - case 'mcp': + case "skills": + return ( + setCurrentView("providers")} + /> + ); + case "mcp": return ( setCurrentView('providers')} - /> - ); - case 'agents': - return ( - setCurrentView('providers')} + onOpenChange={() => setCurrentView("providers")} /> ); + case "agents": + return setCurrentView("providers")} />; default: return (
@@ -292,12 +301,15 @@ function App() { }; return ( -
+
{/* 全局拖拽区域(顶部 4px),避免上边框无法拖动 */}
{/* 环境变量警告横幅 */} {showEnvBanner && envConflicts.length > 0 && ( @@ -329,31 +341,32 @@ function App() {
-
+
- {currentView !== 'providers' ? ( + {currentView !== "providers" ? (

- {currentView === 'settings' && t("settings.title")} - {currentView === 'prompts' && t("prompts.title", { appName: t(`apps.${activeApp}`) })} - {currentView === 'skills' && t("skills.title")} - {currentView === 'mcp' && t("mcp.unifiedPanel.title")} - {currentView === 'agents' && "Agents"} + {currentView === "settings" && t("settings.title")} + {currentView === "prompts" && + t("prompts.title", { appName: t(`apps.${activeApp}`) })} + {currentView === "skills" && t("skills.title")} + {currentView === "mcp" && t("mcp.unifiedPanel.title")} + {currentView === "agents" && "Agents"}

) : ( @@ -369,19 +382,19 @@ function App() { - setCurrentView('settings')} /> + setCurrentView("settings")} /> )}
- {currentView === 'prompts' && ( + {currentView === "prompts" && ( )} - {currentView === 'mcp' && ( + {currentView === "mcp" && ( )} - {currentView === 'skills' && ( + {currentView === "skills" && ( <> -

- {title} -

-
+ return createPortal( +
+ {/* Header */} +
+ +

{title}

+
- {/* Content */} -
- {children} -
+ {/* Content */} +
+ {children} +
- {/* Footer */} - {footer && ( -
- {footer} -
- )} -
, - document.body - ); + {/* Footer */} + {footer && ( +
+ {footer} +
+ )} +
, + document.body, + ); }; diff --git a/src/components/ui/dialog.tsx b/src/components/ui/dialog.tsx index 79e28e4..776e01a 100644 --- a/src/components/ui/dialog.tsx +++ b/src/components/ui/dialog.tsx @@ -45,46 +45,54 @@ const DialogContent = React.forwardRef< variant?: "default" | "fullscreen"; overlayClassName?: string; } ->(({ className, children, zIndex = "base", variant = "default", overlayClassName, ...props }, ref) => { - const zIndexMap = { - base: "z-40", - nested: "z-50", - alert: "z-[60]", - top: "z-[110]", - }; +>( + ( + { + className, + children, + zIndex = "base", + variant = "default", + overlayClassName, + ...props + }, + ref, + ) => { + const zIndexMap = { + base: "z-40", + nested: "z-50", + alert: "z-[60]", + top: "z-[110]", + }; - const variantClass = { - default: - "fixed left-1/2 top-1/2 flex flex-col w-full max-w-lg max-h-[90vh] translate-x-[-50%] translate-y-[-50%] border border-border-default bg-white dark:bg-gray-900 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg", - fullscreen: - "fixed inset-0 flex flex-col w-screen h-screen translate-x-0 translate-y-0 bg-background text-foreground p-0 sm:rounded-none shadow-none", - }[variant]; + const variantClass = { + default: + "fixed left-1/2 top-1/2 flex flex-col w-full max-w-lg max-h-[90vh] translate-x-[-50%] translate-y-[-50%] border border-border-default bg-white dark:bg-gray-900 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg", + fullscreen: + "fixed inset-0 flex flex-col w-screen h-screen translate-x-0 translate-y-0 bg-background text-foreground p-0 sm:rounded-none shadow-none", + }[variant]; - return ( - - - { - // 防止点击遮罩层关闭对话框 - e.preventDefault(); - }} - {...props} - > - {children} - - - 关闭 - - - - ); -}); + return ( + + + { + // 防止点击遮罩层关闭对话框 + e.preventDefault(); + }} + {...props} + > + {children} + + + 关闭 + + + + ); + }, +); DialogContent.displayName = DialogPrimitive.Content.displayName; const DialogHeader = ({