@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,334 @@
1
+ import type {
2
+ Folder,
3
+ MailLabel as Label,
4
+ MailThread as Thread,
5
+ } from "mdxui";
6
+ import * as React from "react";
7
+ import { MailShell } from "../dashboard/mail-shell";
8
+
9
+ /**
10
+ * MailZeroPage - Complete email page with internal state management.
11
+ *
12
+ * This is a self-contained page component that manages all email interactions
13
+ * internally. In a real app, this would integrate with your email API/service.
14
+ *
15
+ * Features:
16
+ * - Thread navigation (previous/next)
17
+ * - Thread selection and active state
18
+ * - Folder navigation
19
+ * - Star, archive, delete, spam actions
20
+ * - Snooze with preset times
21
+ * - Internal state management
22
+ *
23
+ * @example
24
+ * ```tsx
25
+ * <MailZeroPage
26
+ * initialThreads={threads}
27
+ * initialFolders={folders}
28
+ * user={{ name: 'John', email: 'john@example.com.ai' }}
29
+ * />
30
+ * ```
31
+ */
32
+
33
+ export interface MailZeroPageProps {
34
+ /** Initial threads to display */
35
+ initialThreads: Thread[];
36
+ /** Folders for sidebar */
37
+ initialFolders: Folder[];
38
+ /** Optional labels */
39
+ initialLabels?: Label[];
40
+ /** Initial active folder */
41
+ initialFolderId?: string;
42
+ /** User info for sidebar */
43
+ user?: {
44
+ name: string;
45
+ email: string;
46
+ avatar?: string;
47
+ };
48
+ /** Additional className */
49
+ className?: string;
50
+ }
51
+
52
+ function MailZeroPage({
53
+ initialThreads,
54
+ initialFolders,
55
+ initialLabels = [],
56
+ initialFolderId,
57
+ user,
58
+ className,
59
+ }: MailZeroPageProps) {
60
+ // Thread state
61
+ const [threads, setThreads] = React.useState<Thread[]>(initialThreads);
62
+ const [activeThread, setActiveThread] = React.useState<Thread | undefined>();
63
+ const [activeIndex, setActiveIndex] = React.useState<number>(-1);
64
+ const [selectedThreadIds, setSelectedThreadIds] = React.useState<string[]>(
65
+ [],
66
+ );
67
+
68
+ // Folder state
69
+ const [activeFolderId, setActiveFolderId] = React.useState<
70
+ string | undefined
71
+ >(initialFolderId ?? initialFolders[0]?.id);
72
+
73
+ // Sidebar state
74
+ const [sidebarCollapsed, setSidebarCollapsed] = React.useState(false);
75
+
76
+ // Update threads when initialThreads changes
77
+ React.useEffect(() => {
78
+ setThreads(initialThreads);
79
+ }, [initialThreads]);
80
+
81
+ // Navigation helpers
82
+ const hasPreviousThread = activeIndex > 0;
83
+ const hasNextThread = activeIndex >= 0 && activeIndex < threads.length - 1;
84
+
85
+ // =========================================================================
86
+ // Thread Navigation
87
+ // =========================================================================
88
+
89
+ const handleThreadClick = (thread: Thread) => {
90
+ const index = threads.findIndex((t) => t.id === thread.id);
91
+ setActiveThread(thread);
92
+ setActiveIndex(index);
93
+ };
94
+
95
+ const handlePrevious = () => {
96
+ if (hasPreviousThread) {
97
+ const newIndex = activeIndex - 1;
98
+ setActiveIndex(newIndex);
99
+ setActiveThread(threads[newIndex]);
100
+ }
101
+ };
102
+
103
+ const handleNext = () => {
104
+ if (hasNextThread) {
105
+ const newIndex = activeIndex + 1;
106
+ setActiveIndex(newIndex);
107
+ setActiveThread(threads[newIndex]);
108
+ }
109
+ };
110
+
111
+ const handleClose = () => {
112
+ setActiveThread(undefined);
113
+ setActiveIndex(-1);
114
+ };
115
+
116
+ // =========================================================================
117
+ // Folder Navigation
118
+ // =========================================================================
119
+
120
+ const handleFolderClick = (folderId: string) => {
121
+ setActiveFolderId(folderId);
122
+ setActiveThread(undefined);
123
+ setActiveIndex(-1);
124
+ // In a real app: fetch threads for this folder
125
+ console.log("📂 Folder changed to:", folderId);
126
+ };
127
+
128
+ const handleSelectionChange = (ids: string[]) => {
129
+ setSelectedThreadIds(ids);
130
+ };
131
+
132
+ // =========================================================================
133
+ // Compose Actions
134
+ // =========================================================================
135
+
136
+ const handleCompose = () => {
137
+ // In a real app: open compose modal/page
138
+ console.log("📝 Compose new email");
139
+ };
140
+
141
+ const handleReply = (messageId: string) => {
142
+ // In a real app: open reply composer with messageId context
143
+ console.log("↩️ Reply to message:", messageId);
144
+ };
145
+
146
+ const handleReplyAll = (messageId: string) => {
147
+ // In a real app: open reply-all composer
148
+ console.log("↩️↩️ Reply-all to message:", messageId);
149
+ };
150
+
151
+ const handleForward = (messageId: string) => {
152
+ // In a real app: open forward composer
153
+ console.log("➡️ Forward message:", messageId);
154
+ };
155
+
156
+ // =========================================================================
157
+ // Thread Actions
158
+ // =========================================================================
159
+
160
+ const handleStar = () => {
161
+ if (activeThread) {
162
+ const updated = { ...activeThread, isStarred: !activeThread.isStarred };
163
+ setThreads((prev) =>
164
+ prev.map((t) => (t.id === activeThread.id ? updated : t)),
165
+ );
166
+ setActiveThread(updated);
167
+ console.log("⭐ Toggle star:", activeThread.id);
168
+ }
169
+ };
170
+
171
+ const handleStarThread = (threadId: string) => {
172
+ setThreads((prev) =>
173
+ prev.map((t) =>
174
+ t.id === threadId ? { ...t, isStarred: !t.isStarred } : t,
175
+ ),
176
+ );
177
+ console.log("⭐ Toggle star:", threadId);
178
+ };
179
+
180
+ const handleArchive = () => {
181
+ if (activeThread) {
182
+ const threadId = activeThread.id;
183
+ setThreads((prev) => prev.filter((t) => t.id !== threadId));
184
+ // Navigate to next or close
185
+ if (hasNextThread) {
186
+ const newIndex = activeIndex;
187
+ const remainingThreads = threads.filter((t) => t.id !== threadId);
188
+ setActiveIndex(Math.min(newIndex, remainingThreads.length - 1));
189
+ setActiveThread(remainingThreads[Math.min(newIndex, remainingThreads.length - 1)]);
190
+ } else if (hasPreviousThread) {
191
+ handlePrevious();
192
+ } else {
193
+ handleClose();
194
+ }
195
+ console.log("📦 Archive thread:", threadId);
196
+ }
197
+ };
198
+
199
+ const handleDelete = () => {
200
+ if (activeThread) {
201
+ const threadId = activeThread.id;
202
+ setThreads((prev) => prev.filter((t) => t.id !== threadId));
203
+ if (hasNextThread) {
204
+ const newIndex = activeIndex;
205
+ const remainingThreads = threads.filter((t) => t.id !== threadId);
206
+ setActiveIndex(Math.min(newIndex, remainingThreads.length - 1));
207
+ setActiveThread(remainingThreads[Math.min(newIndex, remainingThreads.length - 1)]);
208
+ } else if (hasPreviousThread) {
209
+ handlePrevious();
210
+ } else {
211
+ handleClose();
212
+ }
213
+ console.log("🗑️ Delete thread:", threadId);
214
+ }
215
+ };
216
+
217
+ const handleSpam = () => {
218
+ if (activeThread) {
219
+ const threadId = activeThread.id;
220
+ setThreads((prev) => prev.filter((t) => t.id !== threadId));
221
+ handleClose();
222
+ console.log("⚠️ Mark spam:", threadId);
223
+ }
224
+ };
225
+
226
+ const handleSnooze = (until: string) => {
227
+ if (activeThread) {
228
+ const threadId = activeThread.id;
229
+ setThreads((prev) => prev.filter((t) => t.id !== threadId));
230
+ handleClose();
231
+ console.log("⏰ Snooze thread:", threadId, "until:", until);
232
+ }
233
+ };
234
+
235
+ const handleMove = (folderId: string) => {
236
+ if (activeThread) {
237
+ const threadId = activeThread.id;
238
+ setThreads((prev) => prev.filter((t) => t.id !== threadId));
239
+ handleClose();
240
+ console.log("📁 Move thread:", threadId, "to:", folderId);
241
+ }
242
+ };
243
+
244
+ const handleLabel = (labelIds: string[]) => {
245
+ if (activeThread) {
246
+ console.log("🏷️ Label thread:", activeThread.id, "with:", labelIds);
247
+ // In a real app: update thread labels
248
+ }
249
+ };
250
+
251
+ const handlePrint = () => {
252
+ window.print();
253
+ };
254
+
255
+ // =========================================================================
256
+ // Label Actions
257
+ // =========================================================================
258
+
259
+ const handleLabelClick = (labelId: string) => {
260
+ console.log("🏷️ Filter by label:", labelId);
261
+ // In a real app: filter threads by label
262
+ };
263
+
264
+ const handleCreateLabel = () => {
265
+ console.log("➕ Create new label");
266
+ // In a real app: open create label dialog
267
+ };
268
+
269
+ // =========================================================================
270
+ // Render
271
+ // =========================================================================
272
+
273
+ return (
274
+ <MailShell
275
+ // Sidebar props
276
+ folders={initialFolders}
277
+ activeFolderId={activeFolderId}
278
+ onFolderClick={handleFolderClick}
279
+ onCompose={handleCompose}
280
+ labels={initialLabels}
281
+ onLabelClick={handleLabelClick}
282
+ onCreateLabel={handleCreateLabel}
283
+ sidebarCollapsed={sidebarCollapsed}
284
+ onToggleSidebar={() => setSidebarCollapsed((prev) => !prev)}
285
+ user={user}
286
+ showUpgrade={false}
287
+ // Mail list props
288
+ threads={threads}
289
+ selectedThreadIds={selectedThreadIds}
290
+ activeThreadId={activeThread?.id}
291
+ selectionMode="single"
292
+ displayMode="comfortable"
293
+ isLoadingThreads={false}
294
+ hasMoreThreads={false}
295
+ onThreadClick={handleThreadClick}
296
+ onThreadSelectionChange={handleSelectionChange}
297
+ onStarThread={handleStarThread}
298
+ enableKeyboardNav={true}
299
+ showAvatars={true}
300
+ showSnippets={true}
301
+ emptyMessage="No messages in this folder"
302
+ // Thread display props
303
+ activeThread={activeThread}
304
+ showNavigation={true}
305
+ showThreadPanel={true}
306
+ hasPreviousThread={hasPreviousThread}
307
+ hasNextThread={hasNextThread}
308
+ onPrevious={handlePrevious}
309
+ onNext={handleNext}
310
+ onClose={handleClose}
311
+ onReply={handleReply}
312
+ onReplyAll={handleReplyAll}
313
+ onForward={handleForward}
314
+ onStar={handleStar}
315
+ onArchive={handleArchive}
316
+ onDelete={handleDelete}
317
+ onSpam={handleSpam}
318
+ onSnooze={handleSnooze}
319
+ onMove={handleMove}
320
+ onLabel={handleLabel}
321
+ onPrint={handlePrint}
322
+ // Layout props
323
+ defaultSidebarWidth={20}
324
+ defaultListWidth={35}
325
+ minSidebarWidth={12}
326
+ maxSidebarWidth={25}
327
+ minListWidth={20}
328
+ maxListWidth={50}
329
+ className={className}
330
+ />
331
+ );
332
+ }
333
+
334
+ export { MailZeroPage };