Split website stepper into home-stepper and product-stepper sections (#22149)
Reorganizes the flat `sections/stepper/` (30 files — two distinct steppers plus shared code) into two product-feature-style sections, and moves the shared code to the shared layers. - **`sections/home-stepper/`** — the home-page stepper. Renamed `Stepper` → `HomeStepper` (and the home components → `HomeStepperLottie` / `HomeStepperSteps` / `HomeStepperVisualFrame`) for symmetry with `ProductStepper`. Shell at the root + `components/`/`data/`/`utils/` + barrel. - **`sections/product-stepper/`** — the product-page stepper, same structure. The 3 files both steppers shared can't live in a shared *section* — `check-conventions` forbids a section importing another section. So they moved to the shared layers: - `StepperProgressRail`, `StepperSwipeDeck` → `ui/` - `useBreakpointStepSync` → `platform/motion` Both consumer pages repointed (`@/sections/home-stepper`, `@/sections/product-stepper`); the row-gap allowlist in `check-conventions.mjs` updated to the new paths; explanatory comments stripped across the moved files (CSS-in-template comments and `'use client'` kept). Pure reorganization — no behavior change. typecheck + lint + build all green.
This commit is contained in:
@@ -102,8 +102,8 @@ const ROW_GAP_MULTI_AXIS_FILES = new Set([
|
||||
'sections/pricing-plans/PricingBoard.tsx',
|
||||
'sections/problem/Problem.tsx',
|
||||
'sections/releases-feed/ReleasesFeed.tsx',
|
||||
'sections/stepper/ProductStepper.tsx',
|
||||
'sections/stepper/Stepper.tsx',
|
||||
'sections/product-stepper/ProductStepper.tsx',
|
||||
'sections/home-stepper/HomeStepper.tsx',
|
||||
'sections/testimonials/PartnerTestimonialsCarousel.tsx',
|
||||
'sections/testimonials/TestimonialsCarousel.tsx',
|
||||
'sections/trusted-by/TrustedBy.tsx',
|
||||
|
||||
@@ -11,7 +11,7 @@ import { HomeHero } from '@/sections/home-hero';
|
||||
import { Menu } from '@/sections/menu';
|
||||
import { Testimonials } from '@/sections/testimonials';
|
||||
import { Problem } from '@/sections/problem';
|
||||
import { Stepper } from '@/sections/stepper';
|
||||
import { HomeStepper } from '@/sections/home-stepper';
|
||||
import { ThreeCards } from '@/sections/three-cards';
|
||||
import { TrustedBy } from '@/sections/trusted-by';
|
||||
|
||||
@@ -35,7 +35,7 @@ export default async function HomePage({
|
||||
<TrustedBy />
|
||||
<Problem />
|
||||
<ThreeCards />
|
||||
<Stepper />
|
||||
<HomeStepper />
|
||||
<FeatureCards />
|
||||
<Helped />
|
||||
<Testimonials />
|
||||
|
||||
@@ -15,7 +15,7 @@ import { Menu } from '@/sections/menu';
|
||||
import { ProductDemo } from '@/sections/product-demo';
|
||||
import { ProductFeature } from '@/sections/product-feature';
|
||||
import { ProductHero } from '@/sections/product-hero';
|
||||
import { ProductStepper } from '@/sections/stepper';
|
||||
import { ProductStepper } from '@/sections/product-stepper';
|
||||
import { ProductThreeCards } from '@/sections/three-cards';
|
||||
import { TrustedBy } from '@/sections/trusted-by';
|
||||
|
||||
|
||||
@@ -17,3 +17,4 @@ export { useScheduledOnScroll } from './use-scheduled-on-scroll';
|
||||
export { usePrefersReducedMotion } from './use-prefers-reduced-motion';
|
||||
export { useScrollProgress } from './use-scroll-progress';
|
||||
export { scheduleIdleTask } from './schedule-idle-task';
|
||||
export { useBreakpointStepSync } from './use-breakpoint-step-sync';
|
||||
|
||||
-2
@@ -2,8 +2,6 @@
|
||||
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
|
||||
// Keeps the mobile swipe index continuous with the desktop scroll position
|
||||
// when the viewport crosses the breakpoint mid-interaction.
|
||||
export function useBreakpointStepSync(
|
||||
isMdUp: boolean,
|
||||
scrollProgress: number,
|
||||
+14
-11
@@ -3,15 +3,18 @@
|
||||
import { styled } from '@linaria/react';
|
||||
import { useRef, useState } from 'react';
|
||||
|
||||
import { ScrollProgressEffect, useMediaQuery } from '@/platform/motion';
|
||||
import {
|
||||
ScrollProgressEffect,
|
||||
useBreakpointStepSync,
|
||||
useMediaQuery,
|
||||
} from '@/platform/motion';
|
||||
import { BREAKPOINT_PX, mediaUp, spacing } from '@/tokens';
|
||||
import { SectionShell } from '@/ui';
|
||||
|
||||
import { StepperLottie } from './StepperLottie';
|
||||
import { StepperSteps } from './StepperSteps';
|
||||
import { StepperVisualFrame } from './StepperVisualFrame';
|
||||
import { STEPPER_STEPS } from './stepper.data';
|
||||
import { useBreakpointStepSync } from './use-breakpoint-step-sync';
|
||||
import { HomeStepperLottie } from './components/HomeStepperLottie';
|
||||
import { HomeStepperSteps } from './components/HomeStepperSteps';
|
||||
import { HomeStepperVisualFrame } from './components/HomeStepperVisualFrame';
|
||||
import { STEPPER_STEPS } from './data/stepper.data';
|
||||
|
||||
const ScrollStage = styled.div`
|
||||
display: grid;
|
||||
@@ -59,7 +62,7 @@ const VisualMeasure = styled.div`
|
||||
}
|
||||
`;
|
||||
|
||||
export function Stepper() {
|
||||
export function HomeStepper() {
|
||||
const scrollContainerRef = useRef<HTMLDivElement>(null);
|
||||
const isMdUp = useMediaQuery(`(min-width: ${BREAKPOINT_PX.md}px)`);
|
||||
const [scrollProgress, setScrollProgress] = useState(0);
|
||||
@@ -82,7 +85,7 @@ export function Stepper() {
|
||||
scrollContainerRef={scrollContainerRef}
|
||||
/>
|
||||
<ScrollStage ref={scrollContainerRef}>
|
||||
<StepperSteps
|
||||
<HomeStepperSteps
|
||||
activeStepIndex={activeStepIndex}
|
||||
layoutMode={isMdUp ? 'scroll' : 'swipe'}
|
||||
localProgress={localProgress}
|
||||
@@ -91,9 +94,9 @@ export function Stepper() {
|
||||
/>
|
||||
<VisualColumn>
|
||||
<VisualMeasure>
|
||||
<StepperVisualFrame>
|
||||
<StepperLottie scrollProgress={visualScrollProgress} />
|
||||
</StepperVisualFrame>
|
||||
<HomeStepperVisualFrame>
|
||||
<HomeStepperLottie scrollProgress={visualScrollProgress} />
|
||||
</HomeStepperVisualFrame>
|
||||
</VisualMeasure>
|
||||
</VisualColumn>
|
||||
</ScrollStage>
|
||||
+6
-2
@@ -5,7 +5,7 @@ import { styled } from '@linaria/react';
|
||||
|
||||
import { spacing } from '@/tokens';
|
||||
|
||||
import { useDotLottieScrollSync } from './use-dot-lottie-scroll-sync';
|
||||
import { useDotLottieScrollSync } from '../utils/use-dot-lottie-scroll-sync';
|
||||
|
||||
const LOTTIE_SRC = '/lottie/stepper/stepper.lottie';
|
||||
|
||||
@@ -18,7 +18,11 @@ const LottieSlot = styled.div`
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
export function StepperLottie({ scrollProgress }: { scrollProgress: number }) {
|
||||
export function HomeStepperLottie({
|
||||
scrollProgress,
|
||||
}: {
|
||||
scrollProgress: number;
|
||||
}) {
|
||||
const playerRefCallback = useDotLottieScrollSync(scrollProgress);
|
||||
|
||||
return (
|
||||
+5
-10
@@ -5,15 +5,10 @@ import { styled } from '@linaria/react';
|
||||
import { type CSSProperties } from 'react';
|
||||
|
||||
import { mediaUp, spacing } from '@/tokens';
|
||||
import { Body, Heading } from '@/ui';
|
||||
import { Body, Heading, StepperProgressRail, StepperSwipeDeck } from '@/ui';
|
||||
|
||||
import { StepperProgressRail } from './StepperProgressRail';
|
||||
import { StepperSwipeDeck } from './StepperSwipeDeck';
|
||||
import { type StepperStep } from './stepper.data';
|
||||
import { type StepperStep } from '../data/stepper.data';
|
||||
|
||||
// Each step holds fully visible for most of its scroll chapter, then hands
|
||||
// off to the next over the remaining fraction (values ported from the
|
||||
// authored lottie chapter boundaries).
|
||||
const HOLD_FRACTIONS = [240 / 285, (880 - 285) / (925 - 285), 1];
|
||||
|
||||
const StepsRoot = styled.div`
|
||||
@@ -119,7 +114,7 @@ const computeStepStyle = (
|
||||
return { opacity: 0, transform: 'translateY(40vh)' };
|
||||
};
|
||||
|
||||
export type StepperStepsProps = {
|
||||
export type HomeStepperStepsProps = {
|
||||
activeStepIndex: number;
|
||||
layoutMode: 'scroll' | 'swipe';
|
||||
localProgress: number;
|
||||
@@ -127,13 +122,13 @@ export type StepperStepsProps = {
|
||||
steps: readonly StepperStep[];
|
||||
};
|
||||
|
||||
export function StepperSteps({
|
||||
export function HomeStepperSteps({
|
||||
activeStepIndex,
|
||||
layoutMode,
|
||||
localProgress,
|
||||
onMobileStepIndexChange,
|
||||
steps,
|
||||
}: StepperStepsProps) {
|
||||
}: HomeStepperStepsProps) {
|
||||
const { i18n } = useLingui();
|
||||
|
||||
const renderStepContent = (step: StepperStep) => (
|
||||
+2
-5
@@ -1,14 +1,11 @@
|
||||
import { styled } from '@linaria/react';
|
||||
import { HalftoneImageBackdrop } from '@/platform/visuals/rigs/HalftoneImageBackdrop';
|
||||
|
||||
import { STEPPER_BACKDROP } from './stepper-backdrop-config';
|
||||
import { STEPPER_BACKDROP } from '../data/stepper-backdrop-config';
|
||||
import { type ReactNode } from 'react';
|
||||
|
||||
import { color } from '@/tokens';
|
||||
|
||||
// The notched frame that hosts the stepper visual: halftone backdrop on
|
||||
// the graphite stage behind, content above, hairline border tracing the
|
||||
// same path.
|
||||
const FRAME_MASK_PATH =
|
||||
'M4 0H668a4 4 0 0 1 4 4V701a4 4 0 0 1-4 4H4a4 4 0 0 1-4-4V499L28 462V215L0 178V4a4 4 0 0 1 4-4Z';
|
||||
|
||||
@@ -48,7 +45,7 @@ const FrameBorder = styled.svg`
|
||||
z-index: 2;
|
||||
`;
|
||||
|
||||
export function StepperVisualFrame({ children }: { children: ReactNode }) {
|
||||
export function HomeStepperVisualFrame({ children }: { children: ReactNode }) {
|
||||
return (
|
||||
<FrameRoot data-stepper-visual-pointer-root="">
|
||||
<MaskedBackdrop aria-hidden data-illustration="stepper-backdrop">
|
||||
-4
@@ -2,10 +2,6 @@ import { paletteColorNumber } from '@/tokens';
|
||||
|
||||
import { type HalftoneImageBackdropProps } from '@/platform/visuals/rigs/HalftoneImageBackdrop';
|
||||
|
||||
// The authored stepper backdrop: fog dashes over the worker image inside
|
||||
// the graphite frame, brightening to white where the pointer hovers (the
|
||||
// hover field tracks the whole visual area, not just the canvas). The old
|
||||
// hook skips the visual entirely under reduced motion.
|
||||
export const STEPPER_BACKDROP: Pick<
|
||||
HalftoneImageBackdropProps,
|
||||
'imageUrl' | 'settings'
|
||||
@@ -0,0 +1 @@
|
||||
export { HomeStepper } from './HomeStepper';
|
||||
-3
@@ -1,6 +1,3 @@
|
||||
// The lottie timeline is authored in three chapters; these frame numbers
|
||||
// are tied to the asset (stepper.lottie, 1439 frames) and must change
|
||||
// together with it.
|
||||
const STEP_COUNT = 3;
|
||||
const STEP_1_TRANSITION_END = 285;
|
||||
const STEP_2_TRANSITION_END = 925;
|
||||
+8
-8
@@ -3,18 +3,18 @@
|
||||
import { styled } from '@linaria/react';
|
||||
import { useRef, useState } from 'react';
|
||||
|
||||
import { ScrollProgressEffect, useMediaQuery } from '@/platform/motion';
|
||||
import {
|
||||
ScrollProgressEffect,
|
||||
useBreakpointStepSync,
|
||||
useMediaQuery,
|
||||
} from '@/platform/motion';
|
||||
import { BREAKPOINT_PX, mediaUp, spacing } from '@/tokens';
|
||||
import { SectionShell } from '@/ui';
|
||||
|
||||
import { ProductStepperContent } from './ProductStepperContent';
|
||||
import { PRODUCT_STEPPER_STEPS } from './product-stepper-data';
|
||||
import { ProductStepperVisual } from './ProductStepperVisual';
|
||||
import { useBreakpointStepSync } from './use-breakpoint-step-sync';
|
||||
import { ProductStepperContent } from './components/ProductStepperContent';
|
||||
import { PRODUCT_STEPPER_STEPS } from './data/product-stepper-data';
|
||||
import { ProductStepperVisual } from './components/ProductStepperVisual';
|
||||
|
||||
// The 300vh scroll track the sticky content and visual ride (the old
|
||||
// site put the height on the section; the rhythm shell owns padding, so
|
||||
// the track is the inner stage here).
|
||||
const ScrollTrack = styled.div`
|
||||
${mediaUp('md')} {
|
||||
height: 300vh;
|
||||
-2
@@ -1,5 +1,3 @@
|
||||
// The data-model canvas's mini glyphs (authored scene artwork, verbatim).
|
||||
// Field/header icons are looked up by key so the scene data stays pure.
|
||||
export type DataModelFieldIcon =
|
||||
| 'apps'
|
||||
| 'building'
|
||||
+4
-4
@@ -6,7 +6,10 @@ import { useState } from 'react';
|
||||
import { usePointerDragPositions } from '@/platform/motion';
|
||||
import { PRODUCT_STEPPER_SCENE } from '@/tokens/feature-scenes/product-stepper-scene';
|
||||
|
||||
import { DATA_MODEL_GRAPH, type EntityConnection } from './data-model-data';
|
||||
import {
|
||||
DATA_MODEL_GRAPH,
|
||||
type EntityConnection,
|
||||
} from '../data/data-model-data';
|
||||
import { DATA_MODEL_ICONS } from './DataModelIcons';
|
||||
import { DrawEdge } from './DrawEdge';
|
||||
import { STEPPER_SHELL_CHROME } from './ProductStepperShell';
|
||||
@@ -177,9 +180,6 @@ function getCardCenter(
|
||||
};
|
||||
}
|
||||
|
||||
// The draggable entity graph: cards reposition under the pointer and the
|
||||
// SVG connectors track their centers (positions stay React state because
|
||||
// the edges are rendered paths).
|
||||
export function DataModelVisual({ active }: { active: boolean }) {
|
||||
const {
|
||||
canvasHandlers,
|
||||
-2
@@ -12,8 +12,6 @@ export type DrawEdgeProps = {
|
||||
to: Point;
|
||||
};
|
||||
|
||||
// One elbow connector between two card centers: near-aligned points draw
|
||||
// straight, others elbow in the requested order; endpoints get dots.
|
||||
export function DrawEdge({
|
||||
circleRadius = 2,
|
||||
elbow = 'vertical-first',
|
||||
-1
@@ -1,6 +1,5 @@
|
||||
import { PRODUCT_STEPPER_SCENE } from '@/tokens/feature-scenes/product-stepper-scene';
|
||||
|
||||
// The layout editor's mini glyphs (authored scene artwork, verbatim).
|
||||
const inks = PRODUCT_STEPPER_SCENE.layout;
|
||||
|
||||
export type LayoutFieldIconType =
|
||||
+2
-8
@@ -6,8 +6,8 @@ import {
|
||||
type PointerEvent as ReactPointerEvent,
|
||||
} from 'react';
|
||||
|
||||
import { LAYOUT_CHROME } from './layout-chrome';
|
||||
import { LAYOUT_EDITOR_CONTENT } from './layout-data';
|
||||
import { LAYOUT_CHROME } from '../data/layout-chrome';
|
||||
import { LAYOUT_EDITOR_CONTENT } from '../data/layout-data';
|
||||
import { LAYOUT_GLYPHS } from './LayoutIcons';
|
||||
import { PRODUCT_STEPPER_SCENE } from '@/tokens/feature-scenes/product-stepper-scene';
|
||||
|
||||
@@ -85,12 +85,8 @@ const {
|
||||
WidgetValue,
|
||||
} = LAYOUT_CHROME;
|
||||
|
||||
// One field row is 22px tall; vertical drag distance converts to reorder
|
||||
// steps at that grain (verbatim mechanism — discrete splices).
|
||||
const ROW_STEP_PX = 22;
|
||||
|
||||
// The layout editor: glass panels over the record page, with the fields
|
||||
// list reorderable by drag and visibility toggled per row.
|
||||
export function LayoutVisual({ active }: { active: boolean }) {
|
||||
const [fields, setFields] = useState(LAYOUT_EDITOR_CONTENT.fields);
|
||||
const [draggingId, setDraggingId] = useState<string | null>(null);
|
||||
@@ -144,8 +140,6 @@ export function LayoutVisual({ active }: { active: boolean }) {
|
||||
};
|
||||
|
||||
const handleDragEnd = () => setDraggingId(null);
|
||||
// A stolen capture (alt-tab, gestures) must settle the drag too; the
|
||||
// event bubbles up from the captured row.
|
||||
const handleLostCapture = () => {
|
||||
if (draggingId) {
|
||||
setDraggingId(null);
|
||||
+9
-12
@@ -7,14 +7,19 @@ import { type CSSProperties } from 'react';
|
||||
|
||||
import { INFORMATIVE_MARKS } from '@/icons';
|
||||
import { color, fontFamily, mediaUp, radius, spacing } from '@/tokens';
|
||||
import { Body, Eyebrow, Heading, HeadingPair } from '@/ui';
|
||||
import {
|
||||
Body,
|
||||
Eyebrow,
|
||||
Heading,
|
||||
HeadingPair,
|
||||
StepperProgressRail,
|
||||
StepperSwipeDeck,
|
||||
} from '@/ui';
|
||||
|
||||
import {
|
||||
PRODUCT_STEPPER_STEPS,
|
||||
type ProductStepperStep,
|
||||
} from './product-stepper-data';
|
||||
import { StepperProgressRail } from './StepperProgressRail';
|
||||
import { StepperSwipeDeck } from './StepperSwipeDeck';
|
||||
} from '../data/product-stepper-data';
|
||||
|
||||
const ContentRoot = styled.div`
|
||||
display: grid;
|
||||
@@ -45,9 +50,6 @@ const StepsColumn = styled.div`
|
||||
}
|
||||
`;
|
||||
|
||||
// The intro takes the house grammar (24px eyebrow-to-heading via this
|
||||
// gap, 12px heading-to-body via HeadingPair) — a ratified divergence
|
||||
// from the old site's uniform 8px stack.
|
||||
const IntroBlock = styled.div`
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
@@ -115,9 +117,6 @@ const StepHeading = styled.span`
|
||||
font-family: ${fontFamily('sans')};
|
||||
`;
|
||||
|
||||
// The old choreography, verbatim: passed steps sit settled, the upcoming
|
||||
// step rises 300px through its hand-off window at 0.4 opacity, later
|
||||
// steps wait below.
|
||||
const computeStepStyle = (
|
||||
stepNumber: number,
|
||||
activeStepIndex: number,
|
||||
@@ -156,8 +155,6 @@ export function ProductStepperContent({
|
||||
return (
|
||||
<>
|
||||
<StepRowHeader>
|
||||
{/* The old site inked inactive icons white too (invisible on the
|
||||
light scheme by construction) — ported as-is. */}
|
||||
<StepIconBox data-active={isActive ? '' : undefined}>
|
||||
<Icon color={color('white')} sizePx={14} />
|
||||
</StepIconBox>
|
||||
-3
@@ -105,9 +105,6 @@ const SvgLayer = styled.svg`
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
// The white mini-app window every product stepper visual floats in: logo
|
||||
// chip + title bar, then the bordered editing canvas with an SVG overlay
|
||||
// for connectors.
|
||||
function Shell({
|
||||
active,
|
||||
children,
|
||||
+1
-3
@@ -10,7 +10,7 @@ import { LayoutVisual } from './LayoutVisual';
|
||||
import {
|
||||
PRODUCT_STEPPER_STEPS,
|
||||
type ProductStepperVisualKey,
|
||||
} from './product-stepper-data';
|
||||
} from '../data/product-stepper-data';
|
||||
import { ProductStepperVisualFrame } from './ProductStepperVisualFrame';
|
||||
import { WorkflowVisual } from './WorkflowVisual';
|
||||
|
||||
@@ -68,8 +68,6 @@ const SlideInner = styled.div`
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
// The sticky stage: one slide per step, crossfading as the active step
|
||||
// changes; only the active slide takes the pointer.
|
||||
export function ProductStepperVisual({
|
||||
activeStepIndex,
|
||||
}: {
|
||||
-3
@@ -3,9 +3,6 @@ import { styled } from '@linaria/react';
|
||||
import NextImage from 'next/image';
|
||||
import { type ReactNode } from 'react';
|
||||
|
||||
// The product stepper's stage: its own authored artwork (the dark
|
||||
// notched shape with the dash pattern over it), ported verbatim — unlike
|
||||
// the home frame, the stage here is baked into the two webps.
|
||||
const BACKGROUND_SRC = '/images/product/stepper/background.webp';
|
||||
const SHAPE_SRC = '/images/product/stepper/background-shape.webp';
|
||||
|
||||
-2
@@ -1,6 +1,5 @@
|
||||
import { PRODUCT_STEPPER_SCENE } from '@/tokens/feature-scenes/product-stepper-scene';
|
||||
|
||||
// The workflow nodes' mini glyphs (authored scene artwork, verbatim).
|
||||
export type WorkflowIconName =
|
||||
| 'playlistAdd'
|
||||
| 'plus'
|
||||
@@ -66,7 +65,6 @@ function IconRepeat() {
|
||||
);
|
||||
}
|
||||
|
||||
// The send node draws in its own amber ink, verbatim.
|
||||
function IconSend() {
|
||||
return (
|
||||
<svg
|
||||
+2
-7
@@ -7,8 +7,8 @@ import { PRODUCT_STEPPER_SCENE } from '@/tokens/feature-scenes/product-stepper-s
|
||||
|
||||
import { DrawEdge } from './DrawEdge';
|
||||
import { STEPPER_SHELL_CHROME } from './ProductStepperShell';
|
||||
import { useWorkflowAnimation } from './use-workflow-animation';
|
||||
import { WORKFLOW_GRAPH } from './workflow-data';
|
||||
import { useWorkflowAnimation } from '../utils/use-workflow-animation';
|
||||
import { WORKFLOW_GRAPH } from '../data/workflow-data';
|
||||
import { WORKFLOW_GLYPHS } from './WorkflowIcons';
|
||||
|
||||
const shell = PRODUCT_STEPPER_SCENE.shell;
|
||||
@@ -123,8 +123,6 @@ function getNodeCenter(position: { x: number; y: number }): {
|
||||
return { x: position.x + NODE_WIDTH / 2, y: position.y + NODE_HEIGHT / 2 };
|
||||
}
|
||||
|
||||
// The authored loop-back connector from the iterator out to the email
|
||||
// node, redrawn from live node positions (verbatim path math).
|
||||
function IteratorLoopPath({ positions }: { positions: NodePositions }) {
|
||||
const iteratorPosition = positions.iterator;
|
||||
const emailPosition = positions.email;
|
||||
@@ -172,9 +170,6 @@ function IteratorLoopPath({ positions }: { positions: NodePositions }) {
|
||||
);
|
||||
}
|
||||
|
||||
// The draggable workflow run: nodes reposition under the pointer, edges
|
||||
// and the iteration label track them, and the active step beat ticks
|
||||
// check badges down the sequence while the slide is active.
|
||||
export function WorkflowVisual({ active }: { active: boolean }) {
|
||||
const {
|
||||
canvasHandlers,
|
||||
+1
-2
@@ -3,7 +3,7 @@ import { type PRODUCT_STEPPER_SCENE } from '@/tokens/feature-scenes/product-step
|
||||
import {
|
||||
type DataModelFieldIcon,
|
||||
type DataModelHeaderIcon,
|
||||
} from './DataModelIcons';
|
||||
} from '../components/DataModelIcons';
|
||||
|
||||
export type EntityTone = keyof typeof PRODUCT_STEPPER_SCENE.entityTones;
|
||||
|
||||
@@ -25,7 +25,6 @@ export type EntityConnection = {
|
||||
to: string;
|
||||
};
|
||||
|
||||
// Mock fiction entity graph (product-screenshot copy, English).
|
||||
export const DATA_MODEL_GRAPH: {
|
||||
connections: EntityConnection[];
|
||||
entities: EntityDefinition[];
|
||||
-2
@@ -2,8 +2,6 @@ import { styled } from '@linaria/react';
|
||||
|
||||
import { PRODUCT_STEPPER_SCENE } from '@/tokens/feature-scenes/product-stepper-scene';
|
||||
|
||||
// The layout editor's glassmorphic panel chrome (verbatim from the old
|
||||
// layout-styles, gathered behind one record like RECORD_PANEL_CHROME).
|
||||
const shell = PRODUCT_STEPPER_SCENE.shell;
|
||||
const layout = PRODUCT_STEPPER_SCENE.layout;
|
||||
|
||||
+1
-2
@@ -3,7 +3,7 @@ import { type PRODUCT_STEPPER_SCENE } from '@/tokens/feature-scenes/product-step
|
||||
import {
|
||||
type LayoutFieldIconType,
|
||||
type LayoutNavIconType,
|
||||
} from './LayoutIcons';
|
||||
} from '../components/LayoutIcons';
|
||||
|
||||
type NavTint = keyof typeof PRODUCT_STEPPER_SCENE.navTints;
|
||||
|
||||
@@ -26,7 +26,6 @@ export type LayoutNavItemDefinition = {
|
||||
suffix?: string;
|
||||
};
|
||||
|
||||
// Mock fiction layout editor content (product-screenshot copy, English).
|
||||
export const LAYOUT_EDITOR_CONTENT: {
|
||||
fields: LayoutFieldDefinition[];
|
||||
navItems: LayoutNavItemDefinition[];
|
||||
+1
-3
@@ -1,4 +1,4 @@
|
||||
import { type WorkflowIconName } from './WorkflowIcons';
|
||||
import { type WorkflowIconName } from '../components/WorkflowIcons';
|
||||
|
||||
export type WorkflowNodeDefinition = {
|
||||
badge?: string;
|
||||
@@ -21,8 +21,6 @@ const TRUNK_X = 55;
|
||||
const RIGHT_X = 200;
|
||||
const LEFT_X = 5;
|
||||
|
||||
// Mock fiction workflow graph (product-screenshot copy, English). The
|
||||
// animation activates node prefixes on a fixed beat, then loops.
|
||||
export const WORKFLOW_GRAPH: {
|
||||
animationSequence: string[];
|
||||
edges: WorkflowEdgeDefinition[];
|
||||
-1
@@ -1,2 +1 @@
|
||||
export { Stepper } from './Stepper';
|
||||
export { ProductStepper } from './ProductStepper';
|
||||
+1
-4
@@ -4,11 +4,8 @@ import { useEffect, useState } from 'react';
|
||||
|
||||
import { useTimeoutRegistry } from '@/app-preview/stage/use-timeout-registry';
|
||||
|
||||
import { WORKFLOW_GRAPH } from './workflow-data';
|
||||
import { WORKFLOW_GRAPH } from '../data/workflow-data';
|
||||
|
||||
// Activates the workflow nodes one beat at a time while the visual is
|
||||
// active, then loops; deactivating resets. Self-rescheduling tick on the
|
||||
// house timeout registry (auto-cleanup, fake-timer-testable).
|
||||
export function useWorkflowAnimation(active: boolean): Set<string> {
|
||||
const [activeNodes, setActiveNodes] = useState<Set<string>>(new Set());
|
||||
const timeouts = useTimeoutRegistry();
|
||||
-4
@@ -84,8 +84,6 @@ const PillTrack = styled.div`
|
||||
}
|
||||
`;
|
||||
|
||||
// Fill height rides a CSS variable, so the only inline style is a custom
|
||||
// property; the first render is deterministic (0%) on server and client.
|
||||
const PillFill = styled.div`
|
||||
background-color: ${color('blue')};
|
||||
border-radius: ${radius(8)};
|
||||
@@ -115,8 +113,6 @@ const StepLabel = styled.div`
|
||||
|
||||
export type StepperProgressRailProps = {
|
||||
activeStepIndex: number;
|
||||
// Inactive dot ink; the product stepper runs a stronger one than the
|
||||
// home default.
|
||||
inactiveColor?: string;
|
||||
localProgress: number;
|
||||
stepCount: number;
|
||||
+1
-3
@@ -30,10 +30,8 @@ const Track = styled.div`
|
||||
`;
|
||||
|
||||
const releasePointer = (event: React.PointerEvent<HTMLDivElement>): void => {
|
||||
try {
|
||||
if (event.currentTarget.hasPointerCapture(event.pointerId)) {
|
||||
event.currentTarget.releasePointerCapture(event.pointerId);
|
||||
} catch {
|
||||
// pointer was not captured
|
||||
}
|
||||
};
|
||||
|
||||
@@ -40,3 +40,11 @@ export { SectionStack } from './SectionStack';
|
||||
export { VerticalDivider } from './VerticalDivider';
|
||||
export { SectionShell, type SectionShellProps } from './SectionShell';
|
||||
export { Signoff, type SignoffProps } from './Signoff';
|
||||
export {
|
||||
StepperProgressRail,
|
||||
type StepperProgressRailProps,
|
||||
} from './StepperProgressRail';
|
||||
export {
|
||||
StepperSwipeDeck,
|
||||
type StepperSwipeDeckProps,
|
||||
} from './StepperSwipeDeck';
|
||||
|
||||
Reference in New Issue
Block a user