From d94d2eb67c4342ffff8fb648d8ce3be5bcfd8429 Mon Sep 17 00:00:00 2001 From: "Abdullah." <125115953+mabdullahabaid@users.noreply.github.com> Date: Fri, 15 May 2026 13:58:22 +0500 Subject: [PATCH] [Website] Make product stepper visuals interactive. (#20602) We had low-res screenshots for each step in the stepper. Replaced them with interactive components. https://github.com/user-attachments/assets/d03ff924-a1dd-467f-ba19-cece0ecb3486 --- .../images/product/stepper/step-one.webp | Bin 15994 -> 0 bytes .../images/product/stepper/step-three.webp | Bin 27750 -> 0 bytes .../images/product/stepper/step-two.webp | Bin 16170 -> 0 bytes .../src/app/[locale]/product/page.tsx | 18 +- .../src/sections/ProductStepper/Flow.tsx | 4 +- .../src/sections/ProductStepper/Visual.tsx | 57 +- .../src/sections/ProductStepper/types.ts | 9 +- .../visuals/AppPreviewShell.tsx | 197 +++++++ .../visuals/DataModelVisual.tsx | 314 +++++++++++ .../ProductStepper/visuals/DrawEdge.tsx | 62 +++ .../ProductStepper/visuals/LayoutVisual.tsx | 312 +++++++++++ .../ProductStepper/visuals/WorkflowVisual.tsx | 293 +++++++++++ .../visuals/data/DataModel.data.tsx | 142 +++++ .../visuals/data/layout.data.ts | 104 ++++ .../visuals/data/workflow.data.ts | 111 ++++ .../visuals/icons/DataModelIcons.tsx | 196 +++++++ .../visuals/icons/LayoutIcons.tsx | 450 ++++++++++++++++ .../visuals/icons/WorkflowIcons.tsx | 130 +++++ .../ProductStepper/visuals/layout-styles.ts | 486 ++++++++++++++++++ .../visuals/stepper-visual-tokens.ts | 19 + .../visuals/use-workflow-animation.ts | 39 ++ 21 files changed, 2907 insertions(+), 36 deletions(-) delete mode 100644 packages/twenty-website-new/public/images/product/stepper/step-one.webp delete mode 100644 packages/twenty-website-new/public/images/product/stepper/step-three.webp delete mode 100644 packages/twenty-website-new/public/images/product/stepper/step-two.webp create mode 100644 packages/twenty-website-new/src/sections/ProductStepper/visuals/AppPreviewShell.tsx create mode 100644 packages/twenty-website-new/src/sections/ProductStepper/visuals/DataModelVisual.tsx create mode 100644 packages/twenty-website-new/src/sections/ProductStepper/visuals/DrawEdge.tsx create mode 100644 packages/twenty-website-new/src/sections/ProductStepper/visuals/LayoutVisual.tsx create mode 100644 packages/twenty-website-new/src/sections/ProductStepper/visuals/WorkflowVisual.tsx create mode 100644 packages/twenty-website-new/src/sections/ProductStepper/visuals/data/DataModel.data.tsx create mode 100644 packages/twenty-website-new/src/sections/ProductStepper/visuals/data/layout.data.ts create mode 100644 packages/twenty-website-new/src/sections/ProductStepper/visuals/data/workflow.data.ts create mode 100644 packages/twenty-website-new/src/sections/ProductStepper/visuals/icons/DataModelIcons.tsx create mode 100644 packages/twenty-website-new/src/sections/ProductStepper/visuals/icons/LayoutIcons.tsx create mode 100644 packages/twenty-website-new/src/sections/ProductStepper/visuals/icons/WorkflowIcons.tsx create mode 100644 packages/twenty-website-new/src/sections/ProductStepper/visuals/layout-styles.ts create mode 100644 packages/twenty-website-new/src/sections/ProductStepper/visuals/stepper-visual-tokens.ts create mode 100644 packages/twenty-website-new/src/sections/ProductStepper/visuals/use-workflow-animation.ts diff --git a/packages/twenty-website-new/public/images/product/stepper/step-one.webp b/packages/twenty-website-new/public/images/product/stepper/step-one.webp deleted file mode 100644 index a4df8bae13161c374e2c57d17c0a48e3622f62f5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15994 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 diff --git a/packages/twenty-website-new/public/images/product/stepper/step-three.webp b/packages/twenty-website-new/public/images/product/stepper/step-three.webp deleted file mode 100644 index 037eb1d62dfa5562654ed39e841437ff5f263017..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 27750 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 diff --git a/packages/twenty-website-new/public/images/product/stepper/step-two.webp b/packages/twenty-website-new/public/images/product/stepper/step-two.webp deleted file mode 100644 index b7b9ab045495bd6fc00b3278a07f12182db37124..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16170 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

w@QpY^)W*8T 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._(msg`Data model`)} ), body: msg`Add objects and fields`, - image: { - src: '/images/product/stepper/step-one.webp', - alt: 'Twenty data model: add objects and fields', - }, + visual: DataModelVisual, }, { icon: 'check', @@ -69,10 +69,7 @@ export default async function ProductPage({ params }: ProductPageProps) { {i18n._(msg`Automation`)} ), body: msg`Create a workflow`, - image: { - src: '/images/product/stepper/step-two.webp', - alt: 'Twenty automation: create a workflow', - }, + visual: WorkflowVisual, }, { icon: 'eye', @@ -80,10 +77,7 @@ export default async function ProductPage({ params }: ProductPageProps) { {i18n._(msg`Layout`)} ), body: msg`Tailor record pages, menus, and views`, - image: { - src: '/images/product/stepper/step-three.webp', - alt: 'Twenty layout: record pages, menus, and views', - }, + visual: LayoutVisual, }, ]; diff --git a/packages/twenty-website-new/src/sections/ProductStepper/Flow.tsx b/packages/twenty-website-new/src/sections/ProductStepper/Flow.tsx index 0e22ea9f36c..86df9356f30 100644 --- a/packages/twenty-website-new/src/sections/ProductStepper/Flow.tsx +++ b/packages/twenty-website-new/src/sections/ProductStepper/Flow.tsx @@ -66,7 +66,7 @@ export function Flow({ body, children, eyebrow, steps }: FlowProps) { }), ); - const images = steps.map((step) => step.image); + const stepVisuals = steps.map(({ visual }) => ({ visual })); return ( @@ -86,7 +86,7 @@ export function Flow({ body, children, eyebrow, steps }: FlowProps) { onMobileStepIndexChange={setMobileStepIndex} steps={contentSteps} /> - + ); diff --git a/packages/twenty-website-new/src/sections/ProductStepper/Visual.tsx b/packages/twenty-website-new/src/sections/ProductStepper/Visual.tsx index 7b1c432fe09..f60fb003e55 100644 --- a/packages/twenty-website-new/src/sections/ProductStepper/Visual.tsx +++ b/packages/twenty-website-new/src/sections/ProductStepper/Visual.tsx @@ -1,13 +1,20 @@ -import type { ImageType } from '@/design-system/components/Image'; +'use client'; + import { theme } from '@/theme'; import { css } from '@linaria/core'; import { styled } from '@linaria/react'; -import NextImage from 'next/image'; +import type { ComponentType } from 'react'; + +import type { StepperVisualProps } from './types'; import { StepperVisualFrame } from './StepperVisualFrame'; +type StepVisual = { + visual: ComponentType; +}; + type ProductStepperVisualProps = { activeStepIndex: number; - images: ImageType[]; + stepVisuals: StepVisual[]; }; const PRODUCT_STEPPER_BACKGROUND = '/images/product/stepper/background.webp'; @@ -41,19 +48,30 @@ const VisualFrame = styled.div` } `; -const slideImageClassName = css` - object-fit: contain; - object-position: center; +const slideClassName = css` + inset: 0; opacity: 0; + pointer-events: none; + position: absolute; transition: opacity 0.4s ease; &[data-active='true'] { opacity: 1; + pointer-events: auto; } `; -export function Visual({ activeStepIndex, images }: ProductStepperVisualProps) { - if (!images || images.length === 0) { +const visualWrapperClassName = css` + display: flex; + height: 100%; + width: 100%; +`; + +export function Visual({ + activeStepIndex, + stepVisuals, +}: ProductStepperVisualProps) { + if (!stepVisuals || stepVisuals.length === 0) { return null; } @@ -64,19 +82,20 @@ export function Visual({ activeStepIndex, images }: ProductStepperVisualProps) { backgroundSrc={PRODUCT_STEPPER_BACKGROUND} shapeSrc={PRODUCT_STEPPER_SHAPE} > - {images.map((image, index) => { - if (!image) return null; + {stepVisuals.map((step, index) => { + const isActive = index === activeStepIndex; + const VisualComponent = step.visual; return ( - +

+
+ +
+
); })} diff --git a/packages/twenty-website-new/src/sections/ProductStepper/types.ts b/packages/twenty-website-new/src/sections/ProductStepper/types.ts index 8ab135c268b..11a670eb7c0 100644 --- a/packages/twenty-website-new/src/sections/ProductStepper/types.ts +++ b/packages/twenty-website-new/src/sections/ProductStepper/types.ts @@ -1,10 +1,13 @@ -import type { ImageType } from '@/design-system/components/Image'; import type { MessageDescriptor } from '@lingui/core'; -import type { ReactNode } from 'react'; +import type { ComponentType, ReactNode } from 'react'; + +export type StepperVisualProps = { + active: boolean; +}; export type ProductStepperStepType = { body: MessageDescriptor; heading: ReactNode; icon: string; - image: ImageType; + visual: ComponentType; }; diff --git a/packages/twenty-website-new/src/sections/ProductStepper/visuals/AppPreviewShell.tsx b/packages/twenty-website-new/src/sections/ProductStepper/visuals/AppPreviewShell.tsx new file mode 100644 index 00000000000..cdea9e8eba1 --- /dev/null +++ b/packages/twenty-website-new/src/sections/ProductStepper/visuals/AppPreviewShell.tsx @@ -0,0 +1,197 @@ +'use client'; + +import { styled } from '@linaria/react'; +import type { ReactNode } from 'react'; + +import { + STEPPER_BG, + STEPPER_BORDER_MEDIUM, + STEPPER_BORDER_SUBTLE, + STEPPER_FONT, + STEPPER_HEADER_BG, + STEPPER_HEADER_BORDER, + STEPPER_SHADOW_SM, + STEPPER_TEXT, + STEPPER_TEXT_MUTED, + STEPPER_TEXT_TERTIARY, +} from './stepper-visual-tokens'; + +const Wrapper = styled.div` + background: ${STEPPER_BG}; + border-radius: 2px; + box-shadow: ${STEPPER_SHADOW_SM}; + display: flex; + flex-direction: column; + font-family: ${STEPPER_FONT}; + height: 92%; + margin-left: auto; + margin-top: auto; + overflow: hidden; + width: 88%; +`; + +const Header = styled.div` + align-items: center; + display: flex; + gap: 4px; + height: 30px; + padding: 0 10px; +`; + +const HeaderLogo = styled.span` + align-items: center; + background: ${STEPPER_HEADER_BG}; + border: 1px solid ${STEPPER_HEADER_BORDER}; + border-radius: 3px; + color: ${STEPPER_TEXT}; + display: flex; + height: 14px; + justify-content: center; + width: 14px; +`; + +const HeaderTitle = styled.span` + color: ${STEPPER_TEXT}; + font-size: 12px; + font-weight: 500; + line-height: 1.4; + padding: 0 2px; +`; + +const HeaderActions = styled.div` + align-items: center; + display: flex; + gap: 8px; + margin-left: auto; +`; + +const HeaderBtn = styled.span` + align-items: center; + border: 1px solid ${STEPPER_BORDER_MEDIUM}; + border-radius: 4px; + color: ${STEPPER_TEXT_MUTED}; + display: flex; + height: 22px; + justify-content: center; + width: 22px; +`; + +const HeaderCmdBtn = styled.span` + align-items: center; + border: 1px solid ${STEPPER_BORDER_SUBTLE}; + border-radius: 4px; + color: ${STEPPER_TEXT_TERTIARY}; + display: flex; + font-size: 12px; + font-weight: 500; + gap: 4px; + height: 22px; + padding: 0 6px; +`; + +export const ShellCanvas = styled.div` + background: white; + border: 1px solid ${STEPPER_BORDER_MEDIUM}; + border-radius: 8px; + flex: 1; + margin: 0 10px 10px; + min-height: 0; + overflow: hidden; + position: relative; + user-select: none; +`; + +export const ShellSvgLayer = styled.svg` + height: 100%; + left: 0; + pointer-events: none; + position: absolute; + top: 0; + width: 100%; +`; + +type AppPreviewShellProps = { + active: boolean; + children: ReactNode; + title: string; +}; + +export function AppPreviewShell({ + active, + children, + title, +}: AppPreviewShellProps) { + return ( + +
+ + + + + + + + + + {title} + + + + + + + + + + + + + + + + + + ⌘K + + +
+ {children} +
+ ); +} diff --git a/packages/twenty-website-new/src/sections/ProductStepper/visuals/DataModelVisual.tsx b/packages/twenty-website-new/src/sections/ProductStepper/visuals/DataModelVisual.tsx new file mode 100644 index 00000000000..f978c9a9475 --- /dev/null +++ b/packages/twenty-website-new/src/sections/ProductStepper/visuals/DataModelVisual.tsx @@ -0,0 +1,314 @@ +'use client'; + +import { styled } from '@linaria/react'; +import { useRef, useState } from 'react'; + +import type { StepperVisualProps } from '../types'; + +import { AppPreviewShell, ShellCanvas, ShellSvgLayer } from './AppPreviewShell'; +import { + BADGE_CUSTOM_BG, + BADGE_CUSTOM_BORDER, + BADGE_CUSTOM_TEXT, + BADGE_STANDARD_BG, + BADGE_STANDARD_BORDER, + BADGE_STANDARD_TEXT, + CONNECTIONS, + type ConnectionDef, + ENTITIES, +} from './data/DataModel.data'; +import { DrawEdge } from './DrawEdge'; +import { IconChevronDown } from './icons/DataModelIcons'; +import { + STEPPER_BORDER_LIGHT, + STEPPER_BORDER_MEDIUM, + STEPPER_BORDER_STRONG, + STEPPER_CARD_BG, + STEPPER_TEXT, + STEPPER_TEXT_MUTED, + STEPPER_TEXT_TERTIARY, +} from './stepper-visual-tokens'; + +const EntityCard = styled.div<{ $hovered: boolean }>` + backdrop-filter: blur(14px); + background: ${STEPPER_CARD_BG}; + border: 1px solid + ${({ $hovered }) => + $hovered ? STEPPER_BORDER_STRONG : STEPPER_BORDER_MEDIUM}; + border-radius: 8px; + box-shadow: + 0 0 2px rgba(0, 0, 0, 0.08), + 0 2px 2px rgba(0, 0, 0, 0.04); + cursor: grab; + display: flex; + flex-direction: column; + gap: 6px; + min-width: 130px; + padding: 6px; + position: absolute; + touch-action: none; + transition: border-color 0.15s ease; + z-index: 2; + + &:active { + cursor: grabbing; + z-index: 10; + } +`; + +const EntityHeader = styled.div` + align-items: center; + display: flex; + gap: 4px; + overflow: hidden; + white-space: nowrap; +`; + +const InnerCard = styled.div` + background: white; + border: 1px solid ${STEPPER_BORDER_LIGHT}; + border-radius: 4px; + display: flex; + flex-direction: column; + gap: 2px; + overflow: hidden; + padding: 4px 0; +`; + +const EntityIcon = styled.span<{ $bg: string; $border: string }>` + align-items: center; + background: ${({ $bg }) => $bg}; + border: 1px solid ${({ $border }) => $border}; + border-radius: 3px; + color: ${STEPPER_TEXT}; + display: flex; + height: 14px; + justify-content: center; + width: 14px; +`; + +const EntityLabel = styled.span` + color: ${STEPPER_TEXT}; + font-size: 12px; + font-weight: 500; + line-height: 1.4; + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; +`; + +const EntityMeta = styled.span` + color: ${STEPPER_TEXT_TERTIARY}; + flex-shrink: 0; + font-size: 12px; + font-weight: 500; + line-height: 1.4; +`; + +const MetaBadge = styled.div` + align-items: center; + display: flex; + gap: 4px; + margin-left: auto; +`; + +const MetaBadgeIcon = styled.span<{ + $bg: string; + $border: string; + $color: string; +}>` + align-items: center; + background: ${({ $bg }) => $bg}; + border: 1px solid ${({ $border }) => $border}; + border-radius: 2px; + color: ${({ $color }) => $color}; + display: flex; + font-size: 8px; + font-weight: 500; + height: 14px; + justify-content: center; + width: 14px; +`; + +const MetaBadgeText = styled.span` + color: ${STEPPER_TEXT_MUTED}; + font-size: 9px; + font-weight: 400; +`; + +const FieldRow = styled.div` + align-items: center; + color: ${STEPPER_TEXT}; + display: flex; + font-size: 12px; + font-weight: 400; + gap: 4px; + height: 22px; + line-height: 1.4; + padding: 0 6px; +`; + +const FieldIcon = styled.span` + align-items: center; + color: ${STEPPER_TEXT_MUTED}; + display: flex; + height: 14px; + justify-content: center; + width: 14px; +`; + +const ExpandHint = styled.div` + align-items: center; + color: ${STEPPER_TEXT_TERTIARY}; + display: flex; + font-size: 12px; + font-weight: 400; + gap: 4px; + height: 22px; + line-height: 1.4; + padding: 0 6px; +`; + +const CARD_WIDTH = 140; +const CARD_HEIGHT_ESTIMATE = 110; + +function getCardCenter( + positions: Record, + entityId: string, +): { x: number; y: number } { + const pos = positions[entityId]; + if (!pos) return { x: 0, y: 0 }; + return { x: pos.x + CARD_WIDTH / 2, y: pos.y + CARD_HEIGHT_ESTIMATE / 2 }; +} + +export function DataModelVisual({ active }: StepperVisualProps) { + const [positions, setPositions] = useState< + Record + >(() => + Object.fromEntries( + ENTITIES.map((entity) => [entity.id, { x: entity.x, y: entity.y }]), + ), + ); + const [hoveredEntity, setHoveredEntity] = useState(null); + const [dragging, setDragging] = useState(null); + const dragStartRef = useRef<{ + entityId: string; + posX: number; + posY: number; + startX: number; + startY: number; + } | null>(null); + + const handlePointerDown = (entityId: string, event: React.PointerEvent) => { + event.preventDefault(); + (event.currentTarget as HTMLElement).setPointerCapture(event.pointerId); + const pos = positions[entityId]; + dragStartRef.current = { + entityId, + startX: event.clientX, + startY: event.clientY, + posX: pos.x, + posY: pos.y, + }; + setDragging(entityId); + }; + + const handlePointerMove = (event: React.PointerEvent) => { + if (!dragStartRef.current) return; + const { entityId, startX, startY, posX, posY } = dragStartRef.current; + const dx = event.clientX - startX; + const dy = event.clientY - startY; + setPositions((prev) => ({ + ...prev, + [entityId]: { x: posX + dx, y: posY + dy }, + })); + }; + + const handlePointerUp = () => { + dragStartRef.current = null; + setDragging(null); + }; + + const isConnectionHighlighted = (connection: ConnectionDef) => + hoveredEntity === connection.from || hoveredEntity === connection.to; + + return ( + + + + {CONNECTIONS.map((conn) => ( + + ))} + + + {ENTITIES.map((entity) => { + const pos = positions[entity.id]; + const isHovered = hoveredEntity === entity.id; + return ( + handlePointerDown(entity.id, event)} + onPointerEnter={() => setHoveredEntity(entity.id)} + onPointerLeave={() => setHoveredEntity(null)} + style={{ + left: pos.x, + top: pos.y, + transition: + dragging === entity.id ? 'none' : 'border-color 0.15s', + }} + > + + + {entity.headerIcon} + + {entity.label} + · {entity.meta} + + + L + + + {entity.isCustom ? 'Custom' : 'Standard'} + + + + + {entity.fields.map((field) => ( + + {field.icon} + {field.label} + + ))} + + {entity.expandCount} fields + + + + ); + })} + + + ); +} diff --git a/packages/twenty-website-new/src/sections/ProductStepper/visuals/DrawEdge.tsx b/packages/twenty-website-new/src/sections/ProductStepper/visuals/DrawEdge.tsx new file mode 100644 index 00000000000..4dcfb96ebcd --- /dev/null +++ b/packages/twenty-website-new/src/sections/ProductStepper/visuals/DrawEdge.tsx @@ -0,0 +1,62 @@ +import { + STEPPER_BORDER_MEDIUM, + STEPPER_BORDER_STRONG, +} from './stepper-visual-tokens'; + +type Point = { x: number; y: number }; + +type DrawEdgeProps = { + circleR?: number; + elbow?: 'horizontal-first' | 'vertical-first'; + from: Point; + highlighted: boolean; + to: Point; +}; + +export function DrawEdge({ + circleR = 2, + elbow = 'vertical-first', + from, + highlighted, + to, +}: DrawEdgeProps) { + const color = highlighted ? STEPPER_BORDER_STRONG : STEPPER_BORDER_MEDIUM; + const dx = Math.abs(to.x - from.x); + const dy = Math.abs(to.y - from.y); + + let pathD: string; + let startX = from.x; + let startY = from.y; + let endX = to.x; + let endY = to.y; + + if (dx < 30) { + const avgX = (from.x + to.x) / 2; + startX = avgX; + endX = avgX; + pathD = `M${avgX},${from.y} L${avgX},${to.y}`; + } else if (dy < 30) { + const avgY = (from.y + to.y) / 2; + startY = avgY; + endY = avgY; + pathD = `M${from.x},${avgY} L${to.x},${avgY}`; + } else if (elbow === 'horizontal-first') { + pathD = `M${from.x},${from.y} L${to.x},${from.y} L${to.x},${to.y}`; + } else { + pathD = `M${from.x},${from.y} L${from.x},${to.y} L${to.x},${to.y}`; + } + + return ( + + + + + + ); +} diff --git a/packages/twenty-website-new/src/sections/ProductStepper/visuals/LayoutVisual.tsx b/packages/twenty-website-new/src/sections/ProductStepper/visuals/LayoutVisual.tsx new file mode 100644 index 00000000000..8c9f68d9988 --- /dev/null +++ b/packages/twenty-website-new/src/sections/ProductStepper/visuals/LayoutVisual.tsx @@ -0,0 +1,312 @@ +'use client'; + +import { useRef, useState } from 'react'; + +import type { StepperVisualProps } from '../types'; + +import { FIELDS, NAV } from './data/layout.data'; +import { + CheckSvg, + ChevLeft, + DotsV, + DotsVW, + EyeIcon, + FieldIcon, + GripV, + ListSvg, + NavSvgIcon, + NewSecSvg, + PaintSvg, + PlusSvg, + SparkSvg, +} from './icons/LayoutIcons'; +import { + ActionBtn, + ActionsBar, + BlueHeader, + Canvas, + HeaderCenter, + HeaderLeft, + HeaderSave, + HeaderTitle, + MainCard, + NavBreadcrumb, + NavChevron, + NavIconBox, + NavItem, + NavPanel, + NavSectionLabel, + NavSubItem, + NavSuffix, + RightPanel, + RPActionBtn, + RPAddIconBox, + RPAddSection, + RPAddText, + RPBackBtn, + RPDoneBtn, + RPEditable, + RPEditText, + RPFieldDot, + RPFieldIconBox, + RPFieldLabels, + RPFieldName, + RPFieldRow, + RPFieldType, + RPFields, + RPHeader, + RPIconBox, + RPNewDesc, + RPNewFields, + RPNewTitle, + RPSectionName, + RPSectionRow, + RPSubBar, + RPSubLabel, + RPTitleBold, + RPTitleGroup, + RPTitleSub, + WChip, + WIcon, + WLabel, + WRow, + WSectionLabel, + WValue, + WidgetInner, + WidgetPanel, + WidgetTitle, +} from './layout-styles'; + +export function LayoutVisual({ active }: StepperVisualProps) { + const [fields, setFields] = useState(FIELDS); + const [draggingId, setDraggingId] = useState(null); + const dragStartY = useRef(0); + + const toggleVisibility = (fieldId: string) => { + setFields((prev) => + prev.map((f) => (f.id === fieldId ? { ...f, visible: !f.visible } : f)), + ); + }; + + const handleDragStart = (fieldId: string, event: React.PointerEvent) => { + event.preventDefault(); + (event.currentTarget as HTMLElement).setPointerCapture(event.pointerId); + setDraggingId(fieldId); + dragStartY.current = event.clientY; + }; + + const handleDragMove = (event: React.PointerEvent) => { + if (!draggingId) return; + const dy = event.clientY - dragStartY.current; + const steps = Math.round(dy / 22); + if (steps === 0) return; + setFields((prev) => { + const idx = prev.findIndex((f) => f.id === draggingId); + if (idx === -1) return prev; + const to = Math.max(0, Math.min(prev.length - 1, idx + steps)); + if (to === idx) return prev; + const next = [...prev]; + const [moved] = next.splice(idx, 1); + next.splice(to, 0, moved); + return next; + }); + dragStartY.current = event.clientY; + }; + + const handleDragEnd = () => setDraggingId(null); + const sections = [...new Set(fields.map((f) => f.section))]; + + return ( + + + + + + + + + Layout edition + + + Save + + + + + + + Widget name + General + + + + + URL + qonto.com + + + + + + Account O... + Phil Schiller + + + + + + Address + 18 Rue De Navarin, 750... + + + + + + ICP + ✓ True + + + + + + Workspace + {NAV.map((item) => ( +
+ + + + + {item.label} + {'suffix' in item && item.suffix && ( + · {item.suffix} + )} + {'folder' in item && item.folder && } + + {'children' in item && + item.children?.map((child) => ( + + + + + + {child.label} + + ))} +
+ ))} +
+ + + + New record + ✧ Enrich + ✎ Edit actions + + + + + + + + + + + + Fields + Fields widget + + + + + + + + + + + Layout + + + + {sections.map((section, si) => ( +
+ + + {section} + + + + + + {si === 0 && ( + + Industry + Done + + )} + + {fields + .filter((f) => f.section === section) + .map((field) => ( + handleDragStart(field.id, e)} + > + + + + + {field.label} + · + {field.type} + + { + e.stopPropagation(); + toggleVisibility(field.id); + }} + > + + + + + + + ))} + + {si > 0 && si < sections.length - 1 && ( + + + + + Add a Section + + )} +
+ ))} + + + + + +
+ New fields + Default position/visibility for field… +
+
+ + + + + + Add a Section + +
+
+
+ ); +} diff --git a/packages/twenty-website-new/src/sections/ProductStepper/visuals/WorkflowVisual.tsx b/packages/twenty-website-new/src/sections/ProductStepper/visuals/WorkflowVisual.tsx new file mode 100644 index 00000000000..4e17a204e6f --- /dev/null +++ b/packages/twenty-website-new/src/sections/ProductStepper/visuals/WorkflowVisual.tsx @@ -0,0 +1,293 @@ +'use client'; + +import { styled } from '@linaria/react'; +import { useRef, useState } from 'react'; + +import type { StepperVisualProps } from '../types'; + +import { AppPreviewShell, ShellCanvas, ShellSvgLayer } from './AppPreviewShell'; +import { + COLOR_GRAY, + COLOR_GRAY_BG, + COLOR_GREEN, + COLOR_TEAL_BG, + EDGES, + NODE_HEIGHT, + NODE_WIDTH, + NODES, +} from './data/workflow.data'; +import { DrawEdge } from './DrawEdge'; +import { CheckIcon, NodeIcon } from './icons/WorkflowIcons'; +import { + STEPPER_BORDER_STRONG, + STEPPER_CARD_BG, + STEPPER_TEXT, + STEPPER_TEXT_MUTED, + STEPPER_TEXT_TERTIARY, + STEPPER_TINT, +} from './stepper-visual-tokens'; +import { useWorkflowAnimation } from './use-workflow-animation'; + +const NodeCard = styled.div` + align-items: center; + background: ${STEPPER_CARD_BG}; + border: 1px solid ${STEPPER_BORDER_STRONG}; + border-radius: 8px; + box-shadow: + 0 0 2px rgba(0, 0, 0, 0.08), + 0 1px 2px rgba(0, 0, 0, 0.04); + display: flex; + gap: 8px; + min-width: ${NODE_WIDTH}px; + padding: 8px; + position: absolute; + z-index: 2; +`; + +const NodeIconBox = styled.div` + align-items: center; + background: ${STEPPER_TINT}; + border-radius: 4px; + color: ${STEPPER_TEXT_MUTED}; + display: flex; + flex-shrink: 0; + height: 30px; + justify-content: center; + width: 30px; +`; + +const NodeRight = styled.div` + display: flex; + flex-direction: column; + gap: 2px; + min-width: 0; +`; + +const NodeLabelRow = styled.div` + align-items: center; + display: flex; + gap: 4px; + height: 13px; +`; + +const NodeLabel = styled.span<{ $color: string }>` + color: ${({ $color }) => $color}; + font-size: 10px; + font-weight: 600; +`; + +const NodeCheck = styled.span<{ $bg: string; $visible: boolean }>` + align-items: center; + background: ${({ $bg }) => $bg}; + border-radius: 2px; + display: flex; + height: 12px; + justify-content: center; + opacity: ${({ $visible }) => ($visible ? 1 : 0)}; + transition: opacity 0.3s; + width: 12px; +`; + +const NodeName = styled.div<{ $dimmed?: boolean }>` + color: ${({ $dimmed }) => ($dimmed ? STEPPER_TEXT_TERTIARY : STEPPER_TEXT)}; + font-size: 12px; + font-weight: 500; + line-height: 1.4; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +`; + +const IterationLabel = styled.span` + background: ${STEPPER_CARD_BG}; + border: 1px solid ${STEPPER_BORDER_STRONG}; + border-radius: 4px; + color: ${STEPPER_TEXT_TERTIARY}; + font-size: 9px; + font-weight: 600; + padding: 2px 4px; + position: absolute; + white-space: nowrap; + z-index: 3; +`; + +function getNodeCenter(pos: { x: number; y: number }): { + x: number; + y: number; +} { + return { x: pos.x + NODE_WIDTH / 2, y: pos.y + NODE_HEIGHT / 2 }; +} + +export function WorkflowVisual({ active }: StepperVisualProps) { + const [positions, setPositions] = useState< + Record + >(() => + Object.fromEntries( + NODES.map((node) => [node.id, { x: node.x, y: node.y }]), + ), + ); + const [dragging, setDragging] = useState(null); + const dragStartRef = useRef<{ + nodeId: string; + posX: number; + posY: number; + startX: number; + startY: number; + } | null>(null); + + const activeNodes = useWorkflowAnimation(active); + + const handlePointerDown = (nodeId: string, event: React.PointerEvent) => { + event.preventDefault(); + (event.currentTarget as HTMLElement).setPointerCapture(event.pointerId); + const pos = positions[nodeId]; + dragStartRef.current = { + nodeId, + startX: event.clientX, + startY: event.clientY, + posX: pos.x, + posY: pos.y, + }; + setDragging(nodeId); + }; + + const handlePointerMove = (event: React.PointerEvent) => { + if (!dragStartRef.current) return; + const { nodeId, startX, startY, posX, posY } = dragStartRef.current; + const dx = event.clientX - startX; + const dy = event.clientY - startY; + setPositions((prev) => ({ + ...prev, + [nodeId]: { x: posX + dx, y: posY + dy }, + })); + }; + + const handlePointerUp = () => { + dragStartRef.current = null; + setDragging(null); + }; + + return ( + + + + {EDGES.map((edge) => { + const fromPos = positions[edge.from]; + const toPos = positions[edge.to]; + if (!fromPos || !toPos) return null; + return ( + + ); + })} + + {(() => { + const iterPos = positions['iterator']; + const emailPos = positions['email']; + if (!iterPos || !emailPos) return null; + + const startX = iterPos.x + NODE_WIDTH; + const startY = iterPos.y + NODE_HEIGHT / 2; + const horizEnd = startX + 117; + const r = 8; + const vertEnd = emailPos.y; + const emailCenterX = emailPos.x + NODE_WIDTH / 2; + const midY = vertEnd - 28; + const loopColor = STEPPER_BORDER_STRONG; + const ah = 3; + + return ( + + + + + + + ); + })()} + + + {NODES.map((node) => { + const pos = positions[node.id]; + const isActive = activeNodes.has(node.id); + const checkBg = node.dimmed ? COLOR_GRAY_BG : COLOR_TEAL_BG; + const checkColor = node.dimmed ? COLOR_GRAY : COLOR_GREEN; + return ( + handlePointerDown(node.id, event)} + style={{ + cursor: dragging === node.id ? 'grabbing' : 'grab', + left: pos.x, + top: pos.y, + transition: dragging === node.id ? 'none' : 'box-shadow 0.15s', + }} + > + + + + + + {node.type} + {node.badge && ( + {node.badge} + )} + {node.badge && ( + + + + )} + + {node.label} + + + ); + })} + + + iteration 2/3 + + + + ); +} diff --git a/packages/twenty-website-new/src/sections/ProductStepper/visuals/data/DataModel.data.tsx b/packages/twenty-website-new/src/sections/ProductStepper/visuals/data/DataModel.data.tsx new file mode 100644 index 00000000000..3fb7628bc40 --- /dev/null +++ b/packages/twenty-website-new/src/sections/ProductStepper/visuals/data/DataModel.data.tsx @@ -0,0 +1,142 @@ +import type { ReactNode } from 'react'; + +import { + IconApps, + IconBuilding, + IconBuildingSm, + IconTag, + IconTarget, + IconUser, + IconUserScreenSm, + IconUserSm, + IconUsersSm, + IconTargetSm, +} from '../icons/DataModelIcons'; + +const COLOR_INDIGO_BG = '#d9e2fc'; +const COLOR_INDIGO_BORDER = '#c6d4f9'; +const COLOR_PURPLE_BG = '#eddbf9'; +const COLOR_PURPLE_BORDER = '#e3ccf4'; +const COLOR_RED_BG = '#fdd8d8'; +const COLOR_RED_BORDER = '#f9c6c6'; +const COLOR_GREEN_BG = '#d4f4e2'; +const COLOR_GREEN_BORDER = '#b4e7cf'; + +export const BADGE_STANDARD_BG = '#f0f4ff'; +export const BADGE_STANDARD_BORDER = '#e6edfe'; +export const BADGE_STANDARD_TEXT = '#3e63dd'; +export const BADGE_CUSTOM_BG = '#fff1e7'; +export const BADGE_CUSTOM_BORDER = '#ffe8d7'; +export const BADGE_CUSTOM_TEXT = '#f76808'; + +export type EntityDef = { + expandCount: number; + fields: { icon: ReactNode; label: string }[]; + headerIcon: ReactNode; + iconBg: string; + iconBorder: string; + id: string; + isCustom: boolean; + label: string; + meta: string; + x: number; + y: number; +}; + +export type ConnectionDef = { + from: string; + to: string; +}; + +export const ENTITIES: EntityDef[] = [ + { + id: 'workspaces', + label: 'Workspaces', + meta: '22', + isCustom: true, + headerIcon: , + iconBg: COLOR_GREEN_BG, + iconBorder: COLOR_GREEN_BORDER, + fields: [ + { icon: , label: 'Company' }, + { icon: , label: 'Users' }, + ], + expandCount: 21, + x: 40, + y: 40, + }, + { + id: 'companies', + label: 'Companies', + meta: '39', + isCustom: false, + headerIcon: , + iconBg: COLOR_INDIGO_BG, + iconBorder: COLOR_INDIGO_BORDER, + fields: [ + { icon: , label: 'Workspace' }, + { icon: , label: '31 fields' }, + ], + expandCount: 8, + x: 290, + y: 20, + }, + { + id: 'users', + label: 'Users', + meta: '497', + isCustom: true, + headerIcon: , + iconBg: COLOR_PURPLE_BG, + iconBorder: COLOR_PURPLE_BORDER, + fields: [ + { icon: , label: 'People' }, + { icon: , label: 'Workspace' }, + ], + expandCount: 32, + x: 40, + y: 310, + }, + { + id: 'people', + label: 'People', + meta: '648', + isCustom: false, + headerIcon: , + iconBg: COLOR_INDIGO_BG, + iconBorder: COLOR_INDIGO_BORDER, + fields: [ + { icon: , label: 'Company' }, + { icon: , label: 'Users' }, + { icon: , label: 'Opportunity' }, + ], + expandCount: 4, + x: 280, + y: 400, + }, + { + id: 'opportunities', + label: 'Opportunities', + meta: '42', + isCustom: false, + headerIcon: , + iconBg: COLOR_RED_BG, + iconBorder: COLOR_RED_BORDER, + fields: [ + { icon: , label: 'Company' }, + { icon: , label: '12 fields' }, + ], + expandCount: 23, + x: 380, + y: 190, + }, +]; + +export const CONNECTIONS: ConnectionDef[] = [ + { from: 'workspaces', to: 'companies' }, + { from: 'workspaces', to: 'users' }, + { from: 'users', to: 'people' }, + { from: 'companies', to: 'people' }, + { from: 'companies', to: 'opportunities' }, + { from: 'people', to: 'opportunities' }, +]; diff --git a/packages/twenty-website-new/src/sections/ProductStepper/visuals/data/layout.data.ts b/packages/twenty-website-new/src/sections/ProductStepper/visuals/data/layout.data.ts new file mode 100644 index 00000000000..cccb1456731 --- /dev/null +++ b/packages/twenty-website-new/src/sections/ProductStepper/visuals/data/layout.data.ts @@ -0,0 +1,104 @@ +import type { LayoutFieldIconType } from '../icons/LayoutIcons'; + +export type FieldDef = { + icon: LayoutFieldIconType; + id: string; + label: string; + section: string; + type: string; + visible: boolean; +}; + +export const FIELDS: FieldDef[] = [ + { + id: 'url', + icon: 'link', + label: 'URL', + type: 'Link', + section: 'General', + visible: true, + }, + { + id: 'account-owner', + icon: 'user', + label: 'Account Owner', + type: 'Relation', + section: 'General', + visible: true, + }, + { + id: 'revenue', + icon: 'money', + label: 'Revenue', + type: 'Currency', + section: 'General', + visible: true, + }, + { + id: 'icp', + icon: 'target', + label: 'ICP', + type: 'Boolean', + section: 'Additional', + visible: false, + }, + { + id: 'employees', + icon: 'users', + label: 'Employees', + type: 'Number', + section: 'Other', + visible: true, + }, + { + id: 'address', + icon: 'map', + label: 'Address', + type: 'Address', + section: 'Other', + visible: true, + }, + { + id: 'creation-date', + icon: 'calendar', + label: 'Creation date', + type: 'Date & Time', + section: 'Other', + visible: true, + }, +]; + +export const NAV = [ + { icon: 'building', label: 'Companies', active: true, bg: '#d9e2fc' }, + { icon: 'user', label: 'People', active: false, bg: '#d9e2fc' }, + { icon: 'target', label: 'Opportunities', active: false, bg: '#fdd8d8' }, + { icon: 'checkbox', label: 'Tasks', active: false, bg: '#c7ebe5' }, + { icon: 'notes', label: 'Notes', active: false, bg: '#c7ebe5' }, + { + icon: 'letter-S', + label: 'Sales Dashboard', + active: false, + bg: '#fef2a4', + suffix: 'Dashboard', + }, + { + icon: 'automation', + label: 'Workflows', + active: false, + bg: '#ffdcc3', + folder: true, + children: [ + { icon: 'automation', label: 'Workflows', bg: '#ebebeb' }, + { icon: 'play', label: 'Workflows runs', bg: '#ebebeb' }, + { icon: 'versions', label: 'Workflows versions', bg: '#ebebeb' }, + ], + }, + { icon: 'ai', label: 'Claude', active: false, bg: '#ebebeb' }, + { + icon: 'stripe-S', + label: 'Stripe', + active: false, + bg: '#ebebeb', + folder: true, + }, +] as const; diff --git a/packages/twenty-website-new/src/sections/ProductStepper/visuals/data/workflow.data.ts b/packages/twenty-website-new/src/sections/ProductStepper/visuals/data/workflow.data.ts new file mode 100644 index 00000000000..7acea0e7189 --- /dev/null +++ b/packages/twenty-website-new/src/sections/ProductStepper/visuals/data/workflow.data.ts @@ -0,0 +1,111 @@ +import type { WorkflowIconName } from '../icons/WorkflowIcons'; + +export const COLOR_GREEN = '#30a46c'; +const COLOR_AMBER = '#946800'; +export const COLOR_GRAY = '#999'; + +export const COLOR_TEAL_BG = '#e7f9f5'; +export const COLOR_GRAY_BG = '#f9f9f9'; + +export type NodeDef = { + badge?: string; + dimmed?: boolean; + icon: WorkflowIconName; + id: string; + label: string; + labelColor: string; + type: string; + x: number; + y: number; +}; + +export type EdgeDef = { + from: string; + to: string; +}; + +const TRUNK_X = 55; +const RIGHT_X = 200; +const LEFT_X = 5; + +export const NODES: NodeDef[] = [ + { + id: 'trigger', + type: 'Trigger', + label: 'Record is Created', + icon: 'playlist-add', + labelColor: COLOR_GREEN, + x: TRUNK_X, + y: 16, + badge: '1', + }, + { + id: 'search', + type: 'Action', + label: 'Search Records', + icon: 'search', + labelColor: COLOR_GREEN, + x: TRUNK_X, + y: 92, + badge: '1', + }, + { + id: 'iterator', + type: 'Flow', + label: 'Iterator', + icon: 'repeat', + labelColor: COLOR_AMBER, + x: TRUNK_X, + y: 168, + }, + { + id: 'email', + type: 'Action', + label: 'Send Email', + icon: 'send', + labelColor: COLOR_AMBER, + x: RIGHT_X, + y: 280, + }, + { + id: 'update', + type: 'Action', + label: 'Update Record', + icon: 'reload', + labelColor: COLOR_GRAY, + x: LEFT_X, + y: 340, + badge: '3', + dimmed: true, + }, + { + id: 'create', + type: 'Action', + label: 'Create Record', + icon: 'plus', + labelColor: COLOR_GREEN, + x: RIGHT_X - 5, + y: 400, + badge: '1', + }, +]; + +export const EDGES: EdgeDef[] = [ + { from: 'trigger', to: 'search' }, + { from: 'search', to: 'iterator' }, + { from: 'iterator', to: 'update' }, + { from: 'email', to: 'create' }, +]; + +export const ANIMATION_SEQUENCE = [ + 'trigger', + 'search', + 'iterator', + 'email', + 'update', + 'create', +]; + +export const STEP_INTERVAL_MS = 800; +export const NODE_WIDTH = 170; +export const NODE_HEIGHT = 48; diff --git a/packages/twenty-website-new/src/sections/ProductStepper/visuals/icons/DataModelIcons.tsx b/packages/twenty-website-new/src/sections/ProductStepper/visuals/icons/DataModelIcons.tsx new file mode 100644 index 00000000000..2b75639f734 --- /dev/null +++ b/packages/twenty-website-new/src/sections/ProductStepper/visuals/icons/DataModelIcons.tsx @@ -0,0 +1,196 @@ +export const IconBuilding = () => ( + + + + + + + + + +); + +export const IconUser = () => ( + + + + +); + +export const IconApps = () => ( + + + + + + +); + +export const IconTag = () => ( + + + + +); + +export const IconTarget = () => ( + + + + + + + +); + +export const IconChevronDown = () => ( + + + + +); + +export const IconBuildingSm = () => ( + + + + + + + + + +); + +export const IconUsersSm = () => ( + + + + + + +); + +export const IconTargetSm = () => ( + + + + + + + +); + +export const IconUserScreenSm = () => ( + + + + + +); + +export const IconUserSm = () => ( + + + + +); diff --git a/packages/twenty-website-new/src/sections/ProductStepper/visuals/icons/LayoutIcons.tsx b/packages/twenty-website-new/src/sections/ProductStepper/visuals/icons/LayoutIcons.tsx new file mode 100644 index 00000000000..4835186fbe5 --- /dev/null +++ b/packages/twenty-website-new/src/sections/ProductStepper/visuals/icons/LayoutIcons.tsx @@ -0,0 +1,450 @@ +export type LayoutFieldIconType = + | 'link' + | 'user' + | 'money' + | 'target' + | 'users' + | 'map' + | 'calendar'; + +export function FieldIcon({ type }: { type: LayoutFieldIconType }) { + const c = '#666'; + const z = 10; + switch (type) { + case 'link': + return ( + + + + ); + case 'user': + return ( + + + + + ); + case 'money': + return ( + + + + ); + case 'target': + return ( + + + + + + ); + case 'users': + return ( + + + + + + + ); + case 'map': + return ( + + + + + ); + case 'calendar': + return ( + + + + + ); + default: + return null; + } +} + +export function NavSvgIcon({ type }: { type: string }) { + const c = '#555'; + const z = 7; + switch (type) { + case 'building': + return ( + + + + ); + case 'user': + return ( + + + + + ); + case 'target': + return ( + + + + + + ); + case 'checkbox': + return ( + + + + + ); + case 'notes': + return ( + + + + + ); + case 'automation': + return ( + + + + + ); + case 'play': + return ( + + + + ); + case 'versions': + return ( + + + + + ); + case 'ai': + return ( + + + + ); + case 'letter-S': + return ( + + + S + + + ); + case 'stripe-S': + return ( + + + S + + + ); + default: + return null; + } +} + +export function EyeIcon({ visible }: { visible: boolean }) { + if (visible) + return ( + + + + + ); + return ( + + + + + ); +} + +export function ChevLeft() { + return ( + + + + ); +} + +export function ChevDown() { + return ( + + + + ); +} + +export function DotsV() { + return ( + + + + + + ); +} + +export function DotsVW() { + return ( + + + + + + ); +} + +export function GripV() { + return ( + + + + + + + + + ); +} + +export function PaintSvg() { + return ( + + + + + ); +} + +export function CheckSvg() { + return ( + + + + ); +} + +export function ListSvg() { + return ( + + + + ); +} + +export function SparkSvg() { + return ( + + + + ); +} + +export function NewSecSvg() { + return ( + + + + ); +} + +export function PlusSvg() { + return ( + + + + ); +} diff --git a/packages/twenty-website-new/src/sections/ProductStepper/visuals/icons/WorkflowIcons.tsx b/packages/twenty-website-new/src/sections/ProductStepper/visuals/icons/WorkflowIcons.tsx new file mode 100644 index 00000000000..7cb94340f5e --- /dev/null +++ b/packages/twenty-website-new/src/sections/ProductStepper/visuals/icons/WorkflowIcons.tsx @@ -0,0 +1,130 @@ +export type WorkflowIconName = + | 'playlist-add' + | 'search' + | 'repeat' + | 'send' + | 'reload' + | 'plus'; + +const COLOR_AMBER = '#946800'; + +export function NodeIcon({ name }: { name: WorkflowIconName }) { + switch (name) { + case 'playlist-add': + return ( + + + + + + + + ); + case 'search': + return ( + + + + + ); + case 'repeat': + return ( + + + + + ); + case 'send': + return ( + + + + + ); + case 'reload': + return ( + + + + + ); + case 'plus': + return ( + + + + + ); + } +} + +export function CheckIcon({ color }: { color: string }) { + return ( + + + + ); +} diff --git a/packages/twenty-website-new/src/sections/ProductStepper/visuals/layout-styles.ts b/packages/twenty-website-new/src/sections/ProductStepper/visuals/layout-styles.ts new file mode 100644 index 00000000000..4d577d792e3 --- /dev/null +++ b/packages/twenty-website-new/src/sections/ProductStepper/visuals/layout-styles.ts @@ -0,0 +1,486 @@ +import { styled } from '@linaria/react'; + +import { + STEPPER_BORDER_LIGHT, + STEPPER_BORDER_MEDIUM, + STEPPER_BORDER_STRONG, + STEPPER_BORDER_SUBTLE, + STEPPER_FONT, + STEPPER_TEXT, + STEPPER_TEXT_SECONDARY, + STEPPER_TEXT_TERTIARY, + STEPPER_TINT, +} from './stepper-visual-tokens'; + +const ACCENT = '#3e63dd'; +const GLASS = 'rgba(255,255,255,0.9)'; +const R = '3px'; + +export const Canvas = styled.div` + font-family: ${STEPPER_FONT}; + height: 100%; + position: relative; + width: 100%; +`; + +export const MainCard = styled.div` + background: white; + border-radius: 4px; + height: 84%; + left: 16%; + overflow: hidden; + position: absolute; + top: 8%; + width: 72%; +`; + +export const BlueHeader = styled.div` + align-items: center; + background: ${ACCENT}; + display: flex; + height: 32px; + justify-content: space-between; + padding: 0 10px; + width: 100%; +`; + +export const HeaderLeft = styled.span` + color: white; + display: flex; +`; + +export const HeaderCenter = styled.div` + align-items: center; + display: flex; + gap: 4px; +`; + +export const HeaderTitle = styled.span` + color: white; + font-size: 10px; + font-weight: 500; +`; + +export const HeaderSave = styled.span` + align-items: center; + border: 1px solid rgba(255, 255, 255, 0.7); + border-radius: ${R}; + color: white; + display: flex; + font-size: 8px; + font-weight: 500; + gap: 3px; + padding: 2px 8px; +`; + +export const WidgetPanel = styled.div` + backdrop-filter: blur(5px); + background: ${GLASS}; + border: 0.8px solid ${ACCENT}; + border-radius: ${R}; + left: 50%; + max-height: 36%; + overflow: hidden; + padding: 6px; + position: absolute; + top: 20%; + width: 38%; +`; + +export const WidgetInner = styled.div` + display: flex; + flex-direction: column; + gap: 5px; +`; + +export const WidgetTitle = styled.div` + color: ${STEPPER_TEXT}; + font-size: 10px; + font-weight: 600; + padding: 2px 3px; +`; + +export const WSectionLabel = styled.div` + color: ${STEPPER_TEXT_TERTIARY}; + font-size: 7px; + font-weight: 600; + padding: 0 3px; +`; + +export const WRow = styled.div` + align-items: center; + display: flex; + gap: 3px; + min-height: 16px; + padding: 1px 3px; +`; + +export const WIcon = styled.span` + align-items: center; + color: ${STEPPER_TEXT_TERTIARY}; + display: flex; + flex-shrink: 0; + height: 10px; + justify-content: center; + width: 10px; +`; + +export const WLabel = styled.span` + color: ${STEPPER_TEXT_TERTIARY}; + flex-shrink: 0; + font-size: 8px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + width: 58px; +`; + +export const WValue = styled.span` + color: ${STEPPER_TEXT}; + flex: 1; + font-size: 8px; + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +`; + +export const WChip = styled.span` + background: rgba(0, 0, 0, 0.02); + border: 0.5px solid ${STEPPER_BORDER_STRONG}; + border-radius: 50px; + color: ${STEPPER_TEXT}; + font-size: 8px; + padding: 1px 6px; +`; + +export const NavPanel = styled.div` + backdrop-filter: blur(5px); + background: ${GLASS}; + border: 0.8px solid ${ACCENT}; + border-radius: ${R}; + left: 10%; + max-height: 68%; + overflow-y: auto; + padding: 8px; + position: absolute; + top: 17%; + width: 30%; +`; + +export const NavSectionLabel = styled.div` + color: ${STEPPER_TEXT_TERTIARY}; + font-size: 8px; + font-weight: 600; + padding: 4px 3px; +`; + +export const NavItem = styled.div<{ $active: boolean }>` + align-items: center; + background: ${({ $active }) => ($active ? STEPPER_TINT : 'transparent')}; + border-radius: ${R}; + color: ${({ $active }) => ($active ? STEPPER_TEXT : STEPPER_TEXT_SECONDARY)}; + cursor: pointer; + display: flex; + font-size: 9px; + font-weight: 500; + gap: 5px; + padding: 5px 4px; +`; + +export const NavIconBox = styled.span<{ $bg: string }>` + align-items: center; + background: ${({ $bg }) => $bg}; + border: 0.5px solid ${STEPPER_BORDER_STRONG}; + border-radius: 3px; + display: flex; + flex-shrink: 0; + height: 13px; + justify-content: center; + width: 13px; +`; + +export const NavSubItem = styled.div` + align-items: center; + color: ${STEPPER_TEXT_SECONDARY}; + display: flex; + font-size: 8px; + font-weight: 500; + gap: 4px; + padding: 4px 4px 4px 16px; +`; + +export const NavBreadcrumb = styled.span` + border-bottom: 0.5px solid ${STEPPER_BORDER_STRONG}; + border-left: 0.5px solid ${STEPPER_BORDER_STRONG}; + border-radius: 0 0 0 2px; + flex-shrink: 0; + height: 8px; + margin-left: -6px; + width: 4px; +`; + +export const NavSuffix = styled.span` + color: ${STEPPER_TEXT_TERTIARY}; + font-size: 6px; +`; + +export const NavChevron = styled.span` + color: ${STEPPER_TEXT_TERTIARY}; + font-size: 6px; + margin-left: auto; +`; + +export const ActionsBar = styled.div` + backdrop-filter: blur(5px); + background: ${GLASS}; + border: 0.8px solid ${ACCENT}; + border-radius: ${R}; + display: flex; + gap: 5px; + left: 54%; + padding: 5px 6px; + position: absolute; + top: 14%; + z-index: 3; +`; + +export const ActionBtn = styled.span` + border: 0.8px solid ${STEPPER_BORDER_SUBTLE}; + border-radius: ${R}; + color: ${STEPPER_TEXT_SECONDARY}; + font-size: 8px; + font-weight: 500; + padding: 3px 6px; +`; + +export const RightPanel = styled.div` + backdrop-filter: blur(5px); + background: ${GLASS}; + border: 0.8px solid #4a38f5; + border-radius: ${R}; + bottom: 3%; + display: flex; + flex-direction: column; + left: 42%; + overflow: hidden; + position: absolute; + top: 40%; + width: 54%; + z-index: 4; +`; + +export const RPHeader = styled.div` + align-items: center; + border-bottom: 0.8px solid ${STEPPER_BORDER_MEDIUM}; + display: flex; + flex-shrink: 0; + gap: 2px; + padding: 6px; +`; + +export const RPBackBtn = styled.span` + align-items: center; + color: ${STEPPER_TEXT_TERTIARY}; + cursor: pointer; + display: flex; + height: 16px; + justify-content: center; + width: 16px; +`; + +export const RPIconBox = styled.span` + align-items: center; + background: ${STEPPER_TINT}; + border-radius: 3px; + display: flex; + height: 16px; + justify-content: center; + width: 16px; +`; + +export const RPTitleGroup = styled.div` + align-items: baseline; + display: flex; + flex: 1; + gap: 3px; + min-width: 0; +`; + +export const RPTitleBold = styled.span` + color: ${STEPPER_TEXT}; + font-size: 9px; + font-weight: 600; +`; + +export const RPTitleSub = styled.span` + color: ${STEPPER_TEXT_TERTIARY}; + font-size: 8px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +`; + +export const RPSubBar = styled.div` + align-items: center; + border-bottom: 0.8px solid ${STEPPER_BORDER_LIGHT}; + display: flex; + flex-shrink: 0; + gap: 3px; + padding: 5px 6px; +`; + +export const RPSubLabel = styled.span` + color: ${STEPPER_TEXT_TERTIARY}; + font-size: 8px; + font-weight: 500; +`; + +export const RPFields = styled.div` + display: flex; + flex-direction: column; + gap: 1px; + overflow-y: auto; + padding: 6px; +`; + +export const RPSectionRow = styled.div` + align-items: center; + display: flex; + gap: 5px; + padding: 3px; +`; + +export const RPSectionName = styled.span` + color: ${STEPPER_TEXT_TERTIARY}; + flex: 1; + font-size: 7px; + font-weight: 600; +`; + +export const RPEditable = styled.div` + align-items: center; + background: white; + border: 1px solid ${ACCENT}; + border-radius: ${R}; + display: flex; + gap: 4px; + margin: 2px 0 4px; + padding: 4px 6px; +`; + +export const RPEditText = styled.span` + color: ${STEPPER_TEXT}; + flex: 1; + font-size: 9px; +`; + +export const RPDoneBtn = styled.span` + background: ${ACCENT}; + border-radius: 3px; + color: white; + cursor: pointer; + font-size: 7px; + font-weight: 600; + padding: 2px 8px; +`; + +export const RPFieldRow = styled.div<{ $dragging: boolean }>` + align-items: center; + background: ${({ $dragging }) => + $dragging ? 'rgba(59,130,246,0.04)' : 'transparent'}; + border-radius: ${R}; + cursor: grab; + display: flex; + gap: 5px; + padding: 3px; + touch-action: none; + + &:active { + cursor: grabbing; + } +`; + +export const RPFieldIconBox = styled.span` + align-items: center; + background: ${STEPPER_TINT}; + border-radius: 3px; + display: flex; + flex-shrink: 0; + height: 16px; + justify-content: center; + width: 16px; +`; + +export const RPFieldLabels = styled.div` + align-items: baseline; + display: flex; + flex: 1; + font-size: 8px; + gap: 3px; + min-width: 0; + overflow: hidden; + white-space: nowrap; +`; + +export const RPFieldName = styled.span` + color: ${STEPPER_TEXT_SECONDARY}; + flex-shrink: 0; +`; + +export const RPFieldDot = styled.span` + color: ${STEPPER_TEXT_TERTIARY}; +`; + +export const RPFieldType = styled.span` + color: ${STEPPER_TEXT_TERTIARY}; + overflow: hidden; + text-overflow: ellipsis; +`; + +export const RPActionBtn = styled.span` + cursor: pointer; + display: flex; +`; + +export const RPAddSection = styled.div` + align-items: center; + cursor: pointer; + display: flex; + gap: 5px; + padding: 3px; +`; + +export const RPAddIconBox = styled.span` + align-items: center; + background: ${STEPPER_TINT}; + border-radius: 3px; + display: flex; + height: 16px; + justify-content: center; + width: 16px; +`; + +export const RPAddText = styled.span` + color: ${STEPPER_TEXT_SECONDARY}; + font-size: 8px; +`; + +export const RPNewFields = styled.div` + align-items: center; + border-top: 0.8px solid ${STEPPER_BORDER_MEDIUM}; + display: flex; + gap: 5px; + margin-top: 4px; + padding: 6px 3px 3px; +`; + +export const RPNewTitle = styled.span` + color: ${STEPPER_TEXT_SECONDARY}; + font-size: 8px; +`; + +export const RPNewDesc = styled.span` + color: ${STEPPER_TEXT_TERTIARY}; + font-size: 7px; +`; diff --git a/packages/twenty-website-new/src/sections/ProductStepper/visuals/stepper-visual-tokens.ts b/packages/twenty-website-new/src/sections/ProductStepper/visuals/stepper-visual-tokens.ts new file mode 100644 index 00000000000..dd06a8c35b4 --- /dev/null +++ b/packages/twenty-website-new/src/sections/ProductStepper/visuals/stepper-visual-tokens.ts @@ -0,0 +1,19 @@ +export const STEPPER_BG = '#ffffff'; +export const STEPPER_CARD_BG = '#fcfcfc'; + +export const STEPPER_TEXT = '#333'; +export const STEPPER_TEXT_SECONDARY = '#6b7280'; +export const STEPPER_TEXT_TERTIARY = '#9ca3af'; +export const STEPPER_TEXT_MUTED = '#666'; + +export const STEPPER_BORDER_MEDIUM = '#ebebeb'; +export const STEPPER_BORDER_STRONG = '#d6d6d6'; +export const STEPPER_BORDER_LIGHT = '#f1f1f1'; +export const STEPPER_BORDER_SUBTLE = 'rgba(0, 0, 0, 0.08)'; + +export const STEPPER_FONT = "'Inter', sans-serif"; +export const STEPPER_SHADOW_SM = '0 1px 6px rgba(0, 0, 0, 0.05)'; +export const STEPPER_TINT = 'rgba(0, 0, 0, 0.04)'; + +export const STEPPER_HEADER_BG = '#d9e2fc'; +export const STEPPER_HEADER_BORDER = '#c6d4f9'; diff --git a/packages/twenty-website-new/src/sections/ProductStepper/visuals/use-workflow-animation.ts b/packages/twenty-website-new/src/sections/ProductStepper/visuals/use-workflow-animation.ts new file mode 100644 index 00000000000..0e8c43e6c6b --- /dev/null +++ b/packages/twenty-website-new/src/sections/ProductStepper/visuals/use-workflow-animation.ts @@ -0,0 +1,39 @@ +import { useEffect, useRef, useState } from 'react'; + +import { ANIMATION_SEQUENCE, STEP_INTERVAL_MS } from './data/workflow.data'; + +export function useWorkflowAnimation(active: boolean) { + const [activeNodes, setActiveNodes] = useState>(new Set()); + const intervalRef = useRef | null>(null); + const stepRef = useRef(0); + + useEffect(() => { + if (!active) { + setActiveNodes(new Set()); + stepRef.current = 0; + if (intervalRef.current) { + clearInterval(intervalRef.current); + intervalRef.current = null; + } + return; + } + + intervalRef.current = setInterval(() => { + stepRef.current += 1; + if (stepRef.current > ANIMATION_SEQUENCE.length) { + stepRef.current = 0; + setActiveNodes(new Set()); + } else { + setActiveNodes(new Set(ANIMATION_SEQUENCE.slice(0, stepRef.current))); + } + }, STEP_INTERVAL_MS); + + return () => { + if (intervalRef.current) { + clearInterval(intervalRef.current); + } + }; + }, [active]); + + return activeNodes; +}