SetupStepper.tsx 2.8 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879
  1. 'use client'
  2. import { Check } from 'lucide-react'
  3. import { useTranslations } from 'next-intl'
  4. import { cn } from '@/lib/utils'
  5. export const SETUP_STEPS = ['workshop', 'region', 'tax', 'ready'] as const
  6. export type SetupStep = (typeof SETUP_STEPS)[number]
  7. /** Where the owner is in the four steps; a step already seen can be revisited. */
  8. export function SetupStepper({
  9. current,
  10. reached,
  11. onGo,
  12. }: {
  13. current: SetupStep
  14. /** The furthest step opened so far. */
  15. reached: number
  16. onGo: (step: SetupStep) => void
  17. }) {
  18. const t = useTranslations('onboarding.setup')
  19. const currentIndex = SETUP_STEPS.indexOf(current)
  20. return (
  21. <ol aria-label={t('stepsLabel')} className="flex flex-wrap gap-1.5">
  22. {SETUP_STEPS.map((step, index) => {
  23. const isCurrent = index === currentIndex
  24. const isDone = index < currentIndex || (index <= reached && !isCurrent)
  25. const reachable = index <= reached && !isCurrent
  26. const badge = (
  27. <span
  28. className={cn(
  29. 'inline-flex h-[22px] w-[22px] items-center justify-center rounded-full text-xs',
  30. isCurrent && 'bg-primary text-primary-foreground',
  31. isDone && 'bg-accent text-foreground',
  32. !isCurrent && !isDone && 'border border-border'
  33. )}
  34. >
  35. {isDone ? <Check className="h-3 w-3" strokeWidth={3} aria-hidden="true" /> : index + 1}
  36. </span>
  37. )
  38. // On a phone only the current step is named on screen; the others keep
  39. // their names for screen readers.
  40. const label = (
  41. <span className={isCurrent ? undefined : 'sr-only sm:not-sr-only'}>
  42. {t(`steps.${step}`)}
  43. </span>
  44. )
  45. return (
  46. <li key={step}>
  47. {reachable ? (
  48. <button
  49. type="button"
  50. onClick={() => onGo(step)}
  51. className="flex items-center gap-2 rounded-full py-1.5 pr-1.5 pl-1.5 text-[13px] font-medium text-foreground transition-colors sm:pr-3 hover:bg-accent/70 focus-visible:ring-2 focus-visible:ring-ring focus-visible:outline-none"
  52. >
  53. {badge}
  54. {label}
  55. </button>
  56. ) : (
  57. <span
  58. aria-current={isCurrent ? 'step' : undefined}
  59. className={cn(
  60. 'flex items-center gap-2 rounded-full py-1.5 pl-1.5 text-[13px]',
  61. isCurrent ? 'pr-3' : 'pr-1.5 sm:pr-3',
  62. isCurrent
  63. ? 'bg-foreground font-semibold text-background'
  64. : 'font-medium text-muted-foreground'
  65. )}
  66. >
  67. {badge}
  68. {label}
  69. </span>
  70. )}
  71. </li>
  72. )
  73. })}
  74. </ol>
  75. )
  76. }