feat: stage sidebar submenu panel visibility
This commit is contained in:
@@ -220,12 +220,27 @@
|
||||
/* 移除顶部 padding,避免标题上方出现缝隙 */
|
||||
scrollbar-width: none;
|
||||
/* Firefox 隐藏滚动条 */
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
pointer-events: none;
|
||||
transition:
|
||||
opacity 0.18s ease,
|
||||
visibility 0s linear 0.18s;
|
||||
}
|
||||
|
||||
.sidebar-submenu-panel:empty {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.sidebar.submenu-panel-visible .sidebar-submenu-panel:not(:empty) {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
pointer-events: auto;
|
||||
transition:
|
||||
opacity 0.18s ease,
|
||||
visibility 0s linear 0s;
|
||||
}
|
||||
|
||||
.sidebar-submenu-panel::-webkit-scrollbar {
|
||||
display: none;
|
||||
/* Webkit 浏览器隐藏滚动条 */
|
||||
|
||||
@@ -13,6 +13,10 @@ module.exports = function initUi(state, dom) {
|
||||
themeIcon,
|
||||
} = dom;
|
||||
|
||||
const SUBMENU_PANEL_VISIBLE_CLASS = 'submenu-panel-visible';
|
||||
const SIDEBAR_LAYOUT_TRANSITION_MS = 300;
|
||||
let submenuPanelRevealTimer = null;
|
||||
|
||||
// 移除预加载类,允许 CSS 过渡效果
|
||||
document.documentElement.classList.remove('preload');
|
||||
|
||||
@@ -100,6 +104,32 @@ module.exports = function initUi(state, dom) {
|
||||
return window.innerWidth <= 768;
|
||||
}
|
||||
|
||||
function clearSubmenuPanelRevealTimer() {
|
||||
if (submenuPanelRevealTimer) {
|
||||
window.clearTimeout(submenuPanelRevealTimer);
|
||||
submenuPanelRevealTimer = null;
|
||||
}
|
||||
}
|
||||
|
||||
function hideSubmenuPanelImmediately() {
|
||||
clearSubmenuPanelRevealTimer();
|
||||
sidebar.classList.remove(SUBMENU_PANEL_VISIBLE_CLASS);
|
||||
}
|
||||
|
||||
function showSubmenuPanelImmediately() {
|
||||
clearSubmenuPanelRevealTimer();
|
||||
sidebar.classList.add(SUBMENU_PANEL_VISIBLE_CLASS);
|
||||
}
|
||||
|
||||
function revealSubmenuPanelAfterSidebarTransition() {
|
||||
clearSubmenuPanelRevealTimer();
|
||||
submenuPanelRevealTimer = window.setTimeout(() => {
|
||||
if (!state.isSidebarCollapsed && !sidebar.classList.contains('collapsed')) {
|
||||
sidebar.classList.add(SUBMENU_PANEL_VISIBLE_CLASS);
|
||||
}
|
||||
}, SIDEBAR_LAYOUT_TRANSITION_MS);
|
||||
}
|
||||
|
||||
// 侧边栏折叠功能
|
||||
function toggleSidebarCollapse() {
|
||||
// 仅在交互时启用布局相关动画,避免首屏闪烁
|
||||
@@ -107,6 +137,11 @@ module.exports = function initUi(state, dom) {
|
||||
|
||||
state.isSidebarCollapsed = !state.isSidebarCollapsed;
|
||||
|
||||
if (state.isSidebarCollapsed) {
|
||||
// 收起时立即隐藏目录面板,避免在动画过程中残留。
|
||||
hideSubmenuPanelImmediately();
|
||||
}
|
||||
|
||||
// 使用 requestAnimationFrame 确保平滑过渡
|
||||
requestAnimationFrame(() => {
|
||||
sidebar.classList.toggle('collapsed', state.isSidebarCollapsed);
|
||||
@@ -114,6 +149,11 @@ module.exports = function initUi(state, dom) {
|
||||
|
||||
// 保存折叠状态到 localStorage
|
||||
localStorage.setItem('sidebarCollapsed', state.isSidebarCollapsed ? 'true' : 'false');
|
||||
|
||||
if (!state.isSidebarCollapsed) {
|
||||
// 展开后再淡入目录面板,避免和宽度动画抢节奏。
|
||||
revealSubmenuPanelAfterSidebarTransition();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -125,8 +165,10 @@ module.exports = function initUi(state, dom) {
|
||||
// 图标状态与折叠状态保持一致
|
||||
if (savedState === 'true' && !isMobile()) {
|
||||
state.isSidebarCollapsed = true;
|
||||
hideSubmenuPanelImmediately();
|
||||
} else {
|
||||
state.isSidebarCollapsed = false;
|
||||
showSubmenuPanelImmediately();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -238,10 +280,16 @@ module.exports = function initUi(state, dom) {
|
||||
searchContainer.classList.remove('active');
|
||||
overlay.classList.remove('active');
|
||||
state.isSidebarOpen = false;
|
||||
if (state.isSidebarCollapsed) {
|
||||
hideSubmenuPanelImmediately();
|
||||
} else {
|
||||
showSubmenuPanelImmediately();
|
||||
}
|
||||
} else {
|
||||
// 在移动设备下,重置侧边栏折叠状态
|
||||
sidebar.classList.remove('collapsed');
|
||||
content.classList.remove('expanded');
|
||||
showSubmenuPanelImmediately();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -44,3 +44,26 @@ test('侧边栏样式:收起时不应在页面按钮下方显示目录子菜
|
||||
'收起态的 submenu 应明确设置为 display: none'
|
||||
);
|
||||
});
|
||||
|
||||
test('侧边栏目录面板:应默认隐藏,并通过独立状态类延后淡入', () => {
|
||||
const repoRoot = path.resolve(__dirname, '..');
|
||||
const sidebarStylePath = path.join(repoRoot, 'assets', 'styles', '_sidebar.css');
|
||||
const uiPath = path.join(repoRoot, 'src', 'runtime', 'app', 'ui.js');
|
||||
const styleContent = fs.readFileSync(sidebarStylePath, 'utf8');
|
||||
const uiContent = fs.readFileSync(uiPath, 'utf8');
|
||||
|
||||
assert.match(
|
||||
styleContent,
|
||||
/\.sidebar-submenu-panel\s*\{[\s\S]*opacity:\s*0;[\s\S]*visibility:\s*hidden;/m,
|
||||
'目录面板默认应处于隐藏状态'
|
||||
);
|
||||
assert.match(
|
||||
styleContent,
|
||||
/\.sidebar\.submenu-panel-visible \.sidebar-submenu-panel:not\(:empty\)\s*\{[\s\S]*opacity:\s*1;[\s\S]*visibility:\s*visible;/m,
|
||||
'目录面板应通过 submenu-panel-visible 状态类显现'
|
||||
);
|
||||
assert.ok(
|
||||
uiContent.includes('submenu-panel-visible'),
|
||||
'src/runtime/app/ui.js 应控制 submenu-panel-visible 状态类'
|
||||
);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user