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) => ( +
+
+ +
+

{fullName}

+

{email}

+
+
+
+); + +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": +
+
+
+
+

+ JD +

+
+
+

+ John Doe +

+

+ john.doe@internxt.com +

+
+
+
+
+ , + "container":
+
+
+
+

+ JD +

+
+
+

+ 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": +
+
+
+
+

+ JD +

+
+
+

+ John Doe +

+

+ john.doe@internxt.com +

+
+
+
+
+ , + "container":
+
+
+
+

+ JD +

+
+
+

+ 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', + }, +};