fix #477: make nav menus persistent toggles

This commit is contained in:
devmrko
2026-06-26 07:42:57 +09:00
parent ef47893324
commit 453dac6e5d
3 changed files with 51 additions and 8 deletions

View File

@@ -129,7 +129,8 @@ body {
} }
.rw-menu-trigger:hover, .rw-menu-trigger:hover,
.rw-menu-trigger:focus { .rw-menu-trigger:focus,
.rw-menu-group.open .rw-menu-trigger {
background: var(--rw-surface-muted); background: var(--rw-surface-muted);
border-color: var(--rw-border); border-color: var(--rw-border);
color: var(--rw-text); color: var(--rw-text);
@@ -149,8 +150,7 @@ body {
z-index: 30; z-index: 30;
} }
.rw-menu-group:hover .rw-menu-panel, .rw-menu-group.open .rw-menu-panel {
.rw-menu-group:focus-within .rw-menu-panel {
display: grid; display: grid;
gap: .15rem; gap: .15rem;
} }
@@ -181,7 +181,7 @@ body {
position: static; position: static;
} }
.rw-menu-group:focus-within .rw-menu-panel { .rw-menu-group.open .rw-menu-panel {
display: grid; display: grid;
} }

View File

@@ -59,6 +59,48 @@ function syncPolicyTemplate(select) {
}); });
} }
function closeMenuGroup(group) {
if (!group) {
return;
}
group.classList.remove('open');
group.querySelector('.rw-menu-trigger')?.setAttribute('aria-expanded', 'false');
}
function closeAllMenuGroups(exceptGroup = null) {
document.querySelectorAll('.rw-menu-group.open').forEach((group) => {
if (group !== exceptGroup) {
closeMenuGroup(group);
}
});
}
function initPersistentMenus() {
document.querySelectorAll('.rw-menu-trigger').forEach((button) => {
button.addEventListener('click', (event) => {
event.stopPropagation();
const group = button.closest('.rw-menu-group');
if (!group) {
return;
}
const nextOpen = !group.classList.contains('open');
closeAllMenuGroups(group);
group.classList.toggle('open', nextOpen);
button.setAttribute('aria-expanded', String(nextOpen));
});
});
document.addEventListener('click', (event) => {
if (!event.target.closest('.rw-menu')) {
closeAllMenuGroups();
}
});
document.addEventListener('keydown', (event) => {
if (event.key === 'Escape') {
closeAllMenuGroups();
}
});
}
function escapeHtml(value) { function escapeHtml(value) {
return value return value
.replaceAll('&', '&') .replaceAll('&', '&')
@@ -428,6 +470,7 @@ function initPermissionWizard() {
} }
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
initPersistentMenus();
renderMarkdownViews(); renderMarkdownViews();
const master = document.getElementById('userRoleMaster'); const master = document.getElementById('userRoleMaster');
if (master) { if (master) {

View File

@@ -16,7 +16,7 @@
<a class="navbar-brand" href="/">VPD Backoffice</a> <a class="navbar-brand" href="/">VPD Backoffice</a>
<div class="rw-menu"> <div class="rw-menu">
<div class="rw-menu-group"> <div class="rw-menu-group">
<button class="rw-menu-trigger" type="button">권한 관리</button> <button class="rw-menu-trigger" type="button" aria-expanded="false">권한 관리</button>
<div class="rw-menu-panel"> <div class="rw-menu-panel">
<a class="nav-link" href="/users">사용자</a> <a class="nav-link" href="/users">사용자</a>
<a class="nav-link" href="/roles">역할</a> <a class="nav-link" href="/roles">역할</a>
@@ -25,7 +25,7 @@
</div> </div>
</div> </div>
<div class="rw-menu-group"> <div class="rw-menu-group">
<button class="rw-menu-trigger" type="button">ORDS 서빙</button> <button class="rw-menu-trigger" type="button" aria-expanded="false">ORDS 서빙</button>
<div class="rw-menu-panel"> <div class="rw-menu-panel">
<a class="nav-link" href="/objects">조회 Handler 생성</a> <a class="nav-link" href="/objects">조회 Handler 생성</a>
<a class="nav-link" href="/probe">ORDS 검증</a> <a class="nav-link" href="/probe">ORDS 검증</a>
@@ -33,7 +33,7 @@
</div> </div>
</div> </div>
<div class="rw-menu-group"> <div class="rw-menu-group">
<button class="rw-menu-trigger" type="button">MCP</button> <button class="rw-menu-trigger" type="button" aria-expanded="false">MCP</button>
<div class="rw-menu-panel"> <div class="rw-menu-panel">
<a class="nav-link" href="/mcp-chatbot">Chatbot</a> <a class="nav-link" href="/mcp-chatbot">Chatbot</a>
<a class="nav-link" href="/mcp-reasoning">Reasoning</a> <a class="nav-link" href="/mcp-reasoning">Reasoning</a>
@@ -42,7 +42,7 @@
</div> </div>
</div> </div>
<div class="rw-menu-group"> <div class="rw-menu-group">
<button class="rw-menu-trigger" type="button">운영</button> <button class="rw-menu-trigger" type="button" aria-expanded="false">운영</button>
<div class="rw-menu-panel"> <div class="rw-menu-panel">
<a class="nav-link" href="/operation-status">운영 상태</a> <a class="nav-link" href="/operation-status">운영 상태</a>
<a class="nav-link" href="/vpd-policies">VPD 설정</a> <a class="nav-link" href="/vpd-policies">VPD 설정</a>