fix(viewer): remove topbar settings menu, keep locale switcher only (user request)
Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
+1
-124
@@ -1,9 +1,8 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, onBeforeUnmount, onMounted, ref, watchEffect } from 'vue';
|
import { computed, watchEffect } from 'vue';
|
||||||
import { useRoute } from 'vue-router';
|
import { useRoute } from 'vue-router';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
import LocaleSwitcher from './components/LocaleSwitcher.vue';
|
import LocaleSwitcher from './components/LocaleSwitcher.vue';
|
||||||
import { reducedMotion, toggleReducedMotion } from './ui/prefs';
|
|
||||||
// UI-02 V01 视觉 token:变量全部作用域在 .viewer-shell 类上(非 :root 全局),
|
// UI-02 V01 视觉 token:变量全部作用域在 .viewer-shell 类上(非 :root 全局),
|
||||||
// embed 分支不挂该类 → 嵌入 iframe 不消费任何 token(隔离红线)。
|
// embed 分支不挂该类 → 嵌入 iframe 不消费任何 token(隔离红线)。
|
||||||
import './styles/viewer-tokens.css';
|
import './styles/viewer-tokens.css';
|
||||||
@@ -20,34 +19,6 @@ watchEffect(() => {
|
|||||||
document.title = t('app.title');
|
document.title = t('app.title');
|
||||||
document.documentElement.setAttribute('lang', locale.value);
|
document.documentElement.setAttribute('lang', locale.value);
|
||||||
});
|
});
|
||||||
|
|
||||||
// —— 顶栏轻量菜单(UI-02 §3.2):减少动画入口收进语言旁「⋯」,可发现但不占整行 ——
|
|
||||||
const prefsOpen = ref(false);
|
|
||||||
const prefsRootEl = ref<HTMLElement | null>(null);
|
|
||||||
|
|
||||||
function closePrefs(): void {
|
|
||||||
prefsOpen.value = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
function onPrefsKeydown(event: KeyboardEvent): void {
|
|
||||||
if (event.key === 'Escape') closePrefs();
|
|
||||||
}
|
|
||||||
|
|
||||||
function onOuterPointerDown(event: PointerEvent): void {
|
|
||||||
if (prefsOpen.value && prefsRootEl.value !== null && !prefsRootEl.value.contains(event.target as Node)) {
|
|
||||||
closePrefs();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
document.addEventListener('pointerdown', onOuterPointerDown, true);
|
|
||||||
document.addEventListener('keydown', onPrefsKeydown);
|
|
||||||
});
|
|
||||||
|
|
||||||
onBeforeUnmount(() => {
|
|
||||||
document.removeEventListener('pointerdown', onOuterPointerDown, true);
|
|
||||||
document.removeEventListener('keydown', onPrefsKeydown);
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -63,34 +34,6 @@ onBeforeUnmount(() => {
|
|||||||
</span>
|
</span>
|
||||||
<div class="topbar-actions">
|
<div class="topbar-actions">
|
||||||
<LocaleSwitcher />
|
<LocaleSwitcher />
|
||||||
<div ref="prefsRootEl" class="prefs-menu">
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="icon-btn"
|
|
||||||
:aria-label="t('viewer.settings')"
|
|
||||||
aria-haspopup="menu"
|
|
||||||
:aria-expanded="prefsOpen"
|
|
||||||
@click="prefsOpen = !prefsOpen"
|
|
||||||
>
|
|
||||||
<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false">
|
|
||||||
<circle cx="5" cy="12" r="2" fill="currentColor" />
|
|
||||||
<circle cx="12" cy="12" r="2" fill="currentColor" />
|
|
||||||
<circle cx="19" cy="12" r="2" fill="currentColor" />
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
<div v-if="prefsOpen" class="prefs-popover" role="menu" :aria-label="t('viewer.settings')">
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="prefs-item"
|
|
||||||
role="menuitemcheckbox"
|
|
||||||
:aria-checked="reducedMotion"
|
|
||||||
@click="toggleReducedMotion"
|
|
||||||
>
|
|
||||||
<span class="prefs-check" aria-hidden="true">{{ reducedMotion ? '✓' : '' }}</span>
|
|
||||||
{{ t('viewer.reduceMotion') }}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
<main class="viewer-main">
|
<main class="viewer-main">
|
||||||
@@ -166,72 +109,6 @@ body {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 4px;
|
gap: 4px;
|
||||||
}
|
}
|
||||||
.prefs-menu {
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
.icon-btn {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
width: var(--viewer-touch-target);
|
|
||||||
height: var(--viewer-touch-target);
|
|
||||||
border: none;
|
|
||||||
border-radius: 50%;
|
|
||||||
background: transparent;
|
|
||||||
color: var(--viewer-text-secondary);
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
.icon-btn:hover {
|
|
||||||
background: var(--viewer-page-bg);
|
|
||||||
}
|
|
||||||
.icon-btn:focus-visible {
|
|
||||||
outline: 2px solid var(--viewer-primary);
|
|
||||||
outline-offset: -2px;
|
|
||||||
}
|
|
||||||
.icon-btn svg {
|
|
||||||
width: 22px;
|
|
||||||
height: 22px;
|
|
||||||
}
|
|
||||||
.prefs-popover {
|
|
||||||
position: absolute;
|
|
||||||
top: calc(100% + 2px);
|
|
||||||
right: 0;
|
|
||||||
z-index: 130;
|
|
||||||
min-width: 176px;
|
|
||||||
border: 1px solid var(--viewer-divider);
|
|
||||||
border-radius: 12px;
|
|
||||||
background: var(--viewer-surface);
|
|
||||||
box-shadow: 0 8px 24px rgba(23, 32, 51, 0.12);
|
|
||||||
padding: 4px;
|
|
||||||
display: grid;
|
|
||||||
}
|
|
||||||
.prefs-item {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 8px;
|
|
||||||
min-height: var(--viewer-touch-target);
|
|
||||||
border: none;
|
|
||||||
border-radius: 8px;
|
|
||||||
background: transparent;
|
|
||||||
color: var(--viewer-text);
|
|
||||||
padding: 0 12px;
|
|
||||||
font-size: 0.875rem;
|
|
||||||
text-align: left;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
.prefs-item:hover {
|
|
||||||
background: var(--viewer-page-bg);
|
|
||||||
}
|
|
||||||
.prefs-item:focus-visible {
|
|
||||||
outline: 2px solid var(--viewer-primary);
|
|
||||||
outline-offset: -2px;
|
|
||||||
}
|
|
||||||
.prefs-check {
|
|
||||||
display: inline-block;
|
|
||||||
width: 1em;
|
|
||||||
color: var(--viewer-primary);
|
|
||||||
font-weight: 700;
|
|
||||||
}
|
|
||||||
/* V01 §3.2:内容页不再有全局 16px 包围——宣传图/照片边到边,内边距由
|
/* V01 §3.2:内容页不再有全局 16px 包围——宣传图/照片边到边,内边距由
|
||||||
* 信息区(AlbumHeader)与照片区(PhotoFeed,Task 3)各自负责。 */
|
* 信息区(AlbumHeader)与照片区(PhotoFeed,Task 3)各自负责。 */
|
||||||
.viewer-main {
|
.viewer-main {
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ describe('viewer shell tokens and embed isolation (UI-02 V01)', () => {
|
|||||||
expect(css).toContain('.viewer-shell');
|
expect(css).toContain('.viewer-shell');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('normal routes render .viewer-shell with PhotoLive brand top bar and prefs menu', async () => {
|
it('normal routes render .viewer-shell with PhotoLive brand top bar (locale only, no prefs menu)', async () => {
|
||||||
await router.push('/');
|
await router.push('/');
|
||||||
await router.isReady();
|
await router.isReady();
|
||||||
const app = mountApp();
|
const app = mountApp();
|
||||||
@@ -86,17 +86,9 @@ describe('viewer shell tokens and embed isolation (UI-02 V01)', () => {
|
|||||||
expect(topbar.find('.brand-name').text()).toBe('PhotoLive');
|
expect(topbar.find('.brand-name').text()).toBe('PhotoLive');
|
||||||
expect(topbar.find('.locale-trigger').exists()).toBe(true);
|
expect(topbar.find('.locale-trigger').exists()).toBe(true);
|
||||||
|
|
||||||
// 「⋯」轻量菜单(§3.2):减少动画入口可发现、可切换。
|
// 设置菜单已按用户要求移除(2026-09-23):顶栏仅保留语言切换。
|
||||||
const more = topbar.find('.icon-btn');
|
expect(topbar.find('.prefs-menu').exists()).toBe(false);
|
||||||
expect(more.attributes('aria-haspopup')).toBe('menu');
|
expect(topbar.find('.icon-btn').exists()).toBe(false);
|
||||||
await more.trigger('click');
|
|
||||||
const item = app.find('.prefs-item');
|
|
||||||
expect(item.text()).toBe('减少动画');
|
|
||||||
const before = item.attributes('aria-checked');
|
|
||||||
await item.trigger('click');
|
|
||||||
expect(app.find('.prefs-item').attributes('aria-checked')).toBe(before === 'true' ? 'false' : 'true');
|
|
||||||
// 还原模块级偏好单例,避免影响后续用例。
|
|
||||||
await app.find('.prefs-item').trigger('click');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('embed routes keep the bare shell: no .viewer-shell, no top bar, no outer padding wrapper', async () => {
|
it('embed routes keep the bare shell: no .viewer-shell, no top bar, no outer padding wrapper', async () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user