mirror of
https://git.v0id.ovh/n3wt-innov/n3wt-school.git
synced 2026-01-29 07:53:23 +00:00
187 lines
5.7 KiB
JavaScript
187 lines
5.7 KiB
JavaScript
import React, { useState, useEffect } from 'react';
|
|
|
|
const Step = ({ number, title, isActive, isValid, isCompleted, onClick }) => {
|
|
return (
|
|
<div className="flex-shrink-0 flex justify-center relative mx-4">
|
|
<div
|
|
className={`
|
|
w-8 h-8 rounded-full
|
|
flex items-center justify-center
|
|
text-sm font-semibold
|
|
${
|
|
isCompleted
|
|
? 'bg-emerald-600 text-white'
|
|
: isActive
|
|
? 'bg-emerald-600 text-white'
|
|
: 'bg-gray-200 text-gray-600'
|
|
}
|
|
`}
|
|
>
|
|
{isCompleted ? (
|
|
<svg
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
className="h-5 w-5"
|
|
viewBox="0 0 20 20"
|
|
fill="currentColor"
|
|
>
|
|
<path
|
|
fillRule="evenodd"
|
|
d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z"
|
|
clipRule="evenodd"
|
|
/>
|
|
</svg>
|
|
) : (
|
|
number
|
|
)}
|
|
</div>
|
|
<div className="absolute top-12 left-1/2 -translate-x-1/2">
|
|
<span
|
|
className={`
|
|
text-xs font-medium w-20 text-center block break-words
|
|
${isActive ? 'text-emerald-600' : 'text-gray-500'}
|
|
`}
|
|
>
|
|
{title}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
const SpacerStep = ({ isCompleted }) => {
|
|
return (
|
|
<div
|
|
className={`flex-1 h-0.5 ${isCompleted ? 'bg-emerald-600' : 'bg-gray-200'}`}
|
|
/>
|
|
);
|
|
};
|
|
|
|
const Dots = () => {
|
|
return (
|
|
<div className="text-gray-500 relative flex items-center mx-4">
|
|
<span>...</span>
|
|
<div className="absolute top-8 left-1/2 -translate-x-1/2">
|
|
<span className="text-xs font-medium w-20 text-center block">...</span>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
const ProgressStep = ({
|
|
steps,
|
|
stepTitles,
|
|
currentStep,
|
|
setStep,
|
|
isStepValid,
|
|
}) => {
|
|
const [windowWidth, setWindowWidth] = useState(window.innerWidth);
|
|
const [visibleSteps, setVisibleSteps] = useState(steps);
|
|
|
|
useEffect(() => {
|
|
const handleResize = () => setWindowWidth(window.innerWidth);
|
|
window.addEventListener('resize', handleResize);
|
|
return () => window.removeEventListener('resize', handleResize);
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
const calculateVisibleSteps = () => {
|
|
const minWidth = 150; // Largeur minimale estimée par étape
|
|
const maxVisibleSteps = Math.floor(windowWidth / minWidth);
|
|
|
|
if (maxVisibleSteps >= steps.length) {
|
|
setVisibleSteps(steps);
|
|
return;
|
|
}
|
|
|
|
if (maxVisibleSteps < 4) {
|
|
// Garder seulement première, dernière et courante
|
|
let filtered = [steps[0]];
|
|
if (currentStep > 1 && currentStep < steps.length) {
|
|
filtered.push('...');
|
|
filtered.push(steps[currentStep - 1]);
|
|
}
|
|
if (currentStep < steps.length) {
|
|
filtered.push('...');
|
|
}
|
|
filtered.push(steps[steps.length - 1]);
|
|
setVisibleSteps(filtered);
|
|
} else {
|
|
// Garder première, dernière, courante et quelques étapes adjacentes
|
|
let filtered = [steps[0]];
|
|
if (currentStep > 2) filtered.push('...');
|
|
if (currentStep > 1 && currentStep < steps.length) {
|
|
filtered.push(steps[currentStep - 1]);
|
|
}
|
|
if (currentStep < steps.length - 1) filtered.push('...');
|
|
filtered.push(steps[steps.length - 1]);
|
|
setVisibleSteps(filtered);
|
|
}
|
|
};
|
|
|
|
calculateVisibleSteps();
|
|
}, [windowWidth, currentStep, steps]);
|
|
|
|
const handleStepClick = (stepIndex) => {
|
|
// Vérifie si on peut naviguer vers l'étape (toutes les étapes précédentes doivent être valides)
|
|
const canNavigate = Array.from(
|
|
{ length: stepIndex },
|
|
(_, i) => i + 1
|
|
).every((step) => isStepValid(step));
|
|
|
|
if (canNavigate) {
|
|
setStep(stepIndex + 1);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div className="w-full py-6">
|
|
<div className="flex items-center min-h-[100px]">
|
|
{visibleSteps.map((step, index) => {
|
|
if (step === '...') {
|
|
return (
|
|
<div
|
|
key={`dots-${index}`}
|
|
className="flex-1 flex items-center justify-center"
|
|
>
|
|
<Dots />
|
|
{index !== visibleSteps.length - 1 && (
|
|
<SpacerStep isCompleted={false} />
|
|
)}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
const originalIndex = steps.indexOf(step);
|
|
return (
|
|
<div
|
|
key={index}
|
|
className={`
|
|
flex-1 relative
|
|
${Array.from({ length: originalIndex + 1 }, (_, i) => i + 1).every((s) => isStepValid(s)) ? 'cursor-pointer' : 'cursor-not-allowed'}
|
|
`}
|
|
onClick={() => handleStepClick(originalIndex)}
|
|
>
|
|
<div className="flex items-center">
|
|
<div className="w-full flex items-center">
|
|
<Step
|
|
number={originalIndex + 1}
|
|
title={stepTitles ? stepTitles[originalIndex + 1] : step}
|
|
isActive={currentStep === originalIndex + 1}
|
|
isCompleted={currentStep > originalIndex + 1}
|
|
isValid={isStepValid(originalIndex + 1)}
|
|
/>
|
|
{index !== visibleSteps.length - 1 && (
|
|
<SpacerStep isCompleted={currentStep > originalIndex + 1} />
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
})}
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default ProgressStep;
|