feat: stage sidebar submenu panel visibility
This commit is contained in:
@@ -220,12 +220,27 @@
|
|||||||
/* 移除顶部 padding,避免标题上方出现缝隙 */
|
/* 移除顶部 padding,避免标题上方出现缝隙 */
|
||||||
scrollbar-width: none;
|
scrollbar-width: none;
|
||||||
/* Firefox 隐藏滚动条 */
|
/* Firefox 隐藏滚动条 */
|
||||||
|
opacity: 0;
|
||||||
|
visibility: hidden;
|
||||||
|
pointer-events: none;
|
||||||
|
transition:
|
||||||
|
opacity 0.18s ease,
|
||||||
|
visibility 0s linear 0.18s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar-submenu-panel:empty {
|
.sidebar-submenu-panel:empty {
|
||||||
display: none;
|
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 {
|
.sidebar-submenu-panel::-webkit-scrollbar {
|
||||||
display: none;
|
display: none;
|
||||||
/* Webkit 浏览器隐藏滚动条 */
|
/* Webkit 浏览器隐藏滚动条 */
|
||||||
|
|||||||
@@ -13,6 +13,10 @@ module.exports = function initUi(state, dom) {
|
|||||||
themeIcon,
|
themeIcon,
|
||||||
} = dom;
|
} = dom;
|
||||||
|
|
||||||
|
const SUBMENU_PANEL_VISIBLE_CLASS = 'submenu-panel-visible';
|
||||||
|
const SIDEBAR_LAYOUT_TRANSITION_MS = 300;
|
||||||
|
let submenuPanelRevealTimer = null;
|
||||||
|
|
||||||
// 移除预加载类,允许 CSS 过渡效果
|
// 移除预加载类,允许 CSS 过渡效果
|
||||||
document.documentElement.classList.remove('preload');
|
document.documentElement.classList.remove('preload');
|
||||||
|
|
||||||
@@ -100,6 +104,32 @@ module.exports = function initUi(state, dom) {
|
|||||||
return window.innerWidth <= 768;
|
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() {
|
function toggleSidebarCollapse() {
|
||||||
// 仅在交互时启用布局相关动画,避免首屏闪烁
|
// 仅在交互时启用布局相关动画,避免首屏闪烁
|
||||||
@@ -107,6 +137,11 @@ module.exports = function initUi(state, dom) {
|
|||||||
|
|
||||||
state.isSidebarCollapsed = !state.isSidebarCollapsed;
|
state.isSidebarCollapsed = !state.isSidebarCollapsed;
|
||||||
|
|
||||||
|
if (state.isSidebarCollapsed) {
|
||||||
|
// 收起时立即隐藏目录面板,避免在动画过程中残留。
|
||||||
|
hideSubmenuPanelImmediately();
|
||||||
|
}
|
||||||
|
|
||||||
// 使用 requestAnimationFrame 确保平滑过渡
|
// 使用 requestAnimationFrame 确保平滑过渡
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
sidebar.classList.toggle('collapsed', state.isSidebarCollapsed);
|
sidebar.classList.toggle('collapsed', state.isSidebarCollapsed);
|
||||||
@@ -114,6 +149,11 @@ module.exports = function initUi(state, dom) {
|
|||||||
|
|
||||||
// 保存折叠状态到 localStorage
|
// 保存折叠状态到 localStorage
|
||||||
localStorage.setItem('sidebarCollapsed', state.isSidebarCollapsed ? 'true' : 'false');
|
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()) {
|
if (savedState === 'true' && !isMobile()) {
|
||||||
state.isSidebarCollapsed = true;
|
state.isSidebarCollapsed = true;
|
||||||
|
hideSubmenuPanelImmediately();
|
||||||
} else {
|
} else {
|
||||||
state.isSidebarCollapsed = false;
|
state.isSidebarCollapsed = false;
|
||||||
|
showSubmenuPanelImmediately();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -238,10 +280,16 @@ module.exports = function initUi(state, dom) {
|
|||||||
searchContainer.classList.remove('active');
|
searchContainer.classList.remove('active');
|
||||||
overlay.classList.remove('active');
|
overlay.classList.remove('active');
|
||||||
state.isSidebarOpen = false;
|
state.isSidebarOpen = false;
|
||||||
|
if (state.isSidebarCollapsed) {
|
||||||
|
hideSubmenuPanelImmediately();
|
||||||
|
} else {
|
||||||
|
showSubmenuPanelImmediately();
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// 在移动设备下,重置侧边栏折叠状态
|
// 在移动设备下,重置侧边栏折叠状态
|
||||||
sidebar.classList.remove('collapsed');
|
sidebar.classList.remove('collapsed');
|
||||||
content.classList.remove('expanded');
|
content.classList.remove('expanded');
|
||||||
|
showSubmenuPanelImmediately();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -44,3 +44,26 @@ test('侧边栏样式:收起时不应在页面按钮下方显示目录子菜
|
|||||||
'收起态的 submenu 应明确设置为 display: none'
|
'收起态的 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