From 1faeb3cd1a02218450df0566fee9ba328d7a5e78 Mon Sep 17 00:00:00 2001
From: Xavier Abad <77491413+xabg2@users.noreply.github.com>
Date: Fri, 20 Mar 2026 12:19:54 +0100
Subject: [PATCH] feat: user cheap component
---
package.json | 2 +-
src/components/mail/cheaps/user/UserCheap.tsx | 29 ++
.../cheaps/user/__test__/UserCheap.test.tsx | 49 +++
.../__snapshots__/UserCheap.test.tsx.snap | 366 ++++++++++++++++++
src/components/mail/index.ts | 3 +
.../mail/cheaps/user/UserCheap.stories.tsx | 57 +++
6 files changed, 505 insertions(+), 1 deletion(-)
create mode 100644 src/components/mail/cheaps/user/UserCheap.tsx
create mode 100644 src/components/mail/cheaps/user/__test__/UserCheap.test.tsx
create mode 100644 src/components/mail/cheaps/user/__test__/__snapshots__/UserCheap.test.tsx.snap
create mode 100644 src/stories/components/mail/cheaps/user/UserCheap.stories.tsx
diff --git a/package.json b/package.json
index 38bf600..5175619 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "@internxt/ui",
- "version": "0.1.10",
+ "version": "0.1.11",
"description": "Library of Internxt components",
"repository": {
"type": "git",
diff --git a/src/components/mail/cheaps/user/UserCheap.tsx b/src/components/mail/cheaps/user/UserCheap.tsx
new file mode 100644
index 0000000..2721f7a
--- /dev/null
+++ b/src/components/mail/cheaps/user/UserCheap.tsx
@@ -0,0 +1,29 @@
+import { Avatar } from '@/components/avatar';
+
+interface UserCheapProps {
+ fullName: string;
+ email: string;
+ avatar?: string;
+}
+
+/**
+ * A cheap user component to render a user's information.
+ *
+ * @param {UserCheapProps} props - The props object.
+ * @param {string} props.fullName - The user's full name.
+ * @param {string} props.email - The user's email address.
+ * @param {string} [props.avatar] - The user's avatar URL. If not provided, the avatar will be generated from the user's name.
+ */
+const UserCheap = ({ fullName, email, avatar }: UserCheapProps) => (
+
+);
+
+export default UserCheap;
diff --git a/src/components/mail/cheaps/user/__test__/UserCheap.test.tsx b/src/components/mail/cheaps/user/__test__/UserCheap.test.tsx
new file mode 100644
index 0000000..7e73576
--- /dev/null
+++ b/src/components/mail/cheaps/user/__test__/UserCheap.test.tsx
@@ -0,0 +1,49 @@
+import { render, screen } from '@testing-library/react';
+import { describe, it, expect } from 'vitest';
+import UserCheap from '../UserCheap';
+
+const defaultProps = {
+ fullName: 'John Doe',
+ email: 'john.doe@internxt.com',
+};
+
+describe('UserCheap', () => {
+ it('should match snapshot', () => {
+ const component = render();
+ expect(component).toMatchSnapshot();
+ });
+
+ it('should render full name and email', () => {
+ render();
+
+ expect(screen.getByText('John Doe')).toBeInTheDocument();
+ expect(screen.getByText('john.doe@internxt.com')).toBeInTheDocument();
+ });
+
+ it('should render with avatar when src is provided', () => {
+ const avatar = 'https://example.com/avatar.jpg';
+ const component = render();
+ expect(component).toMatchSnapshot();
+ });
+
+ it('should render without avatar when src is not provided', () => {
+ const component = render();
+ expect(component).toMatchSnapshot();
+ });
+
+ it('should truncate long full name', () => {
+ const longName = 'A Very Long Name That Should Be Truncated In The Component';
+ render();
+
+ const nameEl = screen.getByText(longName);
+ expect(nameEl.className).toContain('truncate');
+ });
+
+ it('should truncate long email', () => {
+ const longEmail = 'a.very.long.email.address.that.should.be.truncated@internxt.com';
+ render();
+
+ const emailEl = screen.getByText(longEmail);
+ expect(emailEl.className).toContain('truncate');
+ });
+});
diff --git a/src/components/mail/cheaps/user/__test__/__snapshots__/UserCheap.test.tsx.snap b/src/components/mail/cheaps/user/__test__/__snapshots__/UserCheap.test.tsx.snap
new file mode 100644
index 0000000..2044214
--- /dev/null
+++ b/src/components/mail/cheaps/user/__test__/__snapshots__/UserCheap.test.tsx.snap
@@ -0,0 +1,366 @@
+// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
+
+exports[`UserCheap > should match snapshot 1`] = `
+{
+ "asFragment": [Function],
+ "baseElement":
+
+
+
+
+
+
+ John Doe
+
+
+ john.doe@internxt.com
+
+
+
+
+
+ ,
+ "container":
+
+
+
+
+
+ John Doe
+
+
+ john.doe@internxt.com
+
+
+
+
+
,
+ "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],
+}
+`;
+
+exports[`UserCheap > should render with avatar when src is provided 1`] = `
+{
+ "asFragment": [Function],
+ "baseElement":
+
+
+
+

+
+
+ John Doe
+
+
+ john.doe@internxt.com
+
+
+
+
+
+ ,
+ "container":
+
+
+

+
+
+ John Doe
+
+
+ john.doe@internxt.com
+
+
+
+
+
,
+ "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],
+}
+`;
+
+exports[`UserCheap > should render without avatar when src is not provided 1`] = `
+{
+ "asFragment": [Function],
+ "baseElement":
+
+
+
+
+
+
+ John Doe
+
+
+ john.doe@internxt.com
+
+
+
+
+
+ ,
+ "container":
+
+
+
+
+
+ John Doe
+
+
+ john.doe@internxt.com
+
+
+
+
+
,
+ "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/index.ts b/src/components/mail/index.ts
index af9c48f..6f23431 100644
--- a/src/components/mail/index.ts
+++ b/src/components/mail/index.ts
@@ -5,3 +5,6 @@ export { default as MessageCheapSkeleton } from './cheaps/MessageCheapSkeleton';
export type { TrayListProps } from './tray/TrayList';
export type { MessageCheapProps } from './cheaps/MessageCheap';
+
+// Cheaps
+export { default as UserCheap } from './cheaps/user/UserCheap';
diff --git a/src/stories/components/mail/cheaps/user/UserCheap.stories.tsx b/src/stories/components/mail/cheaps/user/UserCheap.stories.tsx
new file mode 100644
index 0000000..2431304
--- /dev/null
+++ b/src/stories/components/mail/cheaps/user/UserCheap.stories.tsx
@@ -0,0 +1,57 @@
+import type { Meta, StoryObj } from '@storybook/react-vite';
+import UserCheap from '../../../../../components/mail/cheaps/user/UserCheap';
+
+const meta: Meta = {
+ title: 'Mail/User Cheap',
+ component: UserCheap,
+ parameters: {
+ layout: 'centered',
+ },
+ tags: ['autodocs'],
+ argTypes: {
+ fullName: {
+ control: 'text',
+ description: 'Full name of the user',
+ },
+ email: {
+ control: 'text',
+ description: 'Email address of the user',
+ },
+ avatar: {
+ control: 'text',
+ description: 'URL of the user avatar image',
+ },
+ },
+};
+
+export default meta;
+type Story = StoryObj;
+
+export const Default: Story = {
+ args: {
+ fullName: 'John Doe',
+ email: 'john.doe@example.com',
+ },
+};
+
+export const WithAvatar: Story = {
+ args: {
+ fullName: 'Jane Smith',
+ email: 'jane.smith@example.com',
+ avatar: 'https://i.pravatar.cc/150?img=5',
+ },
+};
+
+export const LongName: Story = {
+ args: {
+ fullName: 'Alexander Maximilian Richardson III',
+ email: 'alexander.richardson@company.com',
+ },
+};
+
+export const LongEmail: Story = {
+ args: {
+ fullName: 'John Doe',
+ email: 'john.doe.very.long.email.address@subdomain.company.com',
+ },
+};