Add Sidebar component

This commit is contained in:
2023-10-17 17:46:52 +08:00
parent 491fb75be8
commit 14380d42dc
10 changed files with 597 additions and 524 deletions

View File

@@ -0,0 +1,64 @@
import React from 'react'
import Icon from '@ant-design/icons'
import { getLoginStatus, logout } from '@/utils/auth'
import { getRedirectUrl } from '@/utils/common'
const SidebarFooter: React.FC = () => {
const matches = useMatches()
const lastMatch = matches.reduce((_, second) => second)
const location = useLocation()
const navigate = useNavigate()
const [exiting, setExiting] = useState(false)
const handleClickAvatar = () => {
if (getLoginStatus()) {
navigate('/user')
} else {
navigate(getRedirectUrl('/login', `${lastMatch.pathname}${location.search}`))
}
}
const handleLogout = () => {
if (exiting) {
return
}
setExiting(true)
void logout().finally(() => {
setTimeout(() => {
window.location.reload()
}, 1500)
})
}
return (
<div className={'footer'}>
<span className={'icon-user'} onClick={handleClickAvatar}>
<Icon component={IconFatwebUser} />
</span>
<span hidden={getLoginStatus()} className={'text'}>
<NavLink to={getRedirectUrl('/login', `${lastMatch.pathname}${location.search}`)}>
</NavLink>
</span>
<span hidden={!getLoginStatus()} className={'text'}>
</span>
<div
hidden={!getLoginStatus()}
className={`submenu-exit${!getLoginStatus() ? ' hide' : ''}`}
>
<div className={'content'}>
<span hidden={!getLoginStatus()} className={'icon-exit'} onClick={handleLogout}>
<Icon
component={exiting ? IconFatwebLoading : IconFatwebExit}
spin={exiting}
/>
</span>
</div>
</div>
</div>
)
}
export default SidebarFooter

View File

@@ -0,0 +1,60 @@
import React from 'react'
import Icon from '@ant-design/icons'
import SidebarSubmenu from '@/components/common/sidebar/SidebarSubmenu'
type ItemProps = {
icon?: IconComponent
text?: string
path: string
children?: React.ReactNode
}
const SidebarItem: React.FC<ItemProps> = (props) => {
const [submenuTop, setSubmenuTop] = useState(0)
const [submenuLeft, setSubmenuLeft] = useState(0)
const showSubmenu = (e: React.MouseEvent) => {
const parentElement = e.currentTarget.parentElement
if (parentElement && parentElement.childElementCount === 2) {
const parentClientRect = parentElement.getBoundingClientRect()
if (parentClientRect.top <= screen.height / 2) {
setSubmenuTop(parentClientRect.top)
} else {
setSubmenuTop(
parentClientRect.top -
(parentElement.lastElementChild?.clientHeight ?? 0) +
e.currentTarget.clientHeight
)
}
setSubmenuLeft(parentClientRect.right)
}
}
return (
<li className={'item'}>
<div className={'menu-bt'} onMouseEnter={showSubmenu}>
<NavLink
to={props.path}
end
className={({ isActive, isPending }) =>
isPending ? 'pending' : isActive ? 'active' : ''
}
>
<div className={'icon-box'}>
{props.icon ? (
<Icon className={'icon'} component={props.icon} />
) : undefined}
</div>
<span className={'text'}>{props.text}</span>
</NavLink>
</div>
{props.children ? (
<SidebarSubmenu submenuTop={submenuTop} submenuLeft={submenuLeft}>
{props.children}
</SidebarSubmenu>
) : undefined}
</li>
)
}
export default SidebarItem

View File

@@ -0,0 +1,7 @@
import React from 'react'
const SidebarItemList: React.FC<React.PropsWithChildren> = (props) => {
return <ul>{props.children}</ul>
}
export default SidebarItemList

View File

@@ -0,0 +1,34 @@
import React, { useImperativeHandle } from 'react'
import HideScrollbar, { HideScrollbarElement } from '@/components/common/HideScrollbar'
export interface SidebarScrollElement {
refreshLayout(): void
}
const SidebarScroll = forwardRef<SidebarScrollElement, React.PropsWithChildren>((props, ref) => {
useImperativeHandle<SidebarScrollElement, SidebarScrollElement>(ref, () => {
return {
refreshLayout() {
hideScrollbarRef.current?.refreshLayout()
}
}
})
const hideScrollbarRef = useRef<HideScrollbarElement>(null)
return (
<div className={'scroll'}>
<HideScrollbar
isShowVerticalScrollbar={true}
scrollbarWidth={2}
animationTransitionTime={300}
autoHideWaitingTime={800}
ref={hideScrollbarRef}
>
{props.children}
</HideScrollbar>
</div>
)
})
export default SidebarScroll

View File

@@ -0,0 +1,11 @@
import React from 'react'
const SidebarSeparate: React.FC<
React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>
> = (props) => {
const { className, ..._props } = props
return <div className={`separate ${className ? ` ${className}` : ''}`} {..._props} />
}
export default SidebarSeparate

View File

@@ -0,0 +1,22 @@
import React from 'react'
interface SidebarSubmenuProps extends React.PropsWithChildren {
submenuTop: number
submenuLeft: number
}
const SidebarSubmenu: React.FC<SidebarSubmenuProps> = (props) => {
return (
<ul
className={'submenu'}
style={{
top: props.submenuTop,
left: props.submenuLeft
}}
>
<div className={'content'}>{props.children}</div>
</ul>
)
}
export default SidebarSubmenu

View File

@@ -0,0 +1,44 @@
import React from 'react'
import Icon from '@ant-design/icons'
import '@/assets/css/components/common/sidebar.scss'
import SidebarSeparate from '@/components/common/sidebar/SidebarSeparate'
import SidebarFooter from '@/components/common/sidebar/SidebarFooter'
import { getLocalStorage, setLocalStorage } from '@/utils/common'
interface SidebarProps extends React.PropsWithChildren {
title: string
width?: string
onSidebarSwitch?: (hidden: boolean) => void
}
const Sidebar: React.FC<SidebarProps> = (props) => {
const [hideSidebar, setHideSidebar] = useState(getLocalStorage('hideSidebar') === 'false')
const switchSidebar = () => {
setHideSidebar(!hideSidebar)
setLocalStorage('hideSidebar', hideSidebar ? 'true' : 'false')
props.onSidebarSwitch && props.onSidebarSwitch(hideSidebar)
}
return (
<>
<div
className={`sidebar${hideSidebar ? ' hide' : ''}`}
style={{ width: props.width ?? 'clamp(180px, 20vw, 240px)' }}
>
<div className={'title'}>
<span className={'icon-box'} onClick={switchSidebar}>
<Icon component={IconFatwebExpand} />
</span>
<span className={'text'}>{props.title}</span>
</div>
<SidebarSeparate style={{ marginTop: 0 }} />
<div className={'content'}>{props.children}</div>
<SidebarSeparate style={{ marginTop: 0, marginBottom: 0 }} />
<SidebarFooter />
</div>
</>
)
}
export default Sidebar