{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "ink/wizard",
  "title": "Wizard",
  "description": "Multi-step wizard with per-step validation",
  "dependencies": [
    "ink"
  ],
  "registryDependencies": [
    "https://termcn.dev/r/ink/interaction.json",
    "https://termcn.dev/r/ink/terminal-symbols.json",
    "https://termcn.dev/r/ink/use-theme.json",
    "https://termcn.dev/r/ink/use-unicode.json"
  ],
  "files": [
    {
      "path": "registry/ui/wizard.tsx",
      "content": "import { Box, Text } from \"ink\";\nimport React, { useState } from \"react\";\nimport type { ReactNode } from \"react\";\n\nimport {\n  isActivationKey,\n  useInteraction,\n} from \"@/hooks/use-interaction\";\nimport type { InteractionProps } from \"@/hooks/use-interaction\";\nimport { useTheme } from \"@/hooks/use-theme\";\nimport { useUnicode } from \"@/hooks/use-unicode\";\nimport {\n  resolveStatusSymbol,\n  resolveTerminalSymbol,\n} from \"@/lib/terminal-symbols\";\n\nexport interface WizardStep {\n  key: string;\n  title: string;\n  content: ReactNode;\n  validate?: () => boolean | string;\n}\n\nexport interface WizardProps extends InteractionProps {\n  steps: WizardStep[];\n  onComplete?: (completedSteps: string[]) => void;\n  onCancel?: () => void;\n  showProgress?: boolean;\n  \"aria-label\"?: string;\n}\n\nexport const Wizard = ({\n  steps,\n  onComplete,\n  onCancel,\n  showProgress = true,\n  id,\n  autoFocus,\n  isActive,\n  disabled,\n  \"aria-label\": ariaLabel = \"Wizard\",\n}: WizardProps) => {\n  const theme = useTheme();\n  const unicode = useUnicode();\n  const [currentStep, setCurrentStep] = useState(0);\n  const [validationError, setValidationError] = useState<string | null>(null);\n\n  const isLast = currentStep === steps.length - 1;\n  const isFirst = currentStep === 0;\n\n  const goForward = () => {\n    const step = steps[currentStep];\n    if (step?.validate) {\n      const result = step.validate();\n      if (result !== true) {\n        setValidationError(\n          typeof result === \"string\" ? result : \"Validation failed\"\n        );\n        return;\n      }\n    }\n    setValidationError(null);\n    if (isLast) {\n      onComplete?.(steps.map((wizardStep) => wizardStep.key));\n    } else {\n      setCurrentStep((index) => index + 1);\n    }\n  };\n\n  const { isFocused } = useInteraction(\n    (input, key) => {\n      if (key.escape) {\n        onCancel?.();\n        return;\n      }\n\n      const goNext = key.rightArrow || isActivationKey(input, key);\n      const goBack = key.leftArrow;\n\n      if (goNext) {\n        goForward();\n      } else if (goBack) {\n        setValidationError(null);\n        if (!isFirst) {\n          setCurrentStep((i) => i - 1);\n        }\n      }\n    },\n    { autoFocus, disabled, id, isActive }\n  );\n\n  return (\n    <Box\n      flexDirection=\"column\"\n      gap={1}\n      aria-role=\"list\"\n      aria-state={{ disabled: disabled || undefined }}\n    >\n      <Text\n        aria-label={`${ariaLabel}. Step ${currentStep + 1} of ${steps.length}: ${steps[currentStep]?.title ?? \"Unknown\"}.`}\n      >\n        {\"\"}\n      </Text>\n      {showProgress && (\n        <Box flexDirection=\"row\" alignItems=\"center\">\n          {steps.map((step, index) => {\n            const isCompleted = index < currentStep;\n            const isCurrent = index === currentStep;\n\n            let icon: string;\n            let iconColor: string;\n            if (isCompleted) {\n              icon = resolveTerminalSymbol(unicode, \"●\", \"[x]\");\n              iconColor = theme.colors.success;\n            } else if (isCurrent) {\n              icon = resolveTerminalSymbol(unicode, \"◉\", \"[*]\");\n              iconColor = theme.colors.primary;\n            } else {\n              icon = resolveTerminalSymbol(unicode, \"○\", \"[ ]\");\n              iconColor = theme.colors.mutedForeground;\n            }\n\n            return (\n              <Box\n                key={step.key}\n                flexDirection=\"row\"\n                alignItems=\"center\"\n                aria-role=\"listitem\"\n                aria-label={`${step.title}, ${isCompleted ? \"completed\" : isCurrent ? \"current\" : \"upcoming\"}`}\n              >\n                <Text color={iconColor} bold={isCurrent}>\n                  {icon} {step.title}\n                </Text>\n                {index < steps.length - 1 && (\n                  <Text color={theme.colors.mutedForeground}>\n                    {unicode ? \" ─── \" : \" --- \"}\n                  </Text>\n                )}\n              </Box>\n            );\n          })}\n        </Box>\n      )}\n\n      <Box flexDirection=\"column\" gap={1}>\n        {steps[currentStep]?.content}\n      </Box>\n\n      {validationError && (\n        <Text\n          color={theme.colors.error}\n          aria-label={`Error: ${validationError}`}\n        >\n          <Text aria-hidden>{resolveStatusSymbol(unicode, \"error\")} </Text>\n          {validationError}\n        </Text>\n      )}\n\n      <Box flexDirection=\"row\" gap={2} aria-role=\"toolbar\">\n        {!isFirst && (\n          <Box aria-role=\"button\" aria-label=\"Back\">\n            <Text color={theme.colors.mutedForeground}>\n              [{resolveTerminalSymbol(unicode, \"←\", \"<-\")} Back]\n            </Text>\n          </Box>\n        )}\n        {isLast ? (\n          <Box aria-role=\"button\" aria-label=\"Finish\">\n            <Text color={theme.colors.primary} bold inverse={isFocused}>\n              [Finish]\n            </Text>\n          </Box>\n        ) : (\n          <Box aria-role=\"button\" aria-label=\"Next\">\n            <Text color={theme.colors.primary} bold inverse={isFocused}>\n              [Next {resolveTerminalSymbol(unicode, \"→\", \"->\")}]\n            </Text>\n          </Box>\n        )}\n      </Box>\n    </Box>\n  );\n};\n",
      "type": "registry:ui"
    }
  ],
  "categories": [
    "forms"
  ],
  "type": "registry:ui"
}