@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.
- package/.storybook/preview.ts +20 -0
- package/.turbo/turbo-typecheck.log +5 -0
- package/ARCHITECTURE.md +415 -0
- package/CHANGELOG.md +80 -0
- package/README.md +205 -0
- package/package.json +43 -0
- package/playwright.config.ts +55 -0
- package/src/components/index.ts +20 -0
- package/src/compose/email-composer.stories.tsx +219 -0
- package/src/compose/email-composer.tsx +619 -0
- package/src/compose/index.ts +14 -0
- package/src/dashboard/index.ts +14 -0
- package/src/dashboard/mail-shell.stories.tsx +272 -0
- package/src/dashboard/mail-shell.tsx +199 -0
- package/src/dashboard/mail-sidebar.stories.tsx +158 -0
- package/src/dashboard/mail-sidebar.tsx +388 -0
- package/src/index.ts +24 -0
- package/src/landing/index.ts +24 -0
- package/src/mail/index.ts +15 -0
- package/src/mail/mail-item.stories.tsx +422 -0
- package/src/mail/mail-item.tsx +229 -0
- package/src/mail/mail-list.stories.tsx +320 -0
- package/src/mail/mail-list.tsx +262 -0
- package/src/mail/message-view.stories.tsx +459 -0
- package/src/mail/message-view.tsx +378 -0
- package/src/mail/thread-display.stories.tsx +260 -0
- package/src/mail/thread-display.tsx +392 -0
- package/src/pages/index.ts +9 -0
- package/src/pages/mail-zero-page.stories.tsx +251 -0
- package/src/pages/mail-zero-page.tsx +334 -0
- package/tests/visual/report/index.html +85 -0
- package/tests/visual/snapshots/zero-components.spec.ts/mail-shell-default.png +0 -0
- package/tests/visual/zero-components.spec.ts +321 -0
- package/tsconfig.json +5 -0
|
@@ -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
|
+
};
|