feat(mobile): 小屏收起侧栏,页头菜单按钮打开分组导航抽屉
- ≤820 隐藏整个侧栏(资料卡 / 本地服务 / 导航都收起),主内容直接顶上来 - 页头新增只在手机显示的菜单按钮,打开左侧抽屉:分组导航 Menu,选中后自动关闭并跳转 - 抽屉带遮罩、Esc 关闭、打开时锁住背后滚动;桌面不显示按钮
This commit is contained in:
@@ -3,11 +3,14 @@ import {
|
||||
Card,
|
||||
Empty,
|
||||
EngineIcon,
|
||||
Icon,
|
||||
IconButton,
|
||||
Layout,
|
||||
Menu,
|
||||
Notice,
|
||||
SearchField,
|
||||
Space,
|
||||
Title,
|
||||
Toaster,
|
||||
type EngineIconName,
|
||||
type MenuItem,
|
||||
@@ -240,6 +243,8 @@ export default function AppNext() {
|
||||
const [loginOpen, setLoginOpen] = useState(false)
|
||||
const [editingKey, setEditingKey] = useState<string | null>(null)
|
||||
const [scrolled, setScrolled] = useState(false)
|
||||
/** 小屏的分组导航抽屉 */
|
||||
const [navOpen, setNavOpen] = useState(false)
|
||||
const searchRef = useRef<HTMLInputElement>(null)
|
||||
const [searchEngineId, setSearchEngineId] = useState<string | null>(readSearchEngine)
|
||||
const [engineOpen, setEngineOpen] = useState(false)
|
||||
@@ -552,6 +557,29 @@ export default function AppNext() {
|
||||
? `g:${route.key}`
|
||||
: `g:${route.key}`
|
||||
|
||||
/** 侧栏与手机抽屉共用:选完分组就关掉抽屉 */
|
||||
const selectNav = (key: string) => {
|
||||
setNavOpen(false)
|
||||
if (key === 'home') navigate('/')
|
||||
else navigate(`/g/${encodeURIComponent(key.slice(2))}`)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (!navOpen) return
|
||||
const onKey = (event: KeyboardEvent) => {
|
||||
if (event.key === 'Escape') setNavOpen(false)
|
||||
}
|
||||
// 抽屉打开时锁住背后的滚动
|
||||
const scroll = document.getElementById('page-scroll')
|
||||
const previous = scroll?.style.overflowY ?? ''
|
||||
if (scroll !== null) scroll.style.overflowY = 'hidden'
|
||||
window.addEventListener('keydown', onKey)
|
||||
return () => {
|
||||
window.removeEventListener('keydown', onKey)
|
||||
if (scroll !== null) scroll.style.overflowY = previous
|
||||
}
|
||||
}, [navOpen])
|
||||
|
||||
function renderEntries(
|
||||
group: GroupNode,
|
||||
entries: Record<string, unknown>[],
|
||||
@@ -886,6 +914,7 @@ export default function AppNext() {
|
||||
void logout().then(() => setSession({ authenticated: false, has_admin: true }))
|
||||
}}
|
||||
onSettings={() => window.location.assign('/admin')}
|
||||
onOpenNav={() => setNavOpen(true)}
|
||||
/>
|
||||
}
|
||||
toolbar={
|
||||
@@ -983,10 +1012,7 @@ export default function AppNext() {
|
||||
items={sidebarItems}
|
||||
activeKey={activeKey}
|
||||
itemHeight={39}
|
||||
onSelect={(key) => {
|
||||
if (key === 'home') navigate('/')
|
||||
else navigate(`/g/${encodeURIComponent(key.slice(2))}`)
|
||||
}}
|
||||
onSelect={selectNav}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
@@ -1015,6 +1041,30 @@ export default function AppNext() {
|
||||
</Layout>
|
||||
|
||||
<BackToTop visible={scrolled} targetId="page-scroll" />
|
||||
|
||||
{navOpen && (
|
||||
<div className="nav-drawer">
|
||||
<button
|
||||
type="button"
|
||||
className="nav-drawer-backdrop"
|
||||
aria-label="关闭分组导航"
|
||||
onClick={() => setNavOpen(false)}
|
||||
/>
|
||||
<div className="nav-drawer-panel" role="dialog" aria-modal="true" aria-label="分组导航">
|
||||
<div className="nav-drawer-head">
|
||||
<Title level={4} as="h2" weight="semibold">
|
||||
分组导航
|
||||
</Title>
|
||||
<IconButton
|
||||
label="关闭分组导航"
|
||||
icon={<Icon name="close_line" />}
|
||||
onClick={() => setNavOpen(false)}
|
||||
/>
|
||||
</div>
|
||||
<Menu items={sidebarItems} activeKey={activeKey} itemHeight={44} onSelect={selectNav} />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user