stepper-header.tsx 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171
  1. "use client";
  2. import React from "react";
  3. import { useTheme } from "next-themes";
  4. import { Check } from "lucide-react";
  5. import { cn } from "@/shared/lib/utils";
  6. import { StepperStepProps } from "../types";
  7. interface StepperHeaderProps {
  8. steps: StepperStepProps[];
  9. currentStep: number;
  10. onStepClick?: (stepNumber: number) => void;
  11. }
  12. function StepperStep({
  13. description,
  14. isActive,
  15. isCompleted,
  16. stepNumber,
  17. title,
  18. currentStep,
  19. onStepClick,
  20. }: StepperStepProps & { currentStep: number; onStepClick?: (stepNumber: number) => void }) {
  21. const canClick = stepNumber < currentStep || isCompleted;
  22. const handleClick = () => {
  23. if (canClick && onStepClick) {
  24. onStepClick(stepNumber);
  25. }
  26. };
  27. return (
  28. <>
  29. {/* Layout mobile - vertical avec texte à droite */}
  30. <div className="flex items-center text-left md:hidden">
  31. {/* Cercle */}
  32. <div
  33. className={cn(
  34. "flex h-12 w-12 items-center justify-center rounded-full border-2 transition-all duration-200 flex-shrink-0",
  35. {
  36. "border-green-500 bg-green-500 text-white": isCompleted,
  37. "border-blue-500 bg-blue-500 text-white": isActive,
  38. "border-gray-300 bg-gray-100 text-gray-400 dark:border-gray-600 dark:bg-gray-800 dark:text-gray-500":
  39. !isActive && !isCompleted,
  40. },
  41. canClick ? "cursor-pointer hover:scale-105" : "cursor-default",
  42. )}
  43. onClick={handleClick}
  44. >
  45. {isCompleted ? <Check className="h-6 w-6" /> : <span className="text-sm font-semibold">{stepNumber}</span>}
  46. </div>
  47. {/* Contenu textuel à droite */}
  48. <div className="ml-4">
  49. <h3
  50. className={cn("font-semibold text-sm transition-colors", {
  51. "text-green-600 dark:text-green-400": isCompleted,
  52. "text-blue-600 dark:text-blue-400": isActive,
  53. "text-gray-500 dark:text-gray-400": !isActive && !isCompleted,
  54. })}
  55. >
  56. {title}
  57. </h3>
  58. <p
  59. className={cn(
  60. "text-xs mt-1 transition-colors",
  61. isActive || isCompleted ? "text-gray-600 dark:text-gray-300" : "text-gray-400 dark:text-gray-500",
  62. )}
  63. >
  64. {description}
  65. </p>
  66. </div>
  67. </div>
  68. {/* Layout desktop - horizontal avec texte en bas */}
  69. <div className="hidden md:flex flex-col items-center text-center">
  70. {/* Cercle */}
  71. <div
  72. className={cn(
  73. "flex h-12 w-12 items-center justify-center rounded-full border-2 transition-all duration-200",
  74. {
  75. "border-green-500 bg-green-500 text-white": isCompleted,
  76. "border-blue-500 bg-blue-500 text-white": isActive,
  77. "border-gray-300 bg-gray-100 text-gray-400 dark:border-gray-600 dark:bg-gray-800 dark:text-gray-500":
  78. !isActive && !isCompleted,
  79. },
  80. canClick ? "cursor-pointer hover:scale-105" : "cursor-default",
  81. )}
  82. onClick={handleClick}
  83. >
  84. {isCompleted ? <Check className="h-6 w-6" /> : <span className="text-sm font-semibold">{stepNumber}</span>}
  85. </div>
  86. {/* Contenu textuel en bas */}
  87. <div className="mt-3">
  88. <h3
  89. className={cn("font-semibold text-sm transition-colors", {
  90. "text-green-600 dark:text-green-400": isCompleted,
  91. "text-blue-600 dark:text-blue-400": isActive,
  92. "text-gray-500 dark:text-gray-400": !isActive && !isCompleted,
  93. })}
  94. >
  95. {title}
  96. </h3>
  97. <p
  98. className={cn(
  99. "text-xs mt-1 transition-colors",
  100. isActive || isCompleted ? "text-gray-600 dark:text-gray-300" : "text-gray-400 dark:text-gray-500",
  101. )}
  102. >
  103. {description}
  104. </p>
  105. </div>
  106. </div>
  107. </>
  108. );
  109. }
  110. export function StepperHeader({ steps, currentStep, onStepClick }: StepperHeaderProps) {
  111. const { resolvedTheme } = useTheme();
  112. return (
  113. <div className={cn("w-full my-8 px-2 sm:px-6")}>
  114. {/* Layout mobile - vertical */}
  115. <div className="flex flex-col space-y-6 md:hidden">
  116. {steps.map((step, index) => (
  117. <div className="relative" key={step.stepNumber}>
  118. <StepperStep {...step} currentStep={currentStep} onStepClick={onStepClick} />
  119. {/* Ligne de connexion verticale */}
  120. {index < steps.length - 1 && (
  121. <div className="absolute left-6 top-12 w-0.5 h-6 -translate-x-0.5">
  122. <div
  123. className={cn(
  124. "w-full h-full transition-colors duration-300",
  125. step.isCompleted ? "bg-green-500" : "bg-gray-300 dark:bg-gray-600",
  126. )}
  127. />
  128. </div>
  129. )}
  130. </div>
  131. ))}
  132. </div>
  133. {/* Layout desktop - horizontal */}
  134. <div className="hidden md:flex items-start">
  135. {steps.map((step, index) => (
  136. <React.Fragment key={step.stepNumber}>
  137. {/* Étape */}
  138. <div className="flex flex-col items-center">
  139. <StepperStep {...step} currentStep={currentStep} onStepClick={onStepClick} />
  140. </div>
  141. {/* Ligne de connexion horizontale */}
  142. {index < steps.length - 1 && (
  143. <div className="flex-1 flex items-center pt-6">
  144. <div
  145. className={cn(
  146. "w-full h-1 transition-colors duration-300",
  147. step.isCompleted ? "bg-green-500" : "bg-gray-300 dark:bg-gray-600",
  148. )}
  149. />
  150. </div>
  151. )}
  152. </React.Fragment>
  153. ))}
  154. </div>
  155. </div>
  156. );
  157. }