From c52890e7215cb7fd6aac6e1a5522d2a4ba6cc54c Mon Sep 17 00:00:00 2001 From: devmrko Date: Wed, 1 Jul 2026 09:38:05 +0900 Subject: [PATCH] [UX] #577 show VPD submenu below primary navigation --- docs/design/577-persona-ux-audit/README.md | 2 +- src/main/resources/static/css/app.css | 62 +++++++---------- src/main/resources/static/js/app.js | 64 ++++++++---------- .../resources/templates/fragments/layout.html | 66 +++++++++---------- .../web/GuidedFlowTemplateTest.java | 8 +++ 5 files changed, 93 insertions(+), 109 deletions(-) diff --git a/docs/design/577-persona-ux-audit/README.md b/docs/design/577-persona-ux-audit/README.md index 8760269..7e7e685 100644 --- a/docs/design/577-persona-ux-audit/README.md +++ b/docs/design/577-persona-ux-audit/README.md @@ -104,6 +104,6 @@ UX-1부터 시작한다. 기존 데이터 모델과 VPD 로직은 바꾸지 않 | 별도 Filter 관리 | 고급 접근 조건 | 예외 정책 기능 | | 운영 상태 / 연결 설정 | 운영 현황 / 시스템 설정 | 운영성·연동 설정 기능 | -상단 메뉴는 DDS와 같은 네 업무 영역인 `권한 관리 · 보호 정책 관리 · 지식자료 관리 · 접근 검증`으로 구성한다. ORDS·MCP·Filter·시스템 설정은 기본 제품 메뉴에서 빼고 관련 화면의 고급 링크에서만 연다. 절차나 구현 방식이 아니라 사용자가 하려는 일을 메뉴명으로 쓴다. 메뉴와 기본 화면에는 `1.`, `2.`, 단계 레일, `01/02` 배지를 두지 않는다. 순서가 실제 입력 제약인 wizard에서만 단계 번호를 사용한다. +상단 메뉴는 DDS와 같은 네 업무 영역인 `권한 관리 · 보호 정책 관리 · 지식자료 관리 · 접근 검증`으로 구성한다. ORDS·MCP·Filter·시스템 설정은 기본 제품 메뉴에서 빼고 관련 화면의 고급 링크에서만 연다. 1차 메뉴를 누르면 하위 메뉴가 팝업으로 뜨지 않고 바로 아래 전폭 줄에 표시된다. 현재 페이지가 속한 메뉴는 처음부터 아래 줄을 열어 둔다. 절차나 구현 방식이 아니라 사용자가 하려는 일을 메뉴명으로 쓴다. 메뉴와 기본 화면에는 `1.`, `2.`, 단계 레일, `01/02` 배지를 두지 않는다. 순서가 실제 입력 제약인 wizard에서만 단계 번호를 사용한다. 기본 페이지는 제목과 현재 설정·주요 행동만 노출한다. VPD/ORDS 처리 원리, 배경 설명, 주의사항은 상단의 공통 `도움말` 버튼을 눌렀을 때만 펼친다. 이 규칙으로 제품 화면이 관리 매뉴얼처럼 보이는 문제를 줄이고, 설정 자체를 전면에 둔다. diff --git a/src/main/resources/static/css/app.css b/src/main/resources/static/css/app.css index 89efdc0..c602129 100644 --- a/src/main/resources/static/css/app.css +++ b/src/main/resources/static/css/app.css @@ -113,10 +113,6 @@ body { gap: .35rem; } -.rw-menu-group { - position: relative; -} - .rw-menu-trigger { background: transparent; border: 1px solid transparent; @@ -130,36 +126,38 @@ body { .rw-menu-trigger:hover, .rw-menu-trigger:focus, -.rw-menu-group.open .rw-menu-trigger { +.rw-menu-trigger.active { background: var(--rw-surface-muted); border-color: var(--rw-border); color: var(--rw-text); } -.rw-menu-panel { - background: var(--rw-surface); - border: 1px solid var(--rw-border); - border-radius: 8px; - box-shadow: var(--rw-shadow); - display: none; - left: 0; - min-width: 190px; - padding: .35rem; - position: absolute; - top: calc(100% + .35rem); - z-index: 30; +.rw-submenu-bar { + background: var(--rw-surface-muted); + border-top: 1px solid var(--rw-border); + width: 100%; } -.rw-menu-group.open .rw-menu-panel { - display: grid; - gap: .15rem; -} - -.rw-menu-panel .nav-link { - border-radius: 8px; - color: var(--rw-text); +.rw-submenu-bar .container { display: block; - padding: .55rem .65rem !important; +} + +.rw-submenu { + align-items: center; + display: flex; + flex-wrap: wrap; + gap: .2rem; + min-height: 2.75rem; + padding: .25rem 0; +} + +.rw-submenu[hidden] { + display: none; +} + +.rw-submenu .nav-link { + color: var(--rw-text); + padding: .4rem .7rem !important; white-space: nowrap; } @@ -181,14 +179,6 @@ body { grid-template-columns: repeat(2, minmax(0, 1fr)); } - .rw-menu-panel { - position: static; - } - - .rw-menu-group.open .rw-menu-panel { - display: grid; - } - .architecture-strip { grid-template-columns: 1fr; } @@ -1786,10 +1776,6 @@ body { width: 100%; } - .rw-menu-group { - min-width: 0; - } - .rw-menu-trigger { padding-left: .65rem; padding-right: .65rem; diff --git a/src/main/resources/static/js/app.js b/src/main/resources/static/js/app.js index b9b0dc1..0298531 100644 --- a/src/main/resources/static/js/app.js +++ b/src/main/resources/static/js/app.js @@ -73,45 +73,39 @@ function syncPolicyTemplate(select) { ); } -function closeMenuGroup(group) { - if (!group) { +function initPersistentMenus() { + const productNav = document.querySelector('[data-product-nav]'); + if (!productNav) { return; } - group.classList.remove('open'); - group.querySelector('.rw-menu-trigger')?.setAttribute('aria-expanded', 'false'); -} + const submenuBar = productNav.querySelector('[data-submenu-bar]'); + const triggers = Array.from(productNav.querySelectorAll('[data-submenu-trigger]')); + const panels = Array.from(productNav.querySelectorAll('[data-submenu-panel]')); + const path = window.location.pathname; -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)); + const activate = (target) => { + const hasTarget = panels.some((panel) => panel.dataset.submenuPanel === target); + submenuBar.hidden = !hasTarget; + triggers.forEach((trigger) => { + const active = trigger.dataset.submenuTrigger === target; + trigger.classList.toggle('active', active); + trigger.setAttribute('aria-expanded', String(active)); }); - }); - document.addEventListener('click', (event) => { - if (!event.target.closest('.rw-menu')) { - closeAllMenuGroups(); - } - }); - document.addEventListener('keydown', (event) => { - if (event.key === 'Escape') { - closeAllMenuGroups(); - } + panels.forEach((panel) => { + panel.hidden = panel.dataset.submenuPanel !== target; + }); + }; + + const currentPanel = panels.find((panel) => Array.from(panel.querySelectorAll('a[href]')).some((link) => { + const href = new URL(link.href, window.location.origin).pathname; + return path === href || (href !== '/' && path.startsWith(`${href}/`)); + })); + if (currentPanel) { + activate(currentPanel.dataset.submenuPanel); + } + + triggers.forEach((trigger) => { + trigger.addEventListener('click', () => activate(trigger.dataset.submenuTrigger)); }); } diff --git a/src/main/resources/templates/fragments/layout.html b/src/main/resources/templates/fragments/layout.html index 44faeed..b079cc8 100644 --- a/src/main/resources/templates/fragments/layout.html +++ b/src/main/resources/templates/fragments/layout.html @@ -11,49 +11,45 @@ -