Bin
2025-12-16 9e0b2ba2c317b1a86212f24cbae3195ad1f3dbfa
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
import { cn } from "@humansignal/ui";
 
interface StepperProps {
  steps: { title: string }[];
  currentStep: number;
  onStepClick?: (stepIndex: number) => void;
  isEditMode?: boolean;
}
 
const MAX_STEPS_COUNT = 4;
 
export const Stepper = ({ steps, currentStep, onStepClick, isEditMode = false }: StepperProps) => {
  // Calculate progress that aligns with circle centers
  const calculateProgressWidth = () => {
    if (currentStep === 0) return 0;
    if (currentStep >= steps.length - 1) return 100;
 
    // Calculate the position of the current step circle (left edge)
    const stepWidth = 100 / MAX_STEPS_COUNT;
    // Stop at the current step's circle, not extend to the next step
    const progressToCurrentStep = currentStep * stepWidth;
 
    return Math.max(0, Math.min(100, progressToCurrentStep));
  };
 
  const handleStepClick = (stepIndex: number) => {
    // In edit mode, allow clicking on all steps
    // In create mode, only allow clicking on completed steps
    if (onStepClick && (isEditMode || stepIndex < currentStep)) {
      onStepClick(stepIndex);
    }
  };
 
  return (
    <div className="w-full mb-tight py-base bg-neutral-background border-b border-neutral-border px-wide">
      <div className="flex flex-col">
        {/* Step titles at the top */}
        <div className="grid grid-cols-4 mb-tight">
          {steps.map((step, index) => (
            <div key={index} className="text-left">
              <span
                className={cn(
                  "text-body-small",
                  currentStep >= index ? "text-primary-content font-semibold" : "text-neutral-content-subtle",
                  // Make steps clickable in edit mode or completed steps in create mode
                  ((isEditMode && onStepClick) || (index < currentStep && onStepClick)) &&
                    "cursor-pointer hover:text-primary-content-subtle transition-colors",
                )}
                onClick={() => handleStepClick(index)}
              >
                {step.title}
              </span>
            </div>
          ))}
        </div>
 
        {/* Progress bar with integrated circles */}
        <div className="relative w-full h-6 flex items-center">
          {/* Background progress bar */}
          <div className="absolute inset-y-0 right-0 h-1 bg-neutral-emphasis rounded-full my-auto left-0" />
 
          {/* Progress fill */}
          <div
            className="absolute inset-y-0 h-1 left-0 bg-primary-surface transition-all duration-300 rounded-full my-auto "
            style={{
              width: `calc(${calculateProgressWidth()}% + 10px)`,
            }}
          />
 
          {/* Step circles positioned along the progress bar */}
          <div className="w-full grid grid-cols-4 absolute justify-center">
            {steps.map((step, index) => (
              <div key={index}>
                <div
                  className={cn(
                    "w-6 h-6 rounded-full flex items-center justify-center text-body-small border-2 transition-all duration-300",
                    currentStep > index
                      ? "bg-primary-surface text-primary-surface-content shadow-sm border-primary-surface cursor-pointer hover:bg-primary-emphasis" // completed - clickable
                      : currentStep === index
                        ? isEditMode
                          ? "bg-primary-surface text-primary-surface-content shadow-sm border-primary-surface cursor-pointer hover:bg-primary-emphasis" // current - clickable in edit mode
                          : "bg-primary-surface text-primary-surface-content shadow-sm border-primary-surface" // current - not clickable in create mode
                        : isEditMode
                          ? "bg-neutral-surface border-neutral-border text-neutral-content-subtle cursor-pointer hover:bg-neutral-emphasis" // upcoming - clickable in edit mode
                          : "bg-neutral-surface border-neutral-border text-neutral-content-subtle", // upcoming - not clickable in create mode
                  )}
                  onClick={() => handleStepClick(index)}
                >
                  {currentStep > index ? (
                    <svg
                      xmlns="http://www.w3.org/2000/svg"
                      width="12"
                      height="12"
                      viewBox="0 0 24 24"
                      fill="none"
                      stroke="currentColor"
                      strokeWidth="2"
                      strokeLinecap="round"
                      strokeLinejoin="round"
                      className="w-3 h-3"
                    >
                      <title>Line</title>
                      <polyline points="20 6 9 17 4 12" />
                    </svg>
                  ) : (
                    <span className="text-xs font-medium">{index + 1}</span>
                  )}
                </div>
              </div>
            ))}
          </div>
        </div>
      </div>
    </div>
  );
};