xiaobo ren
Rebrand to 姣旇抗: Update brand name, colors (primrose yellow #FFF143, fresh green #98FB98), logo icons, and theme throughout application
339fff1
| import React from 'react'; | |
| import { Outlet, useNavigate, useLocation } from 'react-router-dom'; | |
| import LeftNav from './LeftNav'; | |
| import MobileNav from './MobileNav'; | |
| import RightSidebar from './RightSidebar'; | |
| import { useSidebar, SIDEBAR_WIDTH_EXPANDED, SIDEBAR_WIDTH_COLLAPSED } from '../../contexts/SidebarContext'; | |
| import { Star, Calendar, TrendingUp, User, Sparkles, ChevronLeft, ChevronRight } from 'lucide-react'; | |
| import { LifeDestinyResult, UserInput } from '../../types'; | |
| interface AppShellProps { | |
| isLoggedIn: boolean; | |
| userInfo: { email: string; points: number } | null; | |
| onLoginClick: () => void; | |
| onLogout: () => void; | |
| onHistorySelect?: (result: LifeDestinyResult, input: UserInput) => void; | |
| onNewCalculation?: () => void; | |
| isAnalysisPanelOpen?: boolean; | |
| } | |
| // Mini sidebar component for collapsed state | |
| const SidebarMini: React.FC<{ | |
| onGenerate: () => void; | |
| isLoggedIn: boolean; | |
| }> = ({ onGenerate, isLoggedIn }) => { | |
| const navigate = useNavigate(); | |
| const miniItems = [ | |
| { icon: Star, label: '今日', color: 'text-amber-500', onClick: () => navigate('/fortune/daily') }, | |
| { icon: Calendar, label: '本月', color: 'text-blue-500', onClick: () => navigate('/fortune/monthly') }, | |
| { icon: TrendingUp, label: '今年', color: 'text-purple-500', onClick: () => navigate('/fortune/yearly') }, | |
| { icon: Sparkles, label: '测算', color: 'text-primrose', onClick: onGenerate }, | |
| ]; | |
| return ( | |
| <div className="flex flex-col items-center py-4 space-y-2"> | |
| {miniItems.map((item, index) => ( | |
| <button | |
| key={index} | |
| onClick={item.onClick} | |
| className="w-12 h-12 flex flex-col items-center justify-center rounded-xl hover:bg-white/80 transition-colors group" | |
| title={item.label} | |
| > | |
| <item.icon className={`w-5 h-5 ${item.color} group-hover:scale-110 transition-transform`} /> | |
| <span className="text-[10px] text-gray-500 mt-0.5">{item.label}</span> | |
| </button> | |
| ))} | |
| <div className="h-px w-8 bg-gray-200 my-2" /> | |
| <button | |
| onClick={() => navigate(isLoggedIn ? '/dashboard' : '/')} | |
| className="w-12 h-12 flex flex-col items-center justify-center rounded-xl hover:bg-white/80 transition-colors group" | |
| title={isLoggedIn ? '个人中心' : '登录'} | |
| > | |
| <User className="w-5 h-5 text-gray-500 group-hover:scale-110 transition-transform" /> | |
| <span className="text-[10px] text-gray-500 mt-0.5">{isLoggedIn ? '我的' : '登录'}</span> | |
| </button> | |
| </div> | |
| ); | |
| }; | |
| const AppShell: React.FC<AppShellProps> = ({ | |
| isLoggedIn, | |
| userInfo, | |
| onLoginClick, | |
| onLogout, | |
| onHistorySelect, | |
| onNewCalculation, | |
| isAnalysisPanelOpen = false, | |
| }) => { | |
| const navigate = useNavigate(); | |
| const location = useLocation(); | |
| const { isExpanded, isHovered, isCollapsible, setIsHovered, sidebarWidth, toggleExpanded } = useSidebar(); | |
| // Check if on homepage | |
| const isHomepage = location.pathname === '/' || location.pathname === '/home'; | |
| const handleGenerate = () => { | |
| navigate('/'); | |
| window.scrollTo({ top: 0, behavior: 'smooth' }); | |
| }; | |
| // Determine if sidebar should show full content | |
| const showFullSidebar = isExpanded || isHovered || !isCollapsible; | |
| return ( | |
| <div className="min-h-screen bg-cream"> | |
| {/* Desktop: Three-column layout */} | |
| <div className="hidden md:flex max-w-[1440px] mx-auto"> | |
| {/* Left Nav - Fixed/Sticky */} | |
| <aside className="w-[280px] shrink-0"> | |
| <div className="fixed top-0 h-screen w-[280px] overflow-y-auto border-r border-light-mint bg-white"> | |
| <LeftNav | |
| isLoggedIn={isLoggedIn} | |
| userInfo={userInfo} | |
| onLoginClick={onLoginClick} | |
| onLogout={onLogout} | |
| onHistorySelect={onHistorySelect} | |
| onNewCalculation={onNewCalculation} | |
| /> | |
| </div> | |
| </aside> | |
| {/* Main Column - Scrollable, width adjusts based on sidebar */} | |
| <main | |
| className="flex-1 min-w-0 border-r border-light-mint bg-white min-h-screen transition-all duration-300" | |
| style={{ maxWidth: isCollapsible && !showFullSidebar ? '800px' : '680px' }} | |
| > | |
| <Outlet /> | |
| </main> | |
| {/* Right Sidebar - Fixed/Sticky with collapsible support */} | |
| <aside | |
| className="shrink-0 transition-all duration-300 ease-in-out" | |
| style={{ width: sidebarWidth }} | |
| > | |
| <div | |
| className={`fixed top-0 h-screen overflow-y-auto bg-cream transition-all duration-300 ease-in-out ${ | |
| isCollapsible && !showFullSidebar ? 'overflow-hidden' : '' | |
| }`} | |
| style={{ width: sidebarWidth }} | |
| onMouseEnter={() => isCollapsible && setIsHovered(true)} | |
| onMouseLeave={() => isCollapsible && setIsHovered(false)} | |
| > | |
| {/* Collapse/Expand Toggle Button */} | |
| {isCollapsible && ( | |
| <button | |
| onClick={toggleExpanded} | |
| className="absolute left-0 top-1/2 -translate-y-1/2 -translate-x-1/2 z-50 w-6 h-12 bg-white border border-gray-200 rounded-full shadow-md hover:shadow-lg hover:bg-gray-50 flex items-center justify-center transition-all duration-200" | |
| title={isExpanded || isHovered ? '收起侧边栏' : '展开侧边栏'} | |
| > | |
| {isExpanded || isHovered ? ( | |
| <ChevronRight className="w-4 h-4 text-gray-500" /> | |
| ) : ( | |
| <ChevronLeft className="w-4 h-4 text-gray-500" /> | |
| )} | |
| </button> | |
| )} | |
| {/* Full Sidebar Content */} | |
| <div | |
| className={`p-4 transition-opacity duration-200 ${ | |
| showFullSidebar ? 'opacity-100' : 'opacity-0 pointer-events-none absolute' | |
| }`} | |
| > | |
| <RightSidebar | |
| isLoggedIn={isLoggedIn} | |
| userInfo={userInfo} | |
| onLogin={onLoginClick} | |
| onLogout={onLogout} | |
| onGenerate={handleGenerate} | |
| isAnalysisPanelOpen={isAnalysisPanelOpen} | |
| /> | |
| </div> | |
| {/* Mini Sidebar (collapsed state) */} | |
| {isCollapsible && !showFullSidebar && ( | |
| <div className="p-2 opacity-100"> | |
| <SidebarMini onGenerate={handleGenerate} isLoggedIn={isLoggedIn} /> | |
| </div> | |
| )} | |
| </div> | |
| </aside> | |
| </div> | |
| {/* Mobile: Single column with bottom nav */} | |
| <div className="md:hidden"> | |
| <main className="pb-16"> | |
| <Outlet /> | |
| </main> | |
| <MobileNav isLoggedIn={isLoggedIn} /> | |
| </div> | |
| </div> | |
| ); | |
| }; | |
| export default AppShell; | |