切换主题预览方案调整
@@ -1,11 +1,11 @@
|
||||
import { useGlobal } from '@/lib/global'
|
||||
import { Moon, Sun } from './HeroIcons'
|
||||
import { useImperativeHandle } from 'react'
|
||||
import { Moon, Sun } from './HeroIcons'
|
||||
|
||||
/**
|
||||
* 深色模式按钮
|
||||
*/
|
||||
const DarkModeButton = (props) => {
|
||||
const DarkModeButton = props => {
|
||||
const { cRef, className } = props
|
||||
const { isDarkMode, toggleDarkMode } = useGlobal()
|
||||
|
||||
@@ -20,8 +20,17 @@ const DarkModeButton = (props) => {
|
||||
}
|
||||
})
|
||||
|
||||
return <div onClick={toggleDarkMode} className={`${className || ''} flex justify-center dark:text-gray-200 text-gray-800`}>
|
||||
<div id='darkModeButton' className=' hover:scale-110 cursor-pointer transform duration-200 w-5 h-5'> {isDarkMode ? <Sun /> : <Moon />}</div>
|
||||
return (
|
||||
<div
|
||||
onClick={toggleDarkMode}
|
||||
className={`${className || ''} flex justify-center dark:text-gray-200 text-gray-800`}>
|
||||
<div
|
||||
id='darkModeButton'
|
||||
className=' hover:scale-110 cursor-pointer transform duration-200 w-5 h-5'>
|
||||
{' '}
|
||||
{isDarkMode ? <Sun /> : <Moon />}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
export default DarkModeButton
|
||||
|
||||
@@ -2,9 +2,11 @@ import { useEffect, useRef, useState } from 'react'
|
||||
|
||||
/**
|
||||
* 可拖拽组件
|
||||
* @param {children} 渲染的子元素
|
||||
* @param {stick} 是否要吸附
|
||||
* @returns
|
||||
*/
|
||||
export const Draggable = props => {
|
||||
const { children, stick } = props
|
||||
export const Draggable = ({ children, stick }) => {
|
||||
const draggableRef = useRef(null)
|
||||
const rafRef = useRef(null)
|
||||
const [moving, setMoving] = useState(false)
|
||||
@@ -13,75 +15,70 @@ export const Draggable = props => {
|
||||
useEffect(() => {
|
||||
const draggableElements = document.getElementsByClassName('draggable')
|
||||
|
||||
// 标准化鼠标事件对象
|
||||
function e(event) {
|
||||
// 定义事件对象标准化函数
|
||||
if (!event) {
|
||||
// 兼容IE浏览器
|
||||
event = window.event
|
||||
event.target = event.srcElement
|
||||
event.layerX = event.offsetX
|
||||
event.layerY = event.offsetY
|
||||
}
|
||||
// 移动端
|
||||
if (event.type === 'touchstart' || event.type === 'touchmove') {
|
||||
event.clientX = event.touches[0].clientX
|
||||
event.clientY = event.touches[0].clientY
|
||||
}
|
||||
|
||||
event.mx = event.pageX || event.clientX + document.body.scrollLeft
|
||||
// 计算鼠标指针的x轴距离
|
||||
event.my = event.pageY || event.clientY + document.body.scrollTop
|
||||
// 计算鼠标指针的y轴距离
|
||||
|
||||
return event // 返回标准化的事件对象
|
||||
return event
|
||||
}
|
||||
|
||||
// 定义鼠标事件处理函数
|
||||
// document.pointerdown = start
|
||||
document.onmousedown = start
|
||||
document.ontouchstart = start
|
||||
|
||||
function start(event) {
|
||||
// 按下鼠标时,初始化处理
|
||||
if (!draggableElements) return
|
||||
event = e(event) // 获取标准事件对象
|
||||
event = e(event)
|
||||
|
||||
for (const drag of draggableElements) {
|
||||
// 判断鼠标点击的区域是否是拖拽框内
|
||||
if (inDragBox(event, drag)) {
|
||||
currentObj = drag.firstElementChild
|
||||
}
|
||||
}
|
||||
if (currentObj) {
|
||||
if (event.type === 'touchstart') {
|
||||
event.preventDefault() // 阻止默认的滚动行为
|
||||
document.documentElement.style.overflow = 'hidden' // 防止页面一起滚动
|
||||
event.preventDefault()
|
||||
document.documentElement.style.overflow = 'hidden'
|
||||
}
|
||||
|
||||
setMoving(true)
|
||||
offsetX = event.mx - currentObj.offsetLeft
|
||||
offsetY = event.my - currentObj.offsetTop
|
||||
|
||||
document.onmousemove = move // 注册鼠标移动事件处理函数
|
||||
document.onmousemove = move
|
||||
document.ontouchmove = move
|
||||
document.onmouseup = stop // 注册松开鼠标事件处理函数
|
||||
document.onmouseup = stop
|
||||
document.ontouchend = stop
|
||||
}
|
||||
}
|
||||
|
||||
function move(event) {
|
||||
// 鼠标移动处理函数
|
||||
event = e(event)
|
||||
rafRef.current = requestAnimationFrame(() => updatePosition(event))
|
||||
}
|
||||
|
||||
const stop = event => {
|
||||
event = e(event)
|
||||
document.documentElement.style.overflow = 'auto' // 恢复默认的滚动行为
|
||||
document.documentElement.style.overflow = 'auto'
|
||||
cancelAnimationFrame(rafRef.current)
|
||||
setMoving(false)
|
||||
currentObj = document.ontouchmove = document.ontouchend = document.onmousemove = document.onmouseup = null
|
||||
if (stick) {
|
||||
checkInWindow() // 吸附逻辑
|
||||
}
|
||||
currentObj =
|
||||
document.ontouchmove =
|
||||
document.ontouchend =
|
||||
document.onmousemove =
|
||||
document.onmouseup =
|
||||
null
|
||||
}
|
||||
|
||||
const updatePosition = event => {
|
||||
@@ -90,51 +87,41 @@ export const Draggable = props => {
|
||||
const top = event.my - offsetY
|
||||
currentObj.style.left = left + 'px'
|
||||
currentObj.style.top = top + 'px'
|
||||
checkInWindow()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 鼠标是否在可拖拽区域内
|
||||
* @param {*} event
|
||||
* @returns
|
||||
*/
|
||||
function inDragBox(event, drag) {
|
||||
const { clientX, clientY } = event // 鼠标位置
|
||||
const { offsetHeight, offsetWidth, offsetTop, offsetLeft } = drag.firstElementChild // 窗口位置
|
||||
const horizontal = clientX > offsetLeft && clientX < offsetLeft + offsetWidth
|
||||
const { clientX, clientY } = event
|
||||
const { offsetHeight, offsetWidth, offsetTop, offsetLeft } =
|
||||
drag.firstElementChild
|
||||
const horizontal =
|
||||
clientX > offsetLeft && clientX < offsetLeft + offsetWidth
|
||||
const vertical = clientY > offsetTop && clientY < offsetTop + offsetHeight
|
||||
|
||||
if (horizontal && vertical) {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
return horizontal && vertical
|
||||
}
|
||||
|
||||
/**
|
||||
* 若超出窗口则吸附。
|
||||
*/
|
||||
function checkInWindow() {
|
||||
// 检查是否悬浮在窗口内
|
||||
for (const drag of draggableElements) {
|
||||
// 判断鼠标点击的区域是否是拖拽框内
|
||||
const { offsetHeight, offsetWidth, offsetTop, offsetLeft } = drag.firstElementChild
|
||||
const { offsetHeight, offsetWidth, offsetTop, offsetLeft } =
|
||||
drag.firstElementChild
|
||||
const { clientHeight, clientWidth } = document.documentElement
|
||||
if (offsetTop < 0) {
|
||||
drag.firstElementChild.style.top = 0
|
||||
drag.firstElementChild.style.top = '0px'
|
||||
}
|
||||
if (offsetTop > clientHeight - offsetHeight) {
|
||||
drag.firstElementChild.style.top = clientHeight - offsetHeight + 'px'
|
||||
}
|
||||
if (offsetLeft < 0) {
|
||||
drag.firstElementChild.style.left = 0
|
||||
drag.firstElementChild.style.left = '0px'
|
||||
}
|
||||
if (offsetLeft > clientWidth - offsetWidth) {
|
||||
drag.firstElementChild.style.left = clientWidth - offsetWidth + 'px'
|
||||
}
|
||||
if (stick === 'left') {
|
||||
drag.firstElementChild.style.left = 0 + 'px'
|
||||
drag.firstElementChild.style.left = '0px'
|
||||
} else if (stick === 'right') {
|
||||
drag.firstElementChild.style.left = clientWidth - offsetWidth + 'px'
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -142,15 +129,15 @@ export const Draggable = props => {
|
||||
window.addEventListener('resize', checkInWindow)
|
||||
|
||||
return () => {
|
||||
return () => {
|
||||
window.removeEventListener('resize', checkInWindow)
|
||||
cancelAnimationFrame(rafRef.current)
|
||||
}
|
||||
window.removeEventListener('resize', checkInWindow)
|
||||
cancelAnimationFrame(rafRef.current)
|
||||
}
|
||||
}, [])
|
||||
}, [stick])
|
||||
|
||||
return (
|
||||
<div className={`draggable ${moving ? 'cursor-grabbing' : 'cursor-grab'} select-none`} ref={draggableRef}>
|
||||
<div
|
||||
className={`draggable ${moving ? 'cursor-grabbing' : 'cursor-grab'} select-none`}
|
||||
ref={draggableRef}>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -6,7 +6,14 @@ import { useEffect } from 'react'
|
||||
* @returns {JSX.Element}
|
||||
* @constructor
|
||||
*/
|
||||
const SideBarDrawer = ({ children, isOpen, onOpen, onClose, className }) => {
|
||||
const SideBarDrawer = ({
|
||||
children,
|
||||
isOpen,
|
||||
onOpen,
|
||||
onClose,
|
||||
className,
|
||||
showOnPC = false
|
||||
}) => {
|
||||
const router = useRouter()
|
||||
useEffect(() => {
|
||||
const sideBarDrawerRouteListener = () => {
|
||||
@@ -19,32 +26,45 @@ const SideBarDrawer = ({ children, isOpen, onOpen, onClose, className }) => {
|
||||
}, [router.events])
|
||||
|
||||
// 点击按钮更改侧边抽屉状态
|
||||
const switchSideDrawerVisible = (showStatus) => {
|
||||
const switchSideDrawerVisible = showStatus => {
|
||||
if (showStatus) {
|
||||
onOpen && onOpen()
|
||||
} else {
|
||||
onClose && onClose()
|
||||
}
|
||||
const sideBarDrawer = window.document.getElementById('sidebar-drawer')
|
||||
const sideBarDrawerBackground = window.document.getElementById('sidebar-drawer-background')
|
||||
const sideBarDrawerBackground = window.document.getElementById(
|
||||
'sidebar-drawer-background'
|
||||
)
|
||||
|
||||
if (showStatus) {
|
||||
sideBarDrawer?.classList.replace('-ml-60', 'ml-0')
|
||||
sideBarDrawer?.classList.replace('-ml-96', 'ml-0')
|
||||
sideBarDrawerBackground?.classList.replace('hidden', 'block')
|
||||
} else {
|
||||
sideBarDrawer?.classList.replace('ml-0', '-ml-60')
|
||||
sideBarDrawer?.classList.replace('ml-0', '-ml-96')
|
||||
sideBarDrawerBackground?.classList.replace('block', 'hidden')
|
||||
}
|
||||
}
|
||||
|
||||
return <div id='sidebar-wrapper' className={' block lg:hidden top-0 ' + className }>
|
||||
<div id="sidebar-drawer" className={`${isOpen ? 'ml-0 w-60 visible' : '-ml-60 max-w-side invisible'} bg-white dark:bg-gray-900 shadow-black shadow-lg flex flex-col duration-300 fixed h-full left-0 overflow-y-scroll scroll-hidden top-0 z-30`}>
|
||||
{children}
|
||||
</div>
|
||||
return (
|
||||
<div
|
||||
id='sidebar-wrapper'
|
||||
className={` block ${showOnPC ? '' : 'lg:hidden'} top-0`}>
|
||||
<div
|
||||
id='sidebar-drawer'
|
||||
className={`${className} ${isOpen ? 'ml-0 w-96 visible' : '-ml-96 max-w-side invisible'} bg-white dark:bg-gray-900 flex flex-col duration-300 fixed h-full left-0 overflow-y-scroll scroll-hidden top-0 z-30`}>
|
||||
{children}
|
||||
</div>
|
||||
|
||||
{/* 背景蒙版 */}
|
||||
<div id='sidebar-drawer-background' onClick={() => { switchSideDrawerVisible(false) }}
|
||||
className={`${isOpen ? 'block' : 'hidden'} animate__animated animate__fadeIn fixed top-0 duration-300 left-0 z-20 w-full h-full bg-black/70`}/>
|
||||
</div>
|
||||
{/* 背景蒙版 */}
|
||||
<div
|
||||
id='sidebar-drawer-background'
|
||||
onClick={() => {
|
||||
switchSideDrawerVisible(false)
|
||||
}}
|
||||
className={`${isOpen ? 'block' : 'hidden'} animate__animated animate__fadeIn fixed top-0 duration-300 left-0 z-20 w-full h-full bg-black/70`}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
export default SideBarDrawer
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import { useGlobal } from '@/lib/global'
|
||||
import LANGS from '@/lib/lang'
|
||||
import { getQueryParam } from '@/lib/utils'
|
||||
import { THEMES } from '@/themes/theme'
|
||||
import { useRouter } from 'next/router'
|
||||
import { useState } from 'react'
|
||||
import DarkModeButton from './DarkModeButton'
|
||||
import { Draggable } from './Draggable'
|
||||
import LazyImage from './LazyImage'
|
||||
import SideBarDrawer from './SideBarDrawer'
|
||||
/**
|
||||
*
|
||||
* @returns 主题切换
|
||||
@@ -15,26 +16,12 @@ const ThemeSwitch = () => {
|
||||
useGlobal()
|
||||
const router = useRouter()
|
||||
const currentTheme = getQueryParam(router.asPath, 'theme') || theme
|
||||
// const currentLang = getQueryParam(router.asPath, 'lang') || lang
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
const [sideBarVisible, setSideBarVisible] = useState(false)
|
||||
|
||||
// 修改当前路径url中的 theme 参数
|
||||
// 例如 http://localhost?theme=hexo 跳转到 http://localhost?theme=newTheme
|
||||
const onThemeSelectChange = e => {
|
||||
document.ontouchmove =
|
||||
document.ontouchend =
|
||||
document.onmousemove =
|
||||
document.onmouseup =
|
||||
null
|
||||
setIsLoading(true)
|
||||
const newTheme = e.target.value
|
||||
const changeTheme = newTheme => {
|
||||
const query = router.query
|
||||
query.theme = newTheme
|
||||
router.push({ pathname: router.pathname, query }).then(() => {
|
||||
setTimeout(() => {
|
||||
setIsLoading(false)
|
||||
}, 500)
|
||||
})
|
||||
router.push({ pathname: router.pathname, query }).then(() => {})
|
||||
}
|
||||
|
||||
const onLangSelectChange = e => {
|
||||
@@ -49,80 +36,92 @@ const ThemeSwitch = () => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<Draggable>
|
||||
{/* 悬浮的主题切换按钮 */}
|
||||
<Draggable stick={true}>
|
||||
<div
|
||||
id='draggableBox'
|
||||
style={{ left: '0px', top: '80vh' }}
|
||||
className='fixed group flex flex-col items-start space-y-2 overflow-hidden z-50 p-3
|
||||
className='fixed group flex flex-col items-start space-y-2 overflow-hidden z-20 p-3
|
||||
dark:text-white bg-white dark:bg-black
|
||||
rounded-xl shadow-lg hover:scale-105 hover:shadow-2xl '>
|
||||
{/* 主题切换按钮 */}
|
||||
<div className='text-sm flex items-center group-hover:w-32 duration-200'>
|
||||
<div className='text-sm flex items-center group-hover:w-44 h-4 text-center duration-200'>
|
||||
<i className='fa-solid fa-palette w-5' />
|
||||
<div className='w-0 group-hover:w-24 duration-200 overflow-hidden'>
|
||||
<div className='w-0 group-hover:w-32 duration-200 overflow-hidden'>
|
||||
<label htmlFor='themeSelect' className='sr-only'>
|
||||
{locale.COMMON.THEME}
|
||||
</label>
|
||||
<select
|
||||
id='themeSelect'
|
||||
value={currentTheme}
|
||||
onChange={onThemeSelectChange}
|
||||
name='themes'
|
||||
className='pl-1 bg-gray-50 dark:bg-black appearance-none outline-none dark:text-white uppercase cursor-pointer'>
|
||||
{THEMES?.map(t => {
|
||||
return (
|
||||
<option key={t} value={t}>
|
||||
{t}
|
||||
</option>
|
||||
)
|
||||
})}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 深色按钮 */}
|
||||
<div className='text-sm flex items-center w-0 group-hover:w-32 duration-200'>
|
||||
<DarkModeButton />
|
||||
<div
|
||||
onClick={toggleDarkMode}
|
||||
className='cursor-pointer w-0 group-hover:w-24 duration-200 overflow-hidden whitespace-nowrap pl-1 h-auto'>
|
||||
{isDarkMode ? locale.MENU.DARK_MODE : locale.MENU.LIGHT_MODE}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 翻译按钮 */}
|
||||
<div className='text-sm flex items-center group-hover:w-32 duration-200'>
|
||||
<i className='fa-solid fa-language w-5' />
|
||||
<div className='w-0 group-hover:w-24 duration-200 overflow-hidden'>
|
||||
<label htmlFor='langSelect' className='sr-only'>
|
||||
Language Select
|
||||
</label>
|
||||
<select
|
||||
id='langSelect'
|
||||
value={lang}
|
||||
onChange={onLangSelectChange}
|
||||
name='themes'
|
||||
className='pl-1 bg-gray-50 dark:bg-black appearance-none outline-none dark:text-white uppercase cursor-pointer'>
|
||||
{Object.keys(LANGS)?.map(t => {
|
||||
return (
|
||||
<option key={t} value={t}>
|
||||
{LANGS[t].LOCALE}
|
||||
</option>
|
||||
)
|
||||
})}
|
||||
</select>
|
||||
{/* 点击弹出主题切换面板 */}
|
||||
<div
|
||||
onClick={() => {
|
||||
setSideBarVisible(true)
|
||||
}}
|
||||
className='uppercase cursor-pointer'
|
||||
title='Click To Switch Theme'
|
||||
alt='Click To Switch Theme'>
|
||||
{currentTheme}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Draggable>
|
||||
|
||||
{/* 切换主题加载时的全屏遮罩 */}
|
||||
<div
|
||||
className={`${isLoading ? 'opacity-90 ' : 'opacity-0'}
|
||||
w-screen h-screen glassmorphism bg-black text-white shadow-text flex justify-center items-center
|
||||
fixed top-0 left-0 pointer-events-none duration-1000 z-50 shadow-inner`}>
|
||||
<i className='text-3xl mr-5 fas fa-spinner animate-spin' />
|
||||
</div>
|
||||
<SideBarDrawer
|
||||
className='p-10'
|
||||
isOpen={sideBarVisible}
|
||||
showOnPC={true}
|
||||
onClose={() => {
|
||||
setSideBarVisible(false)
|
||||
}}>
|
||||
{/* 开关 */}
|
||||
<div className='flex items-center justify-between font-bold dark:text-white'>
|
||||
{/* 深色模式切换 */}
|
||||
<div className='border text-sm flex items-center w-32 duration-200 hover:bg-green-500 p-2'>
|
||||
<DarkModeButton />
|
||||
<div
|
||||
onClick={toggleDarkMode}
|
||||
className='cursor-pointer w-24 duration-200 overflow-hidden whitespace-nowrap pl-1 h-auto'>
|
||||
{isDarkMode ? locale.MENU.DARK_MODE : locale.MENU.LIGHT_MODE}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 关闭 */}
|
||||
<div
|
||||
className='hover:bg-green-500 px-2 py-1 duration-200 cursor-pointer'
|
||||
onClick={() => {
|
||||
setSideBarVisible(false)
|
||||
}}>
|
||||
<i className='fas fa-times' />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr className='my-4' />
|
||||
|
||||
<div>点击下方主题进行切换.</div>
|
||||
<div> Click below to switch the theme.</div>
|
||||
|
||||
{/* 陈列所有主题 */}
|
||||
<div>
|
||||
{THEMES?.map(t => {
|
||||
return (
|
||||
<div
|
||||
className='my-6'
|
||||
key={t}
|
||||
onClick={() => {
|
||||
changeTheme(t)
|
||||
}}>
|
||||
<div className='text-lg dark:text-white font-bold uppercase mb-4'>
|
||||
{t}
|
||||
</div>
|
||||
<LazyImage
|
||||
src={`/images/themes-preview/${t}.png`}
|
||||
className='cursor-pointer shadow-lg rounded-xl hover:scale-110 duration-200'
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</SideBarDrawer>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
BIN
public/images/themes-preview/commerce.png
Normal file
|
After Width: | Height: | Size: 1020 KiB |
BIN
public/images/themes-preview/example.png
Normal file
|
After Width: | Height: | Size: 344 KiB |
BIN
public/images/themes-preview/fukasawa.png
Normal file
|
After Width: | Height: | Size: 1.1 MiB |
BIN
public/images/themes-preview/game.png
Normal file
|
After Width: | Height: | Size: 665 KiB |
BIN
public/images/themes-preview/gitbook.png
Normal file
|
After Width: | Height: | Size: 185 KiB |
BIN
public/images/themes-preview/heo.png
Normal file
|
After Width: | Height: | Size: 915 KiB |
BIN
public/images/themes-preview/hexo.png
Normal file
|
After Width: | Height: | Size: 783 KiB |
BIN
public/images/themes-preview/landing.png
Normal file
|
After Width: | Height: | Size: 430 KiB |
BIN
public/images/themes-preview/magzine.png
Normal file
|
After Width: | Height: | Size: 430 KiB |
BIN
public/images/themes-preview/matery.png
Normal file
|
After Width: | Height: | Size: 717 KiB |
BIN
public/images/themes-preview/medium.png
Normal file
|
After Width: | Height: | Size: 792 KiB |
BIN
public/images/themes-preview/movie.png
Normal file
|
After Width: | Height: | Size: 1.3 MiB |
BIN
public/images/themes-preview/nav.png
Normal file
|
After Width: | Height: | Size: 129 KiB |
BIN
public/images/themes-preview/next.png
Normal file
|
After Width: | Height: | Size: 127 KiB |
BIN
public/images/themes-preview/nobelium.png
Normal file
|
After Width: | Height: | Size: 97 KiB |
BIN
public/images/themes-preview/plog.png
Normal file
|
After Width: | Height: | Size: 2.4 MiB |
BIN
public/images/themes-preview/simple.png
Normal file
|
After Width: | Height: | Size: 136 KiB |
BIN
public/images/themes-preview/starter.png
Normal file
|
After Width: | Height: | Size: 455 KiB |