w-lab-app/src/components/domains/additional-purchases/Progress/Progress.tsx
dev.daminik00 83d1faaa38 lint:fix
2025-10-28 06:02:25 +01:00

149 lines
5.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"use client";
import { useEffect, useRef, useState } from "react";
import { useTranslations } from "next-intl";
import clsx from "clsx";
import { Icon, IconName, Typography } from "@/components/ui";
import { useDynamicSize } from "@/hooks/DOM/useDynamicSize";
import styles from "./Progress.module.scss";
interface IProgressProps {
items: string[];
activeItemIndex: number;
}
export default function Progress({ items, activeItemIndex }: IProgressProps) {
const t = useTranslations("AdditionalPurchases.Progress");
const { width: containerWidth, elementRef } = useDynamicSize<HTMLDivElement>({
defaultWidth: 327,
});
const wrapperRef = useRef<HTMLDivElement>(null);
const [isScrolled, setIsScrolled] = useState(false);
const [containerHeight, setContainerHeight] = useState(0);
// Всегда добавляем финальный пункт в конец
const finalStep = t("final_step");
const allItems = [...items, finalStep];
// Фиксированные отступы от краев (центр кружочка = 50px от края)
const edgeOffset = 50; // 50px от края до центра кружочка
const totalItems = allItems.length;
// Рассчитываем позицию каждого элемента с равными отступами
const calculateItemPosition = (index: number) => {
if (totalItems === 1) return 50; // Центрируем если один элемент
// Распределяем элементы равномерно между краями
const availableWidth = containerWidth - (edgeOffset * 2);
const spacing = availableWidth / (totalItems - 1);
return edgeOffset + (spacing * index);
};
// Динамическое определение высоты контейнера
useEffect(() => {
if (elementRef.current) {
// Находим все элементы прогресс бара
const items = elementRef.current.querySelectorAll(`.${styles.item}`);
let maxHeight = 0;
items.forEach((item) => {
const height = (item as HTMLElement).offsetHeight;
if (height > maxHeight) {
maxHeight = height;
}
});
setContainerHeight(maxHeight);
}
}, [allItems, containerWidth]);
// Отслеживаем скролл для появления фона
useEffect(() => {
const handleScroll = () => {
if (wrapperRef.current) {
const rect = wrapperRef.current.getBoundingClientRect();
// Показываем фон только когда элемент прокручен вверх (скроллится)
// При начальной загрузке top будет около 0, но мы проверяем window.scrollY
const hasScrolled = window.scrollY > 0 && rect.top <= 1;
setIsScrolled(hasScrolled);
}
};
window.addEventListener("scroll", handleScroll, { passive: true });
// При монтировании проверяем - если страница уже прокручена
handleScroll();
return () => window.removeEventListener("scroll", handleScroll);
}, []);
return (
<div
className={clsx(styles.stickyWrapper, isScrolled && styles.scrolled)}
ref={wrapperRef}
>
<div
className={styles.container}
ref={elementRef}
style={{
minHeight: containerHeight > 0 ? `${containerHeight}px` : undefined,
}}
>
{allItems.map((item, index) => {
const itemPosition = calculateItemPosition(index);
return (
<div
key={index}
className={clsx(
styles.item,
activeItemIndex === index && styles.active,
activeItemIndex > index && styles.done
)}
style={{
left: `${itemPosition}px`,
transform: 'translateX(-50%)', // Центрируем относительно позиции
}}
>
<div className={styles.marker}>
{activeItemIndex > index && styles.done && (
<Icon
name={IconName.Check}
color="#fff"
size={{
width: 12,
height: 12,
}}
/>
)}
<Typography as="span" className={styles.number}>
{index + 1}
</Typography>
</div>
<Typography as="p" align="center" className={styles.text}>
{item}
</Typography>
</div>
);
})}
<div
className={styles.connector}
style={{
width: containerWidth - (edgeOffset * 2),
left: edgeOffset,
background: activeItemIndex > 0
? `linear-gradient(
90deg,
#2866ed 0%,
#2866ed ${((activeItemIndex) / (totalItems - 1)) * 100}%,
#E2E8F0 ${((activeItemIndex) / (totalItems - 1)) * 100}%,
#E2E8F0 100%
)`
: "#E2E8F0",
}}
/>
</div>
</div>
);
}