fix(frontend): align ref menu structure

This commit is contained in:
2026-07-30 19:58:38 +00:00
parent b17843b609
commit 2455c4efb3
4 changed files with 83 additions and 22 deletions
@@ -288,6 +288,17 @@ test('desktop menus preserve ref columns, prefix-safe routes, and controlled dro
await expect(page.locator('.main-mobile-bottom')).toBeHidden(); await expect(page.locator('.main-mobile-bottom')).toBeHidden();
await expect(page.locator('.layout-desktop')).toBeVisible(); await expect(page.locator('.layout-desktop')).toBeVisible();
await expect(page.locator('.layout-mobile')).toHaveCount(0); await expect(page.locator('.layout-mobile')).toHaveCount(0);
const contentOrder = await page
.locator('.record-zone, [data-menu-position="middle"], .desktop-message-panel, [data-menu-position="bottom"]')
.evaluateAll((elements) =>
elements.map((element) => {
if (element.classList.contains('record-zone')) return 'records';
if (element.getAttribute('data-menu-position') === 'middle') return 'middle-menu';
if (element.classList.contains('desktop-message-panel')) return 'messages';
return 'bottom-menu';
})
);
expect(contentOrder).toEqual(['records', 'middle-menu', 'messages', 'bottom-menu']);
const global = page.locator('.main-global-menu').first(); const global = page.locator('.main-global-menu').first();
await expect(global.locator('[data-navigation-id="nation-betting"]')).toHaveAttribute( await expect(global.locator('[data-navigation-id="nation-betting"]')).toHaveAttribute(
@@ -383,8 +394,30 @@ test('the 939/940 boundary and 500px bottom bar match the ref responsive contrac
expect(popupStyle.overflowY).toBe('auto'); expect(popupStyle.overflowY).toBe('auto');
expect(popupStyle.maxHeight).toBe('850px'); expect(popupStyle.maxHeight).toBe('850px');
expect(popupStyle.bottom).toBe('47px'); expect(popupStyle.bottom).toBe('47px');
const globalPopup = page.locator('#mobile-global-menu');
await expect(globalPopup.getByText('게시판', { exact: true })).toHaveCount(1);
await expect(globalPopup.getByText('공식 오픈 톡', { exact: true })).toHaveCount(1);
await expect(globalPopup.getByText('게임정보', { exact: true })).toHaveCount(1);
await expect(globalPopup.getByText('기타 정보', { exact: true })).toHaveCount(1);
await persistArtifact(page, `${basePath.slice(1)}-mobile-global-open-500`);
await page.keyboard.press('Escape'); await page.keyboard.press('Escape');
await expect(externalTrigger).toBeFocused(); await expect(externalTrigger).toBeFocused();
await page.locator('[data-bottom-menu="nation"]').click();
const nationPopup = page.locator('#mobile-nation-menu');
await expect(nationPopup.getByText('금/쌀 경매장', { exact: true })).toHaveCount(1);
await expect(nationPopup.getByText('유니크 경매장', { exact: true })).toHaveCount(1);
await page.keyboard.press('Escape');
await page.locator('[data-bottom-menu="quick"]').click();
const quickPopup = page.locator('#mobile-quick-menu');
for (const heading of ['국가 정보', '동향 정보', '메시지']) {
await expect(quickPopup.locator('.bottom-heading').getByText(heading, { exact: true })).toHaveCount(1);
}
await expect(quickPopup.locator('.bottom-heading')).toHaveCount(3);
await expect(quickPopup.locator('.bottom-divider')).toHaveCount(3);
await persistArtifact(page, `${basePath.slice(1)}-mobile-quick-open-500`);
await page.keyboard.press('Escape');
await persistArtifact(page, `${basePath.slice(1)}-mobile-500`); await persistArtifact(page, `${basePath.slice(1)}-mobile-500`);
}); });
@@ -66,11 +66,24 @@ const onQuick = (item: QuickNavigationItem) => {
@navigate="close()" @navigate="close()"
/> />
</li> </li>
<template v-else> <template v-else-if="entry.kind === 'group'">
<li class="bottom-heading" role="presentation"> <li class="bottom-heading" role="presentation">
{{ entry.kind === 'group' ? entry.label : entry.main.label }} {{ entry.label }}
</li> </li>
<template v-if="entry.kind === 'split'"> <template v-for="item in entry.items" :key="item.id">
<li v-if="item.kind === 'divider'" class="bottom-divider" role="separator"></li>
<li v-else role="none">
<MainNavigationLink
:link="item"
:enabled="isNavigationConfigured(item)"
compact
role="menuitem"
@navigate="close()"
/>
</li>
</template>
</template>
<template v-else-if="entry.kind === 'split'">
<li role="none"> <li role="none">
<MainNavigationLink <MainNavigationLink
:link="entry.main" :link="entry.main"
@@ -80,7 +93,6 @@ const onQuick = (item: QuickNavigationItem) => {
@navigate="close()" @navigate="close()"
/> />
</li> </li>
</template>
<template v-for="item in entry.items" :key="item.id"> <template v-for="item in entry.items" :key="item.id">
<li v-if="item.kind === 'divider'" class="bottom-divider" role="separator"></li> <li v-if="item.kind === 'divider'" class="bottom-divider" role="separator"></li>
<li v-else role="none"> <li v-else role="none">
@@ -123,15 +135,6 @@ const onQuick = (item: QuickNavigationItem) => {
/> />
</li> </li>
<template v-else-if="entry.kind === 'split'"> <template v-else-if="entry.kind === 'split'">
<li role="none">
<MainNavigationLink
:link="entry.main"
:enabled="isNationNavigationEnabled(entry.main, access)"
compact
role="menuitem"
@navigate="close()"
/>
</li>
<li v-for="item in entry.items" :key="item.id" role="none"> <li v-for="item in entry.items" :key="item.id" role="none">
<template v-if="item.kind === 'link'"> <template v-if="item.kind === 'link'">
<MainNavigationLink <MainNavigationLink
@@ -162,7 +165,10 @@ const onQuick = (item: QuickNavigationItem) => {
</button> </button>
<ul v-show="openId === 'quick'" id="mobile-quick-menu" class="bottom-popup" role="menu"> <ul v-show="openId === 'quick'" id="mobile-quick-menu" class="bottom-popup" role="menu">
<template v-for="item in quickNavigation" :key="item.id"> <template v-for="item in quickNavigation" :key="item.id">
<li v-if="'kind' in item" class="bottom-divider" role="separator"></li> <template v-if="'kind' in item">
<li class="bottom-heading" role="presentation">{{ item.label }}</li>
<li class="bottom-divider" role="separator"></li>
</template>
<li v-else role="none"> <li v-else role="none">
<button <button
class="quick-link" class="quick-link"
@@ -17,6 +17,7 @@ export type MainNavigationLink = {
export interface MainNavigationDivider { export interface MainNavigationDivider {
kind: 'divider'; kind: 'divider';
id: string; id: string;
label?: string;
} }
export interface MainNavigationGroup { export interface MainNavigationGroup {
@@ -317,18 +318,18 @@ export const nationNavigation: MainNavigationEntry[] = [
]; ];
export const quickNavigation: Array<QuickNavigationItem | MainNavigationDivider> = [ export const quickNavigation: Array<QuickNavigationItem | MainNavigationDivider> = [
{ kind: 'divider', id: 'quick-nation-heading' }, { kind: 'divider', id: 'quick-nation-heading', label: '국가 정보' },
{ id: 'policy', label: '방침', tab: 'map', selector: '[data-main-target="policy"]' }, { id: 'policy', label: '방침', tab: 'map', selector: '[data-main-target="policy"]' },
{ id: 'commands', label: '명령', tab: 'commands', selector: '[data-main-target="commands"]' }, { id: 'commands', label: '명령', tab: 'commands', selector: '[data-main-target="commands"]' },
{ id: 'nation', label: '국가', tab: 'status', selector: '[data-main-target="nation"]' }, { id: 'nation', label: '국가', tab: 'status', selector: '[data-main-target="nation"]' },
{ id: 'general', label: '장수', tab: 'status', selector: '[data-main-target="general"]' }, { id: 'general', label: '장수', tab: 'status', selector: '[data-main-target="general"]' },
{ id: 'city', label: '도시', tab: 'status', selector: '[data-main-target="city"]' }, { id: 'city', label: '도시', tab: 'status', selector: '[data-main-target="city"]' },
{ kind: 'divider', id: 'quick-record-heading' }, { kind: 'divider', id: 'quick-record-heading', label: '동향 정보' },
{ id: 'map', label: '지도', tab: 'map', selector: '[data-main-target="map"]' }, { id: 'map', label: '지도', tab: 'map', selector: '[data-main-target="map"]' },
{ id: 'global-records', label: '동향', tab: 'world', selector: '[data-main-target="global-records"]' }, { id: 'global-records', label: '동향', tab: 'world', selector: '[data-main-target="global-records"]' },
{ id: 'general-records', label: '개인', tab: 'world', selector: '[data-main-target="general-records"]' }, { id: 'general-records', label: '개인', tab: 'world', selector: '[data-main-target="general-records"]' },
{ id: 'world-history', label: '정세', tab: 'world', selector: '[data-main-target="world-history"]' }, { id: 'world-history', label: '정세', tab: 'world', selector: '[data-main-target="world-history"]' },
{ kind: 'divider', id: 'quick-message-heading' }, { kind: 'divider', id: 'quick-message-heading', label: '메시지' },
{ id: 'public-message', label: '전체', tab: 'messages', selector: '[data-message-type="public"]' }, { id: 'public-message', label: '전체', tab: 'messages', selector: '[data-message-type="public"]' },
{ id: 'national-message', label: '국가', tab: 'messages', selector: '[data-message-type="national"]' }, { id: 'national-message', label: '국가', tab: 'messages', selector: '[data-message-type="national"]' },
{ id: 'private-message', label: '개인', tab: 'messages', selector: '[data-message-type="private"]' }, { id: 'private-message', label: '개인', tab: 'messages', selector: '[data-message-type="private"]' },
+24 -3
View File
@@ -143,7 +143,7 @@ watch(
<template> <template>
<main class="game-shell main-page"> <main class="game-shell main-page">
<MainGlobalMenu :npc-mode="npcMode" :vote-active="voteActive" /> <MainGlobalMenu data-menu-position="top" :npc-mode="npcMode" :vote-active="voteActive" />
<header class="game-shell__header"> <header class="game-shell__header">
<div> <div>
@@ -280,6 +280,13 @@ watch(
</RecordPanel> </RecordPanel>
</div> </div>
<MainGlobalMenu
class="common-menu-middle"
data-menu-position="middle"
:npc-mode="npcMode"
:vote-active="voteActive"
/>
<div v-if="mobileTab === 'messages'" class="mobile-panel"> <div v-if="mobileTab === 'messages'" class="mobile-panel">
<MessagePanel <MessagePanel
class="mobile-message-panel" class="mobile-message-panel"
@@ -383,6 +390,12 @@ watch(
</div> </div>
</RecordPanel> </RecordPanel>
</section> </section>
<MainGlobalMenu
class="common-menu-middle"
data-menu-position="middle"
:npc-mode="npcMode"
:vote-active="voteActive"
/>
<MessagePanel <MessagePanel
class="desktop-message-panel" class="desktop-message-panel"
:messages="messages" :messages="messages"
@@ -405,8 +418,12 @@ watch(
/> />
</section> </section>
<MainGlobalMenu class="common-menu-repeat" :npc-mode="npcMode" :vote-active="voteActive" /> <MainGlobalMenu
<MainGlobalMenu class="common-menu-repeat" :npc-mode="npcMode" :vote-active="voteActive" /> class="common-menu-repeat"
data-menu-position="bottom"
:npc-mode="npcMode"
:vote-active="voteActive"
/>
<MainMobileBottomBar <MainMobileBottomBar
:access="nationAccess" :access="nationAccess"
@@ -515,6 +532,10 @@ button {
grid-column: 1 / -1; grid-column: 1 / -1;
} }
.common-menu-middle {
grid-column: 1 / -1;
}
.record-zone { .record-zone {
grid-column: 1 / -1; grid-column: 1 / -1;
display: grid; display: grid;