Files
MeBox/web/src/components/LayoutSections.tsx
T
truewhile da1fb02c9d 优化
优化
2026-08-25 14:12:59 +08:00

152 lines
3.9 KiB
TypeScript

import { Outlet } from 'react-router-dom'
import { AnimatePresence, motion } from 'framer-motion'
import clsx from 'clsx'
import { LayoutSidebarContent, type LayoutSidebarContentProps } from './LayoutSidebarContent'
import { RouteErrorBoundary } from './RouteErrorBoundary'
import type { useLayoutSidebar } from './useLayoutSidebar'
type LayoutSidebarState = ReturnType<typeof useLayoutSidebar>
type LayoutSidebarProps = {
children: React.ReactNode
isSidebarOpen: boolean
}
type LayoutMobileSidebarProps = {
children: React.ReactNode
isOpen: boolean
onClose: () => void
}
type LayoutSidebarsProps = Omit<
LayoutSidebarContentProps,
'isSidebarOpen' | 'isMobileDrawerOpen' | 'onToggleSidebar' | 'onCloseMobileDrawer'
> & {
sidebar: LayoutSidebarState
showSidebar: boolean
}
type LayoutWorkspaceProps = {
routeKey: string
}
export { LayoutHeader } from './LayoutHeaderSections'
export function LayoutDesktopSidebar({ children, isSidebarOpen }: LayoutSidebarProps) {
return (
<aside
className={clsx(
'hidden lg:flex flex-col h-full shrink-0 transition-all duration-300 ease-out',
isSidebarOpen ? 'w-64' : 'w-20',
)}
>
{children}
</aside>
)
}
export function LayoutMobileSidebar({ children, isOpen, onClose }: LayoutMobileSidebarProps) {
return (
<AnimatePresence>
{isOpen && (
<div className="fixed inset-0 z-50 flex lg:hidden">
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
onClick={onClose}
className="fixed inset-0 bg-black/15 backdrop-blur-sm"
/>
<motion.div
initial={{ x: '-100%' }}
animate={{ x: 0 }}
exit={{ x: '-100%' }}
transition={{ type: 'spring', damping: 25, stiffness: 220 }}
className="relative flex w-64 max-w-xs flex-col h-full z-10 shadow-xl"
>
{children}
</motion.div>
</div>
)}
</AnimatePresence>
)
}
export function LayoutSidebars({
sidebar,
isAdmin,
can,
showSidebar,
}: LayoutSidebarsProps) {
const content = (
<LayoutSidebarContent
isSidebarOpen={sidebar.isSidebarOpen}
isMobileDrawerOpen={sidebar.isMobileDrawerOpen}
isAdmin={isAdmin}
can={can}
onToggleSidebar={sidebar.toggleSidebar}
onCloseMobileDrawer={() => sidebar.setIsMobileDrawerOpen(false)}
/>
)
if (!showSidebar) {
return (
<>
<LayoutMobileSidebar
isOpen={sidebar.isMobileDrawerOpen}
onClose={() => sidebar.setIsMobileDrawerOpen(false)}
>
{content}
</LayoutMobileSidebar>
</>
)
}
return (
<>
<LayoutDesktopSidebar isSidebarOpen={sidebar.isSidebarOpen}>{content}</LayoutDesktopSidebar>
<LayoutMobileSidebar
isOpen={sidebar.isMobileDrawerOpen}
onClose={() => sidebar.setIsMobileDrawerOpen(false)}
>
{content}
</LayoutMobileSidebar>
</>
)
}
export function LayoutWorkspace({ routeKey }: LayoutWorkspaceProps) {
if (routeKey.startsWith('/play')) {
return (
<main className="flex flex-1 h-full w-full overflow-hidden">
<RouteErrorBoundary>
<Outlet />
</RouteErrorBoundary>
</main>
)
}
return (
<main className="flex-1 overflow-y-auto px-4 py-6 md:px-8 md:py-10">
<div className="max-w-7xl mx-auto">
<AnimatePresence mode="wait">
<motion.div
key={routeKey}
initial={{ opacity: 0, y: 12 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -6 }}
transition={{ duration: 0.25, ease: 'easeOut' }}
>
<RouteErrorBoundary>
<Outlet />
</RouteErrorBoundary>
</motion.div>
</AnimatePresence>
</div>
</main>
)
}
export { LayoutSidebarContent }