@mdxui/zero 6.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,388 @@
1
+ import { Avatar, AvatarFallback, AvatarImage } from "@mdxui/primitives/avatar";
2
+ import { Badge } from "@mdxui/primitives/badge";
3
+ import { Button } from "@mdxui/primitives/button";
4
+ import {
5
+ Collapsible,
6
+ CollapsibleContent,
7
+ CollapsibleTrigger,
8
+ } from "@mdxui/primitives/collapsible";
9
+ import { cn } from "@mdxui/primitives/lib/utils";
10
+ import { ScrollArea } from "@mdxui/primitives/scroll-area";
11
+ import { Separator } from "@mdxui/primitives/separator";
12
+ import {
13
+ Tooltip,
14
+ TooltipContent,
15
+ TooltipProvider,
16
+ TooltipTrigger,
17
+ } from "@mdxui/primitives/tooltip";
18
+ import {
19
+ AlertOctagon,
20
+ Archive,
21
+ Calendar,
22
+ ChevronRight,
23
+ Clock,
24
+ File,
25
+ Inbox,
26
+ PenSquare,
27
+ Plus,
28
+ Send,
29
+ Settings,
30
+ Sparkles,
31
+ Star,
32
+ Tag,
33
+ Trash2,
34
+ } from "lucide-react";
35
+ import type { Folder, MailLabel as Label, MailSidebarProps } from "mdxui";
36
+ import * as React from "react";
37
+
38
+ // Default folder icons
39
+ const FOLDER_ICONS: Record<
40
+ string,
41
+ React.ComponentType<{ className?: string }>
42
+ > = {
43
+ inbox: Inbox,
44
+ sent: Send,
45
+ drafts: File,
46
+ spam: AlertOctagon,
47
+ trash: Trash2,
48
+ archive: Archive,
49
+ starred: Star,
50
+ important: Star,
51
+ snoozed: Clock,
52
+ scheduled: Calendar,
53
+ all: Inbox,
54
+ custom: Tag,
55
+ };
56
+
57
+ /**
58
+ * FolderItem - Individual folder in the sidebar.
59
+ */
60
+ function FolderItem({
61
+ folder,
62
+ isActive,
63
+ isCollapsed,
64
+ onClick,
65
+ }: {
66
+ folder: Folder;
67
+ isActive: boolean;
68
+ isCollapsed: boolean;
69
+ onClick?: () => void;
70
+ }) {
71
+ const Icon = FOLDER_ICONS[folder.type] || FOLDER_ICONS.custom;
72
+
73
+ return (
74
+ <TooltipProvider>
75
+ <Tooltip>
76
+ <TooltipTrigger asChild>
77
+ <button
78
+ type="button"
79
+ onClick={onClick}
80
+ className={cn(
81
+ "flex w-full items-center gap-3 rounded-lg px-3 py-2 text-sm transition-colors",
82
+ isActive
83
+ ? "bg-accent text-accent-foreground"
84
+ : "text-muted-foreground hover:bg-accent/50 hover:text-foreground",
85
+ )}
86
+ >
87
+ <Icon className="size-4 shrink-0" />
88
+ {!isCollapsed && (
89
+ <>
90
+ <span className="flex-1 truncate text-left">{folder.name}</span>
91
+ {folder.unreadCount > 0 && (
92
+ <Badge
93
+ variant="secondary"
94
+ className="ml-auto shrink-0 text-xs"
95
+ >
96
+ {folder.unreadCount > 99 ? "99+" : folder.unreadCount}
97
+ </Badge>
98
+ )}
99
+ </>
100
+ )}
101
+ </button>
102
+ </TooltipTrigger>
103
+ {isCollapsed && (
104
+ <TooltipContent side="right">
105
+ {folder.name}
106
+ {folder.unreadCount > 0 && ` (${folder.unreadCount})`}
107
+ </TooltipContent>
108
+ )}
109
+ </Tooltip>
110
+ </TooltipProvider>
111
+ );
112
+ }
113
+
114
+ /**
115
+ * LabelItem - Individual label in the sidebar.
116
+ */
117
+ function LabelItem({
118
+ label,
119
+ isCollapsed,
120
+ onClick,
121
+ }: {
122
+ label: Label;
123
+ isCollapsed: boolean;
124
+ onClick?: () => void;
125
+ }) {
126
+ return (
127
+ <TooltipProvider>
128
+ <Tooltip>
129
+ <TooltipTrigger asChild>
130
+ <button
131
+ type="button"
132
+ onClick={onClick}
133
+ className="text-muted-foreground hover:bg-accent/50 hover:text-foreground flex w-full items-center gap-3 rounded-lg px-3 py-2 text-sm transition-colors"
134
+ >
135
+ <div
136
+ className="size-3 shrink-0 rounded-full"
137
+ style={{ backgroundColor: label.color || "#6b7280" }}
138
+ />
139
+ {!isCollapsed && (
140
+ <span className="flex-1 truncate text-left">{label.name}</span>
141
+ )}
142
+ </button>
143
+ </TooltipTrigger>
144
+ {isCollapsed && (
145
+ <TooltipContent side="right">{label.name}</TooltipContent>
146
+ )}
147
+ </Tooltip>
148
+ </TooltipProvider>
149
+ );
150
+ }
151
+
152
+ /**
153
+ * MailSidebar - Email app sidebar with folder navigation.
154
+ *
155
+ * Features:
156
+ * - Folder navigation with unread counts
157
+ * - Compose button
158
+ * - Labels section
159
+ * - User info
160
+ * - Collapsible state
161
+ * - Upgrade CTA
162
+ */
163
+ function MailSidebar({
164
+ folders,
165
+ activeFolderId,
166
+ onFolderClick,
167
+ onCompose,
168
+ labels,
169
+ onLabelClick,
170
+ onCreateLabel,
171
+ isCollapsed = false,
172
+ onToggleCollapse,
173
+ user,
174
+ showUpgrade = false,
175
+ onUpgradeClick,
176
+ className,
177
+ }: MailSidebarProps) {
178
+ const [labelsExpanded, setLabelsExpanded] = React.useState(true);
179
+
180
+ // Group folders by type
181
+ const primaryFolders = folders.filter((f) =>
182
+ ["inbox", "starred", "snoozed", "sent", "drafts"].includes(f.type),
183
+ );
184
+ const secondaryFolders = folders.filter((f) =>
185
+ ["spam", "trash", "archive", "all"].includes(f.type),
186
+ );
187
+ const customFolders = folders.filter((f) => f.type === "custom");
188
+
189
+ return (
190
+ <div
191
+ data-slot="mail-sidebar"
192
+ className={cn(
193
+ "bg-background flex h-full flex-col border-r transition-all duration-200",
194
+ isCollapsed ? "w-16" : "w-64",
195
+ className,
196
+ )}
197
+ >
198
+ {/* Header */}
199
+ <div className="flex items-center justify-between p-4">
200
+ {!isCollapsed && (
201
+ <div className="flex items-center gap-2">
202
+ <Sparkles className="text-primary size-5" />
203
+ <span className="font-semibold">Zero Mail</span>
204
+ </div>
205
+ )}
206
+ {onToggleCollapse && (
207
+ <Button
208
+ variant="ghost"
209
+ size="icon"
210
+ className="size-8 shrink-0"
211
+ onClick={onToggleCollapse}
212
+ >
213
+ <ChevronRight
214
+ className={cn(
215
+ "size-4 transition-transform",
216
+ !isCollapsed && "rotate-180",
217
+ )}
218
+ />
219
+ </Button>
220
+ )}
221
+ </div>
222
+
223
+ {/* Compose Button */}
224
+ <div className="px-3 pb-4">
225
+ <TooltipProvider>
226
+ <Tooltip>
227
+ <TooltipTrigger asChild>
228
+ <Button
229
+ onClick={onCompose}
230
+ className={cn("w-full", isCollapsed && "px-0")}
231
+ >
232
+ <PenSquare className="size-4" />
233
+ {!isCollapsed && <span className="ml-2">Compose</span>}
234
+ </Button>
235
+ </TooltipTrigger>
236
+ {isCollapsed && (
237
+ <TooltipContent side="right">Compose</TooltipContent>
238
+ )}
239
+ </Tooltip>
240
+ </TooltipProvider>
241
+ </div>
242
+
243
+ {/* Folders */}
244
+ <ScrollArea className="flex-1">
245
+ <div className="space-y-1 px-3">
246
+ {/* Primary Folders */}
247
+ {primaryFolders.map((folder) => (
248
+ <FolderItem
249
+ key={folder.id}
250
+ folder={folder}
251
+ isActive={activeFolderId === folder.id}
252
+ isCollapsed={isCollapsed}
253
+ onClick={() => onFolderClick?.(folder.id)}
254
+ />
255
+ ))}
256
+
257
+ {secondaryFolders.length > 0 && (
258
+ <>
259
+ <Separator className="my-3" />
260
+ {secondaryFolders.map((folder) => (
261
+ <FolderItem
262
+ key={folder.id}
263
+ folder={folder}
264
+ isActive={activeFolderId === folder.id}
265
+ isCollapsed={isCollapsed}
266
+ onClick={() => onFolderClick?.(folder.id)}
267
+ />
268
+ ))}
269
+ </>
270
+ )}
271
+
272
+ {customFolders.length > 0 && (
273
+ <>
274
+ <Separator className="my-3" />
275
+ {customFolders.map((folder) => (
276
+ <FolderItem
277
+ key={folder.id}
278
+ folder={folder}
279
+ isActive={activeFolderId === folder.id}
280
+ isCollapsed={isCollapsed}
281
+ onClick={() => onFolderClick?.(folder.id)}
282
+ />
283
+ ))}
284
+ </>
285
+ )}
286
+
287
+ {/* Labels Section */}
288
+ {labels && labels.length > 0 && (
289
+ <>
290
+ <Separator className="my-3" />
291
+ <Collapsible
292
+ open={labelsExpanded}
293
+ onOpenChange={setLabelsExpanded}
294
+ >
295
+ <CollapsibleTrigger asChild>
296
+ <button
297
+ type="button"
298
+ className="text-muted-foreground hover:text-foreground flex w-full items-center justify-between px-3 py-2 text-xs font-medium uppercase tracking-wider"
299
+ >
300
+ {!isCollapsed && "Labels"}
301
+ <ChevronRight
302
+ className={cn(
303
+ "size-4 transition-transform",
304
+ labelsExpanded && "rotate-90",
305
+ )}
306
+ />
307
+ </button>
308
+ </CollapsibleTrigger>
309
+ <CollapsibleContent className="space-y-1">
310
+ {labels.map((label) => (
311
+ <LabelItem
312
+ key={label.id}
313
+ label={label}
314
+ isCollapsed={isCollapsed}
315
+ onClick={() => onLabelClick?.(label.id)}
316
+ />
317
+ ))}
318
+ {onCreateLabel && !isCollapsed && (
319
+ <button
320
+ type="button"
321
+ onClick={onCreateLabel}
322
+ className="text-muted-foreground hover:text-foreground flex w-full items-center gap-3 px-3 py-2 text-sm"
323
+ >
324
+ <Plus className="size-4" />
325
+ Create label
326
+ </button>
327
+ )}
328
+ </CollapsibleContent>
329
+ </Collapsible>
330
+ </>
331
+ )}
332
+ </div>
333
+ </ScrollArea>
334
+
335
+ {/* Footer */}
336
+ <div className="border-t p-3">
337
+ {/* Upgrade Card */}
338
+ {showUpgrade && !isCollapsed && (
339
+ <button
340
+ type="button"
341
+ onClick={onUpgradeClick}
342
+ className="bg-gradient-to-r from-primary/10 to-primary/5 hover:from-primary/20 hover:to-primary/10 mb-3 w-full rounded-lg p-3 text-left transition-colors"
343
+ >
344
+ <div className="flex items-center gap-2">
345
+ <Sparkles className="text-primary size-4" />
346
+ <span className="text-sm font-medium">Upgrade to Pro</span>
347
+ </div>
348
+ <p className="text-muted-foreground mt-1 text-xs">
349
+ Get AI features and more storage
350
+ </p>
351
+ </button>
352
+ )}
353
+
354
+ {/* User Info */}
355
+ {user && (
356
+ <div className="flex items-center gap-3">
357
+ <Avatar className="size-8">
358
+ <AvatarImage src={user.avatar} alt={user.name} />
359
+ <AvatarFallback>
360
+ {user.name
361
+ .split(" ")
362
+ .map((n) => n[0])
363
+ .join("")
364
+ .toUpperCase()
365
+ .slice(0, 2)}
366
+ </AvatarFallback>
367
+ </Avatar>
368
+ {!isCollapsed && (
369
+ <div className="min-w-0 flex-1">
370
+ <div className="truncate text-sm font-medium">{user.name}</div>
371
+ <div className="text-muted-foreground truncate text-xs">
372
+ {user.email}
373
+ </div>
374
+ </div>
375
+ )}
376
+ {!isCollapsed && (
377
+ <Button variant="ghost" size="icon" className="size-8 shrink-0">
378
+ <Settings className="size-4" />
379
+ </Button>
380
+ )}
381
+ </div>
382
+ )}
383
+ </div>
384
+ </div>
385
+ );
386
+ }
387
+
388
+ export { MailSidebar };
package/src/index.ts ADDED
@@ -0,0 +1,24 @@
1
+ /**
2
+ * @mdxui/zero - Zero Email Components
3
+ *
4
+ * AI-powered email client components for mdxui, ported from github.com/Mail-0/Zero.
5
+ * Provides a complete email client UI including:
6
+ * - Mail list and thread display
7
+ * - Email composition with AI assistance
8
+ * - Landing page components
9
+ * - Dashboard/app shell
10
+ */
11
+
12
+ // Shared UI components
13
+ export * from "./components";
14
+
15
+ // Compose components
16
+ export * from "./compose";
17
+ // Dashboard/shell components
18
+ export * from "./dashboard";
19
+ // Landing page components
20
+ export * from "./landing";
21
+ // Mail components
22
+ export * from "./mail";
23
+ // Page compositions (complete examples with state management)
24
+ export * from "./pages";
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Landing Page Components
3
+ *
4
+ * Marketing and landing page components from 0.email:
5
+ * - ZeroHero: AI-powered email hero section
6
+ * - FeatureTabs: Tab-based feature showcase
7
+ * - SpeedShowcase: Speed/efficiency demo
8
+ * - FeatureCards: Three-column feature grid
9
+ * - ChatDemo: AI chat interface demo
10
+ * - ZeroNavigation: Header navigation
11
+ * - ZeroFooter: Footer component
12
+ */
13
+
14
+ // Components will be added as they are ported from Zero
15
+ // export { ZeroHero } from './zero-hero'
16
+ // export { FeatureTabs } from './feature-tabs'
17
+ // export { SpeedShowcase } from './speed-showcase'
18
+ // export { FeatureCards } from './feature-cards'
19
+ // export { ChatDemo } from './chat-demo'
20
+ // export { ZeroNavigation } from './zero-navigation'
21
+ // export { ZeroFooter } from './zero-footer'
22
+
23
+ // Placeholder export to make this a valid module
24
+ export {};
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Mail Components
3
+ *
4
+ * Core email viewing and management components:
5
+ * - MailList: Virtualized thread list with selection
6
+ * - MailItem: Individual thread item in list
7
+ * - ThreadDisplay: Email thread viewer with actions
8
+ * - MessageView: Individual message rendering
9
+ * - MailSkeleton: Loading states
10
+ */
11
+
12
+ export { MailItem } from "./mail-item";
13
+ export { MailList, MailSkeleton } from "./mail-list";
14
+ export { MessageHeader, MessageView } from "./message-view";
15
+ export { ThreadDisplay } from "./thread-display";