1
2
3
4

Day 2 - Progress Steps

example:

HTML structure

<div class="container-2">
  <div class="progress-container">
    <div class="circle">1</div>
    <div class="circle">2</div>
    <div class="circle">3</div>
    <div class="circle">4</div>
  </div>
  <div class="btn-container">
    <button class="btn" id="prev">prev</button>
    <button class="btn" id="next">next</button>
  </div>
</div>

CSS

For the .progress-container class, we are using the pseudoelements to render a gray line as an outline for the progress, and then we dynamically control the width of the blue line using javascript.

:root {
  --progress: 0%;
  --primary: #48c4c6;
  --secondary: #3da2a4;
}

.progress-container {
  display: flex;
  justify-content: space-between;
  width: clamp(250px, 350px, 100%);
  margin-bottom: 2rem;
  // for pseudo elements to be absolutely positioned
  position: relative;

  // blue line goes on top of gray line, z-index = -1
  &::before {
    content: "";
    position: absolute;
    top: 50%;
    left: 0;
    transform: translateY(-50%);
    // use css variable and dynamically increase value with javascript
    width: var(--progress);
    height: 4px;
    background-color: var(--primary);
    border-radius: 10px;
    z-index: -1;
    transition: 0.4s ease all;
  }
  // grya line is on bottom, z-index = -1
  &::after {
    content: "";
    position: absolute;
    top: 50%;
    left: 0;
    transform: translateY(-50%);
    width: 100%;
    height: 4px;
    background-color: #eceded;
    border-radius: 10px;
    z-index: -2;
    transition: 0.4s ease all;
  }
}

For the circle, we are using flexbox to center the number text We also want an .active class to toggle when the current number is selected

.circle {
  background-color: rgb(247, 247, 247);
  box-shadow: 0px 5px 10px rgba(0, 0, 0, 0.1);
  height: 2rem;
  width: 2rem;
  border: 2px solid rgb(236, 236, 236);
  border-radius: 9999px;
  display: flex;
  justify-content: center;
  align-items: center;
  cursor: pointer;
  // active class
  &.active {
    border-color: var(--primary);
    color: var(--primary);
  }
}

JavaScript

Setup

For the setup, I just set up state with enclosures.

const prevButton = document.querySelector("#prev") as HTMLButtonElement;
const nextButton = document.querySelector("#next") as HTMLButtonElement;

const circles = document.querySelectorAll(
  ".circle"
) as NodeListOf<HTMLDivElement>;

const active = (() => {
  let currentActive = 0;
  return {
    getCurrentActive: () => currentActive,
    setCurrentActive: (value: number) => {
      currentActive = value;
    },
  };
})();

const handleButtonStyles = (button: HTMLButtonElement) => {
  return {
    disable: () => {
      button.disabled = true;
      button.classList.add("btn-disabled");
    },
    enable: () => {
      button.disabled = false;
      button.classList.remove("btn-disabled");
    },
  };
};

Handling button clicks

What the next button and previous button are supposed to do is pretty explanatory:

  1. Increment/decrement active step number
  2. Reflect changes in UI
// 2. handle next button click
nextButton.addEventListener("click", () => {
  const currentActive = active.getCurrentActive();
  if (currentActive < circles.length - 1) {
    active.setCurrentActive(currentActive + 1);
  }
  update();
});

// 3. handle prev button click
prevButton.addEventListener("click", () => {
  const currentActive = active.getCurrentActive();
  if (currentActive > 0) {
    active.setCurrentActive(currentActive - 1);
  }
  update();
});

Then here is how we reflect changes in the UI:

// 4. handle update
function update() {
  const currentActive = active.getCurrentActive();

  // sets width of blue progress line based on the currently active number
  document.documentElement.style.setProperty(
    "--progress",
    `${(currentActive / (circles.length - 1)) * 100}%`
  );

  // working with enabling/disabling buttons
  if (currentActive === 0) {
    handleButtonStyles(prevButton).disable();
    handleButtonStyles(nextButton).enable();
  } else if (currentActive === circles.length - 1) {
    handleButtonStyles(nextButton).disable();
    handleButtonStyles(prevButton).enable();
  } else {
    handleButtonStyles(prevButton).enable();
    handleButtonStyles(nextButton).enable();
  }

  // toggling active class
  circles.forEach((circle, index) => {
    if (index <= currentActive) {
      circle.classList.add("active");
    } else {
      circle.classList.remove("active");
    }
  });
}