From 6ef8bb74e3335d8fd308fa49f84dc2abe18eb47b Mon Sep 17 00:00:00 2001 From: wj-xiao Date: Thu, 8 Jan 2026 14:45:50 +0800 Subject: [PATCH] ui: update menu bar --- web/src/i18n/locales/zh.ts | 2 +- web/src/pages/desktop/menu/image/index.tsx | 22 +-- web/src/pages/desktop/menu/index.tsx | 154 +++++++++--------- .../desktop/menu/operations/collapse.tsx | 22 +++ .../pages/desktop/menu/operations/expand.tsx | 27 +++ .../pages/desktop/menu/operations/index.tsx | 2 + .../menu/settings/appearance/menu-bar.tsx | 25 ++- 7 files changed, 160 insertions(+), 94 deletions(-) create mode 100644 web/src/pages/desktop/menu/operations/collapse.tsx create mode 100644 web/src/pages/desktop/menu/operations/expand.tsx create mode 100644 web/src/pages/desktop/menu/operations/index.tsx diff --git a/web/src/i18n/locales/zh.ts b/web/src/i18n/locales/zh.ts index f7290c8..1f79ae4 100644 --- a/web/src/i18n/locales/zh.ts +++ b/web/src/i18n/locales/zh.ts @@ -214,7 +214,7 @@ const zh = { languageDesc: '选择界面语言', menuBar: '菜单栏', menuBarDesc: '是否在菜单栏中显示图标', - webTitle: '网站标题', + webTitle: '网页标题', webTitleDesc: '自定义网站页面标题' }, device: { diff --git a/web/src/pages/desktop/menu/image/index.tsx b/web/src/pages/desktop/menu/image/index.tsx index 1508169..21b453b 100644 --- a/web/src/pages/desktop/menu/image/index.tsx +++ b/web/src/pages/desktop/menu/image/index.tsx @@ -1,5 +1,5 @@ import { useEffect, useState } from 'react'; -import { Divider, Modal, Segmented } from 'antd'; +import { Divider, Modal, Segmented, Tooltip } from 'antd'; import clsx from 'clsx'; import { useSetAtom } from 'jotai'; import { DiscIcon, HardDriveIcon } from 'lucide-react'; @@ -61,15 +61,17 @@ export const Image = () => { return ( <> -
toggleModal(true)} - > - -
+ +
toggleModal(true)} + > + +
+
toggleModal(false)}>
diff --git a/web/src/pages/desktop/menu/index.tsx b/web/src/pages/desktop/menu/index.tsx index a51153c..c1b01b1 100644 --- a/web/src/pages/desktop/menu/index.tsx +++ b/web/src/pages/desktop/menu/index.tsx @@ -1,10 +1,9 @@ import { useEffect, useRef, useState } from 'react'; -import { Divider, Tooltip } from 'antd'; +import { Divider } from 'antd'; import clsx from 'clsx'; import { useAtom, useAtomValue } from 'jotai'; -import { MenuIcon, XIcon } from 'lucide-react'; +import { GripVerticalIcon } from 'lucide-react'; import Draggable, { DraggableData, DraggableEvent } from 'react-draggable'; -import { useTranslation } from 'react-i18next'; import { getMenuDisabledItems } from '@/lib/localstorage.ts'; import { menuDisabledItemsAtom, submenuOpenCountAtom } from '@/jotai/settings.ts'; @@ -14,6 +13,7 @@ import { Fullscreen } from './fullscreen'; import { Image } from './image'; import { Keyboard } from './keyboard'; import { Mouse } from './mouse'; +import { Collapse, Expand } from './operations'; import { Power } from './power'; import { Screen } from './screen'; import { Script } from './script'; @@ -21,39 +21,29 @@ import { Settings } from './settings'; import { Terminal } from './terminal'; import { Wol } from './wol'; +const HIDE_TIMEOUT = 8000; + export const Menu = () => { - const { t } = useTranslation(); const [menuDisabledItems, setMenuDisabledItems] = useAtom(menuDisabledItemsAtom); const submenuOpenCount = useAtomValue(submenuOpenCountAtom); const [isMenuExpanded, setIsMenuExpanded] = useState(true); const [isMenuMoved, setIsMenuMoved] = useState(false); + const [isMenuHovered, setIsMenuHovered] = useState(false); const [isMenuHidden, setIsMenuHidden] = useState(false); - const [bounds, setBounds] = useState({ left: 0, right: 0, top: 0, bottom: 0 }); + const [menuBounds, setMenuBounds] = useState({ left: 0, right: 0, top: 0, bottom: 0 }); const nodeRef = useRef(null); const timerRef = useRef | null>(null); + // init menu useEffect(() => { const items = getMenuDisabledItems(); setMenuDisabledItems(items); - // react-draggable bounds - const handleResize = () => { - if (!nodeRef.current) return; - - const elementRect = nodeRef.current.getBoundingClientRect(); - const width = (window.innerWidth - elementRect.width) / 2; - - setBounds({ - left: -width, - top: -10, - right: width, - bottom: window.innerHeight - elementRect.height - 10 - }); - }; - + startCountdown(); handleResize(); + window.addEventListener('resize', handleResize); return () => { @@ -62,24 +52,47 @@ export const Menu = () => { }; }, []); + // handle menu bounds useEffect(() => { - if (submenuOpenCount === 0) { - startCountdown(); - } else { - stopCountdown(); - } - }, [submenuOpenCount]); + handleResize(); + }, [menuDisabledItems, isMenuExpanded]); - function startCountdown() { - if (submenuOpenCount > 0) { + // handle hide timer + useEffect(() => { + if (submenuOpenCount === 0 && !isMenuHovered) { + startCountdown(); return; } stopCountdown(); + }, [submenuOpenCount, isMenuHovered]); + + function handleResize() { + if (!nodeRef.current) return; + + const elementRect = nodeRef.current.getBoundingClientRect(); + const width = (window.innerWidth - elementRect.width) / 2; + + setMenuBounds({ + left: -width, + top: -10, + right: width, + bottom: window.innerHeight - elementRect.height - 10 + }); + } + + function startCountdown() { + if (submenuOpenCount > 0 || !isMenuExpanded || isMenuMoved) { + return; + } + + if (timerRef.current) { + clearTimeout(timerRef.current); + } timerRef.current = setTimeout(() => { setIsMenuHidden(true); - }, 5000); + }, HIDE_TIMEOUT); } function stopCountdown() { @@ -89,68 +102,77 @@ export const Menu = () => { } } - function handleDraggableStop(_e: DraggableEvent, data: DraggableData) { - if (data.x !== 0 || data.y !== 0) { - setIsMenuMoved(true); + function handleMoved(_e: DraggableEvent, data: DraggableData) { + if (data.x === 0 && data.y === 0) return; + if (isMenuMoved) return; + + setIsMenuMoved(true); + } + + function handleHovered(hovered: boolean) { + setIsMenuHovered(hovered); + if (hovered) { + setIsMenuHidden(false); } } - function handleMouseEnter() { - stopCountdown(); - setIsMenuHidden(false); + function isEnabled(item: string) { + return !menuDisabledItems.includes(item); } return (
handleHovered(true)} + onMouseLeave={() => handleHovered(false)} + onBlur={() => handleHovered(false)} > {/* Trigger area for auto-show when hidden */} {isMenuExpanded && ( -
+
)} + {/* Menubar */}
-
- sipeed +
+
+ - {!menuDisabledItems.includes('image') && } - {!menuDisabledItems.includes('download') && } - {!menuDisabledItems.includes('script') &&