diff --git a/packages/twenty-website-new/package.json b/packages/twenty-website-new/package.json index c4ad1d9804..45a0a2707f 100644 --- a/packages/twenty-website-new/package.json +++ b/packages/twenty-website-new/package.json @@ -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", diff --git a/packages/twenty-website-new/public/images/home/logo-bar/bayer.png b/packages/twenty-website-new/public/images/home/logo-bar/bayer.png new file mode 100644 index 0000000000..29ed956a1a Binary files /dev/null and b/packages/twenty-website-new/public/images/home/logo-bar/bayer.png differ diff --git a/packages/twenty-website-new/public/images/home/logo-bar/french-republic.png b/packages/twenty-website-new/public/images/home/logo-bar/french-republic.png new file mode 100644 index 0000000000..b02fae1db7 Binary files /dev/null and b/packages/twenty-website-new/public/images/home/logo-bar/french-republic.png differ diff --git a/packages/twenty-website-new/public/images/home/logo-bar/others-icon.svg b/packages/twenty-website-new/public/images/home/logo-bar/others-icon.svg new file mode 100644 index 0000000000..e8c14791e3 --- /dev/null +++ b/packages/twenty-website-new/public/images/home/logo-bar/others-icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/twenty-website-new/public/images/home/logo-bar/others-shape.svg b/packages/twenty-website-new/public/images/home/logo-bar/others-shape.svg new file mode 100644 index 0000000000..db8e890007 --- /dev/null +++ b/packages/twenty-website-new/public/images/home/logo-bar/others-shape.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/twenty-website-new/public/images/home/logo-bar/pwc.png b/packages/twenty-website-new/public/images/home/logo-bar/pwc.png new file mode 100644 index 0000000000..e69ba0f751 Binary files /dev/null and b/packages/twenty-website-new/public/images/home/logo-bar/pwc.png differ diff --git a/packages/twenty-website-new/public/images/home/logo-bar/xero.png b/packages/twenty-website-new/public/images/home/logo-bar/xero.png new file mode 100644 index 0000000000..600d95b388 Binary files /dev/null and b/packages/twenty-website-new/public/images/home/logo-bar/xero.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 300b5b4e16..349baf1c41 100644 --- a/packages/twenty-website-new/src/app/(home)/constants/hero.ts +++ b/packages/twenty-website-new/src/app/(home)/constants/hero.ts @@ -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, + }, + ], }, }; diff --git a/packages/twenty-website-new/src/app/(home)/constants/trusted-by.ts b/packages/twenty-website-new/src/app/(home)/constants/trusted-by.ts index bec3823871..0731458979 100644 --- a/packages/twenty-website-new/src/app/(home)/constants/trusted-by.ts +++ b/packages/twenty-website-new/src/app/(home)/constants/trusted-by.ts @@ -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' }], }; diff --git a/packages/twenty-website-new/src/app/(home)/page.tsx b/packages/twenty-website-new/src/app/(home)/page.tsx index 48e3d897dd..b54cd7a43b 100644 --- a/packages/twenty-website-new/src/app/(home)/page.tsx +++ b/packages/twenty-website-new/src/app/(home)/page.tsx @@ -48,10 +48,7 @@ export default function HomePage() { variant="contained" /> - + diff --git a/packages/twenty-website-new/src/app/layout.tsx b/packages/twenty-website-new/src/app/layout.tsx index d0d6e1a779..d8e50b63e5 100644 --- a/packages/twenty-website-new/src/app/layout.tsx +++ b/packages/twenty-website-new/src/app/layout.tsx @@ -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 ( - + diff --git a/packages/twenty-website-new/src/icons/index.ts b/packages/twenty-website-new/src/icons/index.ts index 5fb9c89d62..a19fe73309 100644 --- a/packages/twenty-website-new/src/icons/index.ts +++ b/packages/twenty-website-new/src/icons/index.ts @@ -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, 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 c8eb569ad4..9d5068b201 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 @@ -1,63 +1,2004 @@ -import { Image } from '@/design-system/components'; -import type { ImageType } from '@/design-system/components/Image/types/Image'; +'use client'; + import { theme } from '@/theme'; import { styled } from '@linaria/react'; +import { + IconBook, + IconBox, + IconBrandApple, + IconBrandLinkedin, + IconBuildingFactory2, + IconBuildingSkyscraper, + IconCalendarEvent, + IconCheck, + IconCheckbox, + IconChevronDown, + IconCopy, + IconCreativeCommonsSa, + IconDotsVertical, + IconFolder, + IconHome2, + IconLayoutSidebarLeftCollapse, + IconLink, + IconList, + IconMap2, + IconMessageCircle, + IconMessageCirclePlus, + IconMoneybag, + IconNotes, + IconPencil, + IconPlayerPlay, + IconPlus, + IconSearch, + IconSettings, + IconSettingsAutomation, + IconTarget, + IconTargetArrow, + IconUser, + IconUserCircle, + IconUsers, + IconVersions, + IconX, +} from '@tabler/icons-react'; +import { + useEffect, + useMemo, + useRef, + useState, + type ReactNode, + type PointerEvent as ReactPointerEvent, +} from 'react'; +import type { + HeroCellEntity, + HeroCellPerson, + HeroCellRelation, + HeroCellValue, + HeroColumnDef, + HeroRowDef, + HeroSidebarEntry, + HeroSidebarFolder, + HeroSidebarIcon, + HeroSidebarItem, + HeroVisualType, +} from '../../types/HeroHomeData'; +import { Chip, ChipVariant } from './homeVisualChip'; +import { VISUAL_TOKENS } from './homeVisualTokens'; + +const APP_FONT = VISUAL_TOKENS.font.family; +const DEFAULT_TABLE_WIDTH = 1700; + +const COLORS = { + accent: VISUAL_TOKENS.accent.accent9, + accentBorder: VISUAL_TOKENS.border.color.blue, + accentSurface: VISUAL_TOKENS.accent.primary, + accentSurfaceSoft: VISUAL_TOKENS.background.transparent.blue, + background: VISUAL_TOKENS.background.primary, + backgroundSecondary: VISUAL_TOKENS.background.secondary, + border: VISUAL_TOKENS.border.color.medium, + borderLight: VISUAL_TOKENS.border.color.light, + borderStrong: VISUAL_TOKENS.border.color.strong, + shadow: '0 14px 34px rgba(0, 0, 0, 0.08), 0 4px 12px rgba(0, 0, 0, 0.04)', + text: VISUAL_TOKENS.font.color.primary, + textSecondary: VISUAL_TOKENS.font.color.secondary, + textTertiary: VISUAL_TOKENS.font.color.tertiary, + textLight: VISUAL_TOKENS.font.color.light, +}; + +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' }, + green: { background: '#ccebd7', border: '#bbe4c9', color: '#153226' }, + orange: { background: '#ffdcc3', border: '#ffcca7', color: '#582d1d' }, + pink: { background: '#ffe1e7', border: '#ffc8d6', color: '#a51853' }, + purple: { background: '#e0e7ff', border: '#c7d2fe', color: '#4f46e5' }, + teal: { background: '#c7ebe5', border: '#b3e3dc', color: '#10302b' }, + violet: { background: '#ebe5ff', border: '#d8cbff', color: '#5b3fd1' }, + red: { background: '#fee2e2', border: '#fecaca', color: '#b91c1c' }, +}; + +const PERSON_TONES: Record = { + amber: { background: '#f6e6d7', color: '#7a4f2a' }, + blue: { background: '#dbeafe', color: '#1d4ed8' }, + gray: { background: '#e5e7eb', color: '#4b5563' }, + green: { background: '#dcfce7', color: '#15803d' }, + pink: { background: '#ffe4e6', color: '#be123c' }, + purple: { background: '#ede9fe', color: '#6d28d9' }, + red: { background: '#fee2e2', color: '#b91c1c' }, + teal: { background: '#ccfbf1', color: '#0f766e' }, +}; + +const TABLER_STROKE = 1.6; +const NAVIGATION_TABLER_STROKE = 2; + +// -- Styled Components -- const StyledHomeVisual = styled.div` - display: grid; - grid-template-columns: 1fr; - grid-template-rows: 1fr; + isolation: isolate; margin-top: ${theme.spacing(5)}; position: relative; - right: calc(${theme.spacing(2)} - ${theme.spacing(6)}); + text-align: left; width: 100%; @media (min-width: ${theme.breakpoints.md}px) { - height: 500px; - margin-top: ${theme.spacing(17.5)}; - overflow: hidden; - right: 0; + margin-top: ${theme.spacing(11)}; } `; -const StyledBackgroundImage = styled(Image)` - grid-area: 1 / 1; - justify-self: end; - width: 98%; - - @media (min-width: ${theme.breakpoints.md}px) { - max-width: 850px; - width: 100%; - } +const ShellScene = styled.div` + margin: 0 auto; + max-width: 980px; + transform-origin: center top; + transition: transform 0.18s ease; + width: 100%; `; -const StyledForegroundImage = styled(Image)` - grid-area: 1 / 1; - justify-self: end; - margin-right: ${theme.spacing(6.5)}; - margin-top: ${theme.spacing(20)}; - width: 98%; +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; + border: 1px solid ${COLORS.border}; + border-radius: 8px; + box-shadow: ${COLORS.shadow}; + overflow: hidden; + position: relative; +`; + +const AppLayout = styled.div` + display: grid; + grid-template-columns: 72px minmax(0, 1fr); + height: 100%; + position: relative; z-index: 1; @media (min-width: ${theme.breakpoints.md}px) { - justify-self: center; - margin-right: ${theme.spacing(40)}; - margin-top: ${theme.spacing(17.5)}; - max-width: 800px; - width: 100%; + grid-template-columns: 220px minmax(0, 1fr); } `; -type HomeVisualProps = { - background: ImageType; - foreground: ImageType; +const SidebarPanel = styled.aside` + background: transparent; + border-right: 1px solid rgba(0, 0, 0, 0.04); + display: grid; + gap: 12px; + grid-template-rows: auto auto minmax(0, 1fr); + padding: 12px 8px; +`; + +const SidebarTopBar = styled.div` + align-items: center; + display: grid; + gap: 8px; + grid-template-columns: minmax(0, 1fr) auto; + min-height: 32px; +`; + +const WorkspaceMenu = styled.div` + align-items: center; + display: grid; + gap: 8px; + grid-template-columns: auto 1fr auto; + min-width: 0; + padding: 6px 4px; +`; + +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}; + height: 16px; + justify-content: center; + width: 16px; +`; + +const WorkspaceLabel = styled.span` + color: ${COLORS.text}; + display: none; + font-family: ${APP_FONT}; + font-size: 13px; + font-weight: ${theme.font.weight.medium}; + line-height: 1.4; + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + + @media (min-width: ${theme.breakpoints.md}px) { + display: block; + } +`; + +const SidebarTopActions = styled.div` + align-items: center; + display: grid; + gap: 2px; + grid-auto-flow: column; +`; + +const SidebarIconButton = styled.div` + align-items: center; + border-radius: 4px; + display: flex; + height: 24px; + justify-content: center; + width: 24px; +`; + +const SidebarControls = styled.div` + align-items: center; + display: grid; + gap: 8px; + grid-template-columns: auto 1fr; +`; + +const SegmentedRail = styled.div` + background: rgba(252, 252, 252, 0.8); + border: 1px solid ${COLORS.border}; + border-radius: 40px; + display: grid; + gap: 2px; + grid-auto-flow: column; + padding: 3px; +`; + +const Segment = styled.div<{ $selected?: boolean }>` + align-items: center; + background: ${({ $selected }) => + $selected ? 'rgba(0, 0, 0, 0.04)' : 'transparent'}; + border-radius: 16px; + display: flex; + height: 22px; + justify-content: center; + width: 22px; +`; + +const NewChat = styled.div` + align-items: center; + background: ${COLORS.backgroundSecondary}; + border: 1px solid ${COLORS.border}; + border-radius: 40px; + color: ${COLORS.textSecondary}; + display: flex; + gap: 4px; + height: 28px; + justify-content: center; + min-width: 0; + padding: 3px; + width: 28px; + + @media (min-width: ${theme.breakpoints.md}px) { + width: 103px; + } +`; + +const NewChatLabel = styled.span` + display: none; + font-family: ${APP_FONT}; + font-size: 13px; + font-weight: ${theme.font.weight.medium}; + line-height: 1.4; + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + + @media (min-width: ${theme.breakpoints.md}px) { + display: block; + } +`; + +const SidebarScroll = styled.div` + display: grid; + gap: 12px; + min-height: 0; + overflow: hidden; +`; + +const SidebarSection = styled.div` + display: grid; + gap: 2px; +`; + +const SidebarSectionLabel = styled.span` + color: ${COLORS.textLight}; + display: none; + font-family: ${APP_FONT}; + font-size: 11px; + font-weight: 600; + line-height: 1; + padding: 0 4px 4px; + + @media (min-width: ${theme.breakpoints.md}px) { + display: block; + } +`; + +const SidebarItemRow = styled.div<{ + $active?: boolean; + $depth?: number; +}>` + align-items: center; + background: ${({ $active }) => + $active ? 'rgba(0, 0, 0, 0.04)' : 'transparent'}; + border-radius: 4px; + display: grid; + gap: 8px; + grid-template-columns: auto minmax(0, 1fr) auto; + height: 28px; + padding: 0 2px 0 ${({ $depth = 0 }) => `${$depth === 0 ? 4 : 24}px`}; + position: relative; +`; + +const SidebarIconSurface = styled.div<{ + $background: string; + $border: string; + $color: string; +}>` + align-items: center; + background: ${({ $background }) => $background}; + border: 1px solid ${({ $border }) => $border}; + border-radius: 4px; + color: ${({ $color }) => $color}; + display: flex; + flex: 0 0 auto; + height: 16px; + justify-content: center; + position: relative; + width: 16px; +`; + +const SidebarItemText = styled.div` + align-items: center; + display: flex; + gap: 2px; + min-width: 0; +`; + +const SidebarItemLabel = styled.span` + color: ${COLORS.textSecondary}; + display: none; + font-family: ${APP_FONT}; + font-size: 13px; + font-weight: ${theme.font.weight.medium}; + line-height: 1.4; + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + + @media (min-width: ${theme.breakpoints.md}px) { + display: block; + } +`; + +const SidebarItemMeta = styled.span` + color: ${COLORS.textLight}; + display: none; + font-family: ${APP_FONT}; + font-size: 13px; + font-weight: ${theme.font.weight.medium}; + line-height: 1.4; + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + + @media (min-width: ${theme.breakpoints.md}px) { + display: block; + } +`; + +const SidebarChevron = styled.div` + color: ${COLORS.textTertiary}; + display: none; + + @media (min-width: ${theme.breakpoints.md}px) { + display: flex; + } +`; + +const SidebarChildStack = styled.div` + display: grid; + gap: 2px; + position: relative; +`; + +const BranchLine = styled.div` + background: ${COLORS.borderStrong}; + bottom: 14px; + left: 15px; + position: absolute; + top: 0; + width: 1px; +`; + +const SidebarAvatar = styled.div<{ + $background: string; + $color: string; + $shape?: 'circle' | 'square'; +}>` + align-items: center; + background: ${({ $background }) => $background}; + border-radius: ${({ $shape }) => ($shape === 'square' ? '4px' : '999px')}; + color: ${({ $color }) => $color}; + display: flex; + flex: 0 0 auto; + font-family: ${APP_FONT}; + font-size: 10px; + font-weight: ${theme.font.weight.medium}; + height: 16px; + justify-content: center; + line-height: 1; + width: 16px; +`; + +const RightPane = styled.div` + display: grid; + gap: 12px; + grid-template-rows: 32px minmax(0, 1fr); + min-width: 0; + padding: 12px 8px 12px 0; + + @media (min-width: ${theme.breakpoints.md}px) { + padding-right: 12px; + } +`; + +const NavbarBar = styled.div` + align-items: center; + background: transparent; + display: flex; + justify-content: space-between; + min-width: 0; +`; + +const Breadcrumb = styled.div` + align-items: center; + display: flex; + gap: 2px; + min-width: 0; +`; + +const BreadcrumbTag = styled.div` + align-items: center; + display: flex; + gap: 4px; + height: 20px; + min-width: 0; + padding: 0 2px; +`; + +const AccentIconSurface = styled.div` + align-items: center; + background: ${COLORS.accentSurface}; + border: 1px solid ${COLORS.accentBorder}; + border-radius: 4px; + color: ${COLORS.accent}; + display: flex; + height: 16px; + justify-content: center; + width: 16px; +`; + +const CrumbLabel = styled.span` + color: ${COLORS.text}; + font-family: ${APP_FONT}; + font-size: 13px; + font-weight: ${theme.font.weight.medium}; + line-height: 1.4; + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +`; + +const NavbarActions = styled.div` + align-items: center; + display: flex; + gap: 8px; + pointer-events: none; +`; + +const DesktopOnlyNavbarAction = styled.div` + display: none; + + @media (min-width: ${theme.breakpoints.md}px) { + display: block; + } +`; + +const NavbarDecorativeChip = styled.div` + align-items: center; + background: transparent; + border: 1px solid ${VISUAL_TOKENS.background.transparent.medium}; + 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}; + font-weight: ${VISUAL_TOKENS.font.weight.medium}; + gap: ${VISUAL_TOKENS.spacing[1]}; + height: 24px; + padding: 0 ${VISUAL_TOKENS.spacing[2]}; + white-space: nowrap; +`; + +const NavbarDecorativeIconWrap = styled.span` + align-items: center; + color: currentColor; + display: flex; + flex: 0 0 auto; + justify-content: center; +`; + +const IndexSurface = styled.div` + background: ${COLORS.background}; + border-radius: 8px; + display: grid; + grid-template-rows: 40px minmax(0, 1fr); + min-width: 0; + overflow: hidden; +`; + +const ViewbarBar = styled.div` + align-items: center; + background: ${COLORS.background}; + border-bottom: 1px solid ${COLORS.borderLight}; + display: flex; + justify-content: space-between; + padding: 8px 8px 8px 12px; +`; + +const ViewSwitcher = styled.div` + align-items: center; + display: flex; + gap: 4px; + height: 24px; + min-width: 0; + padding: 0 4px; +`; + +const ViewName = styled.span` + color: ${COLORS.textSecondary}; + font-family: ${APP_FONT}; + font-size: 13px; + font-weight: ${theme.font.weight.medium}; + line-height: 1.4; + white-space: nowrap; +`; + +const ViewCount = styled.span` + color: ${COLORS.textLight}; + font-family: ${APP_FONT}; + font-size: 13px; + font-weight: ${theme.font.weight.medium}; + line-height: 1.4; + white-space: nowrap; +`; + +const TinyDot = styled.div` + background: ${COLORS.borderStrong}; + border-radius: 999px; + height: 2px; + width: 2px; +`; + +const ViewActions = styled.div` + align-items: center; + display: flex; + gap: 2px; +`; + +const ViewAction = styled.span` + align-items: center; + border-radius: 4px; + color: ${COLORS.textSecondary}; + display: flex; + font-family: ${APP_FONT}; + font-size: 13px; + font-weight: ${theme.font.weight.regular}; + height: 24px; + line-height: 1.4; + padding: 4px 8px; + white-space: nowrap; +`; + +const TableShell = styled.div` + display: flex; + min-height: 0; +`; + +const GripRail = styled.div` + background: ${COLORS.background}; + display: grid; + flex: 0 0 12px; + grid-auto-rows: 32px; + width: 12px; +`; + +const GripCell = styled.div` + background: ${COLORS.background}; + border-bottom: 1px solid ${COLORS.borderLight}; +`; + +const TableViewport = styled.div<{ $dragging: boolean }>` + cursor: ${({ $dragging }) => ($dragging ? 'grabbing' : 'grab')}; + min-height: 0; + overflow-x: auto; + overflow-y: hidden; + overscroll-behavior-x: contain; + scrollbar-width: none; + + &::-webkit-scrollbar { + display: none; + } +`; + +const TableCanvas = styled.div<{ $width: number }>` + min-width: ${({ $width }) => `${$width}px`}; + width: ${({ $width }) => `${$width}px`}; +`; + +const HeaderRow = styled.div` + display: flex; +`; + +const DataRow = styled.div` + display: flex; +`; + +const FooterRow = styled.div` + display: flex; +`; + +const TableCell = styled.div<{ + $align?: 'left' | 'right'; + $header?: boolean; + $hovered?: boolean; + $sticky?: boolean; + $width: number; +}>` + align-items: center; + background: ${({ $header, $hovered }) => { + if ($header) { + return COLORS.background; + } + + return $hovered ? COLORS.backgroundSecondary : COLORS.background; + }}; + border-bottom: 1px solid ${COLORS.borderLight}; + border-right: 1px solid ${COLORS.borderLight}; + box-sizing: border-box; + display: flex; + flex: 0 0 ${({ $width }) => `${$width}px`}; + height: 32px; + justify-content: ${({ $align }) => + $align === 'right' ? 'flex-end' : 'flex-start'}; + left: ${({ $sticky }) => ($sticky ? '0' : 'auto')}; + min-width: ${({ $width }) => `${$width}px`}; + padding: 0 8px; + position: ${({ $sticky }) => ($sticky ? 'sticky' : 'relative')}; + z-index: ${({ $header, $sticky }) => { + if ($sticky && $header) { + return 6; + } + + if ($sticky) { + return 4; + } + + return 1; + }}; +`; + +const EmptyFillCell = styled.div<{ + $footer?: boolean; + $header?: boolean; + $hovered?: boolean; + $width: number; +}>` + background: ${({ $header, $hovered, $footer }) => { + if ($header || $footer) { + return COLORS.background; + } + + return $hovered ? COLORS.backgroundSecondary : COLORS.background; + }}; + border-bottom: 1px solid ${COLORS.borderLight}; + flex: 0 0 ${({ $width }) => `${$width}px`}; + min-width: ${({ $width }) => `${$width}px`}; +`; + +const HeaderCellContent = styled.div` + align-items: center; + display: flex; + gap: 4px; + height: 100%; + min-width: 0; + width: 100%; +`; + +const HeaderLabel = styled.span` + color: ${COLORS.textTertiary}; + font-family: ${APP_FONT}; + font-size: 13px; + font-weight: ${theme.font.weight.medium}; + line-height: 1.4; + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +`; + +const EdgePlus = styled.div` + margin-left: auto; +`; + +const CheckboxContainer = styled.div` + align-items: center; + display: flex; + flex: 0 0 24px; + height: 24px; + justify-content: center; + width: 24px; +`; + +const CheckboxBox = styled.div<{ $checked?: boolean }>` + align-items: center; + background: ${({ $checked }) => + $checked ? COLORS.accentSurfaceSoft : 'transparent'}; + border: 1px solid + ${({ $checked }) => ($checked ? COLORS.accentBorder : COLORS.borderStrong)}; + border-radius: 3px; + display: flex; + flex: 0 0 auto; + height: 14px; + justify-content: center; + width: 14px; +`; + +const EntityCellLayout = styled.div` + align-items: center; + display: flex; + gap: 4px; + min-width: 0; + position: relative; + width: 100%; +`; + +const CellChip = styled(Chip)` + max-width: 100%; + min-width: 0; +`; + +const InlineText = styled.span` + color: ${COLORS.text}; + font-family: ${APP_FONT}; + font-size: 13px; + font-weight: ${theme.font.weight.regular}; + line-height: 1.4; + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +`; + +const MutedText = styled.span` + color: ${COLORS.textTertiary}; + font-family: ${APP_FONT}; + font-size: 13px; + font-weight: ${theme.font.weight.regular}; + line-height: 1.4; + white-space: nowrap; +`; + +const RightAlignedText = styled(InlineText)` + text-align: right; + width: 100%; +`; + +const PersonAvatarCircle = styled.div<{ + $background: string; + $color: string; + $square?: boolean; +}>` + align-items: center; + background: ${({ $background }) => $background}; + border-radius: ${({ $square }) => ($square ? '4px' : '999px')}; + color: ${({ $color }) => $color}; + display: flex; + flex: 0 0 auto; + font-family: ${APP_FONT}; + font-size: 9px; + font-weight: ${theme.font.weight.medium}; + height: 14px; + justify-content: center; + overflow: hidden; + width: 14px; +`; + +const AvatarImage = styled.img` + display: block; + height: 100%; + object-fit: cover; + width: 100%; +`; + +const BooleanRow = styled.div` + align-items: center; + display: inline-flex; + gap: 4px; +`; + +const HoverActions = styled.div<{ $visible: boolean }>` + align-items: center; + background: ${COLORS.backgroundSecondary}; + border: 1px solid ${COLORS.border}; + border-radius: 4px; + box-shadow: ${VISUAL_TOKENS.boxShadow.light}; + display: flex; + gap: 2px; + opacity: ${({ $visible }) => ($visible ? 1 : 0)}; + padding: 2px; + pointer-events: none; + position: absolute; + right: 4px; + top: 4px; + transform: translateX(${({ $visible }) => ($visible ? '0' : '4px')}); + transition: + opacity 0.14s ease, + transform 0.14s ease; +`; + +const MiniAction = styled.div` + align-items: center; + border-radius: 2px; + color: ${COLORS.textSecondary}; + display: flex; + height: 20px; + justify-content: center; + width: 20px; +`; + +const FooterFirstContent = styled.div` + align-items: center; + display: inline-flex; + gap: 4px; + padding-left: 28px; +`; + +const HeaderFillContent = styled.div` + align-items: center; + display: flex; + height: 100%; + padding: 0 8px; +`; + +const TagChip = styled.div` + align-items: center; + background: ${VISUAL_TOKENS.background.transparent.light}; + border-radius: 4px; + color: ${COLORS.textSecondary}; + display: inline-flex; + font-family: ${APP_FONT}; + font-size: 13px; + font-weight: ${theme.font.weight.regular}; + height: 20px; + line-height: 1.4; + max-width: 100%; + min-width: 0; + overflow: hidden; + padding: 0 8px; + text-overflow: ellipsis; + white-space: nowrap; +`; + +const MultiChipStack = styled.div` + align-items: center; + display: flex; + gap: 4px; + min-width: 0; + overflow: hidden; + width: 100%; +`; + +const FaviconImage = styled.img` + display: block; + height: 100%; + object-fit: contain; + width: 100%; +`; + +// -- Icon helpers -- + +const TABLER_ICON_MAP: Record = { + book: IconBook, + box: IconBox, + buildingSkyscraper: IconBuildingSkyscraper, + checkbox: IconCheckbox, + folder: IconFolder, + notes: IconNotes, + playerPlay: IconPlayerPlay, + settings: IconSettings, + settingsAutomation: IconSettingsAutomation, + targetArrow: IconTargetArrow, + user: IconUser, + versions: IconVersions, }; -export function HomeVisual({ background, foreground }: HomeVisualProps) { +const HEADER_ICON_MAP: Record = { + added: IconCalendarEvent, + accountOwner: IconUserCircle, + address: IconMap2, + arr: IconMoneybag, + createdBy: IconCreativeCommonsSa, + employees: IconUsers, + icp: IconTarget, + industry: IconBuildingFactory2, + linkedin: IconBrandLinkedin, + mainContact: IconUser, + opportunities: IconTargetArrow, + url: IconLink, +}; + +// -- Utility functions -- + +const failedFaviconUrls = new Set(); + +function getInitials(value: string) { + return value + .split(' ') + .filter(Boolean) + .slice(0, 2) + .map((part) => part[0]) + .join('') + .toUpperCase(); +} + +function sanitizeURL(link: string | null | undefined) { + return link + ? link.replace(/(https?:\/\/)|(www\.)/g, '').replace(/\/$/, '') + : ''; +} + +function getLogoUrlFromDomainName(domainName?: string): string | undefined { + const sanitizedDomain = sanitizeURL(domainName); + + return sanitizedDomain + ? `https://twenty-icons.com/${sanitizedDomain}` + : undefined; +} + +function isFolder(entry: HeroSidebarEntry): entry is HeroSidebarFolder { + return 'items' in entry; +} + +function findActiveItem( + entries: HeroSidebarEntry[], + activeLabel: string, +): HeroSidebarItem | undefined { + for (const entry of entries) { + if (isFolder(entry)) { + for (const child of entry.items) { + if (child.label === activeLabel) { + return child; + } + } + + continue; + } + + if (entry.children) { + for (const child of entry.children) { + if (child.label === activeLabel) { + return child; + } + } + } + + if (entry.label === activeLabel) { + const entryHasTable = + (entry.columns?.length ?? 0) > 0 || (entry.rows?.length ?? 0) > 0; + + if (!entryHasTable && entry.children && entry.children.length > 0) { + const firstChildWithTable = entry.children.find( + (child) => + (child.columns?.length ?? 0) > 0 || (child.rows?.length ?? 0) > 0, + ); + + if (firstChildWithTable) { + return firstChildWithTable; + } + } + + return entry; + } + } + + return undefined; +} + +// -- Small icon wrappers -- + +type MiniIconProps = { + color?: string; + size?: number; + stroke?: number; +}; + +function ChevronDownMini({ + color = COLORS.textTertiary, + size = 14, +}: MiniIconProps) { + return ( + + ); +} + +function SearchMini({ color = COLORS.textTertiary, size = 16 }: MiniIconProps) { + return ( + + ); +} + +function CollapseSidebarMini({ + color = COLORS.textTertiary, + size = 16, +}: MiniIconProps) { + return ( + + ); +} + +function HomeMini({ color = COLORS.textSecondary, size = 16 }: MiniIconProps) { + return ( + + ); +} + +function CommentMini({ + color = COLORS.textTertiary, + size = 16, +}: MiniIconProps) { + return ( + + ); +} + +function MessageCirclePlusMini({ + color = COLORS.textSecondary, + size = 16, +}: MiniIconProps) { + return ( + + ); +} + +function LinkMini({ color = COLORS.textTertiary, size = 16 }: MiniIconProps) { + return ( + + ); +} + +function ListMini({ color = COLORS.textSecondary, size = 16 }: MiniIconProps) { + return ( + + ); +} + +function PlusMini({ color = COLORS.textSecondary, size = 14 }: MiniIconProps) { + return ( + + ); +} + +function CheckMini({ color = COLORS.text, size = 12 }: MiniIconProps) { + return ( + + ); +} + +function CloseMini({ color = COLORS.text, size = 12 }: MiniIconProps) { + return ; +} + +function PencilMini({ + color = COLORS.textSecondary, + size = 14, +}: MiniIconProps) { + return ( + + ); +} + +function CopyMini({ color = COLORS.textSecondary, size = 14 }: MiniIconProps) { + return ( + + ); +} + +// -- Favicon logo component -- + +function FaviconLogo({ + domain, + label, + size = 14, +}: { + domain?: string; + label?: string; + size?: number; +}) { + const faviconUrl = getLogoUrlFromDomainName(domain); + const [localFailedUrl, setLocalFailedUrl] = useState(null); + const showFavicon = + faviconUrl !== undefined && + !failedFaviconUrls.has(faviconUrl) && + localFailedUrl !== faviconUrl; + + const baseStyle = { + width: `${size}px`, + height: `${size}px`, + borderRadius: '4px', + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + flex: '0 0 auto', + overflow: 'hidden', + fontFamily: APP_FONT, + fontSize: size <= 14 ? '8px' : '9px', + fontWeight: 600, + lineHeight: 1, + } as const; + + if (showFavicon) { + return ( +
+ { + failedFaviconUrls.add(faviconUrl); + setLocalFailedUrl(faviconUrl); + }} + /> +
+ ); + } + + const initials = label ? getInitials(label) : '?'; + + return ( +
+ {initials.slice(0, 1)} +
+ ); +} + +// -- Sidebar icon rendering -- + +function renderSidebarIcon(icon: HeroSidebarIcon): ReactNode { + if (icon.kind === 'brand') { + return ( + + + {icon.overlay === 'link' ? ( +
+ +
+ ) : null} +
+ ); + } + + if (icon.kind === 'avatar') { + const tone = SIDEBAR_TONES[icon.tone] ?? SIDEBAR_TONES.gray; + + return ( + + + {icon.label} + + + ); + } + + const tone = SIDEBAR_TONES[icon.tone] ?? SIDEBAR_TONES.gray; + const TablerIcon = TABLER_ICON_MAP[icon.name]; + + return ( + + {TablerIcon ? ( + + ) : null} + {icon.overlay === 'link' ? ( +
+ +
+ ) : null} +
+ ); +} + +// -- Sidebar item component -- + +function SidebarItemComponent({ + depth = 0, + interactive = true, + item, + onSelect, + selectedLabel, +}: { + depth?: number; + interactive?: boolean; + item: HeroSidebarItem; + onSelect?: (label: string) => void; + selectedLabel?: string; +}) { + const rowActive = + interactive && selectedLabel !== undefined && item.label === selectedLabel; + + 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.children.map((child) => ( + + ))} + + ) : null} + + ); +} + +// -- Cell rendering components -- + +function PersonTokenCell({ + token, + hovered = false, + withCopyAction = true, +}: { + token: HeroCellPerson; + hovered?: boolean; + withCopyAction?: boolean; +}) { + const tone = PERSON_TONES[token.tone ?? 'gray'] ?? PERSON_TONES.gray; + const square = + token.kind === 'api' || + token.kind === 'system' || + token.kind === 'workflow'; + + return ( +
+ + {token.avatarUrl ? ( + + ) : ( + (token.shortLabel ?? getInitials(token.name)) + )} + + } + /> + + {withCopyAction ? ( + + ) : null} + + +
+ ); +} + +function EntityCellComponent({ + cell, + hovered, + isFirstColumn, +}: { + cell: HeroCellEntity; + hovered: boolean; + isFirstColumn: boolean; +}) { + if (isFirstColumn) { + return ( + + + + + } + variant={ChipVariant.Highlighted} + /> + + + + + ); + } + + return ( + } + /> + ); +} + +function RelationCellComponent({ + cell, + hovered, +}: { + cell: HeroCellRelation; + hovered: boolean; +}) { + return ( +
+ + {cell.items.map((item) => { + const tone = PERSON_TONES[item.tone ?? 'gray'] ?? PERSON_TONES.gray; + + return ( + + {item.shortLabel ?? getInitials(item.name)} + + } + /> + ); + })} + + + + + +
+ ); +} + +function renderCellValue( + cell: HeroCellValue, + hovered: boolean, + isFirstColumn: boolean, +): ReactNode { + switch (cell.type) { + case 'text': + return {cell.value}; + case 'number': + return {cell.value}; + case 'link': + return ( +
+ + + + +
+ ); + case 'boolean': + return ( + + {cell.value ? : } + {cell.value ? 'True' : 'False'} + + ); + case 'tag': + return {cell.value}; + case 'person': + return ; + case 'entity': + return ( + + ); + case 'relation': + return ; + } +} + +// -- Main component -- + +export function HomeVisual({ visual }: { visual: HeroVisualType }) { + const shellRef = useRef(null); + const viewportRef = useRef(null); + const dragRef = useRef({ + active: false, + pointerId: -1, + startScrollLeft: 0, + startX: 0, + }); + + const defaultActiveLabel = + visual.workspaceNav.find((entry) => !isFolder(entry) && entry.active) + ?.label ?? + visual.workspaceNav[0]?.label ?? + ''; + + const [activeLabel, setActiveLabel] = useState(defaultActiveLabel); + const [dragging, setDragging] = useState(false); + const [hoveredRowId, setHoveredRowId] = useState(null); + const [tilt, setTilt] = useState({ x: 0, y: 0 }); + + const activeItem = useMemo( + () => findActiveItem(visual.workspaceNav, activeLabel), + [activeLabel, visual.workspaceNav], + ); + + const columns: HeroColumnDef[] = activeItem?.columns ?? []; + const rows: HeroRowDef[] = activeItem?.rows ?? []; + const viewLabel = activeItem?.viewLabel ?? activeLabel; + const viewCount = activeItem?.viewCount ?? rows.length; + const totalTableWidth = visual.tableWidth ?? DEFAULT_TABLE_WIDTH; + const actions = visual.actions ?? []; + + const columnWidth = columns.reduce((sum, column) => sum + column.width, 0); + const fillerWidth = Math.max(totalTableWidth - columnWidth, 0); + + const handleShellPointerMove = (event: ReactPointerEvent) => { + if (event.pointerType !== 'mouse' || dragging || !shellRef.current) { + return; + } + + const bounds = shellRef.current.getBoundingClientRect(); + const horizontal = ((event.clientX - bounds.left) / bounds.width - 0.5) * 2; + const vertical = ((event.clientY - bounds.top) / bounds.height - 0.5) * 2; + + setTilt({ + x: Number((-vertical * 2.2).toFixed(2)), + y: Number((horizontal * 3.8).toFixed(2)), + }); + }; + + const resetTilt = () => { + setTilt({ x: 0, y: 0 }); + }; + + const handlePointerDown = (event: ReactPointerEvent) => { + if ( + event.pointerType !== 'mouse' || + event.button !== 0 || + !viewportRef.current + ) { + return; + } + + dragRef.current = { + active: true, + pointerId: event.pointerId, + startScrollLeft: viewportRef.current.scrollLeft, + startX: event.clientX, + }; + + viewportRef.current.setPointerCapture(event.pointerId); + setDragging(true); + event.preventDefault(); + }; + + const handlePointerMove = (event: ReactPointerEvent) => { + if (!dragRef.current.active || !viewportRef.current) { + return; + } + + viewportRef.current.scrollLeft = + dragRef.current.startScrollLeft - + (event.clientX - dragRef.current.startX); + }; + + const endDragging = () => { + dragRef.current.active = false; + dragRef.current.pointerId = -1; + setDragging(false); + }; + + const handlePointerUp = (event: ReactPointerEvent) => { + if (!viewportRef.current || dragRef.current.pointerId !== event.pointerId) { + return; + } + + viewportRef.current.releasePointerCapture(event.pointerId); + endDragging(); + }; + + useEffect(() => { + const node = viewportRef.current; + + if (!node) { + return; + } + + const onWheel: EventListener = (event) => { + if (!(event instanceof WheelEvent)) { + return; + } + + if ( + node.scrollWidth <= node.clientWidth || + Math.abs(event.deltaY) <= Math.abs(event.deltaX) + ) { + return; + } + + node.scrollLeft += event.deltaY; + event.preventDefault(); + }; + + node.addEventListener('wheel', onWheel, { passive: false }); + + return () => { + node.removeEventListener('wheel', onWheel); + }; + }, []); + + const renderSidebarEntry = (entry: HeroSidebarEntry) => { + if (isFolder(entry)) { + return ( + + ); + } + + return ( + + ); + }; + return ( - - + + + + + + + + + + {visual.workspace.name} + + + + + + + + + + + + + + + {visual.favoritesNav && visual.favoritesNav.length > 0 ? ( + + Favorites + {visual.favoritesNav.map((item) => ( + + ))} + + ) : null} + + Workspace + {visual.workspaceNav.map(renderSidebarEntry)} + + + + + + + + + + + + {activeLabel} + + + + + + + + + + New Record + + + + + + + + + + + + + + + {actions.map((action) => ( + {action} + ))} + + + + + + + + + + {columns.map((column) => ( + + + {column.isFirstColumn ? ( + <> + + + + {renderHeaderIcon(column.id)} + {column.label} + + + ) : ( + <> + {renderHeaderIcon(column.id)} + {column.label} + + )} + + + ))} + + {fillerWidth > 0 ? ( + + + + ) : null} + + + + {rows.map((row) => { + const hovered = hoveredRowId === row.id; + + return ( + setHoveredRowId(row.id)} + onMouseLeave={() => + setHoveredRowId((current) => + current === row.id ? null : current, + ) + } + > + {columns.map((column) => { + const cell = row.cells[column.id]; + + return ( + + {cell + ? renderCellValue( + cell, + hovered, + !!column.isFirstColumn, + ) + : null} + + ); + })} + + + ); + })} + + + {columns.length > 0 ? ( + + + Calculate + + + + ) : null} + {columns.slice(1).map((column) => ( + + ))} + + + + + + + + + + ); } + +function renderHeaderIcon(columnId: string): ReactNode { + const Icon = HEADER_ICON_MAP[columnId]; + + if (Icon) { + return ( + + ); + } + + return ( + + ); +} 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 new file mode 100644 index 0000000000..d55c0a68b5 --- /dev/null +++ b/packages/twenty-website-new/src/sections/Hero/components/HomeVisual/homeVisualChip.tsx @@ -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 +>` + --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 ( + + {leftComponent} + {label} + + ); +}; 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 new file mode 100644 index 0000000000..c553754c8e --- /dev/null +++ b/packages/twenty-website-new/src/sections/Hero/components/HomeVisual/homeVisualTokens.ts @@ -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; 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 4ab69897af..be90e1290b 100644 --- a/packages/twenty-website-new/src/sections/Hero/types/HeroHomeData.ts +++ b/packages/twenty-website-new/src/sections/Hero/types/HeroHomeData.ts @@ -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; +}; + +// -- 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; }; diff --git a/packages/twenty-website-new/src/sections/Hero/types/index.ts b/packages/twenty-website-new/src/sections/Hero/types/index.ts index 66c4e59e85..c186d5606b 100644 --- a/packages/twenty-website-new/src/sections/Hero/types/index.ts +++ b/packages/twenty-website-new/src/sections/Hero/types/index.ts @@ -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'; diff --git a/packages/twenty-website-new/src/sections/TrustedBy/components/ClientCount/ClientCount.tsx b/packages/twenty-website-new/src/sections/TrustedBy/components/ClientCount/ClientCount.tsx index 160e5cee6b..961a249149 100644 --- a/packages/twenty-website-new/src/sections/TrustedBy/components/ClientCount/ClientCount.tsx +++ b/packages/twenty-website-new/src/sections/TrustedBy/components/ClientCount/ClientCount.tsx @@ -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 ( - + - + {label} diff --git a/packages/twenty-website-new/src/sections/TrustedBy/components/Logo/Logo.tsx b/packages/twenty-website-new/src/sections/TrustedBy/components/Logo/Logo.tsx index cfd4b4b70d..72fcc883c5 100644 --- a/packages/twenty-website-new/src/sections/TrustedBy/components/Logo/Logo.tsx +++ b/packages/twenty-website-new/src/sections/TrustedBy/components/Logo/Logo.tsx @@ -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 {children}; +export function Logo({ fit = 'contain', src }: LogoProps) { + return ( + + ); } diff --git a/packages/twenty-website-new/src/sections/TrustedBy/components/Logos/Logos.tsx b/packages/twenty-website-new/src/sections/TrustedBy/components/Logos/Logos.tsx index f6784c807d..dfe34f0b64 100644 --- a/packages/twenty-website-new/src/sections/TrustedBy/components/Logos/Logos.tsx +++ b/packages/twenty-website-new/src/sections/TrustedBy/components/Logos/Logos.tsx @@ -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 ( - {logos.map((logo, index) => { - const IconComponent = CLIENT_ICONS[logo.icon]; - if (!IconComponent) return null; - - return ( - - {index === 1 && } - - - - - ); - })} + + {logos.map((logo, index) => ( + + ))} + + ); } diff --git a/packages/twenty-website-new/src/sections/TrustedBy/components/Root/Root.tsx b/packages/twenty-website-new/src/sections/TrustedBy/components/Root/Root.tsx index 6f9283343c..d0badd67bd 100644 --- a/packages/twenty-website-new/src/sections/TrustedBy/components/Root/Root.tsx +++ b/packages/twenty-website-new/src/sections/TrustedBy/components/Root/Root.tsx @@ -30,7 +30,7 @@ interface RootProps { export function Root({ children }: RootProps) { return ( - + {children} ); diff --git a/packages/twenty-website-new/src/sections/TrustedBy/types/TrustedByData.ts b/packages/twenty-website-new/src/sections/TrustedBy/types/TrustedByData.ts index 224baf100c..d535b4e646 100644 --- a/packages/twenty-website-new/src/sections/TrustedBy/types/TrustedByData.ts +++ b/packages/twenty-website-new/src/sections/TrustedBy/types/TrustedByData.ts @@ -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[]; -} \ No newline at end of file + clientCountLabel: TrustedByClientCountLabelType; + logos: TrustedByLogosType[]; + separator: TrustedBySeparatorType; +}; diff --git a/packages/twenty-website-new/src/sections/TrustedBy/types/TrustedByLogos.ts b/packages/twenty-website-new/src/sections/TrustedBy/types/TrustedByLogos.ts index d8d018bbcb..1654f0c21f 100644 --- a/packages/twenty-website-new/src/sections/TrustedBy/types/TrustedByLogos.ts +++ b/packages/twenty-website-new/src/sections/TrustedBy/types/TrustedByLogos.ts @@ -1,3 +1,4 @@ export type TrustedByLogosType = { - icon: string; -} \ No newline at end of file + fit?: 'contain' | 'cover'; + src: string; +}; diff --git a/yarn.lock b/yarn.lock index 62c5ca04ea..2f6a7a0344 100644 --- a/yarn.lock +++ b/yarn.lock @@ -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"