Files
MailUI4Agents/client/electron/src/components/NarrowNav.tsx

136 lines
4.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { useUIStore, type ViewMode } from '../stores/uiStore';
import { useMailStore } from '../stores/mailStore';
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';
import { countPendingPermissions, splitByPermission } from '../lib/mailGroups';
/**
* 窄屏底部导航。
*
* 移动端把主导航放底部而不是顶部:拇指够得到。
* 宽屏用的是左侧竖条(Sidebar),两者共用 uiStore 的 viewMode,
* 所以从窄拖到宽不会丢失当前位置。
*
* 这里只放最常用的几项 + 一个「更多」入口(打开抽屉式 Sidebar)——
* 底部塞满图标会挤成一排看不懂的小方块。
*/
const items: {
short: string;
mode: 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 }
];
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);
// 与 Sidebar 同一判据:权限请求归「授权」,不算进收件箱未读
const { normal, permissions } = splitByPermission(inbox);
const unread = normal.filter(m => m.status === 'unread').length;
const pendingPerms = countPendingPermissions(permissions);
const contacts = useContactStore(s => s.contacts);
const user = useAuthStore(s => s.user);
const isAdmin = user?.role === 'admin';
const visible = items.filter(n => !n.adminOnly || isAdmin);
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)' }}
>
{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;
return (
<button
key={mode}
onClick={() => setViewMode(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'
}`}
>
<Icon />
<span className="text-[10px] leading-none">{short}</span>
{badge > 0 && (
<span
className={`absolute top-1 right-[22%] min-w-[15px] h-[15px] px-1 rounded-full text-[9px] font-bold flex items-center justify-center ${
mode === 'inbox'
? 'bg-red-600 text-white'
: mode === 'permissions'
? 'bg-orange-700 text-white'
: 'bg-chrome-600 text-chrome-100'
}`}
>
{badge > 99 ? '99+' : badge}
</span>
)}
{active && <span className="absolute top-0 left-1/4 right-1/4 h-0.5 bg-blue-400" />}
</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-[10px] leading-none">新建</span>
</button>
<button
onClick={() => setViewMode('account')}
className={`flex-1 py-2 flex flex-col items-center justify-center gap-0.5 ${
viewMode === 'account' && !composing
? 'text-white'
: 'text-chrome-400 active:bg-chrome-800'
}`}
>
<div className="relative">
<PersonIcon />
<span className="absolute -top-0.5 -right-1.5">
<ConnectionIndicator />
</span>
</div>
<span className="text-[10px] leading-none">我的</span>
</button>
</nav>
);
}