{ "$schema": "https://ui.shadcn.com/schema/registry-item.json", "name": "steps", "title": "Steps", "description": "A segmented steps navigation component for multi-step flows.", "files": [ { "path": "registry/custom-ui/steps.tsx", "content": "\"use client\";\n\nimport * as React from \"react\";\n\nimport { cn } from \"@/lib/utils\";\n\nexport type StepStatus = \"complete\" | \"current\" | \"upcoming\";\n\nexport interface StepItem {\n id: string;\n title: React.ReactNode;\n description?: React.ReactNode;\n href?: string;\n status?: StepStatus;\n disabled?: boolean;\n}\n\nexport interface StepsProps extends Omit<\n React.ComponentProps<\"nav\">,\n \"children\" | \"onChange\"\n> {\n steps: StepItem[];\n currentStep?: string;\n currentStepIndex?: number;\n onStepSelect?: (step: StepItem, index: number) => void;\n showDescriptions?: boolean;\n}\n\nfunction getCurrentIndex(\n steps: StepItem[],\n currentStep?: string,\n currentStepIndex?: number,\n) {\n if (steps.length === 0) return -1;\n\n if (typeof currentStepIndex === \"number\") {\n return Math.min(Math.max(currentStepIndex, 0), steps.length - 1);\n }\n\n if (currentStep) {\n const index = steps.findIndex((step) => step.id === currentStep);\n return index >= 0 ? index : 0;\n }\n\n const explicitCurrentIndex = steps.findIndex(\n (step) => step.status === \"current\",\n );\n return explicitCurrentIndex >= 0 ? explicitCurrentIndex : 0;\n}\n\nfunction getStepStatus(\n step: StepItem,\n index: number,\n currentIndex: number,\n): StepStatus {\n if (step.status) return step.status;\n if (index < currentIndex) return \"complete\";\n if (index === currentIndex) return \"current\";\n return \"upcoming\";\n}\n\nfunction Steps({\n className,\n steps,\n currentStep,\n currentStepIndex,\n onStepSelect,\n showDescriptions = true,\n ...props\n}: StepsProps) {\n const currentIndex = getCurrentIndex(steps, currentStep, currentStepIndex);\n\n return (\n \n \n {steps.map((step, index) => {\n const status = getStepStatus(step, index, currentIndex);\n const interactive = !step.disabled && (step.href || onStepSelect);\n\n const content = (\n <>\n \n {step.title}\n \n {showDescriptions && step.description && (\n \n {step.description}\n \n )}\n \n );\n\n const itemClassName = cn(\n \"group/step flex h-full flex-col border-l-4 py-2 pl-4 text-left transition-colors sm:border-t-4 sm:border-l-0 sm:pt-4 sm:pb-0 sm:pl-0\",\n status === \"complete\" &&\n \"border-primary/60 hover:border-primary/80\",\n status === \"current\" && \"border-primary\",\n status === \"upcoming\" && \"border-muted\",\n interactive &&\n \"cursor-pointer hover:border-primary/70 focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:outline-none\",\n step.disabled &&\n \"pointer-events-none cursor-not-allowed opacity-50\",\n );\n\n return (\n \n {step.href ? (\n {\n if (step.disabled) event.preventDefault();\n }}\n >\n {content}\n \n ) : onStepSelect ? (\n onStepSelect(step, index)}\n type=\"button\"\n >\n {content}\n \n ) : (\n \n {content}\n \n )}\n \n );\n })}\n \n \n );\n}\n\nexport { Steps, getStepStatus };\n", "type": "registry:ui", "target": "components/custom-ui/steps.tsx" } ], "type": "registry:ui" }