136 lines
4.9 KiB
TypeScript
136 lines
4.9 KiB
TypeScript
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>
|
||
);
|
||
}
|