feat(webui): 导航合并 + 悬浮玻璃 + 页面动画 + 控件档透明度 + 圆角无条件生效
用户四封信(同一线索)提的六件事,都在这里: ## ① 导航合并(「导航栏的内容有点多了」) 收件箱/发件箱/授权 → 一项「通信」,进去由**内部页签**区分(CommTabs); 新建 → 「通信」页内**悬浮圆形加号**(ComposeFab);导航只剩 通信/日历/联系人; 管理 → 合进「我的」,管理员在页面**最下面**看得到(非管理员不渲染,不是禁用)。 `viewMode` 仍是原来那三个值(几十处调用点不用改),新增 `commTab` 记住上次用的子页签; 通信项的徽标 = 未读 + 待决策(授权信息不能因为合并而消失)。 ## ② 窄屏底部导航:悬浮玻璃(「还是固定底部延伸,没有玻璃效果,也没有悬浮」) 原来是 `border-t bg-chrome-900` 通栏贴底。改成:左右/离底各留 10px、圆角、 深色半透明 + `blur(18px)`、投影,安全区并入下外边距。 ## ③ 页面动画(「页面极度缺少动画,所有页面都是直接出现」) 在 `<html>` 上挂一个短命类触发外壳直接子面板的入场动画。**没有用 key 重挂载**: 面板是 flex 链上的一环,套 wrapper 会改掉 flex 传递(窄屏覆盖层最先坏)。 并且尊重 `prefers-reduced-motion`(关了就一点都不动)。 ## ④ 控件档透明度(「复选框和地址猜测项…他们才是真正需要拉低透明度的地方」) 新增第三档 `--bg-glass-control`(0.5/0.55):授权多选胶囊、地址建议菜单。 三档递减:正文面 0.88 > 嵌套 0.82 > 控件 0.5。 ## ⑤ 打底 vs 透明(上一封「该打底的地方透了、该透的空白处糊了」) 正文面回到 0.88(读得清优先)、空白/页面底透明**且不模糊**、面板模糊降到 10px。 ## ⑥ 圆角与玻璃**无条件**生效(「大面积缺失圆角与玻璃效果…都是硬截断」) 根因:`.app-shell` 的留缝/圆角/投影原先全写在 `html[data-bg='on']` 里 ⇒ **没开壁纸的账号**看到的是硬边不透明面板。现在几何下沉为无条件基线, 壁纸相关的加强仍叠加在上面。窄屏内容面板同样浮起来。 ## 判据 - `test/nav-merge.test.mjs` 8 条(含自检:重构前的写法必须判红)—— 改完跑老套件 254 条**全绿却一条都没测导航结构**,结构改动必须自带判据。 - `test/manual/nav-restructure-verify.mjs`:真浏览器 9 条(导航项数、页签切换真的换内容、 加号 56×56 且 `elementFromPoint` 命中自己、我的页底部管理入口在退出登录之后、 控件档 α=0.5 对正文面 α=0.88 的反向对照)。 - `test/manual/narrow-glass-verify.mjs` 6 条:悬浮几何(三边留缝 10px、圆角 14px、 缝隙处命中的是页面底 ⇒ 真的浮着)、玻璃(α=0.82 + blur18)、触摸高度最小 49px、 动画真的在跑(静止时 `animationName=none`,切换后 =pane-in)且 reduced-motion 下不动。 - `test/background.test.mjs`:把三条**过时判据**(早前那版"越透越好")按现行契约重写 —— 旧判据留着只会把我拽回错误方向。30 条全绿。 ## 顺带 窄屏探针原先只调视口、**没模拟触屏**,于是 `(hover: hover)` 仍为真, 把「悬停才显形的次要动作」误报成透明 —— 差点去"修"一个本来就对的规则。 已改成 hasTouch + isMobile + DPR 的触屏上下文,且收尾只关自己的 context (CDP 连的是共享 Chromium,`browser.close()` 会把别人的标签页一起关掉)。
This commit is contained in:
@ -1,7 +1,8 @@
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { useAuthStore } from '../stores/authStore';
|
||||
import * as api from '../api/client';
|
||||
import { LockIcon, LogoutIcon } from './icons';
|
||||
import { LockIcon, LogoutIcon, UsersIcon } from './icons';
|
||||
import { useUIStore } from '../stores/uiStore';
|
||||
import KeyPanel from './KeyPanel';
|
||||
import AccountList from './AccountList';
|
||||
import ThemePicker from './ThemePicker';
|
||||
@ -11,6 +12,7 @@ import BackgroundPicker from './BackgroundPicker';
|
||||
export default function AccountPage() {
|
||||
const user = useAuthStore(s => s.user);
|
||||
const logout = useAuthStore(s => s.logout);
|
||||
const setViewMode = useUIStore(s => s.setViewMode);
|
||||
const [oldPw, setOldPw] = useState('');
|
||||
const [newPw, setNewPw] = useState('');
|
||||
const [confirmPw, setConfirmPw] = useState('');
|
||||
@ -240,6 +242,28 @@ export default function AccountPage() {
|
||||
退出登录
|
||||
</button>
|
||||
</section>
|
||||
|
||||
{/*
|
||||
管理入口(仅管理员可见)—— 用户:「将管理和我的合并,管理员视角我的页面
|
||||
拉到最下面有一个管理」。
|
||||
合并的理由与通信一致:导航栏只留日常动作,低频/高危的入口下沉到「我的」。
|
||||
非管理员看不到这一整块(不是禁用,是不渲染 —— 免得让人以为"我缺权限")。
|
||||
*/}
|
||||
{user?.role === 'admin' && (
|
||||
<section className="border-t border-gray-200 pt-6">
|
||||
<h3 className="text-xs font-medium text-gray-500 mb-3">管理</h3>
|
||||
<button
|
||||
onClick={() => setViewMode('admin')}
|
||||
className="tap inline-flex items-center gap-1.5 px-4 py-2 text-sm font-medium rounded-md border border-gray-300 text-gray-700 hover:bg-gray-50 active:bg-gray-100 transition-colors"
|
||||
>
|
||||
<UsersIcon className="w-4 h-4" />
|
||||
用户管理
|
||||
</button>
|
||||
<p className="mt-2 text-2xs text-gray-400">
|
||||
创建/停用账号、查看配额与密钥。只有管理员看得到这一栏。
|
||||
</p>
|
||||
</section>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -181,7 +181,7 @@ export default function AddressInput({
|
||||
|
||||
{open && items.length > 0 && (
|
||||
<div
|
||||
className={`absolute z-20 w-full overflow-y-auto bg-white border border-gray-200 rounded-lg shadow-2 ${
|
||||
className={`absolute z-20 w-full overflow-y-auto glass-control border border-gray-200 rounded-lg shadow-2 ${
|
||||
menuLayout.flip ? 'bottom-full mb-1' : 'top-full mt-1'
|
||||
}`}
|
||||
style={{ maxHeight: menuLayout.maxHeight }}
|
||||
|
||||
94
client/electron/src/components/CommTabs.tsx
Normal file
94
client/electron/src/components/CommTabs.tsx
Normal file
@ -0,0 +1,94 @@
|
||||
import { countPendingPermissions, splitByPermission } from '../lib/mailGroups';
|
||||
import { useMailStore } from '../stores/mailStore';
|
||||
import { type CommTab, useUIStore } from '../stores/uiStore';
|
||||
import { ComposeIcon } from './icons';
|
||||
|
||||
/*
|
||||
* 「通信」这一个导航项的**内部导航**。
|
||||
*
|
||||
* 用户(2026-09-14):「收件发件授权改为一个导航项,通过内部导航区分,然后新建作为
|
||||
* 他们内部的一个悬浮的圆形加号」。
|
||||
*
|
||||
* 于是收件箱 / 发件箱 / 授权 三个页签落在这里,而不是导航栏里。
|
||||
* 页签上带未读/待决策徽标 —— 合并成一项之后,导航栏上看不到"授权有 3 个在等我"了,
|
||||
* 这个信息不能丢:它是"有人被卡住",比未读更急。
|
||||
*/
|
||||
|
||||
const TABS: { key: CommTab; label: string }[] = [
|
||||
{ key: 'inbox', label: '收件箱' },
|
||||
{ key: 'sent', label: '发件箱' },
|
||||
{ key: 'permissions', label: '授权' }
|
||||
];
|
||||
|
||||
export default function CommTabs() {
|
||||
const commTab = useUIStore(s => s.commTab);
|
||||
const setCommTab = useUIStore(s => s.setCommTab);
|
||||
const inbox = useMailStore(s => s.inbox);
|
||||
|
||||
const { normal, permissions } = splitByPermission(inbox);
|
||||
const unread = normal.filter(m => m.status === 'unread').length;
|
||||
const pendingPerms = countPendingPermissions(permissions);
|
||||
|
||||
return (
|
||||
<div
|
||||
data-testid="comm-tabs"
|
||||
className="shrink-0 flex items-stretch gap-0.5 px-1.5 pt-1.5 border-b border-gray-200"
|
||||
role="tablist"
|
||||
aria-label="通信"
|
||||
>
|
||||
{TABS.map(({ key, label }) => {
|
||||
const on = commTab === key;
|
||||
const badge = key === 'inbox' ? unread : key === 'permissions' ? pendingPerms : 0;
|
||||
return (
|
||||
<button
|
||||
key={key}
|
||||
type="button"
|
||||
role="tab"
|
||||
aria-selected={on}
|
||||
onClick={() => setCommTab(key)}
|
||||
className={`relative px-3 py-1.5 text-xs font-medium rounded-t-md transition-colors ${
|
||||
on
|
||||
? 'bg-white text-gray-900 shadow-sm'
|
||||
: 'text-gray-500 hover:text-gray-800 hover:bg-white/60'
|
||||
}`}
|
||||
>
|
||||
{label}
|
||||
{badge > 0 && (
|
||||
<span
|
||||
className={`ml-1.5 min-w-[16px] h-[16px] px-1 rounded-full text-3xs font-bold inline-flex items-center justify-center align-middle ${
|
||||
key === 'permissions' ? 'bg-orange-700 text-white' : 'bg-red-600 text-white'
|
||||
}`}
|
||||
>
|
||||
{badge > 99 ? '99+' : badge}
|
||||
</span>
|
||||
)}
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 悬浮的圆形加号 —— 「新建」从导航栏搬到这里。
|
||||
*
|
||||
* 放在列表栏的右下角(`absolute`),窄屏时正好在底部导航之上;宽屏时在列表栏内悬浮。
|
||||
* 用圆形而不是胶囊:用户点名要"悬浮的圆形加号",且圆形在窄屏下不会挤压页签。
|
||||
*/
|
||||
export function ComposeFab() {
|
||||
const startCompose = useUIStore(s => s.startCompose);
|
||||
const composing = useUIStore(s => s.composing);
|
||||
if (composing) return null;
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
data-testid="compose-fab"
|
||||
onClick={() => startCompose()}
|
||||
title="新建邮件"
|
||||
aria-label="新建邮件"
|
||||
className="absolute bottom-4 right-4 z-10 w-14 h-14 rounded-full bg-blue-600 text-white shadow-lg flex items-center justify-center hover:bg-blue-700 active:bg-blue-800 transition-colors"
|
||||
>
|
||||
<ComposeIcon className="w-6 h-6" />
|
||||
</button>
|
||||
);
|
||||
}
|
||||
@ -697,7 +697,7 @@ export function PermissionPanel({ mail }: { mail: Mail }) {
|
||||
className={`inline-flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium rounded-md border transition-colors disabled:opacity-40 ${
|
||||
on
|
||||
? 'bg-blue-700 text-white border-blue-700 hover:bg-blue-800'
|
||||
: 'bg-white text-gray-700 border-gray-300 hover:bg-gray-50'
|
||||
: 'glass-control text-gray-700 border-gray-300 hover:bg-gray-50'
|
||||
}`}
|
||||
>
|
||||
{on && <CheckIcon className="w-3.5 h-3.5" />}
|
||||
|
||||
@ -4,12 +4,8 @@ import { useContactStore } from '../stores/contactStore';
|
||||
import { useAuthStore } from '../stores/authStore';
|
||||
import {
|
||||
InboxIcon,
|
||||
SentIcon,
|
||||
ContactsIcon,
|
||||
ComposeIcon,
|
||||
UsersIcon,
|
||||
PersonIcon,
|
||||
ShieldIcon,
|
||||
CalendarIcon
|
||||
} from './icons';
|
||||
import { ConnectionIndicator } from './ConnectionIndicator';
|
||||
@ -28,22 +24,22 @@ import { countPendingPermissions, splitByPermission } from '../lib/mailGroups';
|
||||
const items: {
|
||||
short: string;
|
||||
mode: ViewMode;
|
||||
/** 哪些 viewMode 算这一项选中(「通信」含三个子页签) */
|
||||
modes?: ViewMode[];
|
||||
Icon: (p: { className?: string }) => JSX.Element;
|
||||
adminOnly?: boolean;
|
||||
}[] = [
|
||||
{ short: '收件', mode: 'inbox', Icon: InboxIcon },
|
||||
{ short: '授权', mode: 'permissions', Icon: ShieldIcon },
|
||||
{ short: '发件', mode: 'sent', Icon: SentIcon },
|
||||
{ short: '日历', mode: 'calendar', Icon: CalendarIcon },
|
||||
{ short: '联系人', mode: 'contacts', Icon: ContactsIcon },
|
||||
{ short: '管理', mode: 'admin', Icon: UsersIcon, adminOnly: true }
|
||||
// 与桌面侧栏同一套合并(用户:「导航项就只剩通信,日历,联系人」):
|
||||
// 收件/发件/授权 = 一项「通信」,进去由 CommTabs 区分;管理移到「我的」页底部。
|
||||
{ short: '通信', mode: 'inbox', modes: ['inbox', 'sent', 'permissions'], Icon: InboxIcon },
|
||||
{ short: '日历', mode: 'calendar', Icon: CalendarIcon },
|
||||
{ short: '联系人', mode: 'contacts', Icon: ContactsIcon }
|
||||
];
|
||||
|
||||
export default function NarrowNav() {
|
||||
const viewMode = useUIStore(s => s.viewMode);
|
||||
const setViewMode = useUIStore(s => s.setViewMode);
|
||||
const composing = useUIStore(s => s.composing);
|
||||
const startCompose = useUIStore(s => s.startCompose);
|
||||
const narrowPane = useUIStore(s => s.narrowPane);
|
||||
|
||||
const inbox = useMailStore(s => s.inbox);
|
||||
@ -57,29 +53,32 @@ export default function NarrowNav() {
|
||||
const isAdmin = user?.role === 'admin';
|
||||
|
||||
const visible = items.filter(n => !n.adminOnly || isAdmin);
|
||||
// 点「通信」回到上次用的子页签(与桌面侧栏同一语义)
|
||||
const commTab = useUIStore(s => s.commTab);
|
||||
|
||||
return (
|
||||
<nav
|
||||
className="narrow-nav shrink-0 border-t border-chrome-700 bg-chrome-900 flex items-stretch"
|
||||
// 底部安全区:iPhone 的手势条会盖住最后一排
|
||||
style={{ paddingBottom: 'env(safe-area-inset-bottom)' }}
|
||||
// 悬浮玻璃条的样式全在 index.css 的 .narrow-nav 里(含安全区外边距):
|
||||
// 这里不再写 border-t/bg-chrome-900 —— 那是"贴底通栏"的写法,与悬浮冲突
|
||||
className="narrow-nav shrink-0 flex items-stretch"
|
||||
>
|
||||
{visible.map(({ short, mode, Icon }) => {
|
||||
// 详情栏打开时不高亮任何导航项:此刻用户看的是某封邮件,
|
||||
// 高亮「收件」会让人以为点它能回到列表(其实是同一项)
|
||||
const active = viewMode === mode && !composing && narrowPane === 'list';
|
||||
const badge =
|
||||
mode === 'inbox'
|
||||
? unread
|
||||
: mode === 'permissions'
|
||||
? pendingPerms
|
||||
: mode === 'contacts'
|
||||
? contacts.length
|
||||
: 0;
|
||||
{visible.map(({ short, mode, modes, Icon }) => {
|
||||
const itemModes = modes ?? [mode];
|
||||
// 详情栏打开时不高亮任何导航项:此刻用户看的是某封邮件
|
||||
const active =
|
||||
itemModes.includes(viewMode) && !composing && narrowPane === 'list';
|
||||
// 「通信」合并了三项:徽标 = 未读 + 待决策(授权信息不能因为合并而消失)
|
||||
const isComm = itemModes.includes('inbox');
|
||||
const badge = isComm
|
||||
? unread + pendingPerms
|
||||
: itemModes.includes('contacts')
|
||||
? contacts.length
|
||||
: 0;
|
||||
const badgeTone = isComm && pendingPerms > 0 ? 'perm' : isComm ? 'unread' : 'plain';
|
||||
return (
|
||||
<button
|
||||
key={mode}
|
||||
onClick={() => setViewMode(mode)}
|
||||
key={short}
|
||||
onClick={() => setViewMode(isComm ? commTab : mode)}
|
||||
className={`relative flex-1 py-2 flex flex-col items-center justify-center gap-0.5 transition-colors ${
|
||||
active ? 'text-white' : 'text-chrome-400 active:bg-chrome-800'
|
||||
}`}
|
||||
@ -89,9 +88,9 @@ export default function NarrowNav() {
|
||||
{badge > 0 && (
|
||||
<span
|
||||
className={`absolute top-1 right-[22%] min-w-[15px] h-[15px] px-1 rounded-full text-3xs font-bold flex items-center justify-center ${
|
||||
mode === 'inbox'
|
||||
badgeTone === 'unread'
|
||||
? 'bg-red-600 text-white'
|
||||
: mode === 'permissions'
|
||||
: badgeTone === 'perm'
|
||||
? 'bg-orange-700 text-white'
|
||||
: 'bg-chrome-600 text-chrome-100'
|
||||
}`}
|
||||
@ -103,16 +102,7 @@ export default function NarrowNav() {
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
|
||||
<button
|
||||
onClick={() => startCompose()}
|
||||
className={`flex-1 py-2 flex flex-col items-center justify-center gap-0.5 ${
|
||||
composing ? 'text-blue-300' : 'text-blue-400 active:bg-chrome-800'
|
||||
}`}
|
||||
>
|
||||
<ComposeIcon />
|
||||
<span className="text-3xs leading-none">新建</span>
|
||||
</button>
|
||||
{/* 新建改到列表栏的悬浮圆钮(ComposeFab),与桌面一致 */}
|
||||
|
||||
<button
|
||||
onClick={() => setViewMode('account')}
|
||||
|
||||
@ -4,12 +4,8 @@ import { useContactStore } from '../stores/contactStore';
|
||||
import { useAuthStore } from '../stores/authStore';
|
||||
import {
|
||||
InboxIcon,
|
||||
SentIcon,
|
||||
ContactsIcon,
|
||||
ComposeIcon,
|
||||
UsersIcon,
|
||||
LogoutIcon,
|
||||
ShieldIcon,
|
||||
CalendarIcon,
|
||||
BrandMarkIcon
|
||||
} from './icons';
|
||||
@ -17,29 +13,39 @@ import { ConnectionIndicator } from './ConnectionIndicator';
|
||||
import { ThemeToggleButton } from './ThemePicker';
|
||||
import { countPendingPermissions, splitByPermission } from '../lib/mailGroups';
|
||||
|
||||
/*
|
||||
* 导航项只有三项(2026-09-14 用户:「导航栏的内容有点多了…这样导航项就只剩
|
||||
* 通信,日历,联系人」)。
|
||||
*
|
||||
* 收件箱 / 发件箱 / 授权合并成一项「通信」,进去之后由内部页签(CommTabs)区分;
|
||||
* 「管理」不再单列 —— 它在「我的」页面最下面(管理员才看得到)。
|
||||
*
|
||||
* `modes` 是"哪些 viewMode 算这一项处于选中态",`target` 是"点它去哪":
|
||||
* 通信要回到上次用的那个子页签,而不是永远回收件箱。
|
||||
*/
|
||||
const navItems: {
|
||||
short: string;
|
||||
title: string;
|
||||
mode: ViewMode;
|
||||
modes: ViewMode[];
|
||||
target?: ViewMode;
|
||||
Icon: (p: { className?: string }) => JSX.Element;
|
||||
adminOnly?: boolean;
|
||||
}[] = [
|
||||
{ short: '收件', title: '收件箱', mode: 'inbox', Icon: InboxIcon },
|
||||
// 授权紧跟收件箱:它是收件箱的「要动手」那一半,放在联系人之后会让人找不到
|
||||
{ short: '授权', title: '授权请求', mode: 'permissions', Icon: ShieldIcon },
|
||||
{ short: '发件', title: '发件箱', mode: 'sent', Icon: SentIcon },
|
||||
{
|
||||
short: '通信',
|
||||
title: '通信(收件箱 / 发件箱 / 授权)',
|
||||
modes: ['inbox', 'sent', 'permissions'],
|
||||
Icon: InboxIcon
|
||||
},
|
||||
// 日历排在联系人之前:它是「我要安排什么」,与收发信同属日常动作;
|
||||
// 联系人是「查谁在哪」,用得少
|
||||
{ short: '日历', title: '日历', mode: 'calendar', Icon: CalendarIcon },
|
||||
{ short: '联系', title: '联系人', mode: 'contacts', Icon: ContactsIcon },
|
||||
{ short: '用户', title: '用户管理', mode: 'admin', Icon: UsersIcon, adminOnly: true }
|
||||
{ short: '日历', title: '日历', modes: ['calendar'], Icon: CalendarIcon },
|
||||
{ short: '联系', title: '联系人', modes: ['contacts'], Icon: ContactsIcon }
|
||||
];
|
||||
|
||||
export default function Sidebar() {
|
||||
const viewMode = useUIStore(s => s.viewMode);
|
||||
const setViewMode = useUIStore(s => s.setViewMode);
|
||||
const composing = useUIStore(s => s.composing);
|
||||
const startCompose = useUIStore(s => s.startCompose);
|
||||
|
||||
const inbox = useMailStore(s => s.inbox);
|
||||
// 收件箱的未读数只算普通邮件:权限请求归「授权」那一项,
|
||||
@ -50,8 +56,9 @@ export default function Sidebar() {
|
||||
const contacts = useContactStore(s => s.contacts);
|
||||
|
||||
const user = useAuthStore(s => s.user);
|
||||
// 点「通信」时回到上次用的子页签(见 uiStore.commTab)
|
||||
const commTab = useUIStore(s => s.commTab);
|
||||
const logout = useAuthStore(s => s.logout);
|
||||
const isAdmin = user?.role === 'admin';
|
||||
|
||||
return (
|
||||
<div className="w-[60px] h-full shrink-0 flex flex-col items-center py-3 gap-1 bg-chrome-900"
|
||||
@ -75,22 +82,21 @@ export default function Sidebar() {
|
||||
<BrandMarkIcon className="w-5 h-5" />
|
||||
</button>
|
||||
|
||||
{navItems
|
||||
.filter(n => !n.adminOnly || isAdmin)
|
||||
.map(({ short, title, mode, Icon }) => {
|
||||
const active = viewMode === mode && !composing;
|
||||
const badge =
|
||||
mode === 'inbox'
|
||||
? unread
|
||||
: mode === 'permissions'
|
||||
? pendingPerms
|
||||
: mode === 'contacts'
|
||||
{navItems.map(({ short, title, modes, target, Icon }) => {
|
||||
const active = modes.includes(viewMode) && !composing;
|
||||
// 「通信」把两类"要动手"合起来显示;有人的授权在等我时用橙色优先提示,
|
||||
// 因为那是有人被卡住,未读只是"还没看"。
|
||||
const isComm = modes.includes('inbox');
|
||||
const badge = isComm
|
||||
? unread + pendingPerms
|
||||
: modes.includes('contacts')
|
||||
? contacts.length
|
||||
: 0;
|
||||
return (
|
||||
<button
|
||||
key={mode}
|
||||
onClick={() => setViewMode(mode)}
|
||||
const badgeTone = isComm && pendingPerms > 0 ? 'perm' : isComm ? 'unread' : 'plain';
|
||||
return (
|
||||
<button
|
||||
key={short}
|
||||
onClick={() => setViewMode(target || commTab || modes[0])}
|
||||
title={title}
|
||||
className={`relative w-12 h-12 rounded-lg flex flex-col items-center justify-center gap-0.5 transition-colors ${
|
||||
active
|
||||
@ -103,9 +109,9 @@ export default function Sidebar() {
|
||||
{badge > 0 && (
|
||||
<span
|
||||
className={`absolute top-0.5 right-1 min-w-[15px] h-[15px] px-1 rounded-full text-3xs font-bold flex items-center justify-center ${
|
||||
mode === 'inbox'
|
||||
badgeTone === 'unread'
|
||||
? 'bg-red-600 text-white'
|
||||
: mode === 'permissions'
|
||||
: badgeTone === 'perm'
|
||||
? // 待决策的授权用橙色:它跟未读不是一类紧急 ——
|
||||
// 未读是「有内容没看」,待决策是「有 Agent 卡在那儿等我」
|
||||
'bg-orange-700 text-white'
|
||||
@ -120,17 +126,10 @@ export default function Sidebar() {
|
||||
})}
|
||||
|
||||
<div className="flex-1" />
|
||||
|
||||
<button
|
||||
onClick={() => startCompose()}
|
||||
title="新建邮件"
|
||||
className={`w-12 h-12 rounded-lg flex flex-col items-center justify-center gap-0.5 text-white transition-colors ${
|
||||
composing ? 'bg-blue-700 ring-2 ring-blue-300' : 'bg-blue-600 hover:bg-blue-700'
|
||||
}`}
|
||||
>
|
||||
<ComposeIcon />
|
||||
<span className="text-3xs leading-none">新建</span>
|
||||
</button>
|
||||
{/*
|
||||
这里原本是「新建」导航项。用户要求把它变成「通信」页内悬浮的圆形加号
|
||||
(见 App 里的 ComposeFab),导航栏就只剩三项。
|
||||
*/}
|
||||
|
||||
<div className="mt-2 pt-2 w-full flex flex-col items-center gap-1 border-t border-chrome-700">
|
||||
<button
|
||||
|
||||
Reference in New Issue
Block a user