aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/src/components/mobile-navigation.tsx
diff options
context:
space:
mode:
authorshu <nvcsadjkojf@outlook.com>2026-09-07 15:48:36 +0800
committershu <nvcsadjkojf@outlook.com>2026-09-07 15:48:36 +0800
commit481402e09fe760634b826b09e302e2e1bfec88b1 (patch)
tree9b55025bfcd6acd2c5d795f057de27028b422603 /src/components/mobile-navigation.tsx
feat: initialize React Spectrum template with layouts and showcases
Diffstat (limited to 'src/components/mobile-navigation.tsx')
-rw-r--r--src/components/mobile-navigation.tsx64
1 files changed, 64 insertions, 0 deletions
diff --git a/src/components/mobile-navigation.tsx b/src/components/mobile-navigation.tsx
new file mode 100644
index 0000000..abf4f24
--- /dev/null
+++ b/src/components/mobile-navigation.tsx
@@ -0,0 +1,64 @@
+import styles from './mobile-navigation.module.css'
+import { useEffect, useRef, useState } from 'react'
+import { useLocation } from 'react-router'
+import { ActionButton } from '@react-spectrum/s2/ActionButton'
+import CloseIcon from '@react-spectrum/s2/icons/Close'
+import ViewListIcon from '@react-spectrum/s2/icons/ViewList'
+import SidebarMenu from './sidebar-menu'
+
+function MobileNavigation() {
+ const dialogRef = useRef<HTMLDialogElement>(null)
+ const [isOpen, setIsOpen] = useState(false)
+ const { key } = useLocation()
+
+ // Close after all route changes, including selecting the current page.
+ useEffect(() => {
+ dialogRef.current?.close()
+ }, [key])
+
+ useEffect(() => {
+ const media = window.matchMedia('(max-width: 640px)')
+ const closeOnDesktop = () => {
+ if (!media.matches) dialogRef.current?.close()
+ }
+ media.addEventListener('change', closeOnDesktop)
+ return () => media.removeEventListener('change', closeOnDesktop)
+ }, [])
+
+ return (
+ <div className={styles.mobileMenuToggle}>
+ <ActionButton
+ aria-label="打开菜单"
+ aria-haspopup="dialog"
+ aria-expanded={isOpen}
+ aria-controls="mobile-navigation"
+ isQuiet
+ onPress={() => {
+ dialogRef.current?.showModal()
+ setIsOpen(true)
+ }}
+ >
+ <ViewListIcon />
+ </ActionButton>
+ <dialog
+ ref={dialogRef}
+ id="mobile-navigation"
+ className={styles.mobileNavigation}
+ aria-labelledby="mobile-navigation-title"
+ onClose={() => setIsOpen(false)}
+ >
+ <header className={styles.mobileNavigationHeader}>
+ <span id="mobile-navigation-title">菜单</span>
+ <ActionButton aria-label="关闭菜单" isQuiet onPress={() => dialogRef.current?.close()}>
+ <CloseIcon />
+ </ActionButton>
+ </header>
+ <nav className={styles.mobileNavigationContent} aria-label="页面导航">
+ <SidebarMenu />
+ </nav>
+ </dialog>
+ </div>
+ )
+}
+
+export default MobileNavigation