Add Sidebar component
This commit is contained in:
64
src/components/common/sidebar/SidebarFooter.tsx
Normal file
64
src/components/common/sidebar/SidebarFooter.tsx
Normal 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
|
||||
60
src/components/common/sidebar/SidebarItem.tsx
Normal file
60
src/components/common/sidebar/SidebarItem.tsx
Normal 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
|
||||
7
src/components/common/sidebar/SidebarItemList.tsx
Normal file
7
src/components/common/sidebar/SidebarItemList.tsx
Normal file
@@ -0,0 +1,7 @@
|
||||
import React from 'react'
|
||||
|
||||
const SidebarItemList: React.FC<React.PropsWithChildren> = (props) => {
|
||||
return <ul>{props.children}</ul>
|
||||
}
|
||||
|
||||
export default SidebarItemList
|
||||
34
src/components/common/sidebar/SidebarScroll.tsx
Normal file
34
src/components/common/sidebar/SidebarScroll.tsx
Normal 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
|
||||
11
src/components/common/sidebar/SidebarSeparate.tsx
Normal file
11
src/components/common/sidebar/SidebarSeparate.tsx
Normal 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
|
||||
22
src/components/common/sidebar/SidebarSubmenu.tsx
Normal file
22
src/components/common/sidebar/SidebarSubmenu.tsx
Normal 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
|
||||
44
src/components/common/sidebar/index.tsx
Normal file
44
src/components/common/sidebar/index.tsx
Normal 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
|
||||
Reference in New Issue
Block a user