From 491b9e954a6fd7e0c44498a3d403587a1c35c74d Mon Sep 17 00:00:00 2001
From: "Abdullah." <125115953+mabdullahabaid@users.noreply.github.com>
Date: Thu, 25 Jun 2026 15:02:44 +0500
Subject: [PATCH] Rework website EmailsVisual to match twenty-front emails tab
(#22146)
- Rework `EmailsVisual` to match twenty-front's emails tab: bordered
thread card with dividers, `Inbox` header sizing, and twenty-front's
row/sender/date colors and hard-clipped sender names (kept `+ Compose`).
- Replace the "not shared" row with four fully-populated shared threads
(subjects/bodies sourced from twenty-server's dev seed), two
participants each, full `MMM D, YYYY` dates.
---
.../EmailsVisual/EmailsVisual.test.tsx | 48 +++++++++++++++
.../EmailsVisual/EmailsVisual.tsx | 29 +++++----
.../components/ThreadRow.test.tsx | 36 +++++++++++
.../EmailsVisual/components/ThreadRow.tsx | 61 ++++++-------------
.../EmailsVisual/data/threads.ts | 46 ++++++++++----
.../EmailsVisual/types/email-thread.ts | 7 ++-
6 files changed, 157 insertions(+), 70 deletions(-)
create mode 100644 packages/twenty-website/src/sections/product-feature/EmailsVisual/EmailsVisual.test.tsx
create mode 100644 packages/twenty-website/src/sections/product-feature/EmailsVisual/components/ThreadRow.test.tsx
diff --git a/packages/twenty-website/src/sections/product-feature/EmailsVisual/EmailsVisual.test.tsx b/packages/twenty-website/src/sections/product-feature/EmailsVisual/EmailsVisual.test.tsx
new file mode 100644
index 0000000000..ba65001beb
--- /dev/null
+++ b/packages/twenty-website/src/sections/product-feature/EmailsVisual/EmailsVisual.test.tsx
@@ -0,0 +1,48 @@
+import { i18n } from '@lingui/core';
+import { I18nProvider } from '@lingui/react';
+import { render, screen } from '@testing-library/react';
+
+import { EmailsVisual } from './EmailsVisual';
+
+i18n.load('en', {});
+i18n.activate('en');
+
+function renderVisual() {
+ return render(
+
+
+ ,
+ );
+}
+
+describe('EmailsVisual', () => {
+ it('renders the inbox chrome', () => {
+ renderVisual();
+
+ expect(screen.getByText('Compose')).toBeInTheDocument();
+ expect(
+ screen.getByText('Inbox', { exact: false, selector: 'span' }),
+ ).toBeInTheDocument();
+ });
+
+ it('renders every thread subject', () => {
+ renderVisual();
+
+ expect(screen.getByText('Partnerships - Q4 Strategy')).toBeInTheDocument();
+ expect(screen.getByText('Proposal Submission')).toBeInTheDocument();
+ expect(screen.getByText('Follow-up on Discussion')).toBeInTheDocument();
+ expect(screen.getByText('Customer Feedback')).toBeInTheDocument();
+ });
+
+ it('renders thread previews, participants and dates', () => {
+ renderVisual();
+
+ expect(
+ screen.getByText(
+ "Hey team, I've been in touch with Notion and Figma about potential integrations.",
+ ),
+ ).toBeInTheDocument();
+ expect(screen.getByText('Félix, Thomas')).toBeInTheDocument();
+ expect(screen.getByText('Jun 24, 2026')).toBeInTheDocument();
+ });
+});
diff --git a/packages/twenty-website/src/sections/product-feature/EmailsVisual/EmailsVisual.tsx b/packages/twenty-website/src/sections/product-feature/EmailsVisual/EmailsVisual.tsx
index 8865d89b4a..6daf759c88 100644
--- a/packages/twenty-website/src/sections/product-feature/EmailsVisual/EmailsVisual.tsx
+++ b/packages/twenty-website/src/sections/product-feature/EmailsVisual/EmailsVisual.tsx
@@ -1,5 +1,7 @@
'use client';
+import { msg } from '@lingui/core/macro';
+import { useLingui } from '@lingui/react';
import { styled } from '@linaria/react';
import { IconPlus } from '@tabler/icons-react';
import { THEME_LIGHT } from 'twenty-ui/theme';
@@ -41,14 +43,13 @@ const InboxTitle = styled.span`
align-items: baseline;
color: ${THEME_LIGHT.font.color.primary};
display: flex;
- font-size: ${previewFontSize(THEME_LIGHT.font.size.md)};
+ font-size: ${previewFontSize(THEME_LIGHT.font.size.lg)};
font-weight: ${THEME_LIGHT.font.weight.semiBold};
- gap: 6px;
+ gap: ${THEME_LIGHT.spacing(2)};
`;
const InboxCount = styled.span`
color: ${THEME_LIGHT.font.color.light};
- font-weight: ${THEME_LIGHT.font.weight.regular};
`;
const ComposeButton = styled.span`
@@ -63,34 +64,38 @@ const ComposeButton = styled.span`
padding: 4px 8px;
`;
-const ThreadList = styled.div`
- display: flex;
- flex: 1;
- flex-direction: column;
- min-height: 0;
+const ThreadCard = styled.div`
+ border: 1px solid ${THEME_LIGHT.border.color.medium};
+ border-radius: ${THEME_LIGHT.border.radius.sm};
+ margin: 0 16px;
overflow: hidden;
+
+ & > * + * {
+ border-top: 1px solid ${THEME_LIGHT.border.color.light};
+ }
`;
export function EmailsVisual({ active: _active }: { active: boolean }) {
+ const { i18n } = useLingui();
return (
- Inbox
+ {i18n._(msg`Inbox`)}
{THREADS.length}
- Compose
+ {i18n._(msg`Compose`)}
-
+
{THREADS.map((thread) => (
))}
-
+
);
diff --git a/packages/twenty-website/src/sections/product-feature/EmailsVisual/components/ThreadRow.test.tsx b/packages/twenty-website/src/sections/product-feature/EmailsVisual/components/ThreadRow.test.tsx
new file mode 100644
index 0000000000..230feb8614
--- /dev/null
+++ b/packages/twenty-website/src/sections/product-feature/EmailsVisual/components/ThreadRow.test.tsx
@@ -0,0 +1,36 @@
+import { i18n } from '@lingui/core';
+import { I18nProvider } from '@lingui/react';
+import { render, screen } from '@testing-library/react';
+
+import { type EmailThread } from '../types/email-thread';
+import { ThreadRow } from './ThreadRow';
+
+i18n.load('en', {});
+i18n.activate('en');
+
+const THREAD: EmailThread = {
+ date: 'Jan 1, 2026',
+ messageCount: 3,
+ participants: [
+ { avatarUrl: '', name: 'Ada' },
+ { avatarUrl: '', name: 'Linus' },
+ ],
+ preview: { id: 'A short preview.' },
+ subject: { id: 'A subject line' },
+};
+
+describe('ThreadRow', () => {
+ it('renders the subject, preview, participants, count and date', () => {
+ render(
+
+
+ ,
+ );
+
+ expect(screen.getByText('A subject line')).toBeInTheDocument();
+ expect(screen.getByText('A short preview.')).toBeInTheDocument();
+ expect(screen.getByText('Ada, Linus')).toBeInTheDocument();
+ expect(screen.getByText('3')).toBeInTheDocument();
+ expect(screen.getByText('Jan 1, 2026')).toBeInTheDocument();
+ });
+});
diff --git a/packages/twenty-website/src/sections/product-feature/EmailsVisual/components/ThreadRow.tsx b/packages/twenty-website/src/sections/product-feature/EmailsVisual/components/ThreadRow.tsx
index 615a4c1380..fe06338322 100644
--- a/packages/twenty-website/src/sections/product-feature/EmailsVisual/components/ThreadRow.tsx
+++ b/packages/twenty-website/src/sections/product-feature/EmailsVisual/components/ThreadRow.tsx
@@ -1,33 +1,36 @@
+'use client';
+
+import { useLingui } from '@lingui/react';
import { styled } from '@linaria/react';
-import { IconLock } from '@tabler/icons-react';
import { THEME_LIGHT } from 'twenty-ui/theme';
-import { PersonAvatar } from '@/app-preview/primitives/PersonAvatar';
import { previewFontSize } from '@/app-preview/preview-font-size';
+import { PersonAvatar } from '@/app-preview/primitives/PersonAvatar';
import { type EmailThread } from '../types/email-thread';
const Row = styled.div`
align-items: center;
+ background-color: ${THEME_LIGHT.background.secondary};
box-sizing: border-box;
- color: ${THEME_LIGHT.font.color.primary};
+ color: ${THEME_LIGHT.font.color.secondary};
display: flex;
+ font-size: ${previewFontSize(THEME_LIGHT.font.size.md)};
gap: 8px;
height: 48px;
padding: 0 16px;
&:hover {
- background-color: ${THEME_LIGHT.background.transparent.light};
+ background-color: transparent;
}
`;
-const Heading = styled.div`
+const Sender = styled.div`
align-items: center;
display: flex;
- max-width: 40%;
+ flex: 0 0 20%;
min-width: 0;
overflow: hidden;
- width: fit-content;
`;
const AvatarStack = styled.div`
@@ -41,18 +44,15 @@ const AvatarStack = styled.div`
`;
const SenderNames = styled.span`
- font-size: ${previewFontSize(THEME_LIGHT.font.size.md)};
margin: 0 4px;
min-width: 0;
overflow: hidden;
- text-overflow: ellipsis;
white-space: nowrap;
`;
-const ThreadCount = styled.span`
+const MessageCount = styled.span`
color: ${THEME_LIGHT.font.color.tertiary};
flex-shrink: 0;
- font-size: ${previewFontSize(THEME_LIGHT.font.size.md)};
`;
const SubjectBody = styled.div`
@@ -66,9 +66,6 @@ const SubjectBody = styled.div`
const Subject = styled.span`
color: ${THEME_LIGHT.font.color.primary};
- flex-shrink: 0;
- font-size: ${previewFontSize(THEME_LIGHT.font.size.md)};
- max-width: 100%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
@@ -77,29 +74,13 @@ const Subject = styled.span`
const Preview = styled.span`
color: ${THEME_LIGHT.font.color.tertiary};
flex: 1;
- font-size: ${previewFontSize(THEME_LIGHT.font.size.md)};
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
`;
-const NotShared = styled.span`
- align-items: center;
- background: ${THEME_LIGHT.background.transparent.lighter};
- border: 1px solid ${THEME_LIGHT.border.color.light};
- border-radius: ${THEME_LIGHT.border.radius.sm};
- color: ${THEME_LIGHT.font.color.tertiary};
- display: inline-flex;
- flex: 1;
- font-size: ${previewFontSize(THEME_LIGHT.font.size.sm)};
- gap: 4px;
- height: 20px;
- padding: 0 4px;
-`;
-
const ReceivedAt = styled.span`
- color: ${THEME_LIGHT.font.color.tertiary};
flex-shrink: 0;
font-size: ${previewFontSize(THEME_LIGHT.font.size.sm)};
padding: 0 4px;
@@ -107,9 +88,10 @@ const ReceivedAt = styled.span`
`;
export function ThreadRow({ thread }: { thread: EmailThread }) {
+ const { i18n } = useLingui();
return (
-
+
{thread.participants.map((participant) => (
participant.name)
.join(', ')}
- {thread.messageCount}
-
+ {thread.messageCount}
+
- {thread.shared ? (
- <>
- {thread.subject}
- {thread.preview}
- >
- ) : (
-
-
- Not shared
-
- )}
+ {i18n._(thread.subject)}
+ {i18n._(thread.preview)}
{thread.date}
diff --git a/packages/twenty-website/src/sections/product-feature/EmailsVisual/data/threads.ts b/packages/twenty-website/src/sections/product-feature/EmailsVisual/data/threads.ts
index 58071e5965..91b88a0695 100644
--- a/packages/twenty-website/src/sections/product-feature/EmailsVisual/data/threads.ts
+++ b/packages/twenty-website/src/sections/product-feature/EmailsVisual/data/threads.ts
@@ -1,3 +1,5 @@
+import { msg } from '@lingui/core/macro';
+
import { sharedAssetUrls } from '@/app-preview/data/shared-asset-urls';
import { type EmailThread } from '../types/email-thread';
@@ -6,21 +8,43 @@ const PEOPLE = sharedAssetUrls.peopleAvatars;
export const THREADS: EmailThread[] = [
{
- date: '1:30pm',
- messageCount: 2,
- participants: [{ avatarUrl: PEOPLE.anonymousMike, name: 'Mike' }],
- shared: false,
- },
- {
- date: '4 nov 2023',
+ date: 'Jun 24, 2026',
messageCount: 4,
participants: [
{ avatarUrl: PEOPLE.anonymousFelix, name: 'Félix' },
{ avatarUrl: PEOPLE.anonymousThomas, name: 'Thomas' },
],
- preview:
- "Hey team, I've been in touch with Notion and Figma about potential integrations.",
- shared: true,
- subject: 'Partnerships - Q4 Strategy',
+ preview: msg`Hey team, I've been in touch with Notion and Figma about potential integrations.`,
+ subject: msg`Partnerships - Q4 Strategy`,
+ },
+ {
+ date: 'Jun 23, 2026',
+ messageCount: 2,
+ participants: [
+ { avatarUrl: PEOPLE.anonymousLaura, name: 'Laura' },
+ { avatarUrl: PEOPLE.anonymousIndira, name: 'Indira' },
+ ],
+ preview: msg`Dear Team, I am pleased to submit our proposal for your consideration. We have carefully reviewed your requirements.`,
+ subject: msg`Proposal Submission`,
+ },
+ {
+ date: 'Jun 20, 2026',
+ messageCount: 3,
+ participants: [
+ { avatarUrl: PEOPLE.anonymousIndira, name: 'Indira' },
+ { avatarUrl: PEOPLE.anonymousMike, name: 'Mike' },
+ ],
+ preview: msg`Hi, I wanted to follow up on our conversation from last week regarding the new initiative.`,
+ subject: msg`Follow-up on Discussion`,
+ },
+ {
+ date: 'Jun 18, 2026',
+ messageCount: 1,
+ participants: [
+ { avatarUrl: PEOPLE.anonymousThomas, name: 'Thomas' },
+ { avatarUrl: PEOPLE.anonymousLaura, name: 'Laura' },
+ ],
+ preview: msg`Hello, I wanted to share some positive feedback from our recent customer satisfaction survey.`,
+ subject: msg`Customer Feedback`,
},
];
diff --git a/packages/twenty-website/src/sections/product-feature/EmailsVisual/types/email-thread.ts b/packages/twenty-website/src/sections/product-feature/EmailsVisual/types/email-thread.ts
index 57aa48955e..81f7395daa 100644
--- a/packages/twenty-website/src/sections/product-feature/EmailsVisual/types/email-thread.ts
+++ b/packages/twenty-website/src/sections/product-feature/EmailsVisual/types/email-thread.ts
@@ -1,3 +1,5 @@
+import { type MessageDescriptor } from '@lingui/core';
+
type Participant = {
avatarUrl: string;
name: string;
@@ -7,7 +9,6 @@ export type EmailThread = {
date: string;
messageCount: number;
participants: Participant[];
- preview?: string;
- shared: boolean;
- subject?: string;
+ preview: MessageDescriptor;
+ subject: MessageDescriptor;
};