5a657129f0
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.
165 lines
3.4 KiB
TypeScript
165 lines
3.4 KiB
TypeScript
'use client';
|
|
|
|
import { styled } from '@linaria/react';
|
|
import { type CSSProperties } from 'react';
|
|
|
|
import {
|
|
color,
|
|
FONT_WEIGHT,
|
|
fontFamily,
|
|
fontSize,
|
|
mediaUp,
|
|
radius,
|
|
spacing,
|
|
DURATION,
|
|
REDUCED_MOTION,
|
|
} from '@/tokens';
|
|
|
|
const RailContainer = styled.div`
|
|
display: none;
|
|
|
|
${mediaUp('md')} {
|
|
align-items: flex-start;
|
|
align-self: stretch;
|
|
display: flex;
|
|
flex-direction: column;
|
|
min-height: 0;
|
|
}
|
|
`;
|
|
|
|
const StickyViewportCenter = styled.div`
|
|
align-items: center;
|
|
display: flex;
|
|
flex-shrink: 0;
|
|
height: 100vh;
|
|
justify-content: flex-start;
|
|
max-height: 100vh;
|
|
position: sticky;
|
|
top: 0;
|
|
`;
|
|
|
|
const Rail = styled.div`
|
|
align-items: flex-start;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: ${spacing(2)};
|
|
`;
|
|
|
|
const StepRow = styled.div`
|
|
align-items: center;
|
|
display: flex;
|
|
height: 16px;
|
|
justify-content: center;
|
|
position: relative;
|
|
transition: height ${DURATION.lg} ease;
|
|
width: ${spacing(1)};
|
|
|
|
&[data-active] {
|
|
height: 80px;
|
|
}
|
|
|
|
${REDUCED_MOTION} {
|
|
transition: none;
|
|
}
|
|
`;
|
|
|
|
const PillTrack = styled.div`
|
|
background-color: var(--rail-inactive, ${color('black-20')});
|
|
border-radius: 50%;
|
|
display: flex;
|
|
height: ${spacing(1)};
|
|
overflow: hidden;
|
|
transition:
|
|
height ${DURATION.lg} ease,
|
|
border-radius ${DURATION.lg} ease;
|
|
width: ${spacing(1)};
|
|
|
|
&[data-active] {
|
|
border-radius: ${radius(8)};
|
|
height: 100%;
|
|
}
|
|
|
|
${REDUCED_MOTION} {
|
|
transition: none;
|
|
}
|
|
`;
|
|
|
|
const PillFill = styled.div`
|
|
background-color: ${color('blue')};
|
|
border-radius: ${radius(8)};
|
|
height: var(--rail-fill, 0%);
|
|
transition: height ${DURATION.xxs} linear;
|
|
width: 100%;
|
|
`;
|
|
|
|
const StepLabel = styled.div`
|
|
color: ${color('blue')};
|
|
font-family: ${fontFamily('mono')};
|
|
font-size: ${fontSize(3)};
|
|
font-weight: ${FONT_WEIGHT.medium};
|
|
left: calc(100% + ${spacing(4)});
|
|
line-height: 16px;
|
|
opacity: 0;
|
|
position: absolute;
|
|
text-transform: uppercase;
|
|
top: 0;
|
|
transition: opacity 0.3s ease;
|
|
white-space: nowrap;
|
|
|
|
&[data-visible] {
|
|
opacity: 1;
|
|
}
|
|
`;
|
|
|
|
export type StepperProgressRailProps = {
|
|
activeStepIndex: number;
|
|
inactiveColor?: string;
|
|
localProgress: number;
|
|
stepCount: number;
|
|
};
|
|
|
|
export function StepperProgressRail({
|
|
activeStepIndex,
|
|
inactiveColor,
|
|
localProgress,
|
|
stepCount,
|
|
}: StepperProgressRailProps) {
|
|
return (
|
|
<RailContainer
|
|
style={
|
|
inactiveColor
|
|
? ({ '--rail-inactive': inactiveColor } as CSSProperties)
|
|
: undefined
|
|
}
|
|
>
|
|
<StickyViewportCenter>
|
|
<Rail>
|
|
{Array.from({ length: stepCount }, (_, index) => {
|
|
const isActive = index === activeStepIndex;
|
|
const fillPercentage = isActive
|
|
? Math.min(100, Math.max(0, localProgress * 100))
|
|
: 0;
|
|
return (
|
|
<StepRow
|
|
data-active={isActive ? '' : undefined}
|
|
key={`step-${index}`}
|
|
>
|
|
<PillTrack data-active={isActive ? '' : undefined}>
|
|
<PillFill
|
|
style={
|
|
{ '--rail-fill': `${fillPercentage}%` } as CSSProperties
|
|
}
|
|
/>
|
|
</PillTrack>
|
|
<StepLabel data-visible={isActive ? '' : undefined}>
|
|
{String(index + 1).padStart(2, '0')}
|
|
</StepLabel>
|
|
</StepRow>
|
|
);
|
|
})}
|
|
</Rail>
|
|
</StickyViewportCenter>
|
|
</RailContainer>
|
|
);
|
|
}
|