@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,272 @@
1
+ import type { Meta, StoryObj } from "@storybook/react";
2
+ import type { Folder, MailLabel as Label, MailThread as Thread } from "mdxui";
3
+ import { MailShell } from "./mail-shell";
4
+
5
+ // Sample data matching Zero email client
6
+ const sampleFolders: Folder[] = [
7
+ { id: "inbox", name: "Inbox", type: "inbox", unreadCount: 12 },
8
+ { id: "starred", name: "Starred", type: "starred", unreadCount: 0 },
9
+ { id: "snoozed", name: "Snoozed", type: "snoozed", unreadCount: 3 },
10
+ { id: "sent", name: "Sent", type: "sent", unreadCount: 0 },
11
+ { id: "drafts", name: "Drafts", type: "drafts", unreadCount: 2 },
12
+ { id: "spam", name: "Spam", type: "spam", unreadCount: 5 },
13
+ { id: "trash", name: "Trash", type: "trash", unreadCount: 0 },
14
+ ];
15
+
16
+ const sampleLabels: Label[] = [
17
+ { id: "work", name: "Work", color: "#3b82f6", type: "user" },
18
+ { id: "personal", name: "Personal", color: "#10b981", type: "user" },
19
+ { id: "finance", name: "Finance", color: "#f59e0b", type: "user" },
20
+ ];
21
+
22
+ const sampleUser = {
23
+ name: "John Doe",
24
+ email: "john@example.com.ai",
25
+ avatar: "https://api.dicebear.com/7.x/avataaars/svg?seed=John",
26
+ };
27
+
28
+ const sampleThreads: Thread[] = [
29
+ {
30
+ id: "1",
31
+ subject: "Weekly Team Sync - Q4 Planning",
32
+ messages: [
33
+ {
34
+ id: "m1",
35
+ threadId: "1",
36
+ subject: "Weekly Team Sync - Q4 Planning",
37
+ from: { address: "sarah@company.com", name: "Sarah Chen" },
38
+ to: [{ address: "team@company.com", name: "Engineering Team" }],
39
+ date: new Date(Date.now() - 3600000).toISOString(),
40
+ snippet: "Hey team, let's discuss our Q4 roadmap priorities...",
41
+ textBody:
42
+ "Hey team,\n\nLet's discuss our Q4 roadmap priorities. I've prepared a deck with the key initiatives.\n\nTopics:\n1. Customer feedback review\n2. Technical debt allocation\n3. New feature priorities\n\nSee you all at 2pm!\n\nSarah",
43
+ isRead: false,
44
+ isStarred: true,
45
+ isImportant: true,
46
+ isDraft: false,
47
+ attachments: [],
48
+ labels: [],
49
+ },
50
+ ],
51
+ latestDate: new Date(Date.now() - 3600000).toISOString(),
52
+ participants: [
53
+ { address: "sarah@company.com", name: "Sarah Chen" },
54
+ { address: "team@company.com", name: "Engineering Team" },
55
+ ],
56
+ unreadCount: 1,
57
+ labels: [{ id: "work", name: "Work", color: "#3b82f6", type: "user" }],
58
+ isStarred: true,
59
+ isImportant: true,
60
+ hasAttachments: false,
61
+ snippet: "Hey team, let's discuss our Q4 roadmap priorities...",
62
+ },
63
+ {
64
+ id: "2",
65
+ subject: "Re: Design Review - Homepage Redesign",
66
+ messages: [
67
+ {
68
+ id: "m2",
69
+ threadId: "2",
70
+ subject: "Re: Design Review - Homepage Redesign",
71
+ from: { address: "mike@design.co", name: "Mike Johnson" },
72
+ to: [{ address: "me@company.com", name: "Me" }],
73
+ date: new Date(Date.now() - 7200000).toISOString(),
74
+ snippet: "I've updated the mockups based on your feedback...",
75
+ textBody:
76
+ "I've updated the mockups based on your feedback. The new hero section looks much cleaner now.\n\nPlease review the attached files and let me know if we're good to proceed to development.",
77
+ isRead: true,
78
+ isStarred: false,
79
+ isImportant: false,
80
+ isDraft: false,
81
+ attachments: [
82
+ {
83
+ id: "a1",
84
+ filename: "homepage-v3.fig",
85
+ mimeType: "application/figma",
86
+ size: 2400000,
87
+ inline: false,
88
+ },
89
+ ],
90
+ labels: [],
91
+ },
92
+ ],
93
+ latestDate: new Date(Date.now() - 7200000).toISOString(),
94
+ participants: [{ address: "mike@design.co", name: "Mike Johnson" }],
95
+ unreadCount: 0,
96
+ labels: [],
97
+ isStarred: false,
98
+ isImportant: false,
99
+ hasAttachments: true,
100
+ snippet: "I've updated the mockups based on your feedback...",
101
+ },
102
+ {
103
+ id: "3",
104
+ subject: "Invoice #12345 - Payment Confirmation",
105
+ messages: [
106
+ {
107
+ id: "m3",
108
+ threadId: "3",
109
+ subject: "Invoice #12345 - Payment Confirmation",
110
+ from: { address: "billing@stripe.com", name: "Stripe" },
111
+ to: [{ address: "me@company.com", name: "Me" }],
112
+ date: new Date(Date.now() - 86400000).toISOString(),
113
+ snippet: "Your payment of $299.00 has been successfully processed.",
114
+ textBody:
115
+ "Your payment of $299.00 has been successfully processed. Thank you for your business!",
116
+ isRead: true,
117
+ isStarred: false,
118
+ isImportant: false,
119
+ isDraft: false,
120
+ attachments: [],
121
+ labels: [],
122
+ },
123
+ ],
124
+ latestDate: new Date(Date.now() - 86400000).toISOString(),
125
+ participants: [{ address: "billing@stripe.com", name: "Stripe" }],
126
+ unreadCount: 0,
127
+ labels: [
128
+ { id: "finance", name: "Finance", color: "#f59e0b", type: "user" },
129
+ ],
130
+ isStarred: false,
131
+ isImportant: false,
132
+ hasAttachments: false,
133
+ snippet: "Your payment of $299.00 has been successfully processed.",
134
+ },
135
+ ];
136
+
137
+ const meta: Meta<typeof MailShell> = {
138
+ title: "Zero/Dashboard/MailShell",
139
+ component: MailShell,
140
+ parameters: {
141
+ layout: "fullscreen",
142
+ },
143
+ tags: ["autodocs"],
144
+ decorators: [
145
+ (Story) => (
146
+ <div className="h-screen w-full">
147
+ <Story />
148
+ </div>
149
+ ),
150
+ ],
151
+ };
152
+
153
+ export default meta;
154
+ type Story = StoryObj<typeof meta>;
155
+
156
+ /**
157
+ * Complete mail shell layout.
158
+ * Matches the original Zero dashboard with sidebar, list, and thread display.
159
+ */
160
+ export const Default: Story = {
161
+ args: {
162
+ folders: sampleFolders,
163
+ activeFolderId: "inbox",
164
+ labels: sampleLabels,
165
+ user: sampleUser,
166
+ threads: sampleThreads,
167
+ activeThread: sampleThreads[0],
168
+ activeThreadId: "1",
169
+ showUpgrade: true,
170
+ showNavigation: true,
171
+ hasPreviousThread: false,
172
+ hasNextThread: true,
173
+ },
174
+ };
175
+
176
+ /**
177
+ * No thread selected - two panel view.
178
+ */
179
+ export const NoThreadSelected: Story = {
180
+ args: {
181
+ folders: sampleFolders,
182
+ activeFolderId: "inbox",
183
+ labels: sampleLabels,
184
+ user: sampleUser,
185
+ threads: sampleThreads,
186
+ showUpgrade: false,
187
+ showThreadPanel: false,
188
+ },
189
+ };
190
+
191
+ /**
192
+ * Collapsed sidebar.
193
+ */
194
+ export const CollapsedSidebar: Story = {
195
+ args: {
196
+ folders: sampleFolders,
197
+ activeFolderId: "inbox",
198
+ labels: sampleLabels,
199
+ user: sampleUser,
200
+ threads: sampleThreads,
201
+ activeThread: sampleThreads[0],
202
+ activeThreadId: "1",
203
+ sidebarCollapsed: true,
204
+ showUpgrade: false,
205
+ },
206
+ };
207
+
208
+ /**
209
+ * Empty inbox state.
210
+ */
211
+ export const EmptyInbox: Story = {
212
+ args: {
213
+ folders: [
214
+ { id: "inbox", name: "Inbox", type: "inbox", unreadCount: 0 },
215
+ ...sampleFolders.slice(1),
216
+ ],
217
+ activeFolderId: "inbox",
218
+ labels: sampleLabels,
219
+ user: sampleUser,
220
+ threads: [],
221
+ emptyMessage: "Your inbox is empty",
222
+ showUpgrade: false,
223
+ },
224
+ };
225
+
226
+ /**
227
+ * Multiple selection mode.
228
+ */
229
+ export const MultiSelect: Story = {
230
+ args: {
231
+ folders: sampleFolders,
232
+ activeFolderId: "inbox",
233
+ labels: sampleLabels,
234
+ user: sampleUser,
235
+ threads: sampleThreads,
236
+ selectionMode: "multiple",
237
+ selectedThreadIds: ["1", "3"],
238
+ showUpgrade: false,
239
+ },
240
+ };
241
+
242
+ /**
243
+ * Loading threads.
244
+ */
245
+ export const Loading: Story = {
246
+ args: {
247
+ folders: sampleFolders,
248
+ activeFolderId: "inbox",
249
+ labels: sampleLabels,
250
+ user: sampleUser,
251
+ threads: [],
252
+ isLoadingThreads: true,
253
+ showUpgrade: false,
254
+ },
255
+ };
256
+
257
+ /**
258
+ * Compact display mode.
259
+ */
260
+ export const CompactMode: Story = {
261
+ args: {
262
+ folders: sampleFolders,
263
+ activeFolderId: "inbox",
264
+ labels: sampleLabels,
265
+ user: sampleUser,
266
+ threads: sampleThreads,
267
+ displayMode: "compact",
268
+ activeThread: sampleThreads[0],
269
+ activeThreadId: "1",
270
+ showUpgrade: false,
271
+ },
272
+ };
@@ -0,0 +1,199 @@
1
+ import { cn } from "@mdxui/primitives/lib/utils";
2
+ import {
3
+ ResizableHandle,
4
+ ResizablePanel,
5
+ ResizablePanelGroup,
6
+ } from "@mdxui/primitives/resizable";
7
+ import type { MailShellProps, MailThread as Thread } from "mdxui";
8
+ import { MailList } from "../mail/mail-list";
9
+ import { ThreadDisplay } from "../mail/thread-display";
10
+ import { MailSidebar } from "./mail-sidebar";
11
+
12
+ /**
13
+ * MailShell - Complete email app shell layout.
14
+ *
15
+ * Combines:
16
+ * - Sidebar: Folder navigation, labels, compose button
17
+ * - Mail List: Thread list with selection
18
+ * - Thread Display: Email thread viewer
19
+ *
20
+ * Features:
21
+ * - Three-panel resizable layout
22
+ * - Responsive collapse behavior
23
+ * - Panel visibility toggles
24
+ * - Keyboard navigation
25
+ */
26
+ function MailShell({
27
+ // Sidebar props
28
+ folders,
29
+ activeFolderId,
30
+ onFolderClick,
31
+ onCompose,
32
+ labels,
33
+ onLabelClick,
34
+ onCreateLabel,
35
+ sidebarCollapsed = false,
36
+ onToggleSidebar,
37
+ user,
38
+ showUpgrade = false,
39
+ onUpgradeClick,
40
+
41
+ // Mail list props
42
+ threads,
43
+ selectedThreadIds = [],
44
+ activeThreadId,
45
+ selectionMode = "single",
46
+ displayMode = "comfortable",
47
+ isLoadingThreads = false,
48
+ hasMoreThreads = false,
49
+ onThreadClick,
50
+ onThreadSelectionChange,
51
+ onLoadMoreThreads,
52
+ onStarThread,
53
+ enableKeyboardNav = true,
54
+ showAvatars = true,
55
+ showSnippets = true,
56
+ highlightTerms,
57
+ emptyMessage = "No messages",
58
+
59
+ // Thread display props
60
+ activeThread,
61
+ showNavigation = true,
62
+ onPrevious,
63
+ onNext,
64
+ onClose,
65
+ onReply,
66
+ onReplyAll,
67
+ onForward,
68
+ onStar,
69
+ onArchive,
70
+ onDelete,
71
+ onSpam,
72
+ onSnooze,
73
+ onMove,
74
+ onLabel,
75
+ onPrint,
76
+ hasPreviousThread = false,
77
+ hasNextThread = false,
78
+
79
+ // Layout props
80
+ showThreadPanel = true,
81
+ defaultSidebarWidth = 20,
82
+ defaultListWidth = 35,
83
+ minSidebarWidth = 12,
84
+ maxSidebarWidth = 25,
85
+ minListWidth = 20,
86
+ maxListWidth = 50,
87
+ className,
88
+ }: MailShellProps) {
89
+ // Handle thread navigation
90
+ const handleThreadSelect = (thread: Thread) => {
91
+ onThreadClick?.(thread);
92
+ };
93
+
94
+ // Determine if thread panel should be visible
95
+ const showThread = showThreadPanel && activeThread !== undefined;
96
+
97
+ return (
98
+ <div
99
+ data-slot="mail-shell"
100
+ className={cn("bg-background flex h-full", className)}
101
+ >
102
+ <ResizablePanelGroup direction="horizontal" className="h-full">
103
+ {/* Sidebar Panel */}
104
+ <ResizablePanel
105
+ defaultSize={sidebarCollapsed ? 4 : defaultSidebarWidth}
106
+ minSize={sidebarCollapsed ? 4 : minSidebarWidth}
107
+ maxSize={sidebarCollapsed ? 4 : maxSidebarWidth}
108
+ collapsible
109
+ collapsedSize={4}
110
+ onCollapse={() => onToggleSidebar?.()}
111
+ onExpand={() => onToggleSidebar?.()}
112
+ >
113
+ <MailSidebar
114
+ folders={folders}
115
+ activeFolderId={activeFolderId}
116
+ onFolderClick={onFolderClick}
117
+ onCompose={onCompose}
118
+ labels={labels}
119
+ onLabelClick={onLabelClick}
120
+ onCreateLabel={onCreateLabel}
121
+ isCollapsed={sidebarCollapsed}
122
+ onToggleCollapse={onToggleSidebar}
123
+ user={user}
124
+ showUpgrade={showUpgrade}
125
+ onUpgradeClick={onUpgradeClick}
126
+ className="h-full"
127
+ />
128
+ </ResizablePanel>
129
+
130
+ <ResizableHandle withHandle />
131
+
132
+ {/* Mail List Panel */}
133
+ <ResizablePanel
134
+ defaultSize={
135
+ showThread ? defaultListWidth : 100 - defaultSidebarWidth
136
+ }
137
+ minSize={minListWidth}
138
+ maxSize={showThread ? maxListWidth : 100 - minSidebarWidth}
139
+ >
140
+ <MailList
141
+ threads={threads}
142
+ selectedIds={selectedThreadIds}
143
+ activeId={activeThreadId}
144
+ selectionMode={selectionMode}
145
+ displayMode={displayMode}
146
+ isLoading={isLoadingThreads}
147
+ hasMore={hasMoreThreads}
148
+ onThreadClick={handleThreadSelect as () => unknown}
149
+ onSelectionChange={onThreadSelectionChange}
150
+ onLoadMore={onLoadMoreThreads}
151
+ onStar={onStarThread}
152
+ enableKeyboardNav={enableKeyboardNav}
153
+ showAvatars={showAvatars}
154
+ showSnippets={showSnippets}
155
+ highlightTerms={highlightTerms}
156
+ emptyMessage={emptyMessage}
157
+ virtualized={false}
158
+ className="h-full"
159
+ />
160
+ </ResizablePanel>
161
+
162
+ {/* Thread Display Panel */}
163
+ {showThread && (
164
+ <>
165
+ <ResizableHandle withHandle />
166
+ <ResizablePanel
167
+ defaultSize={100 - defaultSidebarWidth - defaultListWidth}
168
+ >
169
+ <ThreadDisplay
170
+ thread={activeThread}
171
+ showNavigation={showNavigation}
172
+ enableAnimations
173
+ onPrevious={onPrevious}
174
+ onNext={onNext}
175
+ onClose={onClose}
176
+ onReply={onReply}
177
+ onReplyAll={onReplyAll}
178
+ onForward={onForward}
179
+ onStar={onStar}
180
+ onArchive={onArchive}
181
+ onDelete={onDelete}
182
+ onSpam={onSpam}
183
+ onSnooze={onSnooze}
184
+ onMove={onMove}
185
+ onLabel={onLabel}
186
+ onPrint={onPrint}
187
+ hasPrevious={hasPreviousThread}
188
+ hasNext={hasNextThread}
189
+ className="h-full"
190
+ />
191
+ </ResizablePanel>
192
+ </>
193
+ )}
194
+ </ResizablePanelGroup>
195
+ </div>
196
+ );
197
+ }
198
+
199
+ export { MailShell };
@@ -0,0 +1,158 @@
1
+ import type { Meta, StoryObj } from "@storybook/react";
2
+ import type { Folder, MailLabel as Label } from "mdxui";
3
+ import { MailSidebar } from "./mail-sidebar";
4
+
5
+ // Sample folders matching Zero email client
6
+ const sampleFolders: Folder[] = [
7
+ { id: "inbox", name: "Inbox", type: "inbox", unreadCount: 12 },
8
+ { id: "starred", name: "Starred", type: "starred", unreadCount: 0 },
9
+ { id: "snoozed", name: "Snoozed", type: "snoozed", unreadCount: 3 },
10
+ { id: "sent", name: "Sent", type: "sent", unreadCount: 0 },
11
+ { id: "drafts", name: "Drafts", type: "drafts", unreadCount: 2 },
12
+ { id: "spam", name: "Spam", type: "spam", unreadCount: 5 },
13
+ { id: "trash", name: "Trash", type: "trash", unreadCount: 0 },
14
+ { id: "archive", name: "All Mail", type: "archive", unreadCount: 0 },
15
+ ];
16
+
17
+ const sampleLabels: Label[] = [
18
+ { id: "work", name: "Work", color: "#3b82f6", type: "user" },
19
+ { id: "personal", name: "Personal", color: "#10b981", type: "user" },
20
+ { id: "finance", name: "Finance", color: "#f59e0b", type: "user" },
21
+ { id: "travel", name: "Travel", color: "#8b5cf6", type: "user" },
22
+ ];
23
+
24
+ const sampleUser = {
25
+ name: "John Doe",
26
+ email: "john@example.com.ai",
27
+ avatar: "https://api.dicebear.com/7.x/avataaars/svg?seed=John",
28
+ };
29
+
30
+ const meta: Meta<typeof MailSidebar> = {
31
+ title: "Zero/Dashboard/MailSidebar",
32
+ component: MailSidebar,
33
+ parameters: {
34
+ layout: "fullscreen",
35
+ },
36
+ tags: ["autodocs"],
37
+ decorators: [
38
+ (Story) => (
39
+ <div className="h-screen w-80 bg-background">
40
+ <Story />
41
+ </div>
42
+ ),
43
+ ],
44
+ };
45
+
46
+ export default meta;
47
+ type Story = StoryObj<typeof meta>;
48
+
49
+ /**
50
+ * Full sidebar with all features.
51
+ * Matches the original Zero mail sidebar.
52
+ */
53
+ export const Default: Story = {
54
+ args: {
55
+ folders: sampleFolders,
56
+ activeFolderId: "inbox",
57
+ labels: sampleLabels,
58
+ user: sampleUser,
59
+ isCollapsed: false,
60
+ showUpgrade: true,
61
+ },
62
+ };
63
+
64
+ /**
65
+ * Collapsed state showing only icons.
66
+ */
67
+ export const Collapsed: Story = {
68
+ args: {
69
+ folders: sampleFolders,
70
+ activeFolderId: "inbox",
71
+ labels: sampleLabels,
72
+ user: sampleUser,
73
+ isCollapsed: true,
74
+ showUpgrade: false,
75
+ },
76
+ decorators: [
77
+ (Story) => (
78
+ <div className="h-screen w-20 bg-background">
79
+ <Story />
80
+ </div>
81
+ ),
82
+ ],
83
+ };
84
+
85
+ /**
86
+ * Without upgrade CTA.
87
+ */
88
+ export const NoUpgrade: Story = {
89
+ args: {
90
+ folders: sampleFolders,
91
+ activeFolderId: "inbox",
92
+ labels: sampleLabels,
93
+ user: sampleUser,
94
+ isCollapsed: false,
95
+ showUpgrade: false,
96
+ },
97
+ };
98
+
99
+ /**
100
+ * Without labels section.
101
+ */
102
+ export const NoLabels: Story = {
103
+ args: {
104
+ folders: sampleFolders,
105
+ activeFolderId: "inbox",
106
+ user: sampleUser,
107
+ isCollapsed: false,
108
+ showUpgrade: false,
109
+ },
110
+ };
111
+
112
+ /**
113
+ * Minimal - no user info or extras.
114
+ */
115
+ export const Minimal: Story = {
116
+ args: {
117
+ folders: sampleFolders,
118
+ activeFolderId: "inbox",
119
+ isCollapsed: false,
120
+ showUpgrade: false,
121
+ },
122
+ };
123
+
124
+ /**
125
+ * Sent folder active.
126
+ */
127
+ export const SentActive: Story = {
128
+ args: {
129
+ folders: sampleFolders,
130
+ activeFolderId: "sent",
131
+ labels: sampleLabels,
132
+ user: sampleUser,
133
+ isCollapsed: false,
134
+ showUpgrade: false,
135
+ },
136
+ };
137
+
138
+ /**
139
+ * With many unread messages.
140
+ */
141
+ export const ManyUnread: Story = {
142
+ args: {
143
+ folders: [
144
+ { id: "inbox", name: "Inbox", type: "inbox", unreadCount: 150 },
145
+ { id: "starred", name: "Starred", type: "starred", unreadCount: 0 },
146
+ { id: "snoozed", name: "Snoozed", type: "snoozed", unreadCount: 25 },
147
+ { id: "sent", name: "Sent", type: "sent", unreadCount: 0 },
148
+ { id: "drafts", name: "Drafts", type: "drafts", unreadCount: 8 },
149
+ { id: "spam", name: "Spam", type: "spam", unreadCount: 47 },
150
+ { id: "trash", name: "Trash", type: "trash", unreadCount: 0 },
151
+ ],
152
+ activeFolderId: "inbox",
153
+ labels: sampleLabels,
154
+ user: sampleUser,
155
+ isCollapsed: false,
156
+ showUpgrade: true,
157
+ },
158
+ };