{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "stepper",
  "type": "registry:ui",
  "title": "Stepper",
  "description": "Static and interactive workflow steps with horizontal and vertical layouts.",
  "dependencies": [
    "lucide-react@^1.27.0"
  ],
  "registryDependencies": [
    "http://127.0.0.1:3000/r/core.json"
  ],
  "files": [
    {
      "path": "src/registry/sources/control-ui/ui/stepper.tsx",
      "target": "@components/control-ui/ui/stepper.tsx",
      "type": "registry:ui",
      "content": "\"use client\";\n\nimport { CheckIcon, CircleAlertIcon } from \"lucide-react\";\nimport { createContext, useContext, useId, useState } from \"react\";\nimport type {\n  StepperContentMode,\n  StepperContentProps,\n  StepperDescriptionProps,\n  StepperIndicatorProps,\n  StepperItemProps,\n  StepperListProps,\n  StepperOrientation,\n  StepperProps,\n  StepperSeparatorProps,\n  StepperState,\n  StepperTitleProps,\n  StepperTriggerProps,\n} from \"@/components/control-ui/contracts\";\nimport { cn } from \"@/components/control-ui/lib/cn\";\nimport { skinSlot } from \"@/components/control-ui/skin\";\n\ntype StepperContextValue = {\n  value: number | null;\n  orientation: StepperOrientation;\n  contentMode: StepperContentMode;\n  responsive: boolean;\n  baseId: string;\n  selectStep: (step: number) => void;\n};\n\nconst StepperContext = createContext<StepperContextValue | null>(null);\n\nfunction useStepper() {\n  const context = useContext(StepperContext);\n  if (!context) throw new Error(\"Stepper parts must be used within <Stepper>.\");\n  return context;\n}\n\ntype StepperItemContextValue = {\n  step: number;\n  state: StepperState;\n  disabled: boolean;\n  invalid: boolean;\n  titleId: string;\n  contentId: string;\n};\n\nconst StepperItemContext = createContext<StepperItemContextValue | null>(null);\n\nfunction useStepperItem() {\n  const context = useContext(StepperItemContext);\n  if (!context) throw new Error(\"Stepper item parts must be used within <StepperItem>.\");\n  return context;\n}\n\nfunction stateForStep(value: number | null, step: number): StepperState {\n  if (value === null) return \"neutral\";\n  if (step < value) return \"complete\";\n  if (step === value) return \"current\";\n  return \"upcoming\";\n}\n\nfunction indicatorStateLabel(state: StepperState, invalid: boolean): string | null {\n  if (invalid) return \"invalid\";\n  if (state === \"neutral\") return null;\n  return state;\n}\n\nfunction defaultIndicatorContent(step: number, state: StepperState, invalid: boolean) {\n  if (invalid) return <CircleAlertIcon className=\"size-4\" />;\n  if (state === \"complete\") return <CheckIcon className=\"size-4\" />;\n  return step + 1;\n}\n\nfunction itemLayout(orientation: StepperOrientation, responsive: boolean) {\n  if (orientation === \"vertical\") {\n    return \"grid min-w-0 grid-cols-[2rem_minmax(0,1fr)] pb-6 text-left last:pb-0\";\n  }\n  return cn(\n    \"relative grid min-w-0 flex-1 grid-cols-1 justify-items-center text-center\",\n    responsive &&\n      \"@max-md/stepper:w-full @max-md/stepper:flex-none @max-md/stepper:grid-cols-[2rem_minmax(0,1fr)] @max-md/stepper:justify-items-stretch @max-md/stepper:pb-6 @max-md/stepper:text-left @max-md/stepper:last:pb-0\",\n  );\n}\n\nfunction partLayout(orientation: StepperOrientation, responsive: boolean, part: \"indicator\" | \"title\" | \"description\") {\n  const vertical = {\n    indicator: \"col-start-1 row-span-2 row-start-1\",\n    title: \"col-start-2 row-start-1 ml-3\",\n    description: \"col-start-2 row-start-2 ml-3\",\n  }[part];\n  if (orientation === \"vertical\") return vertical;\n\n  const horizontal = {\n    indicator: \"col-start-1 row-start-1\",\n    title: \"col-start-1 row-start-2 mt-2\",\n    description: \"col-start-1 row-start-3 mt-0.5\",\n  }[part];\n  if (!responsive) return horizontal;\n\n  const narrow = {\n    indicator: \"@max-md/stepper:col-start-1 @max-md/stepper:row-span-2 @max-md/stepper:row-start-1\",\n    title: \"@max-md/stepper:col-start-2 @max-md/stepper:row-start-1 @max-md/stepper:mt-0 @max-md/stepper:ml-3\",\n    description: \"@max-md/stepper:col-start-2 @max-md/stepper:row-start-2 @max-md/stepper:mt-0 @max-md/stepper:ml-3\",\n  }[part];\n  return cn(horizontal, narrow);\n}\n\nexport function Stepper({\n  value,\n  defaultValue = null,\n  onValueChange,\n  orientation = \"horizontal\",\n  contentMode = \"current\",\n  responsive = true,\n  id,\n  className,\n  children,\n  ...props\n}: StepperProps) {\n  const generatedId = useId();\n  const [internalValue, setInternalValue] = useState<number | null>(defaultValue);\n  const controlled = value !== undefined;\n  const currentValue = value !== undefined ? value : internalValue;\n\n  const selectStep = (step: number) => {\n    if (!controlled) setInternalValue(step);\n    onValueChange?.(step);\n  };\n\n  return (\n    <StepperContext.Provider\n      value={{\n        value: currentValue,\n        orientation,\n        contentMode,\n        responsive,\n        baseId: id ?? generatedId,\n        selectStep,\n      }}\n    >\n      <div\n        {...props}\n        id={id}\n        data-control-ui=\"stepper\"\n        data-slot=\"root\"\n        data-orientation={orientation}\n        data-content-mode={contentMode}\n        data-responsive={responsive ? \"true\" : undefined}\n        className={cn(\n          responsive && \"@container/stepper\",\n          \"w-full\",\n          skinSlot(\"stepper\", \"root\", { orientation, contentMode, responsive }),\n          className,\n        )}\n      >\n        {children}\n      </div>\n    </StepperContext.Provider>\n  );\n}\n\nexport function StepperList({ className, ...props }: StepperListProps) {\n  const { orientation, responsive } = useStepper();\n  return (\n    <ol\n      {...props}\n      data-control-ui=\"stepper\"\n      data-slot=\"list\"\n      data-orientation={orientation}\n      className={cn(\n        orientation === \"horizontal\" ? \"flex w-full items-start\" : \"flex w-full flex-col\",\n        orientation === \"horizontal\" && responsive && \"@max-md/stepper:flex-col @max-md/stepper:items-stretch\",\n        skinSlot(\"stepper\", \"list\", { orientation, responsive }),\n        className,\n      )}\n    />\n  );\n}\n\nexport function StepperItem({ step, disabled = false, invalid = false, className, children, ...props }: StepperItemProps) {\n  const { value, orientation, responsive, baseId } = useStepper();\n  const state = stateForStep(value, step);\n  const contextValue = {\n    step,\n    state,\n    disabled,\n    invalid,\n    titleId: `${baseId}-step-${step}-title`,\n    contentId: `${baseId}-step-${step}-content`,\n  } satisfies StepperItemContextValue;\n\n  return (\n    <StepperItemContext.Provider value={contextValue}>\n      <li\n        {...props}\n        data-control-ui=\"stepper\"\n        data-slot=\"item\"\n        data-step={step}\n        data-state={state}\n        data-disabled={disabled ? \"true\" : undefined}\n        data-invalid={invalid ? \"true\" : undefined}\n        aria-current={state === \"current\" ? \"step\" : undefined}\n        className={cn(\n          \"relative\",\n          itemLayout(orientation, responsive),\n          skinSlot(\"stepper\", \"item\", { state, disabled, invalid }),\n          className,\n        )}\n      >\n        {children}\n      </li>\n    </StepperItemContext.Provider>\n  );\n}\n\nexport function StepperTrigger({ disabled: disabledProp, className, onClick, type = \"button\", ...props }: StepperTriggerProps) {\n  const { orientation, responsive, selectStep } = useStepper();\n  const { step, state, disabled, invalid, contentId } = useStepperItem();\n  const isDisabled = disabled || Boolean(disabledProp);\n\n  return (\n    <button\n      {...props}\n      data-control-ui=\"stepper\"\n      data-slot=\"trigger\"\n      data-state={state}\n      data-invalid={invalid ? \"true\" : undefined}\n      type={type}\n      disabled={isDisabled}\n      aria-controls={contentId}\n      className={cn(\n        \"group/stepper-trigger col-span-full row-span-3 row-start-1 grid w-full min-w-0 grid-cols-1 justify-items-center rounded-sm text-center outline-none transition-colors duration-[var(--duration-fast)] ease-[var(--ease-standard)] focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50\",\n        orientation === \"vertical\" && \"row-span-2 grid-cols-[2rem_minmax(0,1fr)] justify-items-stretch text-left\",\n        orientation === \"horizontal\" &&\n          responsive &&\n          \"@max-md/stepper:row-span-2 @max-md/stepper:grid-cols-[2rem_minmax(0,1fr)] @max-md/stepper:justify-items-stretch @max-md/stepper:text-left\",\n        skinSlot(\"stepper\", \"trigger\", { state, disabled: isDisabled, invalid }),\n        className,\n      )}\n      onClick={(event) => {\n        onClick?.(event);\n        if (!event.defaultPrevented && !isDisabled) selectStep(step);\n      }}\n    />\n  );\n}\n\nexport function StepperIndicator({ className, children, ...props }: StepperIndicatorProps) {\n  const { orientation, responsive } = useStepper();\n  const { step, state, disabled, invalid } = useStepperItem();\n  const stateLabel = indicatorStateLabel(state, invalid);\n  const status = [`Step ${step + 1}`, stateLabel, disabled ? \"disabled\" : null].filter(Boolean).join(\", \");\n  const indicatorContent = children ?? defaultIndicatorContent(step, state, invalid);\n\n  return (\n    <span\n      {...props}\n      data-control-ui=\"stepper\"\n      data-slot=\"indicator\"\n      data-state={state}\n      data-invalid={invalid ? \"true\" : undefined}\n      className={cn(\n        \"relative z-10 flex size-8 shrink-0 items-center justify-center rounded-full border bg-background text-xs font-semibold tabular-nums text-muted-foreground transition-[color,background-color,border-color,box-shadow] duration-[var(--duration-base)] ease-[var(--ease-standard)]\",\n        state === \"complete\" && !invalid && \"border-primary bg-primary text-primary-foreground\",\n        state === \"current\" && !invalid && \"border-primary text-primary-text ring-4 ring-primary/10\",\n        invalid && \"border-dashed border-destructive bg-destructive/10 text-destructive-text\",\n        partLayout(orientation, responsive, \"indicator\"),\n        skinSlot(\"stepper\", \"indicator\", { state, invalid }),\n        className,\n      )}\n    >\n      <span aria-hidden=\"true\">{indicatorContent}</span>\n      <span className=\"sr-only\">{status}</span>\n    </span>\n  );\n}\n\nexport function StepperSeparator({ className, ...props }: StepperSeparatorProps) {\n  const { orientation, responsive } = useStepper();\n  const { state, invalid } = useStepperItem();\n  return (\n    <span\n      {...props}\n      data-control-ui=\"stepper\"\n      data-slot=\"separator\"\n      data-state={state}\n      aria-hidden=\"true\"\n      className={cn(\n        \"absolute bg-border transition-colors duration-[var(--duration-base)] ease-[var(--ease-standard)]\",\n        state === \"complete\" && !invalid && \"bg-primary\",\n        orientation === \"horizontal\" && \"top-4 right-[calc(-50%+1rem)] left-[calc(50%+1rem)] h-px\",\n        orientation === \"vertical\" && \"top-8 bottom-0 left-4 w-px\",\n        orientation === \"horizontal\" &&\n          responsive &&\n          \"@max-md/stepper:top-8 @max-md/stepper:right-auto @max-md/stepper:bottom-0 @max-md/stepper:left-4 @max-md/stepper:h-auto @max-md/stepper:w-px\",\n        skinSlot(\"stepper\", \"separator\", { state, invalid }),\n        className,\n      )}\n    />\n  );\n}\n\nexport function StepperTitle({ className, ...props }: StepperTitleProps) {\n  const { orientation, responsive } = useStepper();\n  const { titleId } = useStepperItem();\n  return (\n    <span\n      {...props}\n      id={titleId}\n      data-control-ui=\"stepper\"\n      data-slot=\"title\"\n      className={cn(\n        \"min-w-0 text-sm font-medium leading-tight text-foreground\",\n        partLayout(orientation, responsive, \"title\"),\n        skinSlot(\"stepper\", \"title\", {}),\n        className,\n      )}\n    />\n  );\n}\n\nexport function StepperDescription({ className, ...props }: StepperDescriptionProps) {\n  const { orientation, responsive } = useStepper();\n  return (\n    <span\n      {...props}\n      data-control-ui=\"stepper\"\n      data-slot=\"description\"\n      className={cn(\n        \"min-w-0 text-sm leading-relaxed text-muted-foreground\",\n        partLayout(orientation, responsive, \"description\"),\n        skinSlot(\"stepper\", \"description\", {}),\n        className,\n      )}\n    />\n  );\n}\n\nexport function StepperContent({ step, keepMounted = true, className, ...props }: StepperContentProps) {\n  const { value, contentMode, baseId } = useStepper();\n  const active = value === step;\n  const hidden = contentMode === \"current\" && !active;\n  if (hidden && !keepMounted) return null;\n\n  return (\n    <section\n      {...props}\n      id={`${baseId}-step-${step}-content`}\n      data-control-ui=\"stepper\"\n      data-slot=\"content\"\n      data-state={active ? \"active\" : \"inactive\"}\n      hidden={hidden}\n      aria-labelledby={`${baseId}-step-${step}-title`}\n      className={cn(\"mt-6 outline-none\", skinSlot(\"stepper\", \"content\", { active }), className)}\n    />\n  );\n}\n"
    }
  ],
  "meta": {}
}
