gbclient/src/mail/components/mail.tsx

245 lines
9.7 KiB
TypeScript
Raw Normal View History

import React, { useState } from 'react';
import { AccountSwitcher } from './account-switcher';
import { MailList } from './mail-list';
import { MailDisplay } from './mail-display';
import { Nav } from './nav';
import { mails, accounts } from '../data';
export function Mail() {
const [isCollapsed, setIsCollapsed] = useState(false);
const [selectedMailId, setSelectedMailId] = useState<string | null>(mails[0].id);
const [activeTab, setActiveTab] = useState('all');
const filteredMails = activeTab === 'all'
? mails
: mails.filter(mail => !mail.read);
// Icons
const ArchiveIcon = () => (
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<rect width="20" height="5" x="2" y="3" rx="1" />
<path d="M4 8v11a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8" />
<path d="M10 12h4" />
</svg>
);
const TrashIcon = () => (
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<path d="M3 6h18" />
<path d="M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6" />
<path d="M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2" />
</svg>
);
const DeleteIcon = () => (
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<path d="M20 6H4" />
<path d="M10 11v6" />
<path d="M14 11v6" />
<path d="M3 6h2l1.5 14a2 2 0 0 0 2 2h9a2 2 0 0 0 2-2L21 6h2" />
</svg>
);
const ClockIcon = () => (
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<circle cx="12" cy="12" r="10" />
<polyline points="12 6 12 12 16 14" />
</svg>
);
const ArrowLeftIcon = () => (
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<path d="m12 19-7-7 7-7" />
<path d="M19 12H5" />
</svg>
);
const ArrowRightIcon = () => (
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<path d="M5 12h14" />
<path d="m12 5 7 7-7 7" />
</svg>
);
const MoreIcon = () => (
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<circle cx="12" cy="12" r="1" />
<circle cx="19" cy="12" r="1" />
<circle cx="5" cy="12" r="1" />
</svg>
);
const SearchIcon = () => (
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<circle cx="11" cy="11" r="8" />
<path d="m21 21-4.3-4.3" />
</svg>
);
const navItems = [
{
title: "Inbox",
label: "128",
icon: (
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<polyline points="22 12 16 12 14 15 10 15 8 12 2 12" />
<path d="M5.45 5.11 2 12v6a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-6l-3.45-6.89A2 2 0 0 0 16.76 4H7.24a2 2 0 0 0-1.79 1.11z" />
</svg>
),
variant: "default" as const,
},
{
title: "Drafts",
label: "9",
icon: (
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7" />
<path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z" />
</svg>
),
variant: "ghost" as const,
},
{
title: "Sent",
icon: (
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<path d="m22 2-7 20-4-9-9-4Z" />
<path d="M22 2 11 13" />
</svg>
),
variant: "ghost" as const,
},
{
title: "Junk",
label: "23",
icon: (
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<rect width="20" height="5" x="2" y="3" rx="1" />
<path d="M4 8v11a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8" />
<path d="M10 12h4" />
</svg>
),
variant: "ghost" as const,
},
{
title: "Trash",
icon: (
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<path d="M3 6h18" />
<path d="M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6" />
<path d="M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2" />
</svg>
),
variant: "ghost" as const,
},
{
title: "Archive",
icon: (
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<rect width="20" height="5" x="2" y="3" rx="1" />
<path d="M4 8v11a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8" />
<path d="M10 12h4" />
</svg>
),
variant: "ghost" as const,
},
];
return (
<div className="flex h-screen bg-white">
{/* Sidebar */}
<div className={`${isCollapsed ? 'w-16' : 'w-64'} border-r flex flex-col transition-all duration-200 bg-white`}>
<div className="p-4 border-b">
<AccountSwitcher isCollapsed={isCollapsed} accounts={accounts} />
</div>
<div className="flex-1 overflow-auto py-2">
<Nav links={navItems} isCollapsed={isCollapsed} />
</div>
<div className="p-4 border-t">
<button
onClick={() => setIsCollapsed(!isCollapsed)}
className="w-full flex items-center justify-center text-sm text-gray-500 hover:text-gray-900"
>
{isCollapsed ? (
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<path d="m9 18 6-6-6-6" />
</svg>
) : (
<div className="flex items-center">
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<path d="m15 18-6-6 6-6" />
</svg>
<span className="ml-2">Collapse</span>
</div>
)}
</button>
</div>
</div>
{/* Mail list */}
<div className="w-80 border-r flex flex-col">
<div className="p-4 border-b flex flex-col gap-4">
<div className="flex justify-between items-center">
<h2 className="text-xl font-semibold">Inbox</h2>
<div className="flex space-x-1">
<button className={`px-3 py-1.5 text-sm rounded-md ${activeTab === 'all' ? 'bg-gray-100 text-gray-900' : 'text-gray-500'}`} onClick={() => setActiveTab('all')}>
All mail
</button>
<button className={`px-3 py-1.5 text-sm rounded-md ${activeTab === 'unread' ? 'bg-gray-100 text-gray-900' : 'text-gray-500'}`} onClick={() => setActiveTab('unread')}>
Unread
</button>
</div>
</div>
<div className="flex relative">
<SearchIcon className="absolute left-3 top-2.5 h-4 w-4 text-gray-500" />
<input
type="text"
placeholder="Search"
className="w-full py-2 pl-9 pr-4 border rounded-md text-sm bg-transparent"
/>
</div>
</div>
<div className="flex gap-1 border-b p-2">
<button className="p-1 rounded-md hover:bg-gray-100">
<ArchiveIcon />
</button>
<button className="p-1 rounded-md hover:bg-gray-100">
<TrashIcon />
</button>
<button className="p-1 rounded-md hover:bg-gray-100">
<DeleteIcon />
</button>
<span className="flex-1"></span>
<button className="p-1 rounded-md hover:bg-gray-100">
<ClockIcon />
</button>
<button className="p-1 rounded-md hover:bg-gray-100">
<ArrowLeftIcon />
</button>
<button className="p-1 rounded-md hover:bg-gray-100">
<ArrowRightIcon />
</button>
<button className="p-1 rounded-md hover:bg-gray-100">
<MoreIcon />
</button>
</div>
<div className="flex-1 overflow-auto">
<MailList
items={filteredMails}
selectedId={selectedMailId}
onSelect={setSelectedMailId}
/>
</div>
</div>
{/* Mail content */}
<div className="flex-1">
<MailDisplay mail={mails.find(mail => mail.id === selectedMailId) || null} />
</div>
</div>
);
}