diff --git a/src/components/Animated-steps-list/AnimatedStepsList.style.js b/src/components/Animated-steps-list/AnimatedStepsList.style.js index ff5144ccde9a5f..c32bf5c14759ee 100644 --- a/src/components/Animated-steps-list/AnimatedStepsList.style.js +++ b/src/components/Animated-steps-list/AnimatedStepsList.style.js @@ -4,7 +4,8 @@ export const AnimatedStepsListWrapper = styled.div` .animated-steps-list { display: flex; & .indicator-wrapper { - & > ul { + & > ul, + & > nav { position: sticky; top: 200px; padding-bottom: 395px; diff --git a/src/components/Animated-steps-list/Steps-indicator/StepsIndicator.style.js b/src/components/Animated-steps-list/Steps-indicator/StepsIndicator.style.js index 4ced5f8be99c3f..19db22f314bdf4 100644 --- a/src/components/Animated-steps-list/Steps-indicator/StepsIndicator.style.js +++ b/src/components/Animated-steps-list/Steps-indicator/StepsIndicator.style.js @@ -1,21 +1,57 @@ import styled from "styled-components"; -export const StepsIndicatorWrapper = styled.ul` +export const StepsIndicatorWrapper = styled.nav` + padding: 0; + margin: 0; + + & .indicator-list { padding: 0; margin: 0; list-style: none; + } + + & li { + margin-bottom: 1rem; - & li { - font-size: 17px; - line-height: 28px; - color: gray; - margin-bottom: 16px; + & .indicator-link { + display: inline-block; + text-decoration: none; + background: transparent; + border: none; + box-shadow: none; + padding: 0; + margin: 0; + font-family: inherit; + font-size: 17px; + line-height: 28px; + color: ${(props) => props.theme.greyB4B4B4ToGrey505050 || "gray"}; + cursor: pointer; + transition: + color 0.2s ease, + font-weight 0.2s ease; - &.active { - font-weight: 600; - color: ${props => props.theme.text}; + &:hover { + color: ${(props) => props.theme.secondaryColor || "#00b39f"}; + background: transparent; + box-shadow: none; + text-decoration: none; + } + &:focus { + outline: none; + box-shadow: none; + } + + &:focus-visible { + outline: 2px solid ${(props) => props.theme.secondaryColor || "#00b39f"}; + outline-offset: 3px; + border-radius: 4px; + } + } - } + &.active .indicator-link { + font-weight: 600; + color: ${(props) => props.theme.text}; } + } `; diff --git a/src/components/Animated-steps-list/Steps-indicator/index.js b/src/components/Animated-steps-list/Steps-indicator/index.js index 315472acc6d3f5..1d7ebdb43d8e62 100644 --- a/src/components/Animated-steps-list/Steps-indicator/index.js +++ b/src/components/Animated-steps-list/Steps-indicator/index.js @@ -1,17 +1,47 @@ import React from "react"; import { StepsIndicatorWrapper } from "./StepsIndicator.style"; -const StepsIndicator = ({ steps, activeIndex }) => { +const StepsIndicator = ({ steps, activeIndex, onItemClick }) => { return ( - - {steps.map((step, index) => ( -
  • - {step.name} -
  • - ))} + + ); }; diff --git a/src/components/Animated-steps-list/Steps-list/Step/Step.style.js b/src/components/Animated-steps-list/Steps-list/Step/Step.style.js index a46828975a32f4..cdb22f05494e8d 100644 --- a/src/components/Animated-steps-list/Steps-list/Step/Step.style.js +++ b/src/components/Animated-steps-list/Steps-list/Step/Step.style.js @@ -1,27 +1,38 @@ import styled from "styled-components"; export const StepWrapper = styled.li` - width: 314px; - + width: 314px; + scroll-margin-top: 9.5rem; + + @media (max-width: 850px) { + width: unset; + scroll-margin-top: 6rem; + } + + & h3 { + margin-bottom: 12px; + margin-top: 0; + scroll-margin-top: 9.5rem; + @media (max-width: 850px) { - width: unset; + scroll-margin-top: 6rem; } - & h3 { - margin-bottom: 12px; - margin-top: 0; - } + &:focus { + outline: none; + } + } + + & .description { + & > p { + font-size: 17px; + line-height: 28px; + letter-spacing: 0.0125em; + color: var(--gray-2); + } + } - & .description { - & > p { - font-size: 17px; - line-height: 28px; - letter-spacing: 0.0125em; - color: var(--gray-2); - } - } - - & > .logo-list { - margin-top: 38px; - } + & > .logo-list { + margin-top: 38px; + } `; diff --git a/src/components/Animated-steps-list/Steps-list/Step/index.js b/src/components/Animated-steps-list/Steps-list/Step/index.js index 9d2ed7e7ffaf76..9ee699f7d5bba8 100644 --- a/src/components/Animated-steps-list/Steps-list/Step/index.js +++ b/src/components/Animated-steps-list/Steps-list/Step/index.js @@ -3,7 +3,7 @@ import { useInView } from "react-intersection-observer"; import LogoList from "../../../Logo-List"; import { StepWrapper } from "./Step.style"; -const Step = ({ name, description, logos, onInViewStatusChanged }) => { +const Step = ({ id, name, description, logos, onInViewStatusChanged }) => { const [ref, inView] = useInView({ threshold: 0.4 }); const [inViewStatus, setInViewStatus] = useState(false); @@ -15,8 +15,8 @@ const Step = ({ name, description, logos, onInViewStatusChanged }) => { }, [inView, inViewStatus]); return ( - -

    {name}

    + +

    {name}

    {description}
    diff --git a/src/components/Animated-steps-list/Steps-list/index.js b/src/components/Animated-steps-list/Steps-list/index.js index 0367a5b201c1f1..d1267380c00195 100644 --- a/src/components/Animated-steps-list/Steps-list/index.js +++ b/src/components/Animated-steps-list/Steps-list/index.js @@ -4,7 +4,7 @@ import Step from "./Step"; const StepsList = ({ steps, className, onFocusedIndexChanged }) => { const [viewportStatus, setViewportStatus] = useState( - new Array(steps.length).fill(false) + new Array(steps.length).fill(false), ); const [focusedStepIndex, setFocusedStepIndex] = useState(0); return ( @@ -13,6 +13,8 @@ const StepsList = ({ steps, className, onFocusedIndexChanged }) => { {steps.map((step, index) => ( { // Determine the new status array of the view status const newStatusArray = [...viewportStatus]; @@ -28,7 +30,6 @@ const StepsList = ({ steps, className, onFocusedIndexChanged }) => { onFocusedIndexChanged(newFocusIndex); } }} - {...step} /> ))} diff --git a/src/components/Animated-steps-list/index.js b/src/components/Animated-steps-list/index.js index 3e2fba23f19d37..366ffeb4fbe46f 100644 --- a/src/components/Animated-steps-list/index.js +++ b/src/components/Animated-steps-list/index.js @@ -1,4 +1,4 @@ -import React, { useState } from "react"; +import React, { useState, useEffect, useRef } from "react"; import useScrollPosition from "./scroll-position"; import StepsList from "./Steps-list"; import StepsIndicator from "./Steps-indicator"; @@ -15,10 +15,10 @@ const animationBottomPadding = [0, 180, 180, 50]; const calculateCurrentFrame = (terminalSteps, currentIndex, scrollPosition) => { const percentage = Math.min( (scrollPosition - breakpoints[currentIndex]) / - (breakpoints[currentIndex + 1] - - breakpoints[currentIndex] - - animationBottomPadding[currentIndex]), - 1 + (breakpoints[currentIndex + 1] - + breakpoints[currentIndex] - + animationBottomPadding[currentIndex]), + 1, ); const currentLines = terminalSteps[currentIndex].lines; let totalFrames = 0; @@ -37,21 +37,139 @@ const AnimatedStepsList = ({ terminalHeroState, steps }) => { const scrollPosition = useScrollPosition(); const [indicatorIndex, setIndicatorIndex] = useState(0); const activeTerminalStateIndex = - scrollPosition <= 300 ? 0 : indicatorIndex + 1; + scrollPosition <= 300 ? 0 : indicatorIndex + 1; const terminalSteps = [terminalHeroState].concat( - steps.map((step) => step.terminal) + steps.map((step) => step.terminal), ); const currentFrame = calculateCurrentFrame( terminalSteps, activeTerminalStateIndex, - scrollPosition + scrollPosition, ); + if (typeof window !== "undefined") { + const hash = window.location.hash.replace(/^#/, ""); + if ( + !hash || + steps.some( + (step) => + (step.id || step.name.toLowerCase().replace(/\s+/g, "-")) === hash, + ) + ) { + window.gatsby_scroll_hash = "none"; + } + } + + const handleStepClick = (index) => { + const step = steps[index]; + if (!step) return false; + + const stepId = step.id || step.name.toLowerCase().replace(/\s+/g, "-"); + if (typeof document !== "undefined") { + const element = document.getElementById(stepId); + if (element) { + const prefersReducedMotion = + typeof window !== "undefined" && + window.matchMedia("(prefers-reduced-motion: reduce)").matches; + element.scrollIntoView({ + behavior: prefersReducedMotion ? "auto" : "smooth", + block: "start", + }); + setIndicatorIndex(index); + if ( + typeof window !== "undefined" && + window.history && + window.history.pushState + ) { + window.gatsby_scroll_hash = "none"; + window.history.pushState(null, "", `#${stepId}`); + } + const heading = element.querySelector("h3"); + if (heading) { + heading.focus({ preventScroll: true }); + } + return true; + } + } + return false; + }; + + const stepsRef = useRef(steps); + stepsRef.current = steps; + + const disarmGatsbyAnchorScroll = () => { + if (typeof window !== "undefined") { + const hash = window.location.hash.replace(/^#/, ""); + if ( + !hash || + stepsRef.current.some( + (step) => + (step.id || step.name.toLowerCase().replace(/\s+/g, "-")) === hash, + ) + ) { + window.gatsby_scroll_hash = "none"; + } + } + }; + + useEffect(() => { + if (typeof window === "undefined") return; + + disarmGatsbyAnchorScroll(); + + const handleHashNavigation = () => { + disarmGatsbyAnchorScroll(); + const hash = window.location.hash.replace(/^#/, ""); + if (!hash) { + setIndicatorIndex(0); + return; + } + + const targetIndex = stepsRef.current.findIndex( + (step) => + (step.id || step.name.toLowerCase().replace(/\s+/g, "-")) === hash, + ); + + if (targetIndex !== -1) { + const stepId = + stepsRef.current[targetIndex].id || + stepsRef.current[targetIndex].name.toLowerCase().replace(/\s+/g, "-"); + const element = document.getElementById(stepId); + if (element) { + const prefersReducedMotion = + typeof window !== "undefined" && + window.matchMedia("(prefers-reduced-motion: reduce)").matches; + element.scrollIntoView({ + behavior: prefersReducedMotion ? "auto" : "smooth", + block: "start", + }); + setIndicatorIndex(targetIndex); + const heading = element.querySelector("h3"); + if (heading) { + heading.focus({ preventScroll: true }); + } + } + } + }; + + const timer = setTimeout(handleHashNavigation, 100); + window.addEventListener("popstate", handleHashNavigation, true); + + return () => { + clearTimeout(timer); + window.removeEventListener("popstate", handleHashNavigation, true); + }; + }, []); + return (
    - +