diff --git a/stash-web/src/App.css b/stash-web/src/App.css index 8a2a8bc..990d891 100644 --- a/stash-web/src/App.css +++ b/stash-web/src/App.css @@ -860,9 +860,8 @@ } @media (max-width: 820px) { - /* 手机上只留分组导航:资料卡 / 本地服务卡太占地,导航还留着方便进分组 */ - #page-scroll > aside .profile-card, - #page-scroll > aside .service-panel { + /* 手机上侧栏整体收起,分组导航改由页头的菜单按钮打开抽屉 */ + #page-scroll > aside { display: none; } @@ -1657,3 +1656,70 @@ html.theme-transition .site-background { display: block; } } + +/* ---------- 小屏分组导航抽屉 ---------- */ + +/* 桌面不显示菜单按钮(抽屉只能从小屏页头打开) */ +.header-nav-toggle { + display: none; +} + +.nav-drawer { + position: fixed; + inset: 0; + z-index: calc(var(--su-z-header) + 10); + display: flex; +} + +.nav-drawer-backdrop { + position: absolute; + inset: 0; + border: none; + background: rgba(0, 0, 0, 0.45); + cursor: pointer; +} + +.nav-drawer-panel { + position: relative; + display: flex; + flex-direction: column; + gap: var(--su-space-3); + width: min(86vw, 340px); + height: 100%; + margin-right: auto; + padding: var(--su-space-4); + background-color: var(--su-bg-card); + box-shadow: var(--su-shadow-pop); + overflow-y: auto; + animation: nav-drawer-in 180ms var(--su-ease-out); +} + +.nav-drawer-head { + display: flex; + align-items: center; + justify-content: space-between; + gap: var(--su-space-2); +} + +@keyframes nav-drawer-in { + from { + transform: translateX(-100%); + } + + to { + transform: none; + } +} + +@media (prefers-reduced-motion: reduce) { + .nav-drawer-panel { + animation: none; + } +} + +@media (max-width: 820px) { + /* 手机页头才显示菜单按钮(必须写在上面的 display: none 之后) */ + .header-nav-toggle { + display: inline-flex; + } +} diff --git a/stash-web/src/AppNext.tsx b/stash-web/src/AppNext.tsx index 6434ff5..28870a3 100644 --- a/stash-web/src/AppNext.tsx +++ b/stash-web/src/AppNext.tsx @@ -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(null) const [scrolled, setScrolled] = useState(false) + /** 小屏的分组导航抽屉 */ + const [navOpen, setNavOpen] = useState(false) const searchRef = useRef(null) const [searchEngineId, setSearchEngineId] = useState(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[], @@ -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} /> )} @@ -1015,6 +1041,30 @@ export default function AppNext() { + + {navOpen && ( +
+
+ )} ) } diff --git a/stash-web/src/components/header/SiteHeader.tsx b/stash-web/src/components/header/SiteHeader.tsx index 2685016..ef5f707 100644 --- a/stash-web/src/components/header/SiteHeader.tsx +++ b/stash-web/src/components/header/SiteHeader.tsx @@ -39,6 +39,8 @@ type SiteHeaderProps = { onLogin: () => void onLogout: () => void onSettings: () => void + /** 小屏打开分组导航抽屉;不传则不显示菜单按钮 */ + onOpenNav?: () => void } const tagTone: Record = { @@ -67,6 +69,7 @@ export function SiteHeader({ onLogin, onLogout, onSettings, + onOpenNav, }: SiteHeaderProps) { const context = { count: projectCount, project: sampleProject, live } @@ -97,6 +100,14 @@ export function SiteHeader({
+ {onOpenNav !== undefined && ( + } + onClick={onOpenNav} + /> + )} {ready && ( <> {site.actions.theme && (