From c062c615dc0db26c7244d62ee37e60194a46e2b3 Mon Sep 17 00:00:00 2001 From: lsdsjy <1356263+lsdsjy@users.noreply.github.com> Date: Wed, 23 Sep 2026 11:12:06 +0800 Subject: [PATCH] feat(account): align signed-out sidebar menu with design --- .../signed-out-menu.expected.md | 4 +++ apps/web/tests/onboarding-native.e2e.ts | 31 +++++++++++++++++-- .../ui-settings-account/README.i18n.yaml | 4 +-- packages/client/ui-settings-account/README.md | 4 +-- .../client/ui-settings-account/README.zh.md | 4 +-- .../src/client/AccountMenu.module.css | 29 +++++++++++++++++ .../src/client/AccountMenu.tsx | 16 ++++++---- .../ui-settings-account/src/client/locales.ts | 4 +-- .../tests/account.client.spec.tsx | 21 +++++++++++++ .../tests/expected/login-failed-en.txt | 2 +- .../tests/expected/menu-signed-out-en.txt | 1 + .../tests/expected/menu-signed-out-zh.txt | 1 + 12 files changed, 103 insertions(+), 18 deletions(-) create mode 100644 apps/web/tests/expected/onboarding-native/signed-out-menu.expected.md create mode 100644 packages/client/ui-settings-account/tests/expected/menu-signed-out-en.txt create mode 100644 packages/client/ui-settings-account/tests/expected/menu-signed-out-zh.txt diff --git a/apps/web/tests/expected/onboarding-native/signed-out-menu.expected.md b/apps/web/tests/expected/onboarding-native/signed-out-menu.expected.md new file mode 100644 index 0000000000..6fe7a0d67b --- /dev/null +++ b/apps/web/tests/expected/onboarding-native/signed-out-menu.expected.md @@ -0,0 +1,4 @@ +- menu: + - menuitem "设置" + - menuitem "联系我们" + - menuitem "登录" diff --git a/apps/web/tests/onboarding-native.e2e.ts b/apps/web/tests/onboarding-native.e2e.ts index c899288606..c59c7f238a 100644 --- a/apps/web/tests/onboarding-native.e2e.ts +++ b/apps/web/tests/onboarding-native.e2e.ts @@ -11,6 +11,7 @@ import { import { openSettings, ZH_BROWSER_LOCALE, saveFailureShot } from './support.ts' const SNAPSHOT_DIR = fileURLToPath(new URL('./expected/onboarding-native', import.meta.url)) +const SIGNED_OUT_MENU_EXPECTED = join(SNAPSHOT_DIR, 'signed-out-menu.expected.md') const MODE = webSnapshotMode() describe.skipIf(MODE === 'record').each([false, true])('web e2e: native credential onboarding (desktop marker: %s)', (desktop) => { @@ -53,8 +54,32 @@ describe.skipIf(MODE === 'record').each([false, true])('web e2e: native credenti acknowledgeReloadConnectionLoss(tripwire, warningsBefore) } const accountMenu = page.getByRole('button', { name: '账号菜单', exact: true }) - if (desktop) await accountMenu.waitFor() - else { + if (desktop) { + await accountMenu.waitFor() + expect(await accountMenu.textContent()).toBe('更多') + const triggerBox = (await accountMenu.boundingBox())! + expect(Math.abs(triggerBox.height - 32)).toBeLessThan(1) + await accountMenu.click() + const menu = page.getByRole('menu') + await menu.waitFor() + expect(await menu.getByRole('menuitem').allTextContents()).toEqual(['设置', '联系我们', '登录']) + const menuBox = (await menu.boundingBox())! + expect(Math.abs(menuBox.width - 124)).toBeLessThan(1) + expect(Math.abs(menuBox.height - 128)).toBeLessThan(1) + expect(Math.abs(triggerBox.y - (menuBox.y + menuBox.height) - 4)).toBeLessThan(1) + for (const row of await menu.getByRole('menuitem').all()) { + const rowBox = (await row.boundingBox())! + expect(Math.abs(rowBox.height - 40)).toBeLessThan(1) + expect(Math.abs(menuBox.width - rowBox.width - 8)).toBeLessThan(1) + const glyph = (await row.locator('svg').first().boundingBox())! + expect(Math.abs(glyph.width - 16)).toBeLessThan(1) + expect(Math.abs(glyph.height - 16)).toBeLessThan(1) + } + const menuAria = await captureStableAria(page, '[role="menu"]', scaffold.workspaceCwd) + await compareOrRefreshGolden(SIGNED_OUT_MENU_EXPECTED, menuAria, MODE) + await page.keyboard.press('Escape') + await menu.waitFor({ state: 'detached' }) + } else { await page.getByRole('button', { name: '设置', exact: true }).waitFor() expect(await accountMenu.count()).toBe(0) expect(await page.getByRole('dialog', { name: '开始你的创作' }).count()).toBe(0) @@ -72,6 +97,6 @@ describe.skipIf(MODE === 'record').each([false, true])('web e2e: native credenti } expect(tripwire.warnings).toEqual([]) expect(tripwire.pageErrors).toEqual([]) - await assertFixtureInventory(SNAPSHOT_DIR, ['models.expected.md']) + await assertFixtureInventory(SNAPSHOT_DIR, ['models.expected.md', 'signed-out-menu.expected.md']) }) }) diff --git a/packages/client/ui-settings-account/README.i18n.yaml b/packages/client/ui-settings-account/README.i18n.yaml index 5b10d61e19..d2992bfd87 100644 --- a/packages/client/ui-settings-account/README.i18n.yaml +++ b/packages/client/ui-settings-account/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages/client/ui-settings-account/README.md -README.md: 2eb0f56daec2ad66c1407e397e30a872bda8c52e -README.zh.md: f1c1a5b66327d9dee85536afb737efd2afa23acb +README.md: 55a747896f5c115d08ebacea6046b4de711a530c +README.zh.md: 582a1b21113631305eb38bd02a140479704d365d diff --git a/packages/client/ui-settings-account/README.md b/packages/client/ui-settings-account/README.md index 2eb0f56dae..55a747896f 100644 --- a/packages/client/ui-settings-account/README.md +++ b/packages/client/ui-settings-account/README.md @@ -24,7 +24,7 @@ Desktop Account settings display DeepSeek login state and offer browser authoriz The client activates only inside Desktop, identified by its preload bridge. Plain Web clients keep the standard Settings launcher and API-key onboarding without account login, account settings, or an account-state subscription. -The sidebar and Account settings display the profile avatar as a circular image, with the account icon as fallback when the URL is absent or the image fails to load. The collapsed sidebar centers the avatar in a 36 × 36 px button. +The sidebar and Account settings display the profile avatar as a circular image, with the account icon as fallback when the URL is absent or the image fails to load. The collapsed sidebar centers the avatar in a 36 × 36 px button. Before sign-in the sidebar launcher shows a More row instead, leading with the ellipsis glyph. The section registers through settings.section and uses the account Remote namespace. The stream survives carrier reconnects through the shared Remote supervisor. The page renders feature-owned English and Chinese copy and keeps API keys separate from account state. @@ -34,7 +34,7 @@ Desktop usage and top-up actions open an isolated native Platform view below a 4 The account menu's Feedback entry opens the Feishu questionnaire in the system browser. It supplies the available build version, UI locale and physical screen resolution as prefill_* parameters, with hide_*=1 for every context field; the account UID, tokens and contact details are excluded. Configure contactFormUrl on the ui-settings-account plugin to select another HTTPS form. contactSource defaults to empty until the questionnaire supports a Harness source option; OS and device fields remain unfilled, matching the Web implementation. -The sidebar account menu uses the shared Menu surface, backdrop blur, spacing, and row typography; feature styles only size its launcher. +The sidebar account menu uses the shared Menu surface and backdrop blur. The signed-in menu keeps the shared row typography; the signed-out menu carries its own wider rows and its Contact us copy for the Feedback entry. The account card’s More account information link opens `https://platform.deepseek.com` in the system browser. diff --git a/packages/client/ui-settings-account/README.zh.md b/packages/client/ui-settings-account/README.zh.md index f1c1a5b663..582a1b2111 100644 --- a/packages/client/ui-settings-account/README.zh.md +++ b/packages/client/ui-settings-account/README.zh.md @@ -24,7 +24,7 @@ Desktop 设置中的账号页面显示 DeepSeek 登录状态,并提供浏览 客户端仅在具有 preload 桥接的 Desktop 内启用。普通 Web 客户端保留标准设置入口和 API Key 引导,不显示账号登录和账号设置,也不订阅账号状态。 -侧边栏和账号设置以圆形图片显示账号头像;头像 URL 缺失或图片加载失败时使用账号图标兜底。折叠侧边栏将头像居中放置在 36 × 36 px 的按钮内。 +侧边栏和账号设置以圆形图片显示账号头像;头像 URL 缺失或图片加载失败时使用账号图标兜底。折叠侧边栏将头像居中放置在 36 × 36 px 的按钮内。登录前侧边栏入口改为「更多」行,以三点图标开头。 该页面通过 settings.section 注册,并使用 account Remote 命名空间。共享 Remote 管理器负责连接恢复后的流重连。页面使用功能自有的中英文文案,将 API Key 与账号状态分开。 @@ -34,7 +34,7 @@ Desktop 的用量和充值操作在 48px 返回栏下方打开隔离的原生 Pl 账号菜单的“意见反馈”入口在系统浏览器中打开飞书问卷。链接通过 prefill_* 参数填写已有的构建版本、界面语言和屏幕物理分辨率,并为所有上下文字段设置 hide_*=1;不传账号 UID、token 或联系方式。可在 ui-settings-account 插件配置 contactFormUrl,切换到另一个 HTTPS 问卷。问卷支持 Harness 来源选项前,contactSource 默认为空;OS 和设备字段沿用 Web 实现,保持未填写。 -侧边栏账号菜单使用公共 Menu 的表面、背景模糊、间距和菜单项排版;功能样式仅设置入口布局。 +侧边栏账号菜单使用公共 Menu 的表面和背景模糊。已登录菜单沿用公共菜单项排版;未登录菜单使用自己的较宽菜单项,并将“意见反馈”入口的文案改为“联系我们”。 账号卡片的“更多账号信息”链接通过系统浏览器打开 `https://platform.deepseek.com`。 diff --git a/packages/client/ui-settings-account/src/client/AccountMenu.module.css b/packages/client/ui-settings-account/src/client/AccountMenu.module.css index 71a1f38572..33dc01d4df 100644 --- a/packages/client/ui-settings-account/src/client/AccountMenu.module.css +++ b/packages/client/ui-settings-account/src/client/AccountMenu.module.css @@ -6,3 +6,32 @@ .avatar { display: flex; align-items: center; justify-content: center; flex: none; width: 32px; height: 32px; border-radius: 16px; background: var(--dsw-alias-bg-skeleton); color: var(--dsw-alias-label-tertiary); } .label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .error { color: var(--dsw-alias-state-error-primary); font-size: 12px; } + +/* Signed-out launcher: a 32px More row carrying the 14px ellipsis. */ +.trigger[data-signed-out='true']:not([data-collapsed='true']) { height: 32px; padding: 6px 2px 6px 6px; line-height: 20px; } + +/* Signed-out menu card and rows; listClassName reaches the portaled card. */ +.signedOutMenu[role='menu'] { + min-width: 124px; + max-width: 280px; + padding: 4px; + border-radius: 12px; + --dsw-elevation-stroke-color: var(--dsw-alias-border-inverted); +} + +.signedOutMenu [role='menuitem'] { + min-height: 40px; + gap: 8px; + padding: 8px 10px; + border-radius: 10px; + font-size: 14px; + line-height: 22px; +} + +.signedOutMenu [role='menuitem'] > span:first-child:not(:last-child) { + width: 16px; + height: 16px; + color: var(--dsw-alias-label-primary); +} + +.signedOutMenu [role='menuitem'] > span:first-child:not(:last-child) svg { width: 16px; height: 16px; } diff --git a/packages/client/ui-settings-account/src/client/AccountMenu.tsx b/packages/client/ui-settings-account/src/client/AccountMenu.tsx index 2ad41b76a3..641716e16b 100644 --- a/packages/client/ui-settings-account/src/client/AccountMenu.tsx +++ b/packages/client/ui-settings-account/src/client/AccountMenu.tsx @@ -1,6 +1,8 @@ /** Sidebar account launcher and locally authoritative sign-out action. */ import { useEffect, useState } from 'react' -import { Toast, Menu, IconPaperPlaneOutlineMedium, IconSettingsOutlineMedium, IconUserOutlineMedium } from '@deepseek-ai/dsh-client-ui-primitives' +import { + Toast, Menu, IconEllipsisOutlineMedium, IconPaperPlaneOutlineMedium, IconSettingsOutlineMedium, IconUserOutlineMedium, +} from '@deepseek-ai/dsh-client-ui-primitives' import type { InjectFace, PropsLocale, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots' import type { AccountSectionInjected } from './AccountSection.tsx' import { SignOutDialog } from './SignOutDialog.tsx' @@ -41,15 +43,17 @@ export function AccountMenu({ const beginSignIn = (): void => { setOpen(false); void start().catch(() => undefined) } return
{expiryNotice && { setExpiryNotice(false) }} />} - { setOpen(value => !value) }}> - - {wide && {signedIn ? label : t('signedOut')}} + {signedIn + ? + : } + {wide && {signedIn ? label : t('more')}} } items={[ { id: 'settings', label: t('settings'), icon: }, - { id: 'contact', label: t('contactUs'), icon: }, + { id: 'contact', label: signedIn ? t('contactUs') : t('contactUsSignedOut'), icon: }, ...(signedIn ? [{ id: 'signout', label: t('signOut'), icon: , disabled: busy }] : [{ id: 'signin', label: t('signIn'), icon: }]), ]} diff --git a/packages/client/ui-settings-account/src/client/locales.ts b/packages/client/ui-settings-account/src/client/locales.ts index 298475e62a..22a61c95a8 100644 --- a/packages/client/ui-settings-account/src/client/locales.ts +++ b/packages/client/ui-settings-account/src/client/locales.ts @@ -9,7 +9,7 @@ export const en = { failureTitle: 'Could not sign in', platformFailed: 'Could not complete the operation. Try again.', platformRetry: 'Retry', loading: 'Loading…', backToHarness: 'Back to DeepSeek Harness', - settings: 'Settings', contactUs: 'Feedback', menu: 'Account menu', + settings: 'Settings', contactUs: 'Feedback', contactUsSignedOut: 'Contact us', menu: 'Account menu', nav: 'Account', signedIn: 'Signed in to DeepSeek', signedOut: 'Not signed in', signIn: 'Sign in', signOut: 'Sign out', signOutUnknownDescription: 'Could not check running tasks. Signing out may interrupt tasks using this account. Sign out now?', @@ -38,7 +38,7 @@ export const zh: Record = { failureTitle: '登录失败', platformFailed: '操作未完成,请重试', platformRetry: '重试', loading: '加载中…', backToHarness: '返回 DeepSeek Harness', - settings: '设置', contactUs: '意见反馈', menu: '账号菜单', + settings: '设置', contactUs: '意见反馈', contactUsSignedOut: '联系我们', menu: '账号菜单', nav: '账号与余额', signedIn: '已登录 DeepSeek', signedOut: '尚未登录', signIn: '登录', signOut: '退出登录', signOutUnknownDescription: '暂时无法确认任务状态。退出登录可能会中断使用此账号的任务,是否继续?', diff --git a/packages/client/ui-settings-account/tests/account.client.spec.tsx b/packages/client/ui-settings-account/tests/account.client.spec.tsx index ea453dd99d..58fbbd01f4 100644 --- a/packages/client/ui-settings-account/tests/account.client.spec.tsx +++ b/packages/client/ui-settings-account/tests/account.client.spec.tsx @@ -95,6 +95,27 @@ it.each([en, zh].flatMap(copy => ([false, true, 'unknown'] as const).map(running expect(screen.queryByRole('menu')).toBeNull() }) +it.each([en, zh])('offers settings, contact and sign-in from the signed-out account menu', async (copy) => { + const openSettings = vi.fn() + const operations = operationsOf({ status: 'signed-out', attempt: null }) + const { AccountMenu } = await import('../src/client/AccountMenu.tsx') + render( selector(operations.hooks.account.getSnapshot())} + useTheme={selector => selector(operations.hooks.theme.getSnapshot())} wide openOnboarding={() => {}} openSettings={openSettings} + t={key => key in copy ? copy[key as AccountKey] : key} />) + const trigger = screen.getByRole('button', { name: copy.menu }) + expect(trigger.textContent).toBe(copy.more) + expect(trigger.querySelector('svg')).not.toBeNull() + fireEvent.click(trigger) + expect(screen.getAllByRole('menuitem').map(item => item.textContent)).toEqual([copy.settings, copy.contactUsSignedOut, copy.signIn]) + await expect(`${screen.getByRole('menu').textContent}\n`).toMatchFileSnapshot(`./expected/menu-signed-out-${copy === en ? 'en' : 'zh'}.txt`) + fireEvent.click(screen.getByRole('menuitem', { name: copy.settings })) + expect(openSettings).toHaveBeenCalledOnce() + fireEvent.click(screen.getByRole('button', { name: copy.menu })) + fireEvent.click(screen.getByRole('menuitem', { name: copy.contactUsSignedOut })) + expect(operations.contactUs).toHaveBeenCalledOnce() +}) + it('reports a failed start in the login dialog, not as a sidebar alert', async () => { const operations = mount({ status: 'signed-out', attempt: null }) cleanup() diff --git a/packages/client/ui-settings-account/tests/expected/login-failed-en.txt b/packages/client/ui-settings-account/tests/expected/login-failed-en.txt index bc8b5ad67b..3d89b41c4f 100644 --- a/packages/client/ui-settings-account/tests/expected/login-failed-en.txt +++ b/packages/client/ui-settings-account/tests/expected/login-failed-en.txt @@ -1 +1 @@ -Not signed inCould not sign inCould not complete the operation. Try again.Add API KeySign in again +MoreCould not sign inCould not complete the operation. Try again.Add API KeySign in again diff --git a/packages/client/ui-settings-account/tests/expected/menu-signed-out-en.txt b/packages/client/ui-settings-account/tests/expected/menu-signed-out-en.txt new file mode 100644 index 0000000000..9875291a51 --- /dev/null +++ b/packages/client/ui-settings-account/tests/expected/menu-signed-out-en.txt @@ -0,0 +1 @@ +SettingsContact usSign in diff --git a/packages/client/ui-settings-account/tests/expected/menu-signed-out-zh.txt b/packages/client/ui-settings-account/tests/expected/menu-signed-out-zh.txt new file mode 100644 index 0000000000..fa5c8d7512 --- /dev/null +++ b/packages/client/ui-settings-account/tests/expected/menu-signed-out-zh.txt @@ -0,0 +1 @@ +设置联系我们登录