diff --git a/src/components/mail/tray/MessageCheap.tsx b/src/components/mail/tray/MessageCheap.tsx
new file mode 100644
index 0000000..61357c8
--- /dev/null
+++ b/src/components/mail/tray/MessageCheap.tsx
@@ -0,0 +1,48 @@
+import { Avatar } from '@/components/avatar';
+
+interface MessageCheapProps {
+ email: {
+ id: string;
+ from: {
+ name: string;
+ avatar: string;
+ };
+ subject: string;
+ createdAt: string;
+ body: string;
+ read: boolean;
+ };
+ active?: boolean;
+ selected?: boolean;
+ onClick: (id: string) => void;
+}
+
+export const MessageCheap = ({ email, active, selected, onClick }: MessageCheapProps) => {
+ const isHighlighted = active || selected;
+
+ return (
+
+ );
+};
diff --git a/src/components/mail/tray/MessageCheapSkeleton.tsx b/src/components/mail/tray/MessageCheapSkeleton.tsx
new file mode 100644
index 0000000..e6779ff
--- /dev/null
+++ b/src/components/mail/tray/MessageCheapSkeleton.tsx
@@ -0,0 +1,19 @@
+export const MessageCheapSkeleton = () => (
+
+
+ {/* Avatar */}
+
+
+ {/* Name and date */}
+
+ {/* Subject */}
+
+ {/* Body */}
+
+
+
+
+);
diff --git a/src/components/mail/tray/TrayList.tsx b/src/components/mail/tray/TrayList.tsx
new file mode 100644
index 0000000..13f42aa
--- /dev/null
+++ b/src/components/mail/tray/TrayList.tsx
@@ -0,0 +1,110 @@
+import { InfiniteScroll } from '@/components/infiniteScroll';
+import { MessageCheapSkeleton } from './MessageCheapSkeleton';
+import { MessageCheap } from './MessageCheap';
+import { ReactNode } from 'react';
+
+interface TrayListProps {
+ mails: {
+ id: string;
+ from: {
+ name: string;
+ avatar: string;
+ };
+ subject: string;
+ createdAt: string;
+ body: string;
+ read: boolean;
+ }[];
+ selectedEmails?: string[];
+ loading: boolean;
+ checked: boolean;
+ activeEmail: string;
+ hasMoreItems?: boolean;
+ emptyState?: ReactNode;
+ onMailSelected: (id: string) => void;
+ onLoadMore?: () => void;
+}
+
+/**
+ *
+ * @param {TrayListProps} TrayListProps - Props for the TrayList component
+ * @prop {Array} TrayListProps.mails - An array of email objects
+ *
+ * @prop {string[]} TrayListProps.selectedEmails - An array of selected email IDs
+ *
+ * @prop {boolean} TrayListProps.loading - A boolean indicating loading state
+ *
+ * @prop {boolean} TrayListProps.checked - A boolean indicating whether all emails are checked
+ *
+ * @prop {string} TrayListProps.activeEmail - The ID of the currently active email
+ *
+ * @prop {boolean} TrayListProps.hasMoreItems - A boolean indicating whether there are more items to load
+ *
+ * @prop {ReactNode} TrayListProps.emptyState - A JSX element to display when there are no emails
+ *
+ * @prop {(id: string) => void} TrayListProps.onMailSelected - A function to handle email selection
+ *
+ * @prop {() => void} TrayListProps.onLoadMore - A function to load more emails
+ *
+ * @returns {JSX.Element} The rendered TrayList component
+ */
+
+export const TrayList = ({
+ mails,
+ selectedEmails = [],
+ loading,
+ checked,
+ activeEmail,
+ hasMoreItems = false,
+ emptyState,
+ onMailSelected,
+ onLoadMore,
+}: TrayListProps) => {
+ const loader = (
+
+ {new Array(3).fill(0).map((_, index) => (
+
+ ))}
+
+ );
+
+ return (
+
+ );
+};
diff --git a/src/components/mail/tray/__test__/MessageCheap.test.tsx b/src/components/mail/tray/__test__/MessageCheap.test.tsx
new file mode 100644
index 0000000..adc886e
--- /dev/null
+++ b/src/components/mail/tray/__test__/MessageCheap.test.tsx
@@ -0,0 +1,94 @@
+import React from 'react';
+import { render, fireEvent, screen } from '@testing-library/react';
+import { describe, it, expect, vi, afterEach } from 'vitest';
+import { MessageCheap } from '../MessageCheap';
+
+const mockEmail = {
+ id: '1',
+ from: {
+ name: 'John Doe',
+ avatar: 'https://example.com/avatar.jpg',
+ },
+ subject: 'Test Subject',
+ createdAt: '2024-01-15',
+ body: 'This is a test email body',
+ read: false,
+};
+
+const mockOnClick = vi.fn();
+
+const renderMessageCheap = (props = {}) =>
+ render();
+
+describe('MessageCheap', () => {
+ afterEach(() => {
+ vi.clearAllMocks();
+ });
+
+ it('should match snapshot', () => {
+ const messageCheap = renderMessageCheap();
+ expect(messageCheap).toMatchSnapshot();
+ });
+
+ it('should render email details correctly', () => {
+ renderMessageCheap();
+
+ expect(screen.getByText('John Doe')).toBeInTheDocument();
+ expect(screen.getByText('Test Subject')).toBeInTheDocument();
+ expect(screen.getByText('This is a test email body')).toBeInTheDocument();
+ expect(screen.getByText('2024-01-15')).toBeInTheDocument();
+ });
+
+ it('should call onClick with email id when clicked', () => {
+ renderMessageCheap();
+
+ const button = screen.getByRole('button');
+ fireEvent.click(button);
+
+ expect(mockOnClick).toHaveBeenCalledTimes(1);
+ expect(mockOnClick).toHaveBeenCalledWith('1');
+ });
+
+ it('should show unread indicator for unread emails', () => {
+ const { container } = renderMessageCheap();
+
+ const unreadIndicator = container.querySelector('.bg-primary');
+ expect(unreadIndicator).toBeInTheDocument();
+ });
+
+ it('should not show unread indicator for read emails', () => {
+ const readEmail = { ...mockEmail, read: true };
+ const { container } = render();
+
+ const unreadIndicator = container.querySelector('.bg-primary.h-2.w-2');
+ expect(unreadIndicator).not.toBeInTheDocument();
+ });
+
+ it('should apply highlighted styles when active', () => {
+ const { container } = renderMessageCheap({ active: true });
+
+ const button = container.querySelector('button');
+ expect(button?.className).toContain('bg-primary/10');
+ });
+
+ it('should apply highlighted styles when selected', () => {
+ const { container } = renderMessageCheap({ selected: true });
+
+ const button = container.querySelector('button');
+ expect(button?.className).toContain('bg-primary/10');
+ });
+
+ it('should not apply highlighted styles when neither active nor selected', () => {
+ const { container } = renderMessageCheap({ active: false, selected: false });
+
+ const button = container.querySelector('button');
+ expect(button?.className).not.toContain('bg-primary/10');
+ });
+
+ it('should render Avatar component with correct props', () => {
+ renderMessageCheap();
+
+ const avatarContainer = screen.getByText('John Doe').closest('.flex.flex-row');
+ expect(avatarContainer).toBeInTheDocument();
+ });
+});
diff --git a/src/components/mail/tray/__test__/MessageCheapSkeleton.test.tsx b/src/components/mail/tray/__test__/MessageCheapSkeleton.test.tsx
new file mode 100644
index 0000000..f6d6c08
--- /dev/null
+++ b/src/components/mail/tray/__test__/MessageCheapSkeleton.test.tsx
@@ -0,0 +1,26 @@
+import React from 'react';
+import { render } from '@testing-library/react';
+import { describe, it, expect } from 'vitest';
+import { MessageCheapSkeleton } from '../MessageCheapSkeleton';
+
+describe('MessageCheapSkeleton', () => {
+ it('should match snapshot', () => {
+ const skeleton = render();
+ expect(skeleton).toMatchSnapshot();
+ });
+
+ it('should render skeleton structure', () => {
+ const { container } = render();
+
+ const skeletonElements = container.querySelectorAll('.animate-pulse');
+ expect(skeletonElements.length).toBeGreaterThan(0);
+ });
+
+ it('should have proper border styling', () => {
+ const { container } = render();
+
+ const wrapper = container.firstChild as HTMLElement;
+ expect(wrapper.className).toContain('border-b');
+ expect(wrapper.className).toContain('border-gray-5');
+ });
+});
diff --git a/src/components/mail/tray/__test__/TrayList.test.tsx b/src/components/mail/tray/__test__/TrayList.test.tsx
new file mode 100644
index 0000000..17c2b11
--- /dev/null
+++ b/src/components/mail/tray/__test__/TrayList.test.tsx
@@ -0,0 +1,185 @@
+/* eslint-disable @typescript-eslint/no-explicit-any */
+import React from 'react';
+import { render, screen, fireEvent } from '@testing-library/react';
+import { describe, it, expect, vi, afterEach, beforeAll, afterAll } from 'vitest';
+import { TrayList } from '../TrayList';
+
+const mockMails = [
+ {
+ id: '1',
+ from: {
+ name: 'John Doe',
+ avatar: 'https://example.com/avatar1.jpg',
+ },
+ subject: 'Test Subject 1',
+ createdAt: '2024-01-15',
+ body: 'This is test email 1',
+ read: false,
+ },
+ {
+ id: '2',
+ from: {
+ name: 'Jane Smith',
+ avatar: 'https://example.com/avatar2.jpg',
+ },
+ subject: 'Test Subject 2',
+ createdAt: '2024-01-16',
+ body: 'This is test email 2',
+ read: true,
+ },
+];
+
+const mockOnMailSelected = vi.fn();
+const mockOnLoadMore = vi.fn();
+
+const renderTrayList = (props = {}) =>
+ render(
+ ,
+ );
+
+describe('TrayList', () => {
+ beforeAll(() => {
+ (globalThis as any).IntersectionObserver = class {
+ observe() {
+ return null;
+ }
+ unobserve() {
+ return null;
+ }
+ disconnect() {
+ return null;
+ }
+ };
+ });
+
+ afterEach(() => {
+ vi.clearAllMocks();
+ });
+
+ afterAll(() => {
+ delete (globalThis as any).IntersectionObserver;
+ });
+
+ it('should match snapshot', () => {
+ const trayList = renderTrayList();
+ expect(trayList).toMatchSnapshot();
+ });
+
+ it('should render list of emails', () => {
+ renderTrayList();
+
+ expect(screen.getByText('John Doe')).toBeInTheDocument();
+ expect(screen.getByText('Test Subject 1')).toBeInTheDocument();
+ expect(screen.getByText('Jane Smith')).toBeInTheDocument();
+ expect(screen.getByText('Test Subject 2')).toBeInTheDocument();
+ });
+
+ it('should show loading skeletons when loading is true', () => {
+ const { container } = renderTrayList({ loading: true });
+
+ const skeletons = container.querySelectorAll('.animate-pulse');
+ expect(skeletons.length).toBeGreaterThan(0);
+ });
+
+ it('should not show emails when loading', () => {
+ renderTrayList({ loading: true });
+
+ expect(screen.queryByText('John Doe')).not.toBeInTheDocument();
+ expect(screen.queryByText('Jane Smith')).not.toBeInTheDocument();
+ });
+
+ it('should show empty state when no emails and not loading', () => {
+ const emptyState = No emails found
;
+ renderTrayList({ mails: [], loading: false, emptyState });
+
+ expect(screen.getByText('No emails found')).toBeInTheDocument();
+ });
+
+ it('should not show empty state when there are emails', () => {
+ const emptyState = No emails found
;
+ renderTrayList({ emptyState });
+
+ expect(screen.queryByText('No emails found')).not.toBeInTheDocument();
+ });
+
+ it('should call onMailSelected when an email is clicked', () => {
+ renderTrayList();
+
+ const emailButtons = screen.getAllByRole('button');
+ fireEvent.click(emailButtons[0]);
+
+ expect(mockOnMailSelected).toHaveBeenCalledTimes(1);
+ expect(mockOnMailSelected).toHaveBeenCalledWith('1');
+ });
+
+ it('should highlight active email', () => {
+ const { container } = renderTrayList({ activeEmail: '1' });
+
+ const buttons = container.querySelectorAll('button');
+ const activeButton = buttons[0];
+ expect(activeButton.className).toContain('bg-primary/10');
+ });
+
+ it('should highlight selected emails', () => {
+ const { container } = renderTrayList({ selectedEmails: ['2'] });
+
+ const buttons = container.querySelectorAll('button');
+ const selectedButton = buttons[1];
+ expect(selectedButton.className).toContain('bg-primary/10');
+ });
+
+ it('should highlight all emails when checked is true', () => {
+ const { container } = renderTrayList({ checked: true });
+
+ const buttons = container.querySelectorAll('button');
+ buttons.forEach((button) => {
+ expect(button.className).toContain('bg-primary/10');
+ });
+ });
+
+ it('should render with correct container dimensions', () => {
+ const { container } = renderTrayList();
+
+ const mainContainer = container.firstChild as HTMLElement;
+ expect(mainContainer.className).toContain('w-[400px]');
+ expect(mainContainer.className).toContain('min-w-[200px]');
+ expect(mainContainer.className).toContain('max-w-[400px]');
+ expect(mainContainer.className).toContain('h-screen');
+ });
+
+ it('should have scrollable container with correct id', () => {
+ const { container } = renderTrayList();
+
+ const scrollContainer = container.querySelector('#tray-scroll-container');
+ expect(scrollContainer).toBeInTheDocument();
+ expect(scrollContainer?.className).toContain('overflow-y-auto');
+ });
+
+ it('should pass hasMoreItems to InfiniteScroll', () => {
+ renderTrayList({ hasMoreItems: true, onLoadMore: mockOnLoadMore });
+
+ expect(screen.getByText('John Doe')).toBeInTheDocument();
+ });
+
+ it('should default selectedEmails to empty array', () => {
+ const { container } = renderTrayList({ selectedEmails: undefined });
+
+ const buttons = container.querySelectorAll('button');
+ buttons.forEach((button) => {
+ expect(button.className).not.toContain('bg-primary/10');
+ });
+ });
+
+ it('should default hasMoreItems to false', () => {
+ renderTrayList({ hasMoreItems: undefined });
+
+ expect(screen.getByText('John Doe')).toBeInTheDocument();
+ });
+});
diff --git a/src/components/mail/tray/__test__/__snapshots__/MessageCheap.test.tsx.snap b/src/components/mail/tray/__test__/__snapshots__/MessageCheap.test.tsx.snap
new file mode 100644
index 0000000..6e329bd
--- /dev/null
+++ b/src/components/mail/tray/__test__/__snapshots__/MessageCheap.test.tsx.snap
@@ -0,0 +1,166 @@
+// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
+
+exports[`MessageCheap > should match snapshot 1`] = `
+{
+ "asFragment": [Function],
+ "baseElement":
+
+
+
+ ,
+ "container":
+
+
,
+ "debug": [Function],
+ "findAllByAltText": [Function],
+ "findAllByDisplayValue": [Function],
+ "findAllByLabelText": [Function],
+ "findAllByPlaceholderText": [Function],
+ "findAllByRole": [Function],
+ "findAllByTestId": [Function],
+ "findAllByText": [Function],
+ "findAllByTitle": [Function],
+ "findByAltText": [Function],
+ "findByDisplayValue": [Function],
+ "findByLabelText": [Function],
+ "findByPlaceholderText": [Function],
+ "findByRole": [Function],
+ "findByTestId": [Function],
+ "findByText": [Function],
+ "findByTitle": [Function],
+ "getAllByAltText": [Function],
+ "getAllByDisplayValue": [Function],
+ "getAllByLabelText": [Function],
+ "getAllByPlaceholderText": [Function],
+ "getAllByRole": [Function],
+ "getAllByTestId": [Function],
+ "getAllByText": [Function],
+ "getAllByTitle": [Function],
+ "getByAltText": [Function],
+ "getByDisplayValue": [Function],
+ "getByLabelText": [Function],
+ "getByPlaceholderText": [Function],
+ "getByRole": [Function],
+ "getByTestId": [Function],
+ "getByText": [Function],
+ "getByTitle": [Function],
+ "queryAllByAltText": [Function],
+ "queryAllByDisplayValue": [Function],
+ "queryAllByLabelText": [Function],
+ "queryAllByPlaceholderText": [Function],
+ "queryAllByRole": [Function],
+ "queryAllByTestId": [Function],
+ "queryAllByText": [Function],
+ "queryAllByTitle": [Function],
+ "queryByAltText": [Function],
+ "queryByDisplayValue": [Function],
+ "queryByLabelText": [Function],
+ "queryByPlaceholderText": [Function],
+ "queryByRole": [Function],
+ "queryByTestId": [Function],
+ "queryByText": [Function],
+ "queryByTitle": [Function],
+ "rerender": [Function],
+ "unmount": [Function],
+}
+`;
diff --git a/src/components/mail/tray/__test__/__snapshots__/MessageCheapSkeleton.test.tsx.snap b/src/components/mail/tray/__test__/__snapshots__/MessageCheapSkeleton.test.tsx.snap
new file mode 100644
index 0000000..94b16c6
--- /dev/null
+++ b/src/components/mail/tray/__test__/__snapshots__/MessageCheapSkeleton.test.tsx.snap
@@ -0,0 +1,126 @@
+// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
+
+exports[`MessageCheapSkeleton > should match snapshot 1`] = `
+{
+ "asFragment": [Function],
+ "baseElement":
+
+ ,
+ "container": ,
+ "debug": [Function],
+ "findAllByAltText": [Function],
+ "findAllByDisplayValue": [Function],
+ "findAllByLabelText": [Function],
+ "findAllByPlaceholderText": [Function],
+ "findAllByRole": [Function],
+ "findAllByTestId": [Function],
+ "findAllByText": [Function],
+ "findAllByTitle": [Function],
+ "findByAltText": [Function],
+ "findByDisplayValue": [Function],
+ "findByLabelText": [Function],
+ "findByPlaceholderText": [Function],
+ "findByRole": [Function],
+ "findByTestId": [Function],
+ "findByText": [Function],
+ "findByTitle": [Function],
+ "getAllByAltText": [Function],
+ "getAllByDisplayValue": [Function],
+ "getAllByLabelText": [Function],
+ "getAllByPlaceholderText": [Function],
+ "getAllByRole": [Function],
+ "getAllByTestId": [Function],
+ "getAllByText": [Function],
+ "getAllByTitle": [Function],
+ "getByAltText": [Function],
+ "getByDisplayValue": [Function],
+ "getByLabelText": [Function],
+ "getByPlaceholderText": [Function],
+ "getByRole": [Function],
+ "getByTestId": [Function],
+ "getByText": [Function],
+ "getByTitle": [Function],
+ "queryAllByAltText": [Function],
+ "queryAllByDisplayValue": [Function],
+ "queryAllByLabelText": [Function],
+ "queryAllByPlaceholderText": [Function],
+ "queryAllByRole": [Function],
+ "queryAllByTestId": [Function],
+ "queryAllByText": [Function],
+ "queryAllByTitle": [Function],
+ "queryByAltText": [Function],
+ "queryByDisplayValue": [Function],
+ "queryByLabelText": [Function],
+ "queryByPlaceholderText": [Function],
+ "queryByRole": [Function],
+ "queryByTestId": [Function],
+ "queryByText": [Function],
+ "queryByTitle": [Function],
+ "rerender": [Function],
+ "unmount": [Function],
+}
+`;
diff --git a/src/components/mail/tray/__test__/__snapshots__/TrayList.test.tsx.snap b/src/components/mail/tray/__test__/__snapshots__/TrayList.test.tsx.snap
new file mode 100644
index 0000000..eaccb7e
--- /dev/null
+++ b/src/components/mail/tray/__test__/__snapshots__/TrayList.test.tsx.snap
@@ -0,0 +1,300 @@
+// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
+
+exports[`TrayList > should match snapshot 1`] = `
+{
+ "asFragment": [Function],
+ "baseElement":
+
+ ,
+ "container": ,
+ "debug": [Function],
+ "findAllByAltText": [Function],
+ "findAllByDisplayValue": [Function],
+ "findAllByLabelText": [Function],
+ "findAllByPlaceholderText": [Function],
+ "findAllByRole": [Function],
+ "findAllByTestId": [Function],
+ "findAllByText": [Function],
+ "findAllByTitle": [Function],
+ "findByAltText": [Function],
+ "findByDisplayValue": [Function],
+ "findByLabelText": [Function],
+ "findByPlaceholderText": [Function],
+ "findByRole": [Function],
+ "findByTestId": [Function],
+ "findByText": [Function],
+ "findByTitle": [Function],
+ "getAllByAltText": [Function],
+ "getAllByDisplayValue": [Function],
+ "getAllByLabelText": [Function],
+ "getAllByPlaceholderText": [Function],
+ "getAllByRole": [Function],
+ "getAllByTestId": [Function],
+ "getAllByText": [Function],
+ "getAllByTitle": [Function],
+ "getByAltText": [Function],
+ "getByDisplayValue": [Function],
+ "getByLabelText": [Function],
+ "getByPlaceholderText": [Function],
+ "getByRole": [Function],
+ "getByTestId": [Function],
+ "getByText": [Function],
+ "getByTitle": [Function],
+ "queryAllByAltText": [Function],
+ "queryAllByDisplayValue": [Function],
+ "queryAllByLabelText": [Function],
+ "queryAllByPlaceholderText": [Function],
+ "queryAllByRole": [Function],
+ "queryAllByTestId": [Function],
+ "queryAllByText": [Function],
+ "queryAllByTitle": [Function],
+ "queryByAltText": [Function],
+ "queryByDisplayValue": [Function],
+ "queryByLabelText": [Function],
+ "queryByPlaceholderText": [Function],
+ "queryByRole": [Function],
+ "queryByTestId": [Function],
+ "queryByText": [Function],
+ "queryByTitle": [Function],
+ "rerender": [Function],
+ "unmount": [Function],
+}
+`;
diff --git a/src/stories/components/mail/tray/Tray.stories.tsx b/src/stories/components/mail/tray/Tray.stories.tsx
new file mode 100644
index 0000000..0e66a35
--- /dev/null
+++ b/src/stories/components/mail/tray/Tray.stories.tsx
@@ -0,0 +1,158 @@
+import type { Meta, StoryObj } from '@storybook/react-vite';
+import { fn } from 'storybook/test';
+import { TrayList } from '../../../../components/mail/tray/TrayList';
+
+const mockMails = [
+ {
+ id: '1',
+ from: { name: 'John Doe', avatar: '' },
+ subject: 'Meeting tomorrow',
+ createdAt: '10:30 AM',
+ body: 'Hi, just a reminder about our meeting tomorrow at 3pm.',
+ read: false,
+ },
+ {
+ id: '2',
+ from: { name: 'Jane Smith', avatar: '' },
+ subject: 'Project update',
+ createdAt: '9:15 AM',
+ body: 'The project is progressing well. Here are the latest updates...',
+ read: true,
+ },
+ {
+ id: '3',
+ from: { name: 'Mike Johnson', avatar: '' },
+ subject: 'Invoice #1234',
+ createdAt: 'Yesterday',
+ body: 'Please find attached the invoice for last month services.',
+ read: true,
+ },
+ {
+ id: '4',
+ from: { name: 'Sarah Wilson', avatar: '' },
+ subject: 'Welcome to the team!',
+ createdAt: 'Yesterday',
+ body: 'We are excited to have you on board. Let me know if you need anything.',
+ read: false,
+ },
+ {
+ id: '5',
+ from: { name: 'Tech Support', avatar: '' },
+ subject: 'Your ticket has been resolved',
+ createdAt: 'Feb 20',
+ body: 'Your support ticket #5678 has been resolved. Please let us know if you have any questions.',
+ read: true,
+ },
+];
+
+const meta: Meta = {
+ title: 'Mail/Tray List',
+ component: TrayList,
+ parameters: {
+ layout: 'centered',
+ },
+ tags: ['autodocs'],
+ argTypes: {
+ loading: {
+ control: 'boolean',
+ description: 'Shows skeleton loaders when true',
+ },
+ checked: {
+ control: 'boolean',
+ description: 'Select all checkbox state',
+ },
+ activeEmail: {
+ control: 'text',
+ description: 'ID of the currently active email',
+ },
+ hasMoreItems: {
+ control: 'boolean',
+ description: 'Whether there are more items to load',
+ },
+ },
+ args: {
+ onMailSelected: fn(),
+ onLoadMore: fn(),
+ },
+};
+
+export default meta;
+type Story = StoryObj;
+
+export const Default: Story = {
+ args: {
+ mails: mockMails,
+ selectedEmails: [],
+ loading: false,
+ checked: false,
+ activeEmail: '',
+ hasMoreItems: false,
+ },
+};
+
+export const WithActiveEmail: Story = {
+ args: {
+ mails: mockMails,
+ selectedEmails: [],
+ loading: false,
+ checked: false,
+ activeEmail: '2',
+ hasMoreItems: false,
+ },
+};
+
+export const WithSelectedEmails: Story = {
+ args: {
+ mails: mockMails,
+ selectedEmails: ['1', '3', '5'],
+ loading: false,
+ checked: false,
+ activeEmail: '',
+ hasMoreItems: false,
+ },
+};
+
+export const AllSelected: Story = {
+ args: {
+ mails: mockMails,
+ selectedEmails: ['1', '3', '5'],
+ loading: false,
+ checked: true,
+ activeEmail: '',
+ hasMoreItems: false,
+ },
+};
+
+export const WithInfiniteScroll: Story = {
+ args: {
+ mails: mockMails,
+ selectedEmails: [],
+ loading: false,
+ checked: false,
+ activeEmail: '',
+ hasMoreItems: true,
+ },
+};
+
+export const Loading: Story = {
+ args: {
+ mails: [],
+ selectedEmails: [],
+ loading: true,
+ checked: false,
+ activeEmail: '',
+ hasMoreItems: false,
+ },
+};
+
+export const Empty: Story = {
+ args: {
+ mails: [],
+ selectedEmails: [],
+ loading: false,
+ checked: false,
+ activeEmail: '',
+ hasMoreItems: false,
+ emptyState: Empty state
,
+ },
+};
diff --git a/src/stories/components/mail/tray/message/MessageCheap.stories.tsx b/src/stories/components/mail/tray/message/MessageCheap.stories.tsx
new file mode 100644
index 0000000..61c7d4c
--- /dev/null
+++ b/src/stories/components/mail/tray/message/MessageCheap.stories.tsx
@@ -0,0 +1,101 @@
+import type { Meta, StoryObj } from '@storybook/react-vite';
+import { fn } from 'storybook/test';
+import { MessageCheap } from '../../../../../components/mail/tray/MessageCheap';
+
+const mockEmail = {
+ id: '1',
+ from: { name: 'John Doe', avatar: '' },
+ subject: 'Meeting tomorrow',
+ createdAt: '10:30 AM',
+ body: 'Hi, just a reminder about our meeting tomorrow at 3pm.',
+ read: false,
+};
+
+const meta: Meta = {
+ title: 'Mail/MessageCheap',
+ component: MessageCheap,
+ parameters: {
+ layout: 'centered',
+ },
+ tags: ['autodocs'],
+ argTypes: {
+ active: {
+ control: 'boolean',
+ description: 'Whether the message is currently selected/active',
+ },
+ },
+ args: {
+ onClick: fn(),
+ },
+ decorators: [
+ (Story) => (
+
+
+
+ ),
+ ],
+};
+
+export default meta;
+type Story = StoryObj;
+
+export const Default: Story = {
+ args: {
+ email: mockEmail,
+ active: false,
+ },
+};
+
+export const Active: Story = {
+ args: {
+ email: mockEmail,
+ active: true,
+ },
+};
+
+export const Read: Story = {
+ args: {
+ email: {
+ ...mockEmail,
+ read: true,
+ },
+ active: false,
+ },
+};
+
+export const Unread: Story = {
+ args: {
+ email: {
+ ...mockEmail,
+ read: false,
+ },
+ active: false,
+ },
+};
+
+export const LongContent: Story = {
+ args: {
+ email: {
+ id: '2',
+ from: { name: 'Alexander Maximilian Richardson III', avatar: '' },
+ subject: 'Important: Project deadline extension request for Q4 deliverables',
+ createdAt: 'Yesterday',
+ body: 'This is a very long email body that should be truncated when it exceeds the available width of the container.',
+ read: false,
+ },
+ active: false,
+ },
+};
+
+export const WithAvatar: Story = {
+ args: {
+ email: {
+ ...mockEmail,
+ from: {
+ name: 'Jane Smith',
+ avatar: 'https://i.pravatar.cc/150?img=5',
+ },
+ },
+ active: false,
+ },
+};
diff --git a/src/stories/components/mail/tray/message/MessageCheapSkeleton.stories.tsx b/src/stories/components/mail/tray/message/MessageCheapSkeleton.stories.tsx
new file mode 100644
index 0000000..11c71dc
--- /dev/null
+++ b/src/stories/components/mail/tray/message/MessageCheapSkeleton.stories.tsx
@@ -0,0 +1,33 @@
+import type { Meta, StoryObj } from '@storybook/react-vite';
+import { MessageCheapSkeleton } from '../../../../../components/mail/tray/MessageCheapSkeleton';
+
+const meta: Meta = {
+ title: 'Mail/MessageCheapSkeleton',
+ component: MessageCheapSkeleton,
+ parameters: {
+ layout: 'centered',
+ },
+ tags: ['autodocs'],
+ decorators: [
+ (Story) => (
+
+
+
+ ),
+ ],
+};
+
+export default meta;
+type Story = StoryObj;
+
+export const Default: Story = {};
+
+export const Multiple: Story = {
+ render: () => (
+
+ {new Array(5).fill(0).map((_, index) => (
+
+ ))}
+
+ ),
+};