117 lines
3.2 KiB
JavaScript
117 lines
3.2 KiB
JavaScript
import { useState, createContext, useContext } from 'react'
|
|
import { motion, AnimatePresence } from 'motion/react'
|
|
import { IconMenu2, IconX } from '@tabler/icons-react'
|
|
import { cn } from '../lib/utils'
|
|
import './ModernSidebar.css'
|
|
|
|
const SidebarContext = createContext(undefined)
|
|
|
|
export const useSidebar = () => {
|
|
const context = useContext(SidebarContext)
|
|
if (!context) {
|
|
throw new Error('useSidebar must be used within a SidebarProvider')
|
|
}
|
|
return context
|
|
}
|
|
|
|
export const SidebarProvider = ({ children, open: openProp, setOpen: setOpenProp, animate = true }) => {
|
|
const [openState, setOpenState] = useState(false)
|
|
const open = openProp !== undefined ? openProp : openState
|
|
const setOpen = setOpenProp !== undefined ? setOpenProp : setOpenState
|
|
|
|
return (
|
|
<SidebarContext.Provider value={{ open, setOpen, animate }}>
|
|
{children}
|
|
</SidebarContext.Provider>
|
|
)
|
|
}
|
|
|
|
export const Sidebar = ({ children, open, setOpen, animate }) => {
|
|
return (
|
|
<SidebarProvider open={open} setOpen={setOpen} animate={animate}>
|
|
{children}
|
|
</SidebarProvider>
|
|
)
|
|
}
|
|
|
|
export const SidebarBody = ({ className, children, ...props }) => {
|
|
return (
|
|
<>
|
|
<DesktopSidebar className={className} {...props}>{children}</DesktopSidebar>
|
|
<MobileSidebar className={className}>{children}</MobileSidebar>
|
|
</>
|
|
)
|
|
}
|
|
|
|
export const DesktopSidebar = ({ className, children, ...props }) => {
|
|
const { open, setOpen, animate } = useSidebar()
|
|
|
|
return (
|
|
<motion.div
|
|
className={cn('modern-sidebar-desktop', className)}
|
|
animate={{
|
|
width: animate ? (open ? '300px' : '60px') : '300px',
|
|
}}
|
|
onMouseEnter={() => setOpen(true)}
|
|
onMouseLeave={() => setOpen(false)}
|
|
{...props}
|
|
>
|
|
{children}
|
|
</motion.div>
|
|
)
|
|
}
|
|
|
|
export const MobileSidebar = ({ className, children }) => {
|
|
const { open, setOpen } = useSidebar()
|
|
|
|
return (
|
|
<>
|
|
<div className="modern-sidebar-mobile-header">
|
|
<div className="modern-sidebar-mobile-toggle">
|
|
<IconMenu2 onClick={() => setOpen(!open)} />
|
|
</div>
|
|
<AnimatePresence>
|
|
{open && (
|
|
<motion.div
|
|
initial={{ x: '-100%', opacity: 0 }}
|
|
animate={{ x: 0, opacity: 1 }}
|
|
exit={{ x: '-100%', opacity: 0 }}
|
|
transition={{ duration: 0.3, ease: 'easeInOut' }}
|
|
className={cn('modern-sidebar-mobile-menu', className)}
|
|
>
|
|
<div className="modern-sidebar-mobile-close" onClick={() => setOpen(!open)}>
|
|
<IconX />
|
|
</div>
|
|
{children}
|
|
</motion.div>
|
|
)}
|
|
</AnimatePresence>
|
|
</div>
|
|
</>
|
|
)
|
|
}
|
|
|
|
export const SidebarLink = ({ link, className, ...props }) => {
|
|
const { open, animate } = useSidebar()
|
|
|
|
return (
|
|
<a
|
|
href={link.href}
|
|
className={cn('modern-sidebar-link', className)}
|
|
{...props}
|
|
>
|
|
<span className="modern-sidebar-link-icon">{link.icon}</span>
|
|
<motion.span
|
|
animate={{
|
|
display: animate ? (open ? 'inline-block' : 'none') : 'inline-block',
|
|
opacity: animate ? (open ? 1 : 0) : 1,
|
|
}}
|
|
className="modern-sidebar-link-label"
|
|
>
|
|
{link.label}
|
|
</motion.span>
|
|
</a>
|
|
)
|
|
}
|
|
|