{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "stepper",
  "type": "registry:component",
  "title": "Stepper",
  "description": "Steps with a progress line that fills from one node to the next; the new step lights up as the stroke arrives and the checkmark draws itself on completion.",
  "author": "Marcelo Treviño Velázquez <https://furin.dev>",
  "dependencies": [],
  "categories": [
    "Navigation"
  ],
  "docs": "Installed in components/furin/stepper/. Docs, live controls and props table: https://furin.dev/components/stepper/",
  "files": [
    {
      "path": "registry/stepper/Stepper.tsx",
      "content": "\"use client\";\nimport { useEffect, useRef, useState } from \"react\";\nimport type { CSSProperties } from \"react\";\nimport \"./Stepper.css\";\n\nexport interface StepperStep {\n  /** Título del paso. */\n  label: string;\n  /** Texto de apoyo debajo del título. */\n  description?: string;\n}\n\nexport interface StepperProps {\n  /** Pasos: textos o { label, description }. */\n  steps?: (string | StepperStep)[];\n  /** Paso actual, desde 0 (modo controlado). */\n  current?: number;\n  /** Paso inicial (modo libre). */\n  defaultCurrent?: number;\n  /** Se llama al cambiar de paso. */\n  onChange?: (index: number) => void;\n  /** Dirección de la lista de pasos. */\n  orientation?: \"horizontal\" | \"vertical\";\n  /** Deja volver a un paso ya completado pulsándolo. */\n  clickable?: boolean;\n  /** Muestra los botones Atrás y Siguiente. */\n  controls?: boolean;\n  /** Color del progreso. */\n  color?: string;\n  /** Etiqueta accesible de la lista. */\n  label?: string;\n  /** Texto del botón para retroceder. */\n  backLabel?: string;\n  /** Texto del botón para avanzar. */\n  nextLabel?: string;\n  /** Texto del botón en el último paso. */\n  finishLabel?: string;\n  /** Texto del botón cuando todo está completo. */\n  restartLabel?: string;\n  /** Segundos entre avances automáticos (0 = apagado; se detiene al interactuar). */\n  autoplay?: number;\n  /** Clase extra para el contenedor. */\n  className?: string;\n  /** Estilos extra para el contenedor. */\n  style?: CSSProperties;\n}\n\nconst PASOS: StepperStep[] = [\n  { label: \"Account\", description: \"Your details\" },\n  { label: \"Team\", description: \"Invite people\" },\n  { label: \"Plan\", description: \"Pick one\" },\n  { label: \"Done\", description: \"Get to work\" },\n];\n\n/** Pasos con línea de progreso que se llena: el trazo avanza primero y el paso siguiente se enciende al llegar. */\nexport function Stepper({\n  steps = PASOS,\n  current,\n  defaultCurrent = 1,\n  onChange,\n  orientation = \"horizontal\",\n  clickable = true,\n  controls = true,\n  color = \"#8EA2FF\",\n  label = \"Progress\",\n  backLabel = \"Back\",\n  nextLabel = \"Next\",\n  finishLabel = \"Finish\",\n  restartLabel = \"Restart\",\n  autoplay = 0,\n  className = \"\",\n  style,\n}: StepperProps) {\n  const lista = steps.map((s) => (typeof s === \"string\" ? { label: s } : s));\n  const n = lista.length;\n  const [interno, setInterno] = useState(defaultCurrent);\n  const actual = Math.min(Math.max(current ?? interno, 0), n);\n  const previo = useRef(actual);\n  const tocado = useRef(false);\n  const dir = actual >= previo.current ? \"adelante\" : \"atras\";\n  useEffect(() => { previo.current = actual; });\n\n  const ir = (i: number) => {\n    const v = Math.min(Math.max(i, 0), n);\n    if (current === undefined) setInterno(v);\n    onChange?.(v);\n  };\n\n  // Avance automático para la demo: recorre los pasos, hace una pausa al terminar y vuelve a empezar.\n  useEffect(() => {\n    if (!autoplay || n < 2) return;\n    const id = window.setInterval(() => {\n      if (tocado.current || document.hidden) return;\n      ir(actual >= n ? 0 : actual + 1);\n    }, autoplay * 1000);\n    return () => window.clearInterval(id);\n  });\n\n  const vars = { \"--furin-stepper-color\": color } as CSSProperties;\n  const terminado = actual >= n;\n  return (\n    <div className={`furin-stepper furin-stepper--${orientation} furin-stepper--${dir} ${className}`} style={{ ...vars, ...style }} onPointerDown={() => { tocado.current = true; }}>\n      <ol className=\"furin-stepper__lista\" aria-label={label}>\n        {lista.map((s, i) => {\n          const estado = i < actual ? \"done\" : i === actual ? \"current\" : \"todo\";\n          const ultimo = i === n - 1;\n          const puedeVolver = clickable && i < actual;\n          const nodo = (\n            <>\n              <svg className=\"furin-stepper__check\" viewBox=\"0 0 16 16\" aria-hidden=\"true\"><path d=\"M3.6 8.4 6.7 11.4 12.4 4.8\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\" pathLength=\"1\" /></svg>\n              <span className=\"furin-stepper__num\" aria-hidden=\"true\">{String(i + 1).padStart(2, \"0\")}</span>\n            </>\n          );\n          return (\n            <li key={i} className={`furin-stepper__paso is-${estado}${ultimo ? \" is-ultimo\" : \"\"}`} aria-current={estado === \"current\" ? \"step\" : undefined}>\n              <span className=\"furin-stepper__fila\">\n                {puedeVolver ? (\n                  <button type=\"button\" className=\"furin-stepper__nodo\" aria-label={`Go back to step ${i + 1}: ${s.label}`} onClick={() => ir(i)}>{nodo}</button>\n                ) : (\n                  <span className=\"furin-stepper__nodo\">{nodo}</span>\n                )}\n                {!ultimo ? <span className=\"furin-stepper__linea\" aria-hidden=\"true\"><i className=\"furin-stepper__relleno\" /></span> : null}\n              </span>\n              <span className=\"furin-stepper__texto\">\n                <span className=\"furin-stepper__titulo\">{s.label}</span>\n                {s.description ? <span className=\"furin-stepper__desc\">{s.description}</span> : null}\n                <span className=\"furin-stepper__sr\">{estado === \"done\" ? \" (completado)\" : estado === \"current\" ? \" (paso actual)\" : \" (pendiente)\"}</span>\n              </span>\n            </li>\n          );\n        })}\n      </ol>\n      {controls ? (\n        <div className=\"furin-stepper__controles\">\n          <button type=\"button\" className=\"furin-stepper__btn\" disabled={actual <= 0} onClick={() => ir(actual - 1)}>{backLabel}</button>\n          <button type=\"button\" className=\"furin-stepper__btn furin-stepper__btn--primario\" onClick={() => ir(terminado ? 0 : actual + 1)}>\n            {terminado ? restartLabel : actual === n - 1 ? finishLabel : nextLabel}\n          </button>\n        </div>\n      ) : null}\n    </div>\n  );\n}\n\nexport default Stepper;\n",
      "type": "registry:component",
      "target": "components/furin/stepper/Stepper.tsx"
    },
    {
      "path": "registry/stepper/Stepper.css",
      "content": ".furin-stepper {\n  --furin-stepper-color: #8ea2ff;\n  --furin-stepper-nodo: 32px;\n  --furin-stepper-ease: cubic-bezier(0.65, 0, 0.35, 1);\n  display: grid;\n  gap: 26px;\n  width: min(440px, 94%);\n  font-family: Geist, system-ui, -apple-system, sans-serif;\n  color: #f2f3f5;\n}\n.furin-stepper__lista { margin: 0; padding: 0; list-style: none; display: flex; }\n.furin-stepper__paso { position: relative; flex: 1 1 0; display: grid; align-content: start; gap: 12px; min-width: 0; }\n.furin-stepper__paso.is-ultimo { flex: 0 0 auto; }\n.furin-stepper__fila { display: flex; align-items: center; }\n.furin-stepper__sr { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }\n\n/* Nodo: círculo con número (pendiente), anillo vivo (actual) o relleno con palomita (completo). */\n.furin-stepper__nodo {\n  position: relative;\n  display: grid;\n  place-items: center;\n  flex: none;\n  width: var(--furin-stepper-nodo);\n  height: var(--furin-stepper-nodo);\n  padding: 0;\n  border-radius: 50%;\n  border: 1px solid rgba(255, 255, 255, 0.16);\n  background: rgba(255, 255, 255, 0.03);\n  color: rgba(255, 255, 255, 0.45);\n  appearance: none;\n  font: inherit;\n  cursor: default;\n  transition: background-color 0.4s var(--furin-stepper-ease), border-color 0.4s var(--furin-stepper-ease), color 0.3s, transform 0.5s cubic-bezier(0.34, 1.7, 0.64, 1), box-shadow 0.5s;\n}\nbutton.furin-stepper__nodo { cursor: pointer; }\nbutton.furin-stepper__nodo:hover { transform: scale(1.08); }\nbutton.furin-stepper__nodo:focus-visible { outline: 2px solid rgba(255, 255, 255, 0.85); outline-offset: 3px; }\n.furin-stepper__num { font: 500 11.5px/1 \"JetBrains Mono\", ui-monospace, \"SF Mono\", Menlo, monospace; letter-spacing: 0.02em; transition: opacity 0.2s, transform 0.3s; }\n.furin-stepper__check { position: absolute; inset: 0; margin: auto; width: 16px; height: 16px; color: #0b0c0f; }\n.furin-stepper__check path { stroke-dasharray: 1; stroke-dashoffset: 1; transition: stroke-dashoffset 0.35s var(--furin-stepper-ease) 0s; }\n\n.is-current .furin-stepper__nodo {\n  border-color: var(--furin-stepper-color);\n  color: #fff;\n  background: color-mix(in srgb, var(--furin-stepper-color) 14%, transparent);\n  box-shadow: 0 0 0 4px color-mix(in srgb, var(--furin-stepper-color) 16%, transparent), 0 0 22px -4px color-mix(in srgb, var(--furin-stepper-color) 60%, transparent);\n}\n.is-current .furin-stepper__nodo::after {\n  content: \"\";\n  position: absolute;\n  inset: -1px;\n  border-radius: 50%;\n  border: 1px solid var(--furin-stepper-color);\n  animation: furin-stepper-pulso 2.2s ease-out infinite;\n}\n.is-done .furin-stepper__nodo { border-color: transparent; background: var(--furin-stepper-color); }\n.is-done .furin-stepper__num { opacity: 0; transform: scale(0.5); }\n.is-done .furin-stepper__check path { stroke-dashoffset: 0; transition-delay: 0.12s; }\n.is-todo .furin-stepper__check { opacity: 0; }\n.is-current .furin-stepper__check { opacity: 0; }\n\n/* Línea de progreso: se llena de un nodo al siguiente; el nodo nuevo se enciende cuando el trazo llega. */\n.furin-stepper__linea { position: relative; flex: 1; height: 2px; margin: 0 10px; border-radius: 2px; background: rgba(255, 255, 255, 0.1); overflow: hidden; }\n.furin-stepper__relleno { position: absolute; inset: 0; transform: scaleX(0); transform-origin: left center; background: linear-gradient(90deg, var(--furin-stepper-color), color-mix(in srgb, var(--furin-stepper-color) 70%, #fff)); transition: transform 0.55s var(--furin-stepper-ease); border-radius: inherit; }\n.is-done .furin-stepper__relleno { transform: scaleX(1); }\n/* Al avanzar, el nodo nuevo espera a que llegue la línea; al retroceder, el nodo se apaga primero. */\n.furin-stepper--adelante .is-current .furin-stepper__nodo { transition-delay: 0.42s; }\n.furin-stepper--adelante .is-current .furin-stepper__nodo::after { animation-delay: 0.42s; }\n.furin-stepper--atras .is-done .furin-stepper__relleno { transition-delay: 0s; }\n.furin-stepper--atras .furin-stepper__paso:not(.is-done) .furin-stepper__relleno { transition-delay: 0.1s; }\n\n.furin-stepper__texto { display: grid; gap: 3px; padding-right: 8px; }\n.furin-stepper__titulo { font-size: 14px; font-weight: 600; letter-spacing: -0.01em; color: rgba(255, 255, 255, 0.5); transition: color 0.35s; }\n.furin-stepper__desc { font-size: 12.5px; color: rgba(255, 255, 255, 0.34); transition: color 0.35s; }\n.is-current .furin-stepper__titulo, .is-done .furin-stepper__titulo { color: #fff; }\n.is-current .furin-stepper__desc, .is-done .furin-stepper__desc { color: rgba(255, 255, 255, 0.55); }\n\n/* Vertical */\n.furin-stepper--vertical .furin-stepper__lista { flex-direction: column; }\n.furin-stepper--vertical .furin-stepper__paso { flex: none; grid-template-columns: var(--furin-stepper-nodo) 1fr; column-gap: 16px; gap: 0 16px; padding-bottom: 26px; }\n.furin-stepper--vertical .furin-stepper__paso.is-ultimo { padding-bottom: 0; }\n.furin-stepper--vertical .furin-stepper__fila { flex-direction: column; align-self: stretch; }\n.furin-stepper--vertical .furin-stepper__linea { width: 2px; height: auto; flex: 1; margin: 6px 0 -20px; }\n.furin-stepper--vertical .furin-stepper__relleno { transform: scaleY(0); transform-origin: center top; background: linear-gradient(180deg, var(--furin-stepper-color), color-mix(in srgb, var(--furin-stepper-color) 70%, #fff)); }\n.furin-stepper--vertical .is-done .furin-stepper__relleno { transform: scaleY(1); }\n.furin-stepper--vertical .furin-stepper__texto { padding-top: 6px; align-content: start; }\n\n/* Botones */\n.furin-stepper__controles { display: flex; gap: 10px; justify-content: flex-end; }\n.furin-stepper__btn { appearance: none; padding: 10px 16px; border-radius: 10px; border: 1px solid rgba(255, 255, 255, 0.14); background: rgba(255, 255, 255, 0.05); font: 500 13.5px/1 Geist, system-ui, sans-serif; color: #e8eaee; cursor: pointer; transition: background-color 0.2s, opacity 0.2s, transform 0.2s; }\n.furin-stepper__btn:hover:not(:disabled) { background: rgba(255, 255, 255, 0.09); }\n.furin-stepper__btn:active:not(:disabled) { transform: scale(0.97); }\n.furin-stepper__btn:disabled { opacity: 0.35; cursor: default; }\n.furin-stepper__btn:focus-visible { outline: 2px solid rgba(255, 255, 255, 0.85); outline-offset: 2px; }\n.furin-stepper__btn--primario { background: #f3f4f7; color: #0b0c0f; border-color: transparent; }\n.furin-stepper__btn--primario:hover:not(:disabled) { background: #fff; }\n\n@keyframes furin-stepper-pulso { 0% { transform: scale(1); opacity: 0.7; } 80%, 100% { transform: scale(1.7); opacity: 0; } }\n@media (prefers-reduced-motion: reduce) {\n  .furin-stepper__nodo, .furin-stepper__relleno, .furin-stepper__check path, .furin-stepper__num { transition: none !important; }\n  .is-current .furin-stepper__nodo::after { animation: none; }\n}\n",
      "type": "registry:file",
      "target": "components/furin/stepper/Stepper.css"
    }
  ]
}
