Blox/
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
TSXReactTailwind
npx @uiblox/cli add email-inboxPreview
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
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152"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>; }