<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>
container-2 : not useful, just a flex container to center on screenprogress-container : flex container, setting min-width of 350. But also uses ::before and ::after pseudo-elements to create the lines between the circles. The ::before and ::after elements are positioned absolutely, and the progress-container is positioned relatively.circle : flex item, setting width and height, centering number text.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);
}
}
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");
},
};
};
What the next button and previous button are supposed to do is pretty explanatory:
// 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");
}
});
}