diff --git a/.gitignore b/.gitignore index 5d42fe9cace..1d94e44b26a 100644 --- a/.gitignore +++ b/.gitignore @@ -52,3 +52,4 @@ mcp.json /.junie/ TRANSLATION_QA_REPORT.md .playwright-mcp/ +.playwright-cli/ diff --git a/packages/twenty-website-new/public/images/home/hero/apple-rainbow-logo.svg b/packages/twenty-website-new/public/images/home/hero/apple-rainbow-logo.svg new file mode 100644 index 00000000000..c7767e8a935 Binary files /dev/null and b/packages/twenty-website-new/public/images/home/hero/apple-rainbow-logo.svg differ diff --git a/packages/twenty-website-new/public/images/home/hero/avatars/katherine-adams.jpg b/packages/twenty-website-new/public/images/home/hero/avatars/katherine-adams.jpg new file mode 100644 index 00000000000..32936f12a92 Binary files /dev/null and b/packages/twenty-website-new/public/images/home/hero/avatars/katherine-adams.jpg differ diff --git a/packages/twenty-website-new/public/images/home/hero/twenty-demo-logo.png b/packages/twenty-website-new/public/images/home/hero/twenty-demo-logo.png new file mode 100644 index 00000000000..aac50ec94cf Binary files /dev/null and b/packages/twenty-website-new/public/images/home/hero/twenty-demo-logo.png differ diff --git a/packages/twenty-website-new/public/images/shared/light-noise.png b/packages/twenty-website-new/public/images/shared/light-noise.png new file mode 100644 index 00000000000..d7b3bc2c064 Binary files /dev/null and b/packages/twenty-website-new/public/images/shared/light-noise.png differ diff --git a/packages/twenty-website-new/src/app/(home)/constants/hero.ts b/packages/twenty-website-new/src/app/(home)/constants/hero.ts index 349baf1c41d..6fd6484acb1 100644 --- a/packages/twenty-website-new/src/app/(home)/constants/hero.ts +++ b/packages/twenty-website-new/src/app/(home)/constants/hero.ts @@ -8,7 +8,7 @@ const PEOPLE_AVATAR_URLS = { jeffWilliams: 'https://twentyhq.github.io/placeholder-images/people/image-22.png', katherineAdams: - 'https://twentyhq.github.io/placeholder-images/people/image-07.png', + '/images/home/hero/avatars/katherine-adams.jpg', philSchiller: 'https://twentyhq.github.io/placeholder-images/people/image-14.png', timCook: @@ -28,66 +28,6 @@ export const HERO_DATA: HeroHomeDataType = { workspace: { icon: 'apple', name: 'Apple' }, tableWidth: 1700, actions: ['Filter', 'Sort', 'Options'], - favoritesNav: [ - { - id: 'fav-to-follow', - label: 'To Follow', - icon: { kind: 'tabler', name: 'folder', tone: 'orange' }, - showChevron: true, - children: [ - { - id: 'fav-stripe', - label: 'Stripe', - meta: 'Company', - icon: { kind: 'brand', brand: 'stripe' }, - }, - { - id: 'fav-airbnb', - label: 'Airbnb', - meta: 'Company', - icon: { kind: 'brand', brand: 'airbnb' }, - }, - { - id: 'fav-brian-chesky', - label: 'Brian Chesky', - meta: 'Person', - icon: { - kind: 'avatar', - label: 'B', - tone: 'violet', - shape: 'circle', - }, - }, - ], - }, - { - id: 'fav-all-companies', - label: 'All Companies', - icon: { kind: 'tabler', name: 'buildingSkyscraper', tone: 'blue' }, - }, - { - id: 'fav-send-nda', - label: 'Send NDA to Qonto - T...', - icon: { kind: 'avatar', label: 'L', tone: 'teal', shape: 'circle' }, - }, - { - id: 'fav-page-layout', - label: 'Page Layout', - icon: { kind: 'brand', brand: 'page-layout' }, - }, - { - id: 'fav-figma', - label: 'Figma', - meta: 'Company', - icon: { kind: 'brand', brand: 'figma' }, - }, - { - id: 'fav-ben-chestnut', - label: 'Ben Chestnut', - meta: 'Person', - icon: { kind: 'brand', brand: 'ben-chestnut' }, - }, - ], workspaceNav: [ { id: 'companies', @@ -369,7 +309,7 @@ export const HERO_DATA: HeroHomeDataType = { { id: 'people', label: 'People', - icon: { kind: 'tabler', name: 'user', tone: 'purple' }, + icon: { kind: 'tabler', name: 'user', tone: 'blue' }, viewLabel: 'All People', viewCount: 5, columns: [ @@ -414,7 +354,7 @@ export const HERO_DATA: HeroHomeDataType = { { id: 'opportunities', label: 'Opportunities', - icon: { kind: 'tabler', name: 'targetArrow', tone: 'pink' }, + icon: { kind: 'tabler', name: 'targetArrow', tone: 'red' }, viewLabel: 'All Opportunities', viewCount: 3, columns: [ @@ -429,7 +369,12 @@ export const HERO_DATA: HeroHomeDataType = { { id: 'enterprise-rollout', cells: { - name: { type: 'text', value: 'Enterprise rollout' }, + name: { + type: 'text', + value: 'Enterprise rollout', + shortLabel: 'E', + tone: 'pink', + }, company: { type: 'entity', name: 'Slack', domain: 'slack.com' }, amount: { type: 'number', value: '$2,300,000' }, stage: { type: 'tag', value: 'Negotiation' }, @@ -440,7 +385,12 @@ export const HERO_DATA: HeroHomeDataType = { { id: 'workspace-upgrade', cells: { - name: { type: 'text', value: 'Workspace upgrade' }, + name: { + type: 'text', + value: 'Workspace upgrade', + shortLabel: 'W', + tone: 'pink', + }, company: { type: 'entity', name: 'Notion', domain: 'notion.com' }, amount: { type: 'number', value: '$750,000' }, stage: { type: 'tag', value: 'Qualified' }, @@ -467,7 +417,12 @@ export const HERO_DATA: HeroHomeDataType = { { id: 'send-nda', cells: { - title: { type: 'text', value: 'Send NDA' }, + title: { + type: 'text', + value: 'Send NDA', + shortLabel: 'S', + tone: 'teal', + }, assignee: { type: 'person', name: 'Tim Cook', tone: 'teal', kind: 'person', avatarUrl: PEOPLE_AVATAR_URLS.timCook }, dueDate: { type: 'text', value: 'Oct 25, 2023' }, relatedTo: { type: 'entity', name: 'Anthropic', domain: 'anthropic.com' }, @@ -477,7 +432,12 @@ export const HERO_DATA: HeroHomeDataType = { { id: 'review-proposal', cells: { - title: { type: 'text', value: 'Review proposal' }, + title: { + type: 'text', + value: 'Review proposal', + shortLabel: 'R', + tone: 'teal', + }, assignee: { type: 'person', name: 'Eddy Cue', tone: 'gray', kind: 'person', avatarUrl: PEOPLE_AVATAR_URLS.eddyCue }, dueDate: { type: 'text', value: 'Oct 28, 2023' }, relatedTo: { type: 'entity', name: 'Slack', domain: 'slack.com' }, @@ -489,7 +449,7 @@ export const HERO_DATA: HeroHomeDataType = { { id: 'notes', label: 'Notes', - icon: { kind: 'tabler', name: 'notes', tone: 'green' }, + icon: { kind: 'tabler', name: 'notes', tone: 'teal' }, viewLabel: 'All Notes', viewCount: 2, columns: [ @@ -502,7 +462,12 @@ export const HERO_DATA: HeroHomeDataType = { { id: 'discovery-call', cells: { - title: { type: 'text', value: 'Discovery call notes' }, + title: { + type: 'text', + value: 'Discovery call notes', + shortLabel: 'D', + tone: 'green', + }, createdBy: { type: 'person', name: 'Phil Schiller', tone: 'amber', kind: 'person', avatarUrl: PEOPLE_AVATAR_URLS.philSchiller }, relatedTo: { type: 'entity', name: 'Notion', domain: 'notion.com' }, added: { type: 'text', value: 'Sep 2, 2023' }, @@ -511,7 +476,12 @@ export const HERO_DATA: HeroHomeDataType = { { id: 'design-system-meeting', cells: { - title: { type: 'text', value: 'Design system meeting' }, + title: { + type: 'text', + value: 'Design system meeting', + shortLabel: 'D', + tone: 'green', + }, createdBy: { type: 'person', name: 'Tim Cook', tone: 'teal', kind: 'person', avatarUrl: PEOPLE_AVATAR_URLS.timCook }, relatedTo: { type: 'entity', name: 'Figma', domain: 'figma.com' }, added: { type: 'text', value: 'Oct 18, 2023' }, @@ -522,7 +492,7 @@ export const HERO_DATA: HeroHomeDataType = { { id: 'sales-dashboard', label: 'Sales Dashboard', - icon: { kind: 'avatar', label: '$', tone: 'amber', shape: 'circle' }, + icon: { kind: 'avatar', label: 'S', tone: 'amber', shape: 'circle' }, meta: 'Dashboard', viewLabel: 'Sales Dashboard', viewCount: 0, @@ -550,7 +520,12 @@ export const HERO_DATA: HeroHomeDataType = { { id: 'new-lead', cells: { - name: { type: 'text', value: 'New Lead Assignment' }, + name: { + type: 'text', + value: 'New Lead Assignment', + shortLabel: 'N', + tone: 'amber', + }, status: { type: 'tag', value: 'Active' }, lastRun: { type: 'text', value: 'Oct 24, 2023 10:00 am' }, }, @@ -558,7 +533,12 @@ export const HERO_DATA: HeroHomeDataType = { { id: 'nurture', cells: { - name: { type: 'text', value: 'Nurture Sequence' }, + name: { + type: 'text', + value: 'Nurture Sequence', + shortLabel: 'N', + tone: 'amber', + }, status: { type: 'tag', value: 'Inactive' }, lastRun: { type: 'text', value: 'Oct 20, 2023 3:15 pm' }, }, @@ -582,7 +562,12 @@ export const HERO_DATA: HeroHomeDataType = { { id: 'run-12345', cells: { - runId: { type: 'text', value: 'run_12345' }, + runId: { + type: 'text', + value: 'run_12345', + shortLabel: 'R', + tone: 'amber', + }, workflow: { type: 'text', value: 'New Lead Assignment' }, status: { type: 'tag', value: 'Success' }, startedAt: { type: 'text', value: 'Oct 24, 2023 10:00 am' }, @@ -592,7 +577,12 @@ export const HERO_DATA: HeroHomeDataType = { { id: 'run-12346', cells: { - runId: { type: 'text', value: 'run_12346' }, + runId: { + type: 'text', + value: 'run_12346', + shortLabel: 'R', + tone: 'amber', + }, workflow: { type: 'text', value: 'Nurture Sequence' }, status: { type: 'tag', value: 'Failed' }, startedAt: { type: 'text', value: 'Oct 20, 2023 3:15 pm' }, @@ -617,7 +607,12 @@ export const HERO_DATA: HeroHomeDataType = { { id: 'v2-lead', cells: { - version: { type: 'text', value: 'v2' }, + version: { + type: 'text', + value: 'v2', + shortLabel: 'V', + tone: 'amber', + }, workflow: { type: 'text', value: 'New Lead Assignment' }, publishedAt: { type: 'text', value: 'Oct 15, 2023 9:00 am' }, publishedBy: { type: 'person', name: 'Ivan Zhao', shortLabel: 'I', tone: 'gray', kind: 'person', avatarUrl: 'https://twentyhq.github.io/placeholder-images/people/image-09.png' }, @@ -626,7 +621,12 @@ export const HERO_DATA: HeroHomeDataType = { { id: 'v1-lead', cells: { - version: { type: 'text', value: 'v1' }, + version: { + type: 'text', + value: 'v1', + shortLabel: 'V', + tone: 'amber', + }, workflow: { type: 'text', value: 'New Lead Assignment' }, publishedAt: { type: 'text', value: 'Sep 10, 2023 1:00 pm' }, publishedBy: { type: 'person', name: 'Ivan Zhao', shortLabel: 'I', tone: 'gray', kind: 'person', avatarUrl: 'https://twentyhq.github.io/placeholder-images/people/image-09.png' }, @@ -637,15 +637,15 @@ export const HERO_DATA: HeroHomeDataType = { ], }, { - id: 'claude', - label: 'Claude', - icon: { kind: 'brand', brand: 'claude', overlay: 'link' }, - }, - { - id: 'workspace-stripe', - label: 'Stripe', - icon: { kind: 'brand', brand: 'stripe' }, - showChevron: true, + id: 'book-demo', + label: 'Book a demo', + href: 'https://cal.com/forms/f7841033-0a20-4958-8c92-4e34ec128a81', + icon: { + kind: 'brand', + brand: 'twenty', + imageSrc: '/images/home/hero/twenty-demo-logo.png', + overlay: 'link', + }, }, ], }, diff --git a/packages/twenty-website-new/src/sections/Hero/components/HomeVisual/HomeVisual.tsx b/packages/twenty-website-new/src/sections/Hero/components/HomeVisual/HomeVisual.tsx index 9d5068b2018..2776ac47cb2 100644 --- a/packages/twenty-website-new/src/sections/Hero/components/HomeVisual/HomeVisual.tsx +++ b/packages/twenty-website-new/src/sections/Hero/components/HomeVisual/HomeVisual.tsx @@ -5,7 +5,6 @@ import { styled } from '@linaria/react'; import { IconBook, IconBox, - IconBrandApple, IconBrandLinkedin, IconBuildingFactory2, IconBuildingSkyscraper, @@ -52,6 +51,7 @@ import type { HeroCellEntity, HeroCellPerson, HeroCellRelation, + HeroCellText, HeroCellValue, HeroColumnDef, HeroRowDef, @@ -66,6 +66,9 @@ import { VISUAL_TOKENS } from './homeVisualTokens'; const APP_FONT = VISUAL_TOKENS.font.family; const DEFAULT_TABLE_WIDTH = 1700; +const APPLE_WORKSPACE_LOGO_SRC = '/images/home/hero/apple-rainbow-logo.svg'; +const TABLE_CELL_HORIZONTAL_PADDING = 8; +const HOVER_ACTION_EDGE_INSET = 4; const COLORS = { accent: VISUAL_TOKENS.accent.accent9, @@ -88,16 +91,16 @@ const SIDEBAR_TONES: Record< string, { background: string; border: string; color: string } > = { - amber: { background: '#fff4d6', border: '#ffd49b', color: '#9a6700' }, - blue: { background: '#d9e2fc', border: '#c6d4f9', color: '#3557c6' }, - gray: { background: '#ebebeb', border: '#d6d6d6', color: '#666666' }, + amber: { background: '#FEF2A4', border: '#FEF2A4', color: '#35290F' }, + blue: { background: '#d9e2fc', border: '#c6d4f9', color: '#3A5CCC' }, + gray: { background: '#ebebeb', border: '#d6d6d6', color: '#838383' }, green: { background: '#ccebd7', border: '#bbe4c9', color: '#153226' }, - orange: { background: '#ffdcc3', border: '#ffcca7', color: '#582d1d' }, + orange: { background: '#ffdcc3', border: '#ffcca7', color: '#ED5F00' }, pink: { background: '#ffe1e7', border: '#ffc8d6', color: '#a51853' }, purple: { background: '#e0e7ff', border: '#c7d2fe', color: '#4f46e5' }, - teal: { background: '#c7ebe5', border: '#b3e3dc', color: '#10302b' }, + teal: { background: '#c7ebe5', border: '#afdfd7', color: '#0E9888' }, violet: { background: '#ebe5ff', border: '#d8cbff', color: '#5b3fd1' }, - red: { background: '#fee2e2', border: '#fecaca', color: '#b91c1c' }, + red: { background: '#fdd8d8', border: '#f9c6c6', color: '#DC3D43' }, }; const PERSON_TONES: Record = { @@ -113,6 +116,10 @@ const PERSON_TONES: Record = { const TABLER_STROKE = 1.6; const NAVIGATION_TABLER_STROKE = 2; +const ROW_HOVER_ACTION_DISABLED_COLUMNS = new Set([ + 'createdBy', + 'accountOwner', +]); // -- Styled Components -- @@ -139,16 +146,7 @@ const ShellScene = styled.div` const Frame = styled.div` aspect-ratio: 1280 / 832; background-color: ${COLORS.background}; - background-image: radial-gradient( - circle at top center, - rgba(0, 0, 0, 0.035), - rgba(0, 0, 0, 0) 55% - ), - ${VISUAL_TOKENS.background.noisy}; - background-position: - center top, - center; - background-repeat: no-repeat, repeat; + background-image: ${VISUAL_TOKENS.background.noisy}; border: 1px solid ${COLORS.border}; border-radius: 8px; box-shadow: ${COLORS.shadow}; @@ -157,24 +155,27 @@ const Frame = styled.div` `; const AppLayout = styled.div` - display: grid; - grid-template-columns: 72px minmax(0, 1fr); + display: flex; height: 100%; + min-height: 0; position: relative; z-index: 1; - - @media (min-width: ${theme.breakpoints.md}px) { - grid-template-columns: 220px minmax(0, 1fr); - } `; const SidebarPanel = styled.aside` background: transparent; - border-right: 1px solid rgba(0, 0, 0, 0.04); display: grid; + flex: 0 0 72px; gap: 12px; grid-template-rows: auto auto minmax(0, 1fr); + min-height: 0; padding: 12px 8px; + width: 72px; + + @media (min-width: ${theme.breakpoints.md}px) { + flex-basis: 220px; + width: 220px; + } `; const SidebarTopBar = styled.div` @@ -196,16 +197,17 @@ const WorkspaceMenu = styled.div` const WorkspaceIcon = styled.div` align-items: center; - background: #111111; - border-radius: 2px; - color: #ffffff; display: flex; - font-family: ${APP_FONT}; - font-size: 11px; - font-weight: ${theme.font.weight.medium}; + flex: 0 0 auto; height: 16px; justify-content: center; - width: 16px; + width: 14px; +`; + +const WorkspaceIconImage = styled.img` + display: block; + height: 100%; + width: 100%; `; const WorkspaceLabel = styled.span` @@ -246,10 +248,17 @@ const SidebarControls = styled.div` display: grid; gap: 8px; grid-template-columns: auto 1fr; + min-width: 0; + + @media (min-width: ${theme.breakpoints.md}px) { + display: flex; + gap: 12px; + justify-content: space-between; + } `; const SegmentedRail = styled.div` - background: rgba(252, 252, 252, 0.8); + background: #fcfcfccc; border: 1px solid ${COLORS.border}; border-radius: 40px; display: grid; @@ -261,12 +270,17 @@ const SegmentedRail = styled.div` const Segment = styled.div<{ $selected?: boolean }>` align-items: center; background: ${({ $selected }) => - $selected ? 'rgba(0, 0, 0, 0.04)' : 'transparent'}; + $selected ? '#0000000a' : 'transparent'}; border-radius: 16px; display: flex; height: 22px; justify-content: center; width: 22px; + + @media (min-width: ${theme.breakpoints.md}px) { + padding: 0 8px; + width: 32px; + } `; const NewChat = styled.div` @@ -305,8 +319,9 @@ const NewChatLabel = styled.span` `; const SidebarScroll = styled.div` - display: grid; - gap: 12px; + display: flex; + flex-direction: column; + gap: 2px; min-height: 0; overflow: hidden; `; @@ -316,14 +331,15 @@ const SidebarSection = styled.div` gap: 2px; `; -const SidebarSectionLabel = styled.span` +const SidebarSectionLabel = styled.span<{ $workspace?: boolean }>` color: ${COLORS.textLight}; display: none; font-family: ${APP_FONT}; font-size: 11px; font-weight: 600; line-height: 1; - padding: 0 4px 4px; + padding: ${({ $workspace }) => + $workspace ? '4px 4px 8px' : '0 4px 4px'}; @media (min-width: ${theme.breakpoints.md}px) { display: block; @@ -333,17 +349,57 @@ const SidebarSectionLabel = styled.span` const SidebarItemRow = styled.div<{ $active?: boolean; $depth?: number; + $interactive?: boolean; + $withBranch?: boolean; }>` align-items: center; background: ${({ $active }) => - $active ? 'rgba(0, 0, 0, 0.04)' : 'transparent'}; + $active ? VISUAL_TOKENS.background.transparent.medium : 'transparent'}; border-radius: 4px; display: grid; - gap: 8px; - grid-template-columns: auto minmax(0, 1fr) auto; + gap: 0; + grid-template-columns: ${({ $withBranch }) => + $withBranch ? '9px minmax(0, 1fr) auto' : 'minmax(0, 1fr) auto'}; height: 28px; - padding: 0 2px 0 ${({ $depth = 0 }) => `${$depth === 0 ? 4 : 24}px`}; + padding: 0 2px 0 ${({ $depth = 0 }) => `${$depth === 0 ? 4 : 11}px`}; position: relative; + text-decoration: none; + transition: background-color 0.14s ease; + + &:hover { + background: ${({ $active, $interactive }) => + $active || $interactive + ? VISUAL_TOKENS.background.transparent.medium + : 'transparent'}; + } +`; + +const SidebarItemRowLink = styled.a<{ + $active?: boolean; + $depth?: number; + $interactive?: boolean; + $withBranch?: boolean; +}>` + align-items: center; + background: ${({ $active }) => + $active ? VISUAL_TOKENS.background.transparent.medium : 'transparent'}; + border-radius: 4px; + display: grid; + gap: 0; + grid-template-columns: ${({ $withBranch }) => + $withBranch ? '9px minmax(0, 1fr) auto' : 'minmax(0, 1fr) auto'}; + height: 28px; + padding: 0 2px 0 ${({ $depth = 0 }) => `${$depth === 0 ? 4 : 11}px`}; + position: relative; + text-decoration: none; + transition: background-color 0.14s ease; + + &:hover { + background: ${({ $active, $interactive }) => + $active || $interactive + ? VISUAL_TOKENS.background.transparent.medium + : 'transparent'}; + } `; const SidebarIconSurface = styled.div<{ @@ -371,8 +427,8 @@ const SidebarItemText = styled.div` min-width: 0; `; -const SidebarItemLabel = styled.span` - color: ${COLORS.textSecondary}; +const SidebarItemLabel = styled.span<{ $active?: boolean }>` + color: ${({ $active }) => ($active ? COLORS.text : COLORS.textSecondary)}; display: none; font-family: ${APP_FONT}; font-size: 13px; @@ -423,12 +479,43 @@ const SidebarChildStack = styled.div` const BranchLine = styled.div` background: ${COLORS.borderStrong}; bottom: 14px; - left: 15px; + left: 11px; position: absolute; top: 0; width: 1px; `; +const SidebarBranchCell = styled.div<{ $isLastChild?: boolean }>` + align-self: stretch; + position: relative; + width: 9px; + + &::before { + background: ${COLORS.borderStrong}; + content: ''; + inset: 0 88.89% 0 0; + opacity: ${({ $isLastChild }) => ($isLastChild ? 0 : 1)}; + position: absolute; + } + + &::after { + border-bottom: 1px solid ${COLORS.borderStrong}; + border-left: 1px solid ${COLORS.borderStrong}; + border-radius: 0 0 0 4px; + content: ''; + inset: 0 0 45.83% 0; + position: absolute; + } +`; + +const SidebarRowMain = styled.div<{ $withBranch?: boolean }>` + align-items: center; + display: flex; + gap: 8px; + min-width: 0; + padding-left: ${({ $withBranch }) => ($withBranch ? '4px' : '0')}; +`; + const SidebarAvatar = styled.div<{ $background: string; $color: string; @@ -450,9 +537,11 @@ const SidebarAvatar = styled.div<{ `; const RightPane = styled.div` - display: grid; + display: flex; + flex: 1 1 auto; + flex-direction: column; gap: 12px; - grid-template-rows: 32px minmax(0, 1fr); + min-height: 0; min-width: 0; padding: 12px 8px 12px 0; @@ -465,6 +554,8 @@ const NavbarBar = styled.div` align-items: center; background: transparent; display: flex; + flex: 0 0 32px; + height: 32px; justify-content: space-between; min-width: 0; `; @@ -524,12 +615,13 @@ const DesktopOnlyNavbarAction = styled.div` } `; -const NavbarDecorativeChip = styled.div` +const NAVBAR_ACTION_BORDER = 'rgba(0, 0, 0, 0.08)'; + +const NavbarActionButton = styled.div` align-items: center; background: transparent; - border: 1px solid ${VISUAL_TOKENS.background.transparent.medium}; + border: 1px solid ${NAVBAR_ACTION_BORDER}; border-radius: ${VISUAL_TOKENS.border.radius.sm}; - color: ${VISUAL_TOKENS.font.color.secondary}; display: inline-flex; font-family: ${APP_FONT}; font-size: ${VISUAL_TOKENS.font.size.md}; @@ -540,19 +632,38 @@ const NavbarDecorativeChip = styled.div` white-space: nowrap; `; -const NavbarDecorativeIconWrap = styled.span` +const NavbarActionIconWrap = styled.span<{ $color?: string }>` align-items: center; - color: currentColor; + color: ${({ $color }) => $color ?? VISUAL_TOKENS.font.color.secondary}; display: flex; flex: 0 0 auto; justify-content: center; `; +const NavbarActionLabel = styled.span<{ $color?: string }>` + color: ${({ $color }) => $color ?? VISUAL_TOKENS.font.color.secondary}; + font-family: inherit; + font-size: inherit; + font-weight: inherit; + line-height: 1.4; + white-space: nowrap; +`; + +const NavbarActionSeparator = styled.div` + background: ${VISUAL_TOKENS.background.transparent.medium}; + border-radius: 56px; + height: 100%; + width: 1px; +`; + const IndexSurface = styled.div` background: ${COLORS.background}; + border: 1px solid ${COLORS.border}; border-radius: 8px; - display: grid; - grid-template-rows: 40px minmax(0, 1fr); + display: flex; + flex: 1 1 auto; + flex-direction: column; + min-height: 0; min-width: 0; overflow: hidden; `; @@ -563,15 +674,19 @@ const ViewbarBar = styled.div` border-bottom: 1px solid ${COLORS.borderLight}; display: flex; justify-content: space-between; + min-width: 0; padding: 8px 8px 8px 12px; + width: 100%; `; const ViewSwitcher = styled.div` align-items: center; display: flex; + flex: 1 1 auto; gap: 4px; height: 24px; min-width: 0; + overflow: hidden; padding: 0 4px; `; @@ -603,7 +718,11 @@ const TinyDot = styled.div` const ViewActions = styled.div` align-items: center; display: flex; + flex: 0 0 auto; gap: 2px; + margin-left: auto; + position: relative; + z-index: 1; `; const ViewAction = styled.span` @@ -622,7 +741,11 @@ const ViewAction = styled.span` const TableShell = styled.div` display: flex; + flex: 1 1 auto; + min-width: 0; min-height: 0; + overflow: hidden; + width: 100%; `; const GripRail = styled.div` @@ -640,11 +763,14 @@ const GripCell = styled.div` const TableViewport = styled.div<{ $dragging: boolean }>` cursor: ${({ $dragging }) => ($dragging ? 'grabbing' : 'grab')}; + flex: 1 1 auto; min-height: 0; + min-width: 0; overflow-x: auto; overflow-y: hidden; overscroll-behavior-x: contain; scrollbar-width: none; + width: 100%; &::-webkit-scrollbar { display: none; @@ -652,6 +778,10 @@ const TableViewport = styled.div<{ $dragging: boolean }>` `; const TableCanvas = styled.div<{ $width: number }>` + display: flex; + flex-direction: column; + height: 100%; + min-height: 100%; min-width: ${({ $width }) => `${$width}px`}; width: ${({ $width }) => `${$width}px`}; `; @@ -693,7 +823,7 @@ const TableCell = styled.div<{ $align === 'right' ? 'flex-end' : 'flex-start'}; left: ${({ $sticky }) => ($sticky ? '0' : 'auto')}; min-width: ${({ $width }) => `${$width}px`}; - padding: 0 8px; + padding: 0 ${TABLE_CELL_HORIZONTAL_PADDING}px; position: ${({ $sticky }) => ($sticky ? 'sticky' : 'relative')}; z-index: ${({ $header, $sticky }) => { if ($sticky && $header) { @@ -778,6 +908,16 @@ const EntityCellLayout = styled.div` align-items: center; display: flex; gap: 4px; + height: 100%; + min-width: 0; + position: relative; + width: 100%; +`; + +const CellHoverAnchor = styled.div` + align-items: center; + display: flex; + height: 100%; min-width: 0; position: relative; width: 100%; @@ -826,7 +966,7 @@ const PersonAvatarCircle = styled.div<{ display: flex; flex: 0 0 auto; font-family: ${APP_FONT}; - font-size: 9px; + font-size: 10px; font-weight: ${theme.font.weight.medium}; height: 14px; justify-content: center; @@ -847,24 +987,29 @@ const BooleanRow = styled.div` gap: 4px; `; -const HoverActions = styled.div<{ $visible: boolean }>` +const HoverActions = styled.div<{ $rightInset?: number; $visible: boolean }>` align-items: center; - background: ${COLORS.backgroundSecondary}; - border: 1px solid ${COLORS.border}; + background: ${VISUAL_TOKENS.background.transparent.primary}; + border: 1px solid ${VISUAL_TOKENS.background.transparent.light}; border-radius: 4px; + bottom: 4px; + box-sizing: border-box; box-shadow: ${VISUAL_TOKENS.boxShadow.light}; display: flex; - gap: 2px; + gap: 0; + justify-content: center; opacity: ${({ $visible }) => ($visible ? 1 : 0)}; - padding: 2px; + padding: 0 4px; pointer-events: none; position: absolute; - right: 4px; + right: ${({ $rightInset = HOVER_ACTION_EDGE_INSET - TABLE_CELL_HORIZONTAL_PADDING }) => + `${$rightInset}px`}; top: 4px; transform: translateX(${({ $visible }) => ($visible ? '0' : '4px')}); transition: opacity 0.14s ease, transform 0.14s ease; + width: 24px; `; const MiniAction = styled.div` @@ -872,9 +1017,9 @@ const MiniAction = styled.div` border-radius: 2px; color: ${COLORS.textSecondary}; display: flex; - height: 20px; + height: 16px; justify-content: center; - width: 20px; + width: 16px; `; const FooterFirstContent = styled.div` @@ -960,6 +1105,7 @@ const HEADER_ICON_MAP: Record = { // -- Utility functions -- +const failedAvatarUrls = new Set(); const failedFaviconUrls = new Set(); function getInitials(value: string) { @@ -1157,15 +1303,17 @@ function CopyMini({ color = COLORS.textSecondary, size = 14 }: MiniIconProps) { // -- Favicon logo component -- function FaviconLogo({ + src, domain, label, size = 14, }: { + src?: string; domain?: string; label?: string; size?: number; }) { - const faviconUrl = getLogoUrlFromDomainName(domain); + const faviconUrl = src ?? getLogoUrlFromDomainName(domain); const [localFailedUrl, setLocalFailedUrl] = useState(null); const showFavicon = faviconUrl !== undefined && @@ -1213,6 +1361,31 @@ function FaviconLogo({ // -- Sidebar icon rendering -- +function PersonAvatarContent({ token }: { token: HeroCellPerson }) { + const [localFailedUrl, setLocalFailedUrl] = useState(null); + const showAvatar = + token.avatarUrl !== undefined && + !failedAvatarUrls.has(token.avatarUrl) && + localFailedUrl !== token.avatarUrl; + + if (showAvatar) { + return ( + { + if (token.avatarUrl) { + failedAvatarUrls.add(token.avatarUrl); + setLocalFailedUrl(token.avatarUrl); + } + }} + /> + ); + } + + return token.shortLabel ?? getInitials(token.name); +} + function renderSidebarIcon(icon: HeroSidebarIcon): ReactNode { if (icon.kind === 'brand') { return ( @@ -1223,14 +1396,16 @@ function renderSidebarIcon(icon: HeroSidebarIcon): ReactNode { > {icon.overlay === 'link' ? (
void; selectedLabel?: string; }) { + const showBranch = depth > 0; + const rowSelectable = interactive && item.href === undefined; + const rowInteractive = rowSelectable || item.href !== undefined; const rowActive = - interactive && selectedLabel !== undefined && item.label === selectedLabel; + rowSelectable && selectedLabel !== undefined && item.label === selectedLabel; + const childItems = item.children ?? []; + const rowContent = ( + <> + {showBranch ? : null} + + {renderSidebarIcon(item.icon)} + + {item.label} + {item.meta ? · {item.meta} : null} + + + {item.showChevron || (item.children && item.children.length > 0) ? ( + + + + ) : null} + + ); return ( <> - onSelect?.(item.label) : undefined} - style={{ cursor: interactive ? 'pointer' : 'default' }} - > - {renderSidebarIcon(item.icon)} - - {item.label} - {item.meta ? · {item.meta} : null} - - {item.showChevron || (item.children && item.children.length > 0) ? ( - - - - ) : null} - - {item.children && item.children.length > 0 ? ( + {item.href ? ( + + {rowContent} + + ) : ( + onSelect?.(item.label) : undefined} + style={{ cursor: rowInteractive ? 'pointer' : 'default' }} + > + {rowContent} + + )} + {childItems.length > 0 ? ( - {item.children.map((child) => ( + {childItems.map((child, index) => ( + - {token.avatarUrl ? ( - - ) : ( - (token.shortLabel ?? getInitials(token.name)) - )} + } /> @@ -1413,11 +1616,8 @@ function PersonTokenCell({ ) : null} - -
+ ); } @@ -1468,7 +1668,7 @@ function RelationCellComponent({ hovered: boolean; }) { return ( -
+ {cell.items.map((item) => { const tone = PERSON_TONES[item.tone ?? 'gray'] ?? PERSON_TONES.gray; @@ -1494,11 +1694,37 @@ function RelationCellComponent({ - -
+ + ); +} + +function TextCellComponent({ + cell, + isFirstColumn, +}: { + cell: HeroCellText; + isFirstColumn: boolean; +}) { + if (!isFirstColumn || !cell.shortLabel) { + return {cell.value}; + } + + const tone = PERSON_TONES[cell.tone ?? 'gray'] ?? PERSON_TONES.gray; + + return ( + + {cell.shortLabel} + + } + /> ); } @@ -1506,10 +1732,13 @@ function renderCellValue( cell: HeroCellValue, hovered: boolean, isFirstColumn: boolean, + columnId: string, ): ReactNode { + const showHoverAction = !ROW_HOVER_ACTION_DISABLED_COLUMNS.has(columnId); + switch (cell.type) { case 'text': - return {cell.value}; + return ; case 'number': return {cell.value}; case 'link': @@ -1520,11 +1749,6 @@ function renderCellValue( label={cell.value} variant={ChipVariant.Static} /> - - - ); case 'boolean': @@ -1537,17 +1761,24 @@ function renderCellValue( case 'tag': return {cell.value}; case 'person': - return ; + return ( + + ); case 'entity': return ( ); case 'relation': - return ; + return ( + + ); } } @@ -1728,11 +1959,10 @@ export function HomeVisual({ visual }: { visual: HeroVisualType }) { - {visual.workspace.name} @@ -1777,7 +2007,7 @@ export function HomeVisual({ visual }: { visual: HeroVisualType }) { ) : null} - Workspace + Workspace {visual.workspaceNav.map(renderSidebarEntry)} @@ -1800,26 +2030,30 @@ export function HomeVisual({ visual }: { visual: HeroVisualType }) { - - + + - - New Record - + + New Record + - - + + - - + + + + ⌘K + + @@ -1930,6 +2164,7 @@ export function HomeVisual({ visual }: { visual: HeroVisualType }) { cell, hovered, !!column.isFirstColumn, + column.id, ) : null} diff --git a/packages/twenty-website-new/src/sections/Hero/components/HomeVisual/homeVisualChip.tsx b/packages/twenty-website-new/src/sections/Hero/components/HomeVisual/homeVisualChip.tsx index d55c0a68b5c..8d9fbd0e46a 100644 --- a/packages/twenty-website-new/src/sections/Hero/components/HomeVisual/homeVisualChip.tsx +++ b/packages/twenty-website-new/src/sections/Hero/components/HomeVisual/homeVisualChip.tsx @@ -25,7 +25,7 @@ const StyledContainer = styled.div< Pick >` --chip-horizontal-padding: ${VISUAL_TOKENS.spacing[1]}; - --chip-vertical-padding: ${VISUAL_TOKENS.spacing[1]}; + --chip-vertical-padding: 3px; align-items: center; background-color: ${({ variant }) => @@ -70,6 +70,9 @@ const StyledContainer = styled.div< ? VISUAL_TOKENS.spacing[2] : 'var(--chip-horizontal-padding)'}; user-select: none; + font-family: ${VISUAL_TOKENS.font.family}; + font-size: ${VISUAL_TOKENS.font.size.md}; + line-height: 1.4; font-weight: ${({ isBold }) => isBold @@ -83,7 +86,7 @@ const StyledContainer = styled.div< : variant === ChipVariant.Highlighted ? VISUAL_TOKENS.background.transparent.medium : variant === ChipVariant.Static - ? VISUAL_TOKENS.background.transparent.light + ? VISUAL_TOKENS.background.transparent.lighter : 'inherit'}; } @@ -94,7 +97,7 @@ const StyledContainer = styled.div< : variant === ChipVariant.Highlighted ? VISUAL_TOKENS.background.transparent.strong : variant === ChipVariant.Static - ? VISUAL_TOKENS.background.transparent.light + ? VISUAL_TOKENS.background.transparent.lighter : 'inherit'}; } @@ -105,8 +108,10 @@ const StyledContainer = styled.div< const StyledLabel = styled.span` color: inherit; + font-family: inherit; font-size: inherit; font-weight: inherit; + line-height: inherit; max-width: 100%; min-width: 0; overflow: hidden; diff --git a/packages/twenty-website-new/src/sections/Hero/components/HomeVisual/homeVisualTokens.ts b/packages/twenty-website-new/src/sections/Hero/components/HomeVisual/homeVisualTokens.ts index c553754c8e4..93eff831517 100644 --- a/packages/twenty-website-new/src/sections/Hero/components/HomeVisual/homeVisualTokens.ts +++ b/packages/twenty-website-new/src/sections/Hero/components/HomeVisual/homeVisualTokens.ts @@ -35,8 +35,7 @@ export const VISUAL_TOKENS = { }, }, background: { - noisy: - "url(\"data:image/svg+xml,%3Csvg viewBox='0 0 512 512' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noiseFilter'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='1.5' numOctaves='6' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noiseFilter)' opacity='0.03'/%3E%3C/svg%3E\")", + noisy: 'url("/images/shared/light-noise.png")', primary: '#ffffff', secondary: '#fcfcfc', transparent: { diff --git a/packages/twenty-website-new/src/sections/Hero/types/HeroHomeData.ts b/packages/twenty-website-new/src/sections/Hero/types/HeroHomeData.ts index be90e1290b2..39932e9d3eb 100644 --- a/packages/twenty-website-new/src/sections/Hero/types/HeroHomeData.ts +++ b/packages/twenty-website-new/src/sections/Hero/types/HeroHomeData.ts @@ -2,7 +2,12 @@ import type { HeroBaseDataType } from '@/sections/Hero/types/HeroBaseData'; // -- Cell value types -- -export type HeroCellText = { type: 'text'; value: string }; +export type HeroCellText = { + type: 'text'; + value: string; + shortLabel?: string; + tone?: string; +}; export type HeroCellNumber = { type: 'number'; value: string }; export type HeroCellLink = { type: 'link'; value: string }; export type HeroCellBoolean = { type: 'boolean'; value: boolean }; @@ -57,7 +62,13 @@ export type HeroRowDef = { export type HeroSidebarIcon = | { kind: 'tabler'; name: string; tone: string; overlay?: 'link' } - | { kind: 'brand'; brand: string; overlay?: 'link' } + | { + kind: 'brand'; + brand: string; + domain?: string; + imageSrc?: string; + overlay?: 'link'; + } | { kind: 'avatar'; label: string; @@ -70,6 +81,7 @@ export type HeroSidebarIcon = export type HeroSidebarItem = { id: string; label: string; + href?: string; icon: HeroSidebarIcon; meta?: string; active?: boolean;