Interactive home visual for website. (#19261)

This PR makes the Home Visual interactive.

Not the perfect code, but does the trick for now to convey what we're
trying to do. As per the conversation with Thomas, we will iterate over
it and make it better when we get past the first release.

---------

Co-authored-by: Thomas des Francs <tdesfrancs@gmail.com>
This commit is contained in:
Abdullah.
2026-04-02 18:45:22 +05:00
committed by GitHub
parent e1caf6caa1
commit d7e31ab2f0
24 changed files with 3081 additions and 132 deletions
+1
View File
@@ -12,6 +12,7 @@
"@base-ui/utils": "^0.2.6",
"@linaria/core": "^7.0.0",
"@linaria/react": "^7.0.1",
"@tabler/icons-react": "^3.41.1",
"@wyw-in-js/babel-preset": "^0.8.1",
"next": "16.1.7",
"next-with-linaria": "^1.3.0",
Binary file not shown.

After

Width:  |  Height:  |  Size: 129 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

@@ -0,0 +1,3 @@
<svg preserveAspectRatio="none" width="100%" height="100%" overflow="visible" style="display: block;" viewBox="0 0 21.8529 20.8291" fill="none" xmlns="http://www.w3.org/2000/svg">
<path id="Union" d="M7.56177 12.375C9.85775 12.375 11.9839 13.1432 13.7385 14.4404C15.0286 15.3944 15.356 16.8384 14.969 18.0791H18.4436C18.8262 18.0585 19.0148 17.995 19.0989 17.9531C19.0949 17.8812 19.0718 17.7266 18.9465 17.4668C18.7448 17.0483 18.3836 16.5921 17.9719 16.25C17.7705 16.0826 17.5819 15.9633 17.426 15.8906C17.2682 15.8171 17.19 15.8127 17.1868 15.8125V13.0625C18.1759 13.0625 19.0867 13.6004 19.7297 14.1348C20.4114 14.7013 21.0335 15.4624 21.4241 16.2725C21.7897 17.0311 22.0936 18.1461 21.5803 19.1904C21.023 20.3237 19.8247 20.7654 18.5549 20.8271L18.5217 20.8291H11.5872V20.6182C11.5322 20.6205 11.4769 20.625 11.4211 20.625H3.70239C2.10636 20.6249 0.819051 19.6708 0.277588 18.418C-0.287504 17.1103 -0.0241157 15.4825 1.38501 14.4404C3.13962 13.1431 5.2658 12.375 7.56177 12.375ZM7.56177 15.125C5.89402 15.125 4.33401 15.6797 3.01978 16.6514C2.83273 16.7898 2.7794 16.9126 2.76099 16.9873C2.73982 17.0736 2.74306 17.1907 2.802 17.3271C2.92436 17.61 3.24339 17.8749 3.70239 17.875H11.4211C11.8802 17.875 12.1991 17.61 12.3215 17.3271C12.3805 17.1907 12.3837 17.0736 12.3625 16.9873C12.3441 16.9125 12.2909 16.7898 12.1038 16.6514C10.7895 15.6797 9.22953 15.125 7.56177 15.125ZM8.07446 0C11.0155 0 13.3996 2.38417 13.3997 5.3252C13.3997 8.26626 11.0155 10.6504 8.07446 10.6504C5.13349 10.6503 2.74927 8.2662 2.74927 5.3252C2.74931 2.38423 5.13351 9.87628e-05 8.07446 0ZM15.4827 1.01953C17.86 1.01982 19.7871 2.94781 19.7874 5.3252C19.7872 7.70263 17.8601 9.6296 15.4827 9.62988C15.1773 9.62988 14.8794 9.59755 14.592 9.53711V6.59863C14.8445 6.77536 15.1511 6.87987 15.4827 6.87988C16.3413 6.8796 17.0372 6.18385 17.0374 5.3252C17.0371 4.46659 16.3413 3.76982 15.4827 3.76953C15.1514 3.76955 14.8444 3.87416 14.592 4.05078V1.1123C14.8794 1.05187 15.1774 1.01954 15.4827 1.01953ZM8.07446 2.75C6.65229 2.7501 5.49931 3.90301 5.49927 5.3252C5.49927 6.74741 6.65227 7.90029 8.07446 7.90039C9.49674 7.90039 10.6497 6.74748 10.6497 5.3252C10.6496 3.90295 9.49672 2.75 8.07446 2.75Z" fill="var(--fill-0, #4A38F5)"/>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

@@ -0,0 +1,3 @@
<svg preserveAspectRatio="none" width="100%" height="100%" overflow="visible" style="display: block;" viewBox="0 0 157 48" fill="none" xmlns="http://www.w3.org/2000/svg">
<path id="shape" d="M4 0.5H153C154.933 0.5 156.5 2.067 156.5 4V30.8594C156.5 31.6178 156.253 32.3556 155.798 32.9619L145.922 46.1025C145.261 46.9821 144.224 47.4999 143.124 47.5H4C2.067 47.5 0.5 45.933 0.5 44V4C0.5 2.067 2.067 0.5 4 0.5Z" fill="var(--fill-0, #FAFAFA)" stroke="var(--stroke-0, #8174F8)"/>
</svg>

After

Width:  |  Height:  |  Size: 483 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

@@ -1,4 +1,19 @@
import { HeroHomeDataType } from '@/sections/Hero/types';
import type { HeroHomeDataType } from '@/sections/Hero/types';
const PEOPLE_AVATAR_URLS = {
craigFederighi:
'https://twentyhq.github.io/placeholder-images/people/image-33.png',
eddyCue:
'https://twentyhq.github.io/placeholder-images/people/image-18.png',
jeffWilliams:
'https://twentyhq.github.io/placeholder-images/people/image-22.png',
katherineAdams:
'https://twentyhq.github.io/placeholder-images/people/image-07.png',
philSchiller:
'https://twentyhq.github.io/placeholder-images/people/image-14.png',
timCook:
'https://twentyhq.github.io/placeholder-images/people/image-27.png',
} as const;
export const HERO_DATA: HeroHomeDataType = {
heading: [
@@ -9,12 +24,629 @@ export const HERO_DATA: HeroHomeDataType = {
body: {
text: 'Twenty gives technical teams the building blocks for a custom CRM that meets complex business needs and quickly adapts as the business evolves.',
},
background: {
src: '/images/home/hero/background.png',
alt: '',
},
foreground: {
src: '/images/home/hero/foreground.png',
alt: '',
visual: {
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',
label: 'Companies',
icon: { kind: 'tabler', name: 'buildingSkyscraper', tone: 'blue' },
active: true,
viewLabel: 'All Companies',
viewCount: 9,
columns: [
{ id: 'company', label: 'Companies', width: 180, isFirstColumn: true },
{ id: 'url', label: 'Url', width: 140 },
{ id: 'createdBy', label: 'Created By', width: 150 },
{ id: 'address', label: 'Address', width: 120 },
{ id: 'accountOwner', label: 'Account Owner', width: 150 },
{ id: 'icp', label: 'ICP', width: 80 },
{ id: 'arr', label: 'ARR', width: 120, align: 'right' },
{ id: 'linkedin', label: 'Linkedin', width: 96 },
{ id: 'industry', label: 'Industry', width: 96 },
{ id: 'mainContact', label: 'Main contact', width: 120 },
{ id: 'employees', label: 'Employees', width: 120, align: 'right' },
{ id: 'opportunities', label: 'Opportunities', width: 122 },
{ id: 'added', label: 'Added', width: 120 },
],
rows: [
{
id: 'anthropic',
cells: {
company: { type: 'entity', name: 'Anthropic', domain: 'anthropic.com' },
url: { type: 'link', value: 'qonto.com' },
createdBy: { type: 'person', name: 'Jeff Williams', tone: 'blue', kind: 'person', avatarUrl: PEOPLE_AVATAR_URLS.jeffWilliams },
address: { type: 'text', value: '18 Rue De Navarin' },
accountOwner: { type: 'person', name: 'Phil Schiller', tone: 'amber', kind: 'person', avatarUrl: PEOPLE_AVATAR_URLS.philSchiller },
icp: { type: 'boolean', value: true },
arr: { type: 'number', value: '$500,000' },
linkedin: { type: 'link', value: 'anthropic' },
industry: { type: 'tag', value: 'AI Research' },
mainContact: { type: 'person', name: 'Dario Amodei', shortLabel: 'D', tone: 'gray', kind: 'person', avatarUrl: 'https://twentyhq.github.io/placeholder-images/people/image-11.png' },
employees: { type: 'number', value: '612' },
opportunities: { type: 'relation', items: [{ name: 'Enterprise Expansion', shortLabel: 'E', tone: 'blue' }] },
added: { type: 'text', value: 'Jul 1, 2023' },
},
},
{
id: 'linkedin',
cells: {
company: { type: 'entity', name: 'Linkedin', domain: 'linkedin.com' },
url: { type: 'link', value: 'linkedin.com' },
createdBy: { type: 'person', name: 'Craig Federighi', tone: 'purple', kind: 'person', avatarUrl: PEOPLE_AVATAR_URLS.craigFederighi },
address: { type: 'text', value: '1226 Moises Causeway' },
accountOwner: { type: 'person', name: 'Craig Federighi', tone: 'purple', kind: 'person', avatarUrl: PEOPLE_AVATAR_URLS.craigFederighi },
icp: { type: 'boolean', value: false },
arr: { type: 'number', value: '$1,000,000' },
linkedin: { type: 'link', value: 'linkedin' },
industry: { type: 'tag', value: 'Professional Networking' },
mainContact: { type: 'person', name: 'Ryan Roslansky', shortLabel: 'R', tone: 'teal', kind: 'person', avatarUrl: 'https://twentyhq.github.io/placeholder-images/people/image-26.png' },
employees: { type: 'number', value: '19,300' },
opportunities: { type: 'relation', items: [{ name: 'Talent Outreach', shortLabel: 'T', tone: 'purple' }] },
added: { type: 'text', value: 'Jul 3, 2023' },
},
},
{
id: 'slack',
cells: {
company: { type: 'entity', name: 'Slack', domain: 'slack.com' },
url: { type: 'link', value: 'slack.com' },
createdBy: { type: 'person', name: 'Eddy Cue', tone: 'gray', kind: 'person', avatarUrl: PEOPLE_AVATAR_URLS.eddyCue },
address: { type: 'text', value: '1316 Dameon Mountain' },
accountOwner: { type: 'person', name: 'Katherine Adams', tone: 'red', kind: 'person', avatarUrl: PEOPLE_AVATAR_URLS.katherineAdams },
icp: { type: 'boolean', value: true },
arr: { type: 'number', value: '$2,300,000' },
linkedin: { type: 'link', value: 'slack' },
industry: { type: 'tag', value: 'Collaboration Software' },
mainContact: { type: 'person', name: 'Lidiane Jones', shortLabel: 'LJ', tone: 'pink', kind: 'person', avatarUrl: 'https://twentyhq.github.io/placeholder-images/people/image-04.png' },
employees: { type: 'number', value: '4,500' },
opportunities: { type: 'relation', items: [{ name: 'Workspace Renewal', shortLabel: 'W', tone: 'teal' }] },
added: { type: 'text', value: 'Jul 5, 2023' },
},
},
{
id: 'notion',
cells: {
company: { type: 'entity', name: 'Notion', domain: 'notion.com' },
url: { type: 'link', value: 'notion.com' },
createdBy: { type: 'person', name: 'API - Key name', tone: 'gray', kind: 'api', shortLabel: 'API' },
address: { type: 'text', value: '1162 Sammy Creek' },
accountOwner: { type: 'person', name: 'Phil Schiller', tone: 'amber', kind: 'person', avatarUrl: PEOPLE_AVATAR_URLS.philSchiller },
icp: { type: 'boolean', value: false },
arr: { type: 'number', value: '$750,000' },
linkedin: { type: 'link', value: 'notion' },
industry: { type: 'tag', value: 'Productivity Software' },
mainContact: { type: 'person', name: 'Ivan Zhao', shortLabel: 'I', tone: 'gray', kind: 'person', avatarUrl: 'https://twentyhq.github.io/placeholder-images/people/image-09.png' },
employees: { type: 'number', value: '620' },
opportunities: { type: 'relation', items: [{ name: 'Workspace Consolidation', shortLabel: 'W', tone: 'gray' }] },
added: { type: 'text', value: 'Jul 8, 2023' },
},
},
{
id: 'figma',
cells: {
company: { type: 'entity', name: 'Figma', domain: 'figma.com' },
url: { type: 'link', value: 'figma.com' },
createdBy: { type: 'person', name: 'Workflow name', tone: 'gray', kind: 'workflow', shortLabel: 'WF' },
address: { type: 'text', value: '110 Oswald Junction' },
accountOwner: { type: 'person', name: 'Tim Cook', tone: 'teal', kind: 'person', avatarUrl: PEOPLE_AVATAR_URLS.timCook },
icp: { type: 'boolean', value: true },
arr: { type: 'number', value: '$3,500,000' },
linkedin: { type: 'link', value: 'figma' },
industry: { type: 'tag', value: 'Design Tools' },
mainContact: { type: 'person', name: 'Dylan Field', shortLabel: 'D', tone: 'purple', kind: 'person', avatarUrl: 'https://twentyhq.github.io/placeholder-images/people/image-31.png' },
employees: { type: 'number', value: '1,300' },
opportunities: { type: 'relation', items: [{ name: 'AI Prototyping', shortLabel: 'AI', tone: 'purple' }, { name: 'Design Ops', shortLabel: 'D', tone: 'teal' }] },
added: { type: 'text', value: 'Jul 12, 2023' },
},
},
{
id: 'github',
cells: {
company: { type: 'entity', name: 'Github', domain: 'github.com' },
url: { type: 'link', value: 'github.com' },
createdBy: { type: 'person', name: 'Jeff Williams', tone: 'blue', kind: 'person', avatarUrl: PEOPLE_AVATAR_URLS.jeffWilliams },
address: { type: 'text', value: '3891 Ranchview Drive' },
accountOwner: { type: 'person', name: 'Jeff Williams', tone: 'blue', kind: 'person', avatarUrl: PEOPLE_AVATAR_URLS.jeffWilliams },
icp: { type: 'boolean', value: true },
arr: { type: 'number', value: '$900,000' },
linkedin: { type: 'link', value: 'github' },
industry: { type: 'tag', value: 'Developer Platform' },
mainContact: { type: 'person', name: 'Thomas Dohmke', shortLabel: 'T', tone: 'gray', kind: 'person', avatarUrl: 'https://twentyhq.github.io/placeholder-images/people/image-24.png' },
employees: { type: 'number', value: '3,800' },
opportunities: { type: 'relation', items: [{ name: 'Copilot Rollout', shortLabel: 'C', tone: 'blue' }] },
added: { type: 'text', value: 'Jul 14, 2023' },
},
},
{
id: 'airbnb',
cells: {
company: { type: 'entity', name: 'Airbnb', domain: 'airbnb.com' },
url: { type: 'link', value: 'airbnb.com' },
createdBy: { type: 'person', name: 'Tim Cook', tone: 'teal', kind: 'person', avatarUrl: PEOPLE_AVATAR_URLS.timCook },
address: { type: 'text', value: '4517 Washington Avenue' },
accountOwner: { type: 'person', name: 'Eddy Cue', tone: 'gray', kind: 'person', avatarUrl: PEOPLE_AVATAR_URLS.eddyCue },
icp: { type: 'boolean', value: true },
arr: { type: 'number', value: '$4,200,000' },
linkedin: { type: 'link', value: 'airbnb' },
industry: { type: 'tag', value: 'Travel' },
mainContact: { type: 'person', name: 'Brian Chesky', shortLabel: 'B', tone: 'pink', kind: 'person', avatarUrl: 'https://twentyhq.github.io/placeholder-images/people/image-01.png' },
employees: { type: 'number', value: '6,900' },
opportunities: { type: 'relation', items: [{ name: 'Host Ops', shortLabel: 'H', tone: 'pink' }] },
added: { type: 'text', value: 'Jul 15, 2023' },
},
},
{
id: 'stripe',
cells: {
company: { type: 'entity', name: 'Stripe', domain: 'stripe.com' },
url: { type: 'link', value: 'stripe.com' },
createdBy: { type: 'person', name: 'Katherine Adams', tone: 'red', kind: 'person', avatarUrl: PEOPLE_AVATAR_URLS.katherineAdams },
address: { type: 'text', value: '2118 Thornridge Circle' },
accountOwner: { type: 'person', name: 'Tim Cook', tone: 'teal', kind: 'person', avatarUrl: PEOPLE_AVATAR_URLS.timCook },
icp: { type: 'boolean', value: true },
arr: { type: 'number', value: '$1,800,000' },
linkedin: { type: 'link', value: 'stripe' },
industry: { type: 'tag', value: 'Payments' },
mainContact: { type: 'person', name: 'Patrick Collison', shortLabel: 'P', tone: 'blue', kind: 'person', avatarUrl: 'https://twentyhq.github.io/placeholder-images/people/image-16.png' },
employees: { type: 'number', value: '7,400' },
opportunities: { type: 'relation', items: [{ name: 'Billing Expansion', shortLabel: 'B', tone: 'purple' }] },
added: { type: 'text', value: 'Jul 17, 2023' },
},
},
{
id: 'sequoia',
cells: {
company: { type: 'entity', name: 'Sequoia', domain: 'sequoia.com' },
url: { type: 'link', value: 'sequoia.com' },
createdBy: { type: 'person', name: 'Phil Schiller', tone: 'amber', kind: 'person', avatarUrl: PEOPLE_AVATAR_URLS.philSchiller },
address: { type: 'text', value: '1316 Dameon Mountain' },
accountOwner: { type: 'person', name: 'Phil Schiller', tone: 'amber', kind: 'person', avatarUrl: PEOPLE_AVATAR_URLS.philSchiller },
icp: { type: 'boolean', value: false },
arr: { type: 'number', value: '$6,000,000' },
linkedin: { type: 'link', value: 'sequoia' },
industry: { type: 'tag', value: 'Venture Capital' },
mainContact: { type: 'person', name: 'Roelof Botha', shortLabel: 'R', tone: 'green', kind: 'person', avatarUrl: 'https://twentyhq.github.io/placeholder-images/people/image-13.png' },
employees: { type: 'number', value: '1,100' },
opportunities: { type: 'relation', items: [{ name: 'Fund Ops', shortLabel: 'F', tone: 'green' }] },
added: { type: 'text', value: 'Jul 20, 2023' },
},
},
{
id: 'segment',
cells: {
company: { type: 'entity', name: 'Segment', domain: 'segment.com' },
url: { type: 'link', value: 'segment.com' },
createdBy: { type: 'person', name: 'Phil Schiller', tone: 'amber', kind: 'person', avatarUrl: PEOPLE_AVATAR_URLS.philSchiller },
address: { type: 'text', value: '8502 Preston Rd. East' },
accountOwner: { type: 'person', name: 'Eddy Cue', tone: 'gray', kind: 'person', avatarUrl: PEOPLE_AVATAR_URLS.eddyCue },
icp: { type: 'boolean', value: true },
arr: { type: 'number', value: '$2,750,000' },
linkedin: { type: 'link', value: 'segment' },
industry: { type: 'tag', value: 'Customer Data' },
mainContact: { type: 'person', name: 'Peter Reinhardt', shortLabel: 'P', tone: 'teal', kind: 'person', avatarUrl: 'https://twentyhq.github.io/placeholder-images/people/image-20.png' },
employees: { type: 'number', value: '1,550' },
opportunities: { type: 'relation', items: [{ name: 'Warehouse Rollout', shortLabel: 'W', tone: 'teal' }] },
added: { type: 'text', value: 'Jul 21, 2023' },
},
},
{
id: 'mailchimp',
cells: {
company: { type: 'entity', name: 'Mailchimp', domain: 'mailchimp.com' },
url: { type: 'link', value: 'mailchimp.com' },
createdBy: { type: 'person', name: 'Eddy Cue', tone: 'gray', kind: 'person', avatarUrl: PEOPLE_AVATAR_URLS.eddyCue },
address: { type: 'text', value: '3517 W. Gray St.' },
accountOwner: { type: 'person', name: 'Craig Federighi', tone: 'purple', kind: 'person', avatarUrl: PEOPLE_AVATAR_URLS.craigFederighi },
icp: { type: 'boolean', value: false },
arr: { type: 'number', value: '$1,250,000' },
linkedin: { type: 'link', value: 'mailchimp' },
industry: { type: 'tag', value: 'Marketing Automation' },
mainContact: { type: 'person', name: 'Rania Succar', shortLabel: 'R', tone: 'amber', kind: 'person', avatarUrl: 'https://twentyhq.github.io/placeholder-images/people/image-28.png' },
employees: { type: 'number', value: '1,900' },
opportunities: { type: 'relation', items: [{ name: 'Lifecycle Campaigns', shortLabel: 'L', tone: 'amber' }] },
added: { type: 'text', value: 'Jul 23, 2023' },
},
},
{
id: 'accel',
cells: {
company: { type: 'entity', name: 'Accel', domain: 'accel.com' },
url: { type: 'link', value: 'accel.com' },
createdBy: { type: 'person', name: 'Craig Federighi', tone: 'purple', kind: 'person', avatarUrl: PEOPLE_AVATAR_URLS.craigFederighi },
address: { type: 'text', value: '4140 Parker Rd.' },
accountOwner: { type: 'person', name: 'Katherine Adams', tone: 'red', kind: 'person', avatarUrl: PEOPLE_AVATAR_URLS.katherineAdams },
icp: { type: 'boolean', value: true },
arr: { type: 'number', value: '$5,800,000' },
linkedin: { type: 'link', value: 'accel' },
industry: { type: 'tag', value: 'Venture Capital' },
mainContact: { type: 'person', name: 'Ping Li', shortLabel: 'P', tone: 'purple', kind: 'person', avatarUrl: 'https://twentyhq.github.io/placeholder-images/people/image-30.png' },
employees: { type: 'number', value: '540' },
opportunities: { type: 'relation', items: [{ name: 'Portfolio Sync', shortLabel: 'P', tone: 'purple' }] },
added: { type: 'text', value: 'Jul 24, 2023' },
},
},
{
id: 'founders-fund',
cells: {
company: { type: 'entity', name: 'Founders Fund', domain: 'foundersfund.com' },
url: { type: 'link', value: 'foundersfund.com' },
createdBy: { type: 'person', name: 'System', tone: 'gray', kind: 'system', shortLabel: 'SYS' },
address: { type: 'text', value: '2715 Ash Dr. San Jose' },
accountOwner: { type: 'person', name: 'Tim Cook', tone: 'teal', kind: 'person', avatarUrl: PEOPLE_AVATAR_URLS.timCook },
icp: { type: 'boolean', value: true },
arr: { type: 'number', value: '$2,100,000' },
linkedin: { type: 'link', value: 'foundersfund' },
industry: { type: 'tag', value: 'Private Equity' },
mainContact: { type: 'person', name: 'Peter Thiel', shortLabel: 'P', tone: 'gray', kind: 'person', avatarUrl: 'https://twentyhq.github.io/placeholder-images/people/image-05.png' },
employees: { type: 'number', value: '734' },
opportunities: { type: 'relation', items: [{ name: 'Fundraising', shortLabel: 'F', tone: 'gray' }] },
added: { type: 'text', value: 'Jul 25, 2023' },
},
},
{
id: 'google',
cells: {
company: { type: 'entity', name: 'Google', domain: 'google.com' },
url: { type: 'link', value: 'google.com' },
createdBy: { type: 'person', name: 'Tim Cook', tone: 'teal', kind: 'person', avatarUrl: PEOPLE_AVATAR_URLS.timCook },
address: { type: 'text', value: '4140 Parker Rd.' },
accountOwner: { type: 'person', name: 'Jeff Williams', tone: 'blue', kind: 'person', avatarUrl: PEOPLE_AVATAR_URLS.jeffWilliams },
icp: { type: 'boolean', value: false },
arr: { type: 'number', value: '$7,500,000' },
linkedin: { type: 'link', value: 'google' },
industry: { type: 'tag', value: 'Computer Software' },
mainContact: { type: 'person', name: 'Sundar Pichai', shortLabel: 'S', tone: 'teal', kind: 'person', avatarUrl: 'https://twentyhq.github.io/placeholder-images/people/image-32.png' },
employees: { type: 'number', value: '734' },
opportunities: { type: 'relation', items: [{ name: 'Google AI and Data Solutions', shortLabel: 'G', tone: 'teal' }, { name: 'Relation 2', shortLabel: 'L', tone: 'teal' }, { name: 'Relation 3', shortLabel: 'L', tone: 'teal' }] },
added: { type: 'text', value: 'Jul 1, 2023 2:25 pm' },
},
},
],
},
{
id: 'people',
label: 'People',
icon: { kind: 'tabler', name: 'user', tone: 'purple' },
viewLabel: 'All People',
viewCount: 5,
columns: [
{ id: 'name', label: 'Name', width: 180, isFirstColumn: true },
{ id: 'company', label: 'Company', width: 160 },
{ id: 'email', label: 'Email', width: 200 },
{ id: 'phone', label: 'Phone', width: 160 },
{ id: 'jobTitle', label: 'Job Title', width: 160 },
{ id: 'city', label: 'City', width: 120 },
{ id: 'linkedin', label: 'Linkedin', width: 140 },
{ id: 'added', label: 'Added', width: 160 },
],
rows: [
{
id: 'jeff-williams',
cells: {
name: { type: 'person', name: 'Jeff Williams', tone: 'blue', kind: 'person', avatarUrl: PEOPLE_AVATAR_URLS.jeffWilliams },
company: { type: 'entity', name: 'Anthropic', domain: 'anthropic.com' },
email: { type: 'link', value: 'jeff@anthropic.com' },
phone: { type: 'text', value: '+33 1 23 45 67 89' },
jobTitle: { type: 'text', value: 'COO' },
city: { type: 'text', value: 'Paris' },
linkedin: { type: 'link', value: 'jeff-williams' },
added: { type: 'text', value: 'Jul 3, 2023' },
},
},
{
id: 'craig-federighi',
cells: {
name: { type: 'person', name: 'Craig Federighi', tone: 'purple', kind: 'person', avatarUrl: PEOPLE_AVATAR_URLS.craigFederighi },
company: { type: 'entity', name: 'Linkedin', domain: 'linkedin.com' },
email: { type: 'link', value: 'craig@linkedin.com' },
phone: { type: 'text', value: '+1 234 567 8900' },
jobTitle: { type: 'text', value: 'SVP Software Engineering' },
city: { type: 'text', value: 'Tabithaville' },
linkedin: { type: 'link', value: 'craig-federighi' },
added: { type: 'text', value: 'Jul 28, 2023' },
},
},
],
},
{
id: 'opportunities',
label: 'Opportunities',
icon: { kind: 'tabler', name: 'targetArrow', tone: 'pink' },
viewLabel: 'All Opportunities',
viewCount: 3,
columns: [
{ id: 'name', label: 'Name', width: 200, isFirstColumn: true },
{ id: 'company', label: 'Company', width: 160 },
{ id: 'amount', label: 'Amount', width: 140, align: 'right' },
{ id: 'stage', label: 'Stage', width: 140 },
{ id: 'expectedCloseDate', label: 'Expected Close Date', width: 180 },
{ id: 'accountOwner', label: 'Account Owner', width: 160 },
],
rows: [
{
id: 'enterprise-rollout',
cells: {
name: { type: 'text', value: 'Enterprise rollout' },
company: { type: 'entity', name: 'Slack', domain: 'slack.com' },
amount: { type: 'number', value: '$2,300,000' },
stage: { type: 'tag', value: 'Negotiation' },
expectedCloseDate: { type: 'text', value: 'Dec 15, 2023' },
accountOwner: { type: 'person', name: 'Katherine Adams', tone: 'red', kind: 'person', avatarUrl: PEOPLE_AVATAR_URLS.katherineAdams },
},
},
{
id: 'workspace-upgrade',
cells: {
name: { type: 'text', value: 'Workspace upgrade' },
company: { type: 'entity', name: 'Notion', domain: 'notion.com' },
amount: { type: 'number', value: '$750,000' },
stage: { type: 'tag', value: 'Qualified' },
expectedCloseDate: { type: 'text', value: 'Nov 30, 2023' },
accountOwner: { type: 'person', name: 'Phil Schiller', tone: 'amber', kind: 'person', avatarUrl: PEOPLE_AVATAR_URLS.philSchiller },
},
},
],
},
{
id: 'tasks',
label: 'Tasks',
icon: { kind: 'tabler', name: 'checkbox', tone: 'teal' },
viewLabel: 'All Tasks',
viewCount: 2,
columns: [
{ id: 'title', label: 'Title', width: 220, isFirstColumn: true },
{ id: 'assignee', label: 'Assignee', width: 160 },
{ id: 'dueDate', label: 'Due Date', width: 160 },
{ id: 'relatedTo', label: 'Related To', width: 160 },
{ id: 'status', label: 'Status', width: 140 },
],
rows: [
{
id: 'send-nda',
cells: {
title: { type: 'text', value: 'Send NDA' },
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' },
status: { type: 'tag', value: 'To Do' },
},
},
{
id: 'review-proposal',
cells: {
title: { type: 'text', value: 'Review proposal' },
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' },
status: { type: 'tag', value: 'In Progress' },
},
},
],
},
{
id: 'notes',
label: 'Notes',
icon: { kind: 'tabler', name: 'notes', tone: 'green' },
viewLabel: 'All Notes',
viewCount: 2,
columns: [
{ id: 'title', label: 'Title', width: 240, isFirstColumn: true },
{ id: 'createdBy', label: 'Created By', width: 160 },
{ id: 'relatedTo', label: 'Related To', width: 160 },
{ id: 'added', label: 'Added', width: 180 },
],
rows: [
{
id: 'discovery-call',
cells: {
title: { type: 'text', value: 'Discovery call notes' },
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' },
},
},
{
id: 'design-system-meeting',
cells: {
title: { type: 'text', value: 'Design system meeting' },
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' },
},
},
],
},
{
id: 'sales-dashboard',
label: 'Sales Dashboard',
icon: { kind: 'avatar', label: '$', tone: 'amber', shape: 'circle' },
meta: 'Dashboard',
viewLabel: 'Sales Dashboard',
viewCount: 0,
columns: [],
rows: [],
},
{
id: 'workflows',
label: 'Workflows',
icon: { kind: 'tabler', name: 'settingsAutomation', tone: 'orange' },
showChevron: true,
children: [
{
id: 'workflow-list',
label: 'All Workflows',
icon: { kind: 'tabler', name: 'settingsAutomation', tone: 'gray' },
viewLabel: 'All Workflows',
viewCount: 2,
columns: [
{ id: 'name', label: 'Name', width: 240, isFirstColumn: true },
{ id: 'status', label: 'Status', width: 140 },
{ id: 'lastRun', label: 'Last Run', width: 200 },
],
rows: [
{
id: 'new-lead',
cells: {
name: { type: 'text', value: 'New Lead Assignment' },
status: { type: 'tag', value: 'Active' },
lastRun: { type: 'text', value: 'Oct 24, 2023 10:00 am' },
},
},
{
id: 'nurture',
cells: {
name: { type: 'text', value: 'Nurture Sequence' },
status: { type: 'tag', value: 'Inactive' },
lastRun: { type: 'text', value: 'Oct 20, 2023 3:15 pm' },
},
},
],
},
{
id: 'workflow-runs',
label: 'Workflows runs',
icon: { kind: 'tabler', name: 'playerPlay', tone: 'gray' },
viewLabel: 'All Runs',
viewCount: 2,
columns: [
{ id: 'runId', label: 'Run ID', width: 160, isFirstColumn: true },
{ id: 'workflow', label: 'Workflow', width: 200 },
{ id: 'status', label: 'Status', width: 120 },
{ id: 'startedAt', label: 'Started At', width: 200 },
{ id: 'duration', label: 'Duration', width: 120 },
],
rows: [
{
id: 'run-12345',
cells: {
runId: { type: 'text', value: 'run_12345' },
workflow: { type: 'text', value: 'New Lead Assignment' },
status: { type: 'tag', value: 'Success' },
startedAt: { type: 'text', value: 'Oct 24, 2023 10:00 am' },
duration: { type: 'text', value: '2s' },
},
},
{
id: 'run-12346',
cells: {
runId: { type: 'text', value: 'run_12346' },
workflow: { type: 'text', value: 'Nurture Sequence' },
status: { type: 'tag', value: 'Failed' },
startedAt: { type: 'text', value: 'Oct 20, 2023 3:15 pm' },
duration: { type: 'text', value: '5s' },
},
},
],
},
{
id: 'workflow-versions',
label: 'Workflows versions',
icon: { kind: 'tabler', name: 'versions', tone: 'gray' },
viewLabel: 'All Versions',
viewCount: 2,
columns: [
{ id: 'version', label: 'Version', width: 120, isFirstColumn: true },
{ id: 'workflow', label: 'Workflow', width: 200 },
{ id: 'publishedAt', label: 'Published At', width: 200 },
{ id: 'publishedBy', label: 'Published By', width: 160 },
],
rows: [
{
id: 'v2-lead',
cells: {
version: { type: 'text', value: 'v2' },
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' },
},
},
{
id: 'v1-lead',
cells: {
version: { type: 'text', value: 'v1' },
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' },
},
},
],
},
],
},
{
id: 'claude',
label: 'Claude',
icon: { kind: 'brand', brand: 'claude', overlay: 'link' },
},
{
id: 'workspace-stripe',
label: 'Stripe',
icon: { kind: 'brand', brand: 'stripe' },
showChevron: true,
},
],
},
};
@@ -1,7 +1,12 @@
import { TrustedByDataType } from '@/sections/TrustedBy/types';
import { type TrustedByDataType } from '@/sections/TrustedBy/types';
export const TRUSTED_BY_DATA: TrustedByDataType = {
clientCountLabel: { text: '+10k others' },
logos: [
{ src: '/images/home/logo-bar/french-republic.png' },
{ src: '/images/home/logo-bar/bayer.png' },
{ fit: 'cover', src: '/images/home/logo-bar/pwc.png' },
{ src: '/images/home/logo-bar/xero.png' },
],
separator: { text: 'trusted by' },
clientCountLabel: { text: '+ 10 000 other clients' },
logos: [{ icon: 'flexport' }, { icon: 'zapier' }],
};
@@ -48,10 +48,7 @@ export default function HomePage() {
variant="contained"
/>
</Hero.Cta>
<Hero.HomeVisual
background={HERO_DATA.background}
foreground={HERO_DATA.foreground}
/>
<Hero.HomeVisual visual={HERO_DATA.visual} />
</Hero.Root>
<TrustedBy.Root>
@@ -6,6 +6,7 @@ import { css } from '@linaria/core';
import { styled } from '@linaria/react';
import type { Metadata } from 'next';
import { Aleo, Azeret_Mono, Host_Grotesk } from 'next/font/google';
import '../../../twenty-ui/dist/theme-light.css';
const hostGrotesk = Host_Grotesk({
subsets: ['latin'],
@@ -65,7 +66,7 @@ export default function RootLayout({
children,
}: Readonly<{ children: React.ReactNode }>) {
return (
<html lang="en">
<html lang="en" className="light">
<body
className={`${cssVariables} ${hostGrotesk.variable} ${aleo.variable} ${azeretMono.variable}`}
>
+12 -12
View File
@@ -1,4 +1,13 @@
export { CLIENT_ICONS, FlexportIcon, ZapierIcon } from "./clients";
export { CLIENT_ICONS, FlexportIcon, ZapierIcon } from './clients';
export { ArrowLeftIcon } from './functional/ArrowLeft';
export { ArrowRightIcon } from './functional/ArrowRight';
export { ArrowRightUpIcon } from './functional/ArrowRightUp';
export { CloseIcon } from './functional/Close';
export { MenuIcon } from './functional/Menu';
export { MinusIcon } from './functional/Minus';
export { PlusIcon } from './functional/Plus';
export { RectangleFillIcon } from './functional/RectangleFill';
export { RectangleOutlineIcon } from './functional/RectangleOutline';
export {
CheckIcon,
EditIcon,
@@ -7,17 +16,8 @@ export {
SearchIcon,
UsersIcon,
type InformativeIconProps,
} from "./informative";
export { ArrowLeftIcon } from "./functional/ArrowLeft";
export { ArrowRightIcon } from "./functional/ArrowRight";
export { ArrowRightUpIcon } from "./functional/ArrowRightUp";
export { CloseIcon } from "./functional/Close";
export { MenuIcon } from "./functional/Menu";
export { MinusIcon } from "./functional/Minus";
export { PlusIcon } from "./functional/Plus";
export { RectangleFillIcon } from "./functional/RectangleFill";
export { RectangleOutlineIcon } from "./functional/RectangleOutline";
export { Logo } from "./logo/Logo";
} from './informative';
export { Logo } from './logo/Logo';
export {
DiscordIcon,
GitHubIcon,
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,138 @@
import { styled } from '@linaria/react';
import { type ReactNode } from 'react';
import { VISUAL_TOKENS } from './homeVisualTokens';
export enum ChipVariant {
Highlighted = 'highlighted',
Regular = 'regular',
Transparent = 'transparent',
Rounded = 'rounded',
Static = 'static',
}
export type ChipProps = {
label: string;
clickable?: boolean;
isBold?: boolean;
leftComponent?: ReactNode | null;
className?: string;
maxWidth?: number;
variant?: ChipVariant;
};
const StyledContainer = styled.div<
Pick<ChipProps, 'clickable' | 'isBold' | 'maxWidth' | 'variant'>
>`
--chip-horizontal-padding: ${VISUAL_TOKENS.spacing[1]};
--chip-vertical-padding: ${VISUAL_TOKENS.spacing[1]};
align-items: center;
background-color: ${({ variant }) =>
variant === ChipVariant.Static
? VISUAL_TOKENS.background.transparent.lighter
: variant === ChipVariant.Highlighted
? VISUAL_TOKENS.background.transparent.light
: 'inherit'};
border: ${({ variant }) =>
variant === ChipVariant.Static
? `1px solid ${VISUAL_TOKENS.border.color.strong}`
: 'none'};
border-radius: ${({ variant }) =>
variant === ChipVariant.Rounded || variant === ChipVariant.Static
? VISUAL_TOKENS.border.radius.pill
: VISUAL_TOKENS.border.radius.sm};
color: ${VISUAL_TOKENS.font.color.primary};
cursor: ${({ clickable, variant }) =>
variant === ChipVariant.Transparent
? 'inherit'
: clickable
? 'pointer'
: 'inherit'};
display: inline-flex;
gap: ${VISUAL_TOKENS.spacing[1]};
height: 20px;
justify-content: flex-start;
max-width: ${({ maxWidth }) =>
maxWidth
? `calc(${maxWidth}px - 2 * var(--chip-horizontal-padding))`
: '100%'};
overflow: hidden;
padding: var(--chip-vertical-padding) var(--chip-horizontal-padding);
padding-left: ${({ variant }) =>
variant === ChipVariant.Transparent
? VISUAL_TOKENS.spacing[0]
: variant === ChipVariant.Static
? VISUAL_TOKENS.spacing[2]
: 'var(--chip-horizontal-padding)'};
padding-right: ${({ variant }) =>
variant === ChipVariant.Static
? VISUAL_TOKENS.spacing[2]
: 'var(--chip-horizontal-padding)'};
user-select: none;
font-weight: ${({ isBold }) =>
isBold
? VISUAL_TOKENS.font.weight.medium
: VISUAL_TOKENS.font.weight.regular};
&:hover {
background-color: ${({ variant }) =>
variant === ChipVariant.Regular
? VISUAL_TOKENS.background.transparent.light
: variant === ChipVariant.Highlighted
? VISUAL_TOKENS.background.transparent.medium
: variant === ChipVariant.Static
? VISUAL_TOKENS.background.transparent.light
: 'inherit'};
}
&:active {
background-color: ${({ variant }) =>
variant === ChipVariant.Regular
? VISUAL_TOKENS.background.transparent.medium
: variant === ChipVariant.Highlighted
? VISUAL_TOKENS.background.transparent.strong
: variant === ChipVariant.Static
? VISUAL_TOKENS.background.transparent.light
: 'inherit'};
}
& > svg {
flex-shrink: 0;
}
`;
const StyledLabel = styled.span`
color: inherit;
font-size: inherit;
font-weight: inherit;
max-width: 100%;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
`;
export const Chip = ({
label,
clickable = false,
isBold = false,
leftComponent = null,
className,
maxWidth,
variant = ChipVariant.Regular,
}: ChipProps) => {
return (
<StyledContainer
clickable={clickable}
isBold={isBold}
variant={variant}
className={className}
maxWidth={maxWidth}
>
{leftComponent}
<StyledLabel>{label}</StyledLabel>
</StyledContainer>
);
};
@@ -0,0 +1,67 @@
// Resolved CSS token values that replace twenty-ui's themeCssVariables.
// These are the light-theme defaults for Twenty's design system.
export const VISUAL_TOKENS = {
font: {
family: "'Inter', sans-serif",
color: {
primary: '#333333',
secondary: '#666666',
tertiary: '#999999',
light: '#b3b3b3',
},
size: {
md: '13px',
},
weight: {
regular: '400',
medium: '500',
},
},
spacing: {
0: '0px',
1: '4px',
2: '8px',
},
border: {
color: {
strong: '#d6d6d6',
medium: '#ebebeb',
light: '#f1f1f1',
blue: '#c6d4f9',
},
radius: {
sm: '4px',
pill: '999px',
},
},
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\")",
primary: '#ffffff',
secondary: '#fcfcfc',
transparent: {
primary: 'rgba(255, 255, 255, 0.9)',
medium: 'rgba(0, 0, 0, 0.04)',
light: 'rgba(0, 0, 0, 0.03)',
lighter: 'rgba(0, 0, 0, 0.02)',
strong: 'rgba(0, 0, 0, 0.07)',
blue: 'rgba(62, 99, 221, 0.04)',
},
},
accent: {
accent9: '#3e63dd',
primary: '#d9e2fc',
},
boxShadow: {
light:
'0px 2px 4px rgba(0, 0, 0, 0.04), 0px 0px 4px rgba(0, 0, 0, 0.08)',
},
icon: {
size: {
sm: '14',
},
stroke: {
sm: '1.6',
},
},
} as const;
@@ -1,7 +1,108 @@
import { ImageType } from '@/design-system/components/Image/types/Image';
import { HeroBaseDataType } from '@/sections/Hero/types/HeroBaseData';
import type { HeroBaseDataType } from '@/sections/Hero/types/HeroBaseData';
// -- Cell value types --
export type HeroCellText = { type: 'text'; value: string };
export type HeroCellNumber = { type: 'number'; value: string };
export type HeroCellLink = { type: 'link'; value: string };
export type HeroCellBoolean = { type: 'boolean'; value: boolean };
export type HeroCellTag = { type: 'tag'; value: string };
export type HeroCellPerson = {
type: 'person';
name: string;
avatarUrl?: string;
kind?: 'api' | 'person' | 'system' | 'workflow';
shortLabel?: string;
tone?: string;
};
export type HeroCellEntity = {
type: 'entity';
name: string;
domain?: string;
};
export type HeroCellRelation = {
type: 'relation';
items: { name: string; shortLabel?: string; tone?: string }[];
};
export type HeroCellValue =
| HeroCellText
| HeroCellNumber
| HeroCellLink
| HeroCellBoolean
| HeroCellTag
| HeroCellPerson
| HeroCellEntity
| HeroCellRelation;
// -- Column & row --
export type HeroColumnDef = {
id: string;
label: string;
width: number;
align?: 'left' | 'right';
isFirstColumn?: boolean;
};
export type HeroRowDef = {
id: string;
cells: Record<string, HeroCellValue>;
};
// -- Sidebar icon --
export type HeroSidebarIcon =
| { kind: 'tabler'; name: string; tone: string; overlay?: 'link' }
| { kind: 'brand'; brand: string; overlay?: 'link' }
| {
kind: 'avatar';
label: string;
tone: string;
shape?: 'circle' | 'square';
};
// -- Sidebar navigation --
export type HeroSidebarItem = {
id: string;
label: string;
icon: HeroSidebarIcon;
meta?: string;
active?: boolean;
showChevron?: boolean;
children?: HeroSidebarItem[];
columns?: HeroColumnDef[];
rows?: HeroRowDef[];
viewLabel?: string;
viewCount?: number;
};
export type HeroSidebarFolder = {
id: string;
label: string;
icon: HeroSidebarIcon;
defaultOpen?: boolean;
showChevron?: boolean;
children?: HeroSidebarItem[];
items: HeroSidebarItem[];
};
export type HeroSidebarEntry = HeroSidebarItem | HeroSidebarFolder;
// -- Top-level visual --
export type HeroVisualType = {
workspace: { icon: string; name: string };
favoritesNav?: HeroSidebarItem[];
workspaceNav: HeroSidebarEntry[];
tableWidth?: number;
actions?: string[];
};
export type HeroHomeDataType = HeroBaseDataType & {
background: ImageType;
foreground: ImageType;
visual: HeroVisualType;
};
@@ -1,4 +1,22 @@
export type { HeroBaseDataType } from './HeroBaseData';
export type { HeroHomeDataType } from './HeroHomeData';
export type {
HeroCellBoolean,
HeroCellEntity,
HeroCellLink,
HeroCellNumber,
HeroCellPerson,
HeroCellRelation,
HeroCellTag,
HeroCellText,
HeroCellValue,
HeroColumnDef,
HeroHomeDataType,
HeroRowDef,
HeroSidebarEntry,
HeroSidebarFolder,
HeroSidebarIcon,
HeroSidebarItem,
HeroVisualType,
} from './HeroHomeData';
export type { HeroIllustrationDataType } from './HeroIllustrationData';
export type { HeroWhyTwentyDataType } from './HeroWhyTwentyData';
@@ -1,29 +1,23 @@
import { UsersIcon } from '@/icons';
import { theme } from "@/theme";
import { styled } from "@linaria/react";
import { ClientCountShape } from "./ClientCountShape";
import { theme } from '@/theme';
import { styled } from '@linaria/react';
import NextImage from 'next/image';
const StyledBlock = styled.div`
display: flex;
align-items: center;
gap: ${theme.spacing(2)};
padding-top: ${theme.spacing(1)};
padding-right: ${theme.spacing(3.75)};
padding-bottom: ${theme.spacing(1)};
padding-left: ${theme.spacing(1)};
border-radius: ${theme.radius(2)};
height: 48px;
padding: ${theme.spacing(1)} ${theme.spacing(3.75)} ${theme.spacing(1)}
${theme.spacing(1)};
position: relative;
overflow: clip;
flex-shrink: 0;
width: 157px;
`;
const ShapeWrapper = styled.div`
position: absolute;
top: 50%;
right: 0;
transform: translateY(-50%);
height: 48px;
width: 234px;
inset: 0;
z-index: 0;
`;
@@ -34,12 +28,14 @@ const IconWrapper = styled.div`
width: 40px;
height: 40px;
background-color: ${theme.colors.primary.text[5]};
border-radius: ${theme.radius(1)};
border-radius: 2px;
position: relative;
overflow: clip;
z-index: 1;
`;
const StyledText = styled.p`
font-family: ${theme.font.family.sans};
font-weight: ${theme.font.weight.medium};
font-size: ${theme.font.size(4)};
line-height: ${theme.lineHeight(5.5)};
@@ -57,10 +53,23 @@ export function ClientCount({ label }: ClientCountProps) {
return (
<StyledBlock>
<ShapeWrapper>
<ClientCountShape strokeColor={theme.colors.highlight[70]} />
<NextImage
alt=""
fill
sizes="157px"
src="/images/home/logo-bar/others-shape.svg"
unoptimized
style={{ objectFit: 'fill' }}
/>
</ShapeWrapper>
<IconWrapper>
<UsersIcon size={22} fillColor={theme.colors.highlight[100]} />
<NextImage
alt=""
height={21}
src="/images/home/logo-bar/others-icon.svg"
unoptimized
width={22}
/>
</IconWrapper>
<StyledText>{label}</StyledText>
</StyledBlock>
@@ -1,21 +1,34 @@
import { styled } from "@linaria/react";
import type { ReactNode } from "react";
import type { TrustedByLogosType } from '@/sections/TrustedBy/types';
import { theme } from '@/theme';
import { styled } from '@linaria/react';
import NextImage from 'next/image';
const StyledLogo = styled.div`
display: flex;
align-items: center;
justify-content: center;
width: 140px;
height: inherit;
position: relative;
height: 32px;
overflow: clip;
flex-shrink: 0;
position: relative;
width: 64px;
@media (min-width: ${theme.breakpoints.md}px) {
height: 40px;
width: 80px;
}
`;
type LogoProps = {
children: ReactNode;
}
type LogoProps = TrustedByLogosType;
export function Logo({ children }: LogoProps) {
return <StyledLogo>{children}</StyledLogo>;
export function Logo({ fit = 'contain', src }: LogoProps) {
return (
<StyledLogo aria-hidden="true">
<NextImage
alt=""
fill
sizes="(min-width: 921px) 80px, 64px"
src={src}
unoptimized
style={{ objectFit: fit, objectPosition: 'center' }}
/>
</StyledLogo>
);
}
@@ -1,11 +1,9 @@
import { CLIENT_ICONS } from '@/icons';
import {
TrustedByClientCountLabelType,
TrustedByLogosType,
type TrustedByClientCountLabelType,
type TrustedByLogosType,
} from '@/sections/TrustedBy/types';
import { theme } from '@/theme';
import { styled } from '@linaria/react';
import React from 'react';
import { ClientCount } from '../ClientCount/ClientCount';
import { Logo } from '../Logo/Logo';
@@ -13,15 +11,27 @@ const StyledLogos = styled.div`
align-items: center;
display: flex;
flex-direction: column;
row-gap: ${theme.spacing(4)};
gap: ${theme.spacing(5)};
justify-content: center;
position: relative;
width: 100%;
@media (min-width: ${theme.breakpoints.md}px) {
flex-direction: row;
column-gap: ${theme.spacing(14)};
height: 48px;
gap: ${theme.spacing(14)};
}
`;
const LogoStrip = styled.div`
align-items: center;
display: grid;
gap: ${theme.spacing(4)} ${theme.spacing(6)};
grid-template-columns: repeat(2, minmax(0, max-content));
justify-items: center;
@media (min-width: ${theme.breakpoints.md}px) {
display: flex;
gap: ${theme.spacing(14)};
}
`;
@@ -33,22 +43,12 @@ type LogosProps = {
export function Logos({ clientCountLabel, logos }: LogosProps) {
return (
<StyledLogos>
{logos.map((logo, index) => {
const IconComponent = CLIENT_ICONS[logo.icon];
if (!IconComponent) return null;
return (
<React.Fragment key={`${logo.icon}-${index}`}>
{index === 1 && <ClientCount label={clientCountLabel.text} />}
<Logo>
<IconComponent
fillColor={theme.colors.secondary.background[100]}
size={115}
/>
</Logo>
</React.Fragment>
);
})}
<LogoStrip>
{logos.map((logo, index) => (
<Logo fit={logo.fit} key={`${logo.src}-${index}`} src={logo.src} />
))}
</LogoStrip>
<ClientCount label={clientCountLabel.text} />
</StyledLogos>
);
}
@@ -30,7 +30,7 @@ interface RootProps {
export function Root({ children }: RootProps) {
return (
<StyledSection>
<StyledSection aria-label="Trusted by leading organizations">
<StyledContainer>{children}</StyledContainer>
</StyledSection>
);
@@ -1,9 +1,9 @@
import { TrustedByClientCountLabelType } from "./TrustedByClientCountLabel";
import { TrustedBySeparatorType } from "./TrustedBySeparator"
import { TrustedByLogosType } from "./TrustedByLogos";
import type { TrustedByClientCountLabelType } from './TrustedByClientCountLabel';
import type { TrustedByLogosType } from './TrustedByLogos';
import type { TrustedBySeparatorType } from './TrustedBySeparator';
export type TrustedByDataType = {
separator: TrustedBySeparatorType;
clientCountLabel: TrustedByClientCountLabelType;
logos: TrustedByLogosType[];
}
clientCountLabel: TrustedByClientCountLabelType;
logos: TrustedByLogosType[];
separator: TrustedBySeparatorType;
};
@@ -1,3 +1,4 @@
export type TrustedByLogosType = {
icon: string;
}
fit?: 'contain' | 'cover';
src: string;
};
+19
View File
@@ -23445,6 +23445,17 @@ __metadata:
languageName: node
linkType: hard
"@tabler/icons-react@npm:^3.41.1":
version: 3.41.1
resolution: "@tabler/icons-react@npm:3.41.1"
dependencies:
"@tabler/icons": "npm:3.41.1"
peerDependencies:
react: ">= 16"
checksum: 10c0/d103816d91df212ef6a4fc05e9f8d5f01deec2197d3accc4a398d325d1cfa8910e06b4daaf82cd08749f242c3908885ac60b21c4129b21094ece9fe0ecd46e4c
languageName: node
linkType: hard
"@tabler/icons@npm:3.31.0":
version: 3.31.0
resolution: "@tabler/icons@npm:3.31.0"
@@ -23452,6 +23463,13 @@ __metadata:
languageName: node
linkType: hard
"@tabler/icons@npm:3.41.1":
version: 3.41.1
resolution: "@tabler/icons@npm:3.41.1"
checksum: 10c0/b1238157fe6e57c0202f1f7153621ad937a6f73ff7c0f0af5a71fcbdace18a3281cddd6632229029c3a3df7c530ae8bcefa75c18e35db4b65e7f884282762cf1
languageName: node
linkType: hard
"@tanstack/react-store@npm:0.7.7":
version: 0.7.7
resolution: "@tanstack/react-store@npm:0.7.7"
@@ -60236,6 +60254,7 @@ __metadata:
"@base-ui/utils": "npm:^0.2.6"
"@linaria/core": "npm:^7.0.0"
"@linaria/react": "npm:^7.0.1"
"@tabler/icons-react": "npm:^3.41.1"
"@types/node": "npm:^20"
"@types/react": "npm:^19"
"@types/react-dom": "npm:^19"