Email Inbox

A complete email interface with folder navigation, email list, message preview, and compose functionality. Great for email clients and notification centers.

FoldersSearchPreviewActions

Installation

Terminal
ReactTailwind
TSX
npx @uiblox/cli add email-inbox

Preview

Project Update: Q4

S

Sarah Johnson

sarah@company.com

Hi team, I wanted to share the latest updates on our quarterly objectives and the progress we've made...

Source Code

email-inbox.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
"use client"; import { useState } from "react"; import { cn } from "@/lib/utils"; interface Email { id: number; from: string; email: string; subject: string; preview: string; time: string; unread: boolean; starred: boolean; } const emails: Email[] = [ { id: 1, from: "Sarah Johnson", email: "sarah@company.com", subject: "Project Update: Q4 Goals", preview: "Hi team, I wanted to share the latest updates on our quarterly objectives...", time: "10:32 AM", unread: true, starred: true }, { id: 2, from: "GitHub", email: "noreply@github.com", subject: "[PR] Fix authentication bug", preview: "A new pull request has been opened by john-doe: Fixes #123 - Authentication...", time: "9:15 AM", unread: true, starred: false }, { id: 3, from: "Mike Chen", email: "mike@design.co", subject: "Re: Design Review", preview: "Thanks for the feedback! I've made the changes you suggested...", time: "Yesterday", unread: false, starred: false }, { id: 4, from: "Stripe", email: "billing@stripe.com", subject: "Invoice #1234", preview: "Your monthly invoice is ready. Amount due: $99.00...", time: "Yesterday", unread: false, starred: true }, { id: 5, from: "Alex Thompson", email: "alex@startup.io", subject: "Partnership Opportunity", preview: "Hi, I came across your product and would love to discuss...", time: "Dec 18", unread: false, starred: false }, ]; const folders = [ { name: "Inbox", count: 12, icon: "inbox" }, { name: "Starred", count: 5, icon: "star" }, { name: "Sent", count: 0, icon: "send" }, { name: "Drafts", count: 2, icon: "file" }, { name: "Spam", count: 3, icon: "alert" }, { name: "Trash", count: 0, icon: "trash" }, ]; export function EmailInbox() { const [selectedEmail, setSelectedEmail] = useState<Email | null>(null); const [selectedFolder, setSelectedFolder] = useState("Inbox"); return ( <div className="flex h-[600px] bg-white dark:bg-slate-900 rounded-xl border border-slate-200 dark:border-slate-800 overflow-hidden"> {/* Sidebar */} <div className="w-56 border-r border-slate-200 dark:border-slate-800 flex flex-col"> <div className="p-4"> <button className="w-full flex items-center justify-center gap-2 px-4 py-2.5 bg-purple-600 hover:bg-purple-700 text-white rounded-lg font-medium transition-colors"> <PlusIcon /> Compose </button> </div> <nav className="flex-1 px-2"> {folders.map((folder) => ( <button key={folder.name} onClick={() => setSelectedFolder(folder.name)} className={cn( "w-full flex items-center justify-between px-3 py-2 rounded-lg text-sm transition-colors", selectedFolder === folder.name ? "bg-purple-50 dark:bg-purple-900/20 text-purple-600 dark:text-purple-400" : "text-slate-600 dark:text-slate-400 hover:bg-slate-100 dark:hover:bg-slate-800" )} > <span className="flex items-center gap-3"> <FolderIcon name={folder.icon} /> {folder.name} </span> {folder.count > 0 && ( <span className="text-xs bg-slate-200 dark:bg-slate-700 px-2 py-0.5 rounded-full"> {folder.count} </span> )} </button> ))} </nav> </div> {/* Email List */} <div className="w-80 border-r border-slate-200 dark:border-slate-800 flex flex-col"> <div className="p-4 border-b border-slate-200 dark:border-slate-800"> <input type="text" placeholder="Search emails..." className="w-full px-4 py-2 bg-slate-100 dark:bg-slate-800 rounded-lg text-sm outline-none" /> </div> <div className="flex-1 overflow-y-auto"> {emails.map((email) => ( <button key={email.id} onClick={() => setSelectedEmail(email)} className={cn( "w-full text-left px-4 py-3 border-b border-slate-100 dark:border-slate-800 hover:bg-slate-50 dark:hover:bg-slate-800/50 transition-colors", selectedEmail?.id === email.id && "bg-purple-50 dark:bg-purple-900/20", email.unread && "bg-blue-50/50 dark:bg-blue-900/10" )} > <div className="flex items-center justify-between mb-1"> <span className={cn("text-sm", email.unread ? "font-semibold text-slate-900 dark:text-white" : "text-slate-700 dark:text-slate-300")}> {email.from} </span> <span className="text-xs text-slate-500">{email.time}</span> </div> <p className={cn("text-sm truncate", email.unread ? "font-medium text-slate-800 dark:text-slate-200" : "text-slate-600 dark:text-slate-400")}> {email.subject} </p> <p className="text-xs text-slate-500 truncate mt-0.5">{email.preview}</p> </button> ))} </div> </div> {/* Email Content */} <div className="flex-1 flex flex-col"> {selectedEmail ? ( <> <div className="p-4 border-b border-slate-200 dark:border-slate-800 flex items-center justify-between"> <div className="flex items-center gap-4"> <button className="p-2 hover:bg-slate-100 dark:hover:bg-slate-800 rounded-lg"><ArchiveIcon /></button> <button className="p-2 hover:bg-slate-100 dark:hover:bg-slate-800 rounded-lg"><TrashIcon /></button> <button className="p-2 hover:bg-slate-100 dark:hover:bg-slate-800 rounded-lg"><ReplyIcon /></button> </div> </div> <div className="flex-1 p-6 overflow-y-auto"> <h2 className="text-xl font-semibold text-slate-900 dark:text-white mb-4">{selectedEmail.subject}</h2> <div className="flex items-center gap-3 mb-6"> <div className="w-10 h-10 rounded-full bg-gradient-to-br from-purple-500 to-pink-500 flex items-center justify-center text-white font-semibold"> {selectedEmail.from[0]} </div> <div> <p className="font-medium text-slate-900 dark:text-white">{selectedEmail.from}</p> <p className="text-sm text-slate-500">{selectedEmail.email}</p> </div> </div> <div className="prose dark:prose-invert max-w-none"> <p className="text-slate-600 dark:text-slate-400">{selectedEmail.preview}</p> </div> </div> </> ) : ( <div className="flex-1 flex items-center justify-center text-slate-500"> Select an email to read </div> )} </div> </div> ); } // Icon components... function PlusIcon() { return <svg className="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 4v16m8-8H4" /></svg>; } function FolderIcon({ name }: { name: string }) { /* icon logic */ return <svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" /></svg>; } function ArchiveIcon() { return <svg className="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 8h14M5 8a2 2 0 110-4h14a2 2 0 110 4M5 8v10a2 2 0 002 2h10a2 2 0 002-2V8m-9 4h4" /></svg>; } function TrashIcon() { return <svg className="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" /></svg>; } function ReplyIcon() { return <svg className="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M3 10h10a8 8 0 018 8v2M3 10l6 6m-6-6l6-6" /></svg>; }