Feat(Theme): Support dark mode

This commit is contained in:
2024-10-23 10:17:45 +08:00
parent b7c3fb8524
commit dbce6b9cf2
180 changed files with 3478 additions and 3199 deletions

View File

@@ -1,16 +1,12 @@
import { DetailedHTMLProps, HTMLAttributes } from 'react'
import styles from '@/assets/css/components/common/card.module.less'
import useStyles from '@/assets/css/components/common/card.style'
type CardProps = DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>
const Card = forwardRef<HTMLDivElement, CardProps>(({ className, ...props }, ref) => {
return (
<div
className={`${styles.cardBox}${className ? ` ${className}` : ''}`}
{...props}
ref={ref}
/>
)
const { styles, cx } = useStyles()
return <div className={cx(styles.cardBox, className)} {...props} ref={ref} />
})
export default Card

View File

@@ -1,16 +1,20 @@
import { DetailedHTMLProps, HTMLAttributes } from 'react'
import styles from '@/assets/css/components/common/fit-center.module.less'
import useStyles from '@/assets/css/components/common/fit-center.style'
interface FitCenterProps extends DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement> {
vertical?: boolean
}
const FitCenter = ({ className, vertical, ...props }: FitCenterProps) => {
const { styles, cx } = useStyles()
return (
<div
className={`${styles.fitCenter}${className ? ` ${className}` : ''}${
className={cx(
styles.fitCenter,
className,
vertical ? ' flex-vertical' : ' flex-horizontal'
}`}
)}
{...props}
/>
)

View File

@@ -1,5 +1,5 @@
import { DetailedHTMLProps, HTMLAttributes } from 'react'
import styles from '@/assets/css/components/common/fit-fullscreen.module.less'
import useStyles from '@/assets/css/components/common/fit-fullscreen.style'
interface FitFullscreenProps
extends DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement> {
@@ -9,9 +9,11 @@ interface FitFullscreenProps
const FitFullscreen = forwardRef<HTMLDivElement, FitFullscreenProps>(
({ zIndex, backgroundColor, className, style, ...props }, ref) => {
const { styles, cx } = useStyles()
return (
<div
className={`${styles.fitFullscreen}${className ? ` ${className}` : ''}`}
className={cx(styles.fitFullscreen, className)}
style={{
zIndex,
backgroundColor,

View File

@@ -1,5 +1,5 @@
import { DetailedHTMLProps, HTMLAttributes } from 'react'
import styles from '@/assets/css/components/common/flex-box.module.less'
import useStyles from '@/assets/css/components/common/flex-box.style'
interface FlexBoxProps extends DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement> {
direction?: 'horizontal' | 'vertical'
@@ -8,11 +8,15 @@ interface FlexBoxProps extends DetailedHTMLProps<HTMLAttributes<HTMLDivElement>,
const FlexBox = forwardRef<HTMLDivElement, FlexBoxProps>(
({ className, direction, gap, style, ...props }, ref) => {
const { styles, cx } = useStyles()
return (
<div
className={`${styles.flexBox} ${
className={cx(
styles.flexBox,
className,
direction === 'horizontal' ? 'flex-horizontal' : 'flex-vertical'
}${className ? ` ${className}` : ''}`}
)}
style={{ gap, ...style }}
{...props}
ref={ref}

View File

@@ -1,14 +1,15 @@
import Icon from '@ant-design/icons'
import styles from '@/assets/css/components/common/fullscreen-loading-mask.module.less'
import { COLOR_FONT_MAIN } from '@/constants/common.constants'
import useStyles from '@/assets/css/components/common/fullscreen-loading-mask.style'
import FitFullscreen from '@/components/common/FitFullscreen'
const FullscreenLoadingMask = () => {
const { styles, theme } = useStyles()
const loadingIcon = (
<>
<Icon
component={IconOxygenLoading}
style={{ fontSize: 24, color: COLOR_FONT_MAIN }}
style={{ fontSize: 24, color: theme.colorText }}
spin
/>
</>

View File

@@ -6,7 +6,7 @@ import {
HTMLAttributes,
UIEvent
} from 'react'
import styles from '@/assets/css/components/common/hide-scrollbar.module.less'
import useStyles from '@/assets/css/components/common/hide-scrollbar.style'
interface HideScrollbarProps
extends DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement> {
@@ -82,6 +82,8 @@ const HideScrollbar = forwardRef<HideScrollbarElement, HideScrollbarProps>(
},
ref
) => {
const { styles, cx } = useStyles()
useImperativeHandle<HideScrollbarElement, HideScrollbarElement>(ref, () => {
return {
scrollTo(x, y, smooth?: boolean) {
@@ -538,7 +540,7 @@ const HideScrollbar = forwardRef<HideScrollbarElement, HideScrollbarProps>(
>
<div
ref={rootRef}
className={`${styles.hideScrollbarSelection}${className ? ` ${className}` : ''}`}
className={cx(styles.hideScrollbarSelection, className)}
tabIndex={0}
style={{
width: `calc(${maskRef.current?.clientWidth}px + ${verticalScrollbarWidth}px)`,
@@ -566,9 +568,11 @@ const HideScrollbar = forwardRef<HideScrollbarElement, HideScrollbarProps>(
{isShowVerticalScrollbar &&
(!isHiddenVerticalScrollbarWhenFull || verticalScrollbarLength < 100) && (
<div
className={`${styles.scrollbar} ${styles.verticalScrollbar}${
className={cx(
styles.scrollbar,
styles.verticalScrollbar,
isVerticalScrollbarAutoHide ? ` ${styles.hide}` : ''
}`}
)}
style={{
height: maskRef.current
? maskRef.current?.clientHeight - 1
@@ -582,9 +586,12 @@ const HideScrollbar = forwardRef<HideScrollbarElement, HideScrollbarProps>(
padding: `${scrollbarAsidePadding}px ${scrollbarEdgePadding}px`
}}
>
<div className={styles.box} style={{ width: scrollbarWidth }}>
<div
className={styles.scrollbarBox}
style={{ width: scrollbarWidth }}
>
<div
className={styles.block}
className={styles.scrollbarBoxBlock}
style={{
height: `${verticalScrollbarLength}%`,
top: `clamp(0px, ${verticalScrollbarPosition}%, ${
@@ -609,9 +616,11 @@ const HideScrollbar = forwardRef<HideScrollbarElement, HideScrollbarProps>(
(!isHiddenHorizontalScrollbarWhenFull ||
horizontalScrollbarLength < 100) && (
<div
className={`${styles.scrollbar} ${styles.horizontalScrollbar}${
className={cx(
styles.scrollbar,
styles.horizontalScrollbar,
isHorizontalScrollbarAutoHide ? ` ${styles.hide}` : ''
}`}
)}
style={{
width: maskRef.current
? maskRef.current?.clientWidth - 1
@@ -625,9 +634,12 @@ const HideScrollbar = forwardRef<HideScrollbarElement, HideScrollbarProps>(
padding: `${scrollbarEdgePadding}px ${scrollbarAsidePadding}px`
}}
>
<div className={styles.box} style={{ height: scrollbarWidth }}>
<div
className={styles.scrollbarBox}
style={{ height: scrollbarWidth }}
>
<div
className={styles.block}
className={styles.scrollbarBoxBlock}
style={{
width: `${horizontalScrollbarLength}%`,
left: `clamp(0px, ${horizontalScrollbarPosition}%, ${

View File

@@ -1,36 +0,0 @@
import _ from 'lodash'
import styles from '@/assets/css/components/common/indicator.module.less'
interface IndicatorProps {
total: number
current: number
onSwitch?: (index: number) => void
}
const Indicator = ({ total, current, onSwitch }: IndicatorProps) => {
const handleClick = (index: number) => {
return () => {
onSwitch?.(index)
}
}
return (
<>
<ul className={`${styles.dotList} flex-vertical`}>
{_.range(0, total).map((_value, index) => {
return (
<li
key={index}
className={`${styles.item} center-box${index === current ? ` ${styles.active}` : ''}`}
onClick={handleClick(index)}
>
<div className={styles.dot} />
</li>
)
})}
</ul>
</>
)
}
export default Indicator

View File

@@ -1,7 +1,6 @@
import { PropsWithChildren, ReactNode } from 'react'
import Icon from '@ant-design/icons'
import styles from '@/assets/css/components/common/loading-mask.module.less'
import { COLOR_FONT_MAIN } from '@/constants/common.constants'
import useStyles from '@/assets/css/components/common/loading-mask.style'
interface LoadingMaskProps extends PropsWithChildren {
hidden?: boolean
@@ -9,11 +8,13 @@ interface LoadingMaskProps extends PropsWithChildren {
}
const LoadingMask = (props: LoadingMaskProps) => {
const { styles, theme } = useStyles()
const loadingIcon = (
<>
<Icon
component={IconOxygenLoading}
style={{ fontSize: 24, color: COLOR_FONT_MAIN }}
style={{ fontSize: 24, color: theme.colorText }}
spin
/>
</>

View File

@@ -1,12 +1,14 @@
import Icon from '@ant-design/icons'
import styles from '@/assets/css/components/common/sidebar.module.less'
import { COLOR_ERROR } from '@/constants/common.constants'
import useStyles from '@/assets/css/components/common/sidebar/footer.style'
import { SidebarContext } from '@/components/common/Sidebar/index'
import { getRedirectUrl } from '@/util/route'
import { getAvatar, getLoginStatus, getNickname, removeToken } from '@/util/auth'
import { navigateToLogin, navigateToUser } from '@/util/navigation'
import { r_auth_logout } from '@/services/auth'
const Footer = () => {
const { styles, theme, cx } = useStyles()
const { isCollapse } = useContext(SidebarContext)
const matches = useMatches()
const lastMatch = matches.reduce((_, second) => second)
const location = useLocation()
@@ -33,7 +35,7 @@ const Footer = () => {
removeToken()
notification.info({
message: '已退出登录',
icon: <Icon component={IconOxygenExit} style={{ color: COLOR_ERROR }} />
icon: <Icon component={IconOxygenExit} style={{ color: theme.colorErrorText }} />
})
setTimeout(() => {
window.location.reload()
@@ -58,7 +60,7 @@ const Footer = () => {
return (
<div className={styles.footer}>
<span
className={styles.iconUser}
className={styles.icon}
onClick={handleClickAvatar}
title={getLoginStatus() ? '个人中心' : '登录'}
>
@@ -74,17 +76,29 @@ const Footer = () => {
</NavLink>
</span>
<span hidden={!getLoginStatus()} className={styles.text} title={nickname}>
<span
hidden={!getLoginStatus()}
className={cx(styles.text, isCollapse ? styles.collapsedExit : '')}
title={nickname}
>
{nickname}
</span>
<div
hidden={!getLoginStatus()}
className={`${styles.submenuExit}${!getLoginStatus() ? ` ${styles.hide}` : ''}`}
className={cx(
isCollapse ? styles.collapsedExit : '',
!getLoginStatus() ? styles.hide : ''
)}
>
<div className={styles.content}>
<div
className={cx(
styles.exitContent,
isCollapse ? styles.collapsedExitContent : ''
)}
>
<span
hidden={!getLoginStatus()}
className={styles.iconExit}
className={cx(styles.exitIcon, isCollapse ? styles.collapsedExitIcon : '')}
onClick={handleLogout}
>
<Icon

View File

@@ -1,7 +1,10 @@
import { ReactNode, MouseEvent } from 'react'
import Icon from '@ant-design/icons'
import styles from '@/assets/css/components/common/sidebar.module.less'
import Submenu from '@/components/common/Sidebar/Submenu'
import useStyles from '@/assets/css/components/common/sidebar/item.style'
import { SidebarContext } from '@/components/common/Sidebar/index'
import Submenu, { SubmenuContext } from '@/components/common/Sidebar/Submenu'
export const ItemContext = createContext({ isHover: false })
type ItemProps = {
icon?: IconComponent | string
@@ -13,6 +16,10 @@ type ItemProps = {
}
const Item = (props: ItemProps) => {
const { styles, cx } = useStyles()
const { isCollapse } = useContext(SidebarContext)
const { isInSubmenu } = useContext(SubmenuContext)
const [isHover, setIsHover] = useState(false)
const [submenuTop, setSubmenuTop] = useState(Number.MAX_VALUE)
const [submenuLeft, setSubmenuLeft] = useState(Number.MAX_VALUE)
@@ -34,34 +41,52 @@ const Item = (props: ItemProps) => {
}
return (
<li className={styles.item}>
<div className={styles.menuBt} onMouseEnter={showSubmenu}>
<NavLink
end={props.end}
to={props.path}
className={({ isActive, isPending }) =>
isPending ? 'pending' : isActive ? `${styles.active}` : ''
}
>
{props.icon && (
<div className={styles.iconBox}>
{typeof props.icon === 'string' ? (
<img src={`data:image/svg+xml;base64,${props.icon}`} alt={'icon'} />
) : (
<Icon component={props.icon} />
<ItemContext.Provider value={{ isHover }}>
<li
className={cx(styles.item, isInSubmenu ? styles.submenuItem : '')}
onMouseOver={() => setIsHover(true)}
onMouseLeave={() => setIsHover(false)}
>
<div className={cx(styles.menuBt, 'dnd-delete-mask')} onMouseEnter={showSubmenu}>
<NavLink
end={props.end}
to={props.path}
className={({ isActive, isPending }) =>
isPending ? 'pending' : isActive ? `${styles.active}` : ''
}
>
{props.icon && (
<div className={styles.icon}>
{typeof props.icon === 'string' ? (
<img
src={`data:image/svg+xml;base64,${props.icon}`}
alt={'icon'}
/>
) : (
<Icon component={props.icon} />
)}
</div>
)}
<span
className={cx(
styles.text,
isCollapse && !isInSubmenu ? styles.collapsedText : ''
)}
>
{props.text}
</span>
<div className={isCollapse ? styles.collapsedExtend : ''}>
{props.extend}
</div>
)}
<span className={styles.text}>{props.text}</span>
<div className={styles.extend}>{props.extend}</div>
</NavLink>
</div>
{props.children && (
<Submenu submenuTop={submenuTop} submenuLeft={submenuLeft}>
{props.children}
</Submenu>
)}
</li>
</NavLink>
</div>
{props.children && (
<Submenu submenuTop={submenuTop} submenuLeft={submenuLeft}>
{props.children}
</Submenu>
)}
</li>
</ItemContext.Provider>
)
}

View File

@@ -1,8 +1,10 @@
import { PropsWithChildren } from 'react'
import styles from '@/assets/css/components/common/sidebar.module.less'
import useStyles from '@/assets/css/components/common/sidebar/scroll.style'
import HideScrollbar from '@/components/common/HideScrollbar'
const Scroll = (props: PropsWithChildren) => {
const { styles } = useStyles()
return (
<div className={styles.scroll}>
<HideScrollbar

View File

@@ -1,11 +1,13 @@
import { DetailedHTMLProps, HTMLAttributes } from 'react'
import styles from '@/assets/css/components/common/sidebar.module.less'
import useStyles from '@/assets/css/components/common/sidebar/separate'
const Separate = ({
className,
...props
}: DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>) => {
return <div className={`${styles.separate}${className ? ` ${className}` : ''}`} {...props} />
const { styles, cx } = useStyles()
return <div className={cx(styles.separate, className)} {...props} />
}
export default Separate

View File

@@ -1,5 +1,8 @@
import { PropsWithChildren } from 'react'
import styles from '@/assets/css/components/common/sidebar.module.less'
import useStyles from '@/assets/css/components/common/sidebar/submenu.style'
import { ItemContext } from '@/components/common/Sidebar/Item'
export const SubmenuContext = createContext({ isInSubmenu: false })
interface SidebarSubmenuProps extends PropsWithChildren {
submenuTop: number
@@ -7,16 +10,21 @@ interface SidebarSubmenuProps extends PropsWithChildren {
}
const Submenu = (props: SidebarSubmenuProps) => {
const { styles, cx } = useStyles()
const { isHover } = useContext(ItemContext)
return (
<ul
className={styles.submenu}
style={{
top: props.submenuTop,
left: props.submenuLeft
}}
>
<div className={styles.content}>{props.children}</div>
</ul>
<SubmenuContext.Provider value={{ isInSubmenu: true }}>
<ul
className={cx(styles.submenu, isHover ? styles.hoveredSubmenu : '')}
style={{
top: props.submenuTop,
left: props.submenuLeft - 1
}}
>
<div className={styles.content}>{props.children}</div>
</ul>
</SubmenuContext.Provider>
)
}

View File

@@ -1,6 +1,6 @@
import { PropsWithChildren, ReactNode } from 'react'
import Icon from '@ant-design/icons'
import styles from '@/assets/css/components/common/sidebar.module.less'
import useStyles from '@/assets/css/components/common/sidebar/index.style'
import { getLocalStorage, setLocalStorage } from '@/util/browser'
import Item from '@/components/common/Sidebar/Item'
import ItemList from '@/components/common/Sidebar/ItemList'
@@ -9,6 +9,8 @@ import Separate from '@/components/common/Sidebar/Separate'
import Submenu from '@/components/common/Sidebar/Submenu'
import Footer from '@/components/common/Sidebar/Footer'
export const SidebarContext = createContext({ isCollapse: false })
interface SidebarProps extends PropsWithChildren {
title: string
width?: string
@@ -17,35 +19,38 @@ interface SidebarProps extends PropsWithChildren {
}
const Sidebar = (props: SidebarProps) => {
const [isHideSidebar, setIsHideSidebar] = useState(getLocalStorage('HIDE_SIDEBAR') === 'true')
const { styles, cx } = useStyles()
const [isCollapseSidebar, setIsCollapseSidebar] = useState(
getLocalStorage('COLLAPSE_SIDEBAR') === 'true'
)
const switchSidebar = () => {
setLocalStorage('HIDE_SIDEBAR', !isHideSidebar ? 'true' : 'false')
setIsHideSidebar(!isHideSidebar)
props.onSidebarSwitch?.(isHideSidebar)
setLocalStorage('COLLAPSE_SIDEBAR', !isCollapseSidebar ? 'true' : 'false')
setIsCollapseSidebar(!isCollapseSidebar)
props.onSidebarSwitch?.(isCollapseSidebar)
}
return (
<>
<SidebarContext.Provider value={{ isCollapse: isCollapseSidebar }}>
<div
className={`${styles.sidebar}${isHideSidebar ? ` ${styles.hide}` : ''}`}
className={cx(styles.sidebar, isCollapseSidebar ? styles.collapse : '')}
style={{ width: props.width ?? 'clamp(180px, 20vw, 240px)' }}
>
<div className={styles.title}>
<span className={styles.iconBox} onClick={switchSidebar}>
<span className={styles.titleIcon} onClick={switchSidebar}>
<Icon component={IconOxygenExpand} />
</span>
<span className={styles.text}>{props.title}</span>
<span className={styles.titleText}>{props.title}</span>
</div>
<Separate style={{ marginTop: 0 }} />
<div className={styles.content}>{props.children}</div>
<div className={styles.bottomFixed} style={{ flex: 'none' }}>
<div className={styles.content} style={{ flex: 'none' }}>
{props.bottomFixed}
</div>
<Separate style={{ marginTop: 0, marginBottom: 0 }} />
<Footer />
</div>
</>
</SidebarContext.Provider>
)
}

View File

@@ -1,7 +1,7 @@
import { DetailedHTMLProps, HTMLAttributes, ReactNode } from 'react'
import Icon from '@ant-design/icons'
import VanillaTilt, { TiltOptions } from 'vanilla-tilt'
import styles from '@/assets/css/components/common/url-card.module.less'
import useStyles from '@/assets/css/components/common/url-card.style'
import Card from '@/components/common/Card'
import FlexBox from '@/components/common/FlexBox'
@@ -26,6 +26,7 @@ const UrlCard = ({
children,
...props
}: UrlCardProps) => {
const { styles } = useStyles()
const navigate = useNavigate()
const cardRef = useRef<HTMLDivElement>(null)
@@ -39,13 +40,12 @@ const UrlCard = ({
return (
<Card
className={styles.root}
style={{ overflow: 'visible', ...style }}
{...props}
ref={cardRef}
onClick={handleCardOnClick}
>
<FlexBox className={styles.urlCard}>
<FlexBox className={styles.root}>
<Icon component={icon} className={styles.icon} />
<div className={styles.text}>{children}</div>
<div>{description}</div>