{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "steps",
  "type": "registry:ui",
  "title": "Steps",
  "description": "Data-driven branching process indicator with an optional workflow controller, provider hooks, guarded future-step mutations, typed dynamic panels, semantic ordered-list structure, horizontal and vertical layouts, and reduced-motion-aware choreography.",
  "dependencies": [
    "motion@^12.43.0"
  ],
  "devDependencies": [],
  "registryDependencies": [
    "https://components.dethink.co.uk/r/dethink-base.json"
  ],
  "files": [
    {
      "path": "packages/components/src/components/steps/steps.tsx",
      "type": "registry:file",
      "target": "~/components/dethink/components/steps/steps.tsx",
      "content": "import {\n  forwardRef,\n  useEffect,\n  useId,\n  useMemo,\n  useState,\n  type ForwardedRef,\n  type HTMLAttributes,\n  type ReactElement,\n  type ReactNode,\n  type RefAttributes,\n} from \"react\";\nimport {\n  AnimatePresence,\n  LayoutGroup,\n  MotionConfig,\n  motion as motionElement,\n  useReducedMotion,\n  type Transition,\n} from \"motion/react\";\nimport { cn } from \"../../utils/cn\";\n\nexport type StepStatus =\n  \"complete\" | \"current\" | \"upcoming\" | \"error\" | \"skipped\";\n\nexport type StepsOrientation = \"horizontal\" | \"vertical\";\nexport type StepsSize = \"sm\" | \"md\" | \"lg\";\nexport type StepsMotionPreset = \"none\" | \"subtle\" | \"standard\" | \"expressive\";\n\nexport interface StepItemData<TData = unknown> {\n  id: string;\n  label: ReactNode;\n  description?: ReactNode;\n  icon?: ReactNode;\n  disabled?: boolean;\n  optional?: boolean;\n  status?: Exclude<StepStatus, \"current\">;\n  data?: TData;\n}\n\nexport interface StepRenderState {\n  index: number;\n  count: number;\n  current: boolean;\n  disabled: boolean;\n  interactive: boolean;\n  optional: boolean;\n  orientation: StepsOrientation;\n  size: StepsSize;\n  status: StepStatus;\n  percentage: number;\n}\n\nexport interface StepsProps<TData = unknown> extends Omit<\n  HTMLAttributes<HTMLDivElement>,\n  \"defaultValue\" | \"onChange\"\n> {\n  items: StepItemData<TData>[];\n  interactive?: boolean;\n  motionPreset?: StepsMotionPreset;\n  orientation?: StepsOrientation;\n  size?: StepsSize;\n  showProgress?: boolean;\n  progressValue?: number;\n  formatProgress?: (\n    percentage: number,\n    context: {\n      count: number;\n      currentIndex: number;\n      currentValue: string | undefined;\n    },\n  ) => ReactNode;\n  renderItem?: (item: StepItemData<TData>, state: StepRenderState) => ReactNode;\n  value?: string;\n  defaultValue?: string;\n  onValueChange?: (value: string) => void;\n}\n\nconst rootClasses = \"grid min-w-0 gap-[var(--dt-space-3)]\";\nconst horizontalViewportClasses =\n  \"-mt-[var(--dt-space-2)] min-w-0 overflow-x-auto overscroll-x-contain pb-[var(--dt-space-2)] pt-[var(--dt-space-2)]\";\nconst verticalViewportClasses = \"min-w-0 overflow-visible\";\nconst listBaseClasses = \"flex list-none p-0\";\nconst listOrientationClasses: Record<StepsOrientation, string> = {\n  horizontal: \"min-w-max items-start\",\n  vertical: \"min-w-0 flex-col items-stretch\",\n};\nconst itemBaseClasses = \"relative flex flex-1 items-start\";\nconst itemOrientationClasses: Record<StepsOrientation, string> = {\n  horizontal: \"min-w-36\",\n  vertical: \"w-full min-w-0 pb-[var(--dt-space-5)] last:pb-0\",\n};\nconst surfaceClasses =\n  \"relative z-[1] isolate flex w-full min-w-0 gap-[var(--dt-space-2)]\";\nconst surfaceOrientationClasses: Record<StepsOrientation, string> = {\n  horizontal: \"flex-col items-center px-[var(--dt-space-2)] text-center\",\n  vertical: \"flex-row items-start px-0 text-start\",\n};\nconst interactiveSurfaceClasses =\n  \"rounded-lg outline-none motion-safe:transition-[color,box-shadow,transform] motion-safe:duration-150 motion-safe:ease-out focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background active:scale-[0.98] disabled:cursor-not-allowed disabled:opacity-50 motion-reduce:transition-none\";\nconst hoverLayerClasses =\n  \"pointer-events-none absolute -z-[1] rounded-xl bg-primary/5 ring-1 ring-inset ring-primary/10\";\nconst hoverLayerOrientationClasses: Record<StepsOrientation, string> = {\n  horizontal: \"-inset-x-[var(--dt-space-1)] -inset-y-[var(--dt-space-2)]\",\n  vertical: \"-inset-x-[var(--dt-space-2)] -inset-y-[var(--dt-space-1)]\",\n};\nconst indicatorClasses =\n  \"relative isolate grid shrink-0 place-items-center rounded-full border font-semibold shadow-sm contrast-more:border-foreground\";\nconst indicatorSizeClasses: Record<StepsSize, string> = {\n  sm: \"size-7 text-[0.6875rem]\",\n  md: \"size-8 text-xs\",\n  lg: \"size-10 text-sm\",\n};\nconst bodyOrientationClasses: Record<StepsOrientation, string> = {\n  horizontal: \"flex max-w-44 flex-col items-center gap-1\",\n  vertical: \"flex min-w-0 flex-1 flex-col items-start gap-1 pt-0.5\",\n};\nconst labelSizeClasses: Record<StepsSize, string> = {\n  sm: \"text-xs leading-4\",\n  md: \"text-sm leading-5\",\n  lg: \"text-base leading-6\",\n};\nconst labelClasses = \"font-medium text-foreground\";\nconst descriptionClasses = \"text-xs leading-5 text-muted-foreground\";\nconst optionalClasses =\n  \"rounded-full bg-muted px-[var(--dt-space-2)] py-[var(--dt-space-0-5)] text-[0.6875rem] font-medium text-muted-foreground\";\nconst connectorBaseClasses = \"pointer-events-none absolute\";\nconst horizontalConnectorSizeClasses: Record<StepsSize, string> = {\n  sm: \"start-1/2 top-3.5 flex w-full -translate-y-1/2 items-center justify-center\",\n  md: \"start-1/2 top-4 flex w-full -translate-y-1/2 items-center justify-center\",\n  lg: \"start-1/2 top-5 flex w-full -translate-y-1/2 items-center justify-center\",\n};\nconst verticalConnectorSizeClasses: Record<StepsSize, string> = {\n  sm: \"bottom-0 start-3.5 top-7 w-0.5 -translate-x-1/2 rtl:translate-x-1/2\",\n  md: \"bottom-0 start-4 top-8 w-0.5 -translate-x-1/2 rtl:translate-x-1/2\",\n  lg: \"bottom-0 start-5 top-10 w-0.5 -translate-x-1/2 rtl:translate-x-1/2\",\n};\nconst horizontalConnectorLineClasses =\n  \"block h-px w-[clamp(3rem,34%,5rem)] rounded-full bg-border contrast-more:h-0.5\";\nconst progressClasses =\n  \"mx-auto flex max-w-full min-w-0 items-center justify-center gap-[var(--dt-space-2-5)]\";\nconst progressTrackClasses =\n  \"block h-1 w-20 shrink-0 overflow-hidden rounded-full bg-muted sm:w-24 contrast-more:ring-1 contrast-more:ring-border\";\nconst progressIndicatorClasses =\n  \"block h-full origin-left rounded-full bg-primary rtl:origin-right\";\nconst progressValueClasses =\n  \"min-w-0 text-xs font-medium leading-5 tabular-nums text-muted-foreground\";\n\nconst indicatorStatusClasses: Record<StepStatus, string> = {\n  complete:\n    \"border-primary bg-primary text-primary-foreground ring-2 ring-primary/15 ring-offset-2 ring-offset-background\",\n  current:\n    \"border-primary bg-primary text-primary-foreground ring-4 ring-primary/15 ring-offset-2 ring-offset-background\",\n  upcoming: \"border-border bg-background text-muted-foreground\",\n  error:\n    \"border-destructive bg-destructive text-destructive-foreground ring-2 ring-destructive/15 ring-offset-2 ring-offset-background\",\n  skipped: \"border-border bg-muted text-muted-foreground\",\n};\n\nconst statusLabels: Record<StepStatus, string> = {\n  complete: \"Complete\",\n  current: \"Current step\",\n  upcoming: \"Upcoming\",\n  error: \"Error\",\n  skipped: \"Skipped\",\n};\n\nconst currentMarkerClasses =\n  \"absolute -inset-1.5 -z-[1] rounded-full bg-primary/15 ring-1 ring-primary/25\";\n\nconst motionSettings: Record<\n  StepsMotionPreset,\n  { bounce: number; duration: number }\n> = {\n  none: { bounce: 0, duration: 0 },\n  subtle: { bounce: 0, duration: 0.16 },\n  standard: { bounce: 0.06, duration: 0.24 },\n  expressive: { bounce: 0.16, duration: 0.34 },\n};\n\nconst hoverMotionSettings: Record<\n  StepsMotionPreset,\n  { bounce: number; duration: number }\n> = {\n  none: { bounce: 0, duration: 0 },\n  subtle: { bounce: 0, duration: 0.14 },\n  standard: { bounce: 0.03, duration: 0.2 },\n  expressive: { bounce: 0.08, duration: 0.26 },\n};\n\nfunction createStepsTransition(\n  motionPreset: StepsMotionPreset,\n  reducedMotion: boolean,\n): Transition {\n  const settings = reducedMotion\n    ? motionSettings.none\n    : motionSettings[motionPreset];\n\n  if (settings.duration === 0) {\n    return { duration: 0 };\n  }\n\n  return {\n    type: \"spring\",\n    visualDuration: settings.duration,\n    bounce: settings.bounce,\n  };\n}\n\nfunction createStepsHoverTransition(\n  motionPreset: StepsMotionPreset,\n  reducedMotion: boolean,\n): Transition {\n  const settings = reducedMotion\n    ? hoverMotionSettings.none\n    : hoverMotionSettings[motionPreset];\n\n  if (settings.duration === 0) {\n    return { duration: 0 };\n  }\n\n  return {\n    type: \"spring\",\n    visualDuration: settings.duration,\n    bounce: settings.bounce,\n  };\n}\n\nfunction getResolvedStatus({\n  currentIndex,\n  index,\n  override,\n}: {\n  currentIndex: number;\n  index: number;\n  override?: Exclude<StepStatus, \"current\">;\n}): StepStatus {\n  if (override) {\n    return override;\n  }\n\n  if (index === currentIndex) {\n    return \"current\";\n  }\n\n  if (currentIndex >= 0 && index < currentIndex) {\n    return \"complete\";\n  }\n\n  return \"upcoming\";\n}\n\nfunction getDuplicateStepIds<TData>(items: StepItemData<TData>[]) {\n  const seen = new Set<string>();\n  const duplicates = new Set<string>();\n\n  for (const item of items) {\n    if (seen.has(item.id)) {\n      duplicates.add(item.id);\n    }\n\n    seen.add(item.id);\n  }\n\n  return [...duplicates];\n}\n\nfunction clampPercentage(value: number) {\n  if (!Number.isFinite(value)) {\n    return 0;\n  }\n\n  return Math.min(Math.max(value, 0), 100);\n}\n\nfunction defaultFormatProgress(percentage: number) {\n  return `${Math.round(percentage)}%`;\n}\n\nfunction CompleteIcon() {\n  return (\n    <svg\n      aria-hidden=\"true\"\n      viewBox=\"0 0 16 16\"\n      className=\"size-4\"\n      fill=\"none\"\n      stroke=\"currentColor\"\n      strokeLinecap=\"round\"\n      strokeLinejoin=\"round\"\n      strokeWidth=\"2\"\n    >\n      <path d=\"m3 8.5 3 3 7-7\" />\n    </svg>\n  );\n}\n\nfunction DefaultIndicator({\n  index,\n  item,\n  status,\n}: {\n  index: number;\n  item: StepItemData;\n  status: StepStatus;\n}) {\n  if (item.icon !== undefined) {\n    return <span aria-hidden=\"true\">{item.icon}</span>;\n  }\n\n  if (status === \"complete\") {\n    return <CompleteIcon />;\n  }\n\n  if (status === \"error\") {\n    return <span aria-hidden=\"true\">!</span>;\n  }\n\n  if (status === \"skipped\") {\n    return <span aria-hidden=\"true\">–</span>;\n  }\n\n  return <span aria-hidden=\"true\">{index + 1}</span>;\n}\n\nfunction StepsInner<TData>(\n  {\n    \"aria-label\": ariaLabel = \"Progress steps\",\n    \"aria-labelledby\": ariaLabelledBy,\n    className,\n    defaultValue,\n    formatProgress = defaultFormatProgress,\n    interactive = false,\n    items,\n    motionPreset = \"standard\",\n    onValueChange,\n    orientation = \"horizontal\",\n    progressValue,\n    renderItem,\n    showProgress = false,\n    size = \"md\",\n    value,\n    ...props\n  }: StepsProps<TData>,\n  ref: ForwardedRef<HTMLDivElement>,\n) {\n  const controlled = value !== undefined;\n  const [uncontrolledValue, setUncontrolledValue] = useState(defaultValue);\n  const [hoveredValue, setHoveredValue] = useState<string>();\n  const generatedId = useId();\n  const prefersReducedMotion = useReducedMotion();\n  const reducedMotion =\n    motionPreset === \"none\" || prefersReducedMotion === true;\n  const motionEnabled = !reducedMotion;\n  const transition = useMemo(\n    () => createStepsTransition(motionPreset, reducedMotion),\n    [motionPreset, reducedMotion],\n  );\n  const hoverTransition = useMemo(\n    () => createStepsHoverTransition(motionPreset, reducedMotion),\n    [motionPreset, reducedMotion],\n  );\n  const layoutScope = `${generatedId}-steps`;\n  const currentMarkerLayoutId = `${layoutScope}-current`;\n  const hoverLayerLayoutId = `${layoutScope}-hover`;\n  const currentValue = value ?? uncontrolledValue ?? items[0]?.id;\n  const currentIndex = items.findIndex((item) => item.id === currentValue);\n  const count = items.length;\n  const duplicateIds = useMemo(() => getDuplicateStepIds(items), [items]);\n  const duplicateIdSignature = duplicateIds.join(\",\");\n  const derivedProgress =\n    currentIndex >= 0 && count > 0 ? ((currentIndex + 1) / count) * 100 : 0;\n  const resolvedProgress = clampPercentage(progressValue ?? derivedProgress);\n  const progressText = formatProgress(resolvedProgress, {\n    count,\n    currentIndex,\n    currentValue,\n  });\n  const progressValueText =\n    typeof progressText === \"string\" || typeof progressText === \"number\"\n      ? String(progressText)\n      : undefined;\n  const progressLabel =\n    typeof ariaLabel === \"string\" ? `${ariaLabel} progress` : \"Step progress\";\n\n  useEffect(() => {\n    if (duplicateIds.length > 0) {\n      console.warn(\n        `[Steps] items must use stable unique ids. Duplicate ids: ${duplicateIds.join(\", \")}.`,\n      );\n    }\n  }, [duplicateIdSignature, duplicateIds]);\n\n  useEffect(() => {\n    if (currentValue !== undefined && count > 0 && currentIndex === -1) {\n      console.warn(\n        `[Steps] current value \"${currentValue}\" is not present in items. Removing the current step is unsupported; keep it in the active branch until navigation changes.`,\n      );\n    }\n  }, [count, currentIndex, currentValue]);\n\n  const selectValue = (nextValue: string, disabled: boolean) => {\n    if (!interactive || disabled || nextValue === currentValue) {\n      return;\n    }\n\n    if (!controlled) {\n      setUncontrolledValue(nextValue);\n    }\n\n    onValueChange?.(nextValue);\n  };\n\n  return (\n    <MotionConfig reducedMotion=\"user\">\n      <LayoutGroup id={layoutScope}>\n        <div\n          {...props}\n          ref={ref}\n          data-slot=\"steps\"\n          data-interactive={interactive ? \"true\" : undefined}\n          data-layout-scope={layoutScope}\n          data-motion-preset={motionPreset}\n          data-orientation={orientation}\n          data-reduced-motion={reducedMotion ? \"true\" : undefined}\n          data-size={size}\n          className={cn(rootClasses, className)}\n        >\n          {showProgress ? (\n            <div\n              role=\"progressbar\"\n              aria-label={progressLabel}\n              aria-valuemax={100}\n              aria-valuemin={0}\n              aria-valuenow={resolvedProgress}\n              aria-valuetext={progressValueText}\n              data-slot=\"steps-progress\"\n              className={progressClasses}\n            >\n              <span\n                aria-hidden=\"true\"\n                data-slot=\"steps-progress-track\"\n                className={progressTrackClasses}\n              >\n                <motionElement.span\n                  initial={false}\n                  animate={\n                    motionEnabled\n                      ? { scaleX: resolvedProgress / 100 }\n                      : undefined\n                  }\n                  transition={motionEnabled ? transition : undefined}\n                  data-motion-enabled={motionEnabled ? \"true\" : undefined}\n                  data-motion-preset={motionPreset}\n                  data-reduced-motion={reducedMotion ? \"true\" : undefined}\n                  data-slot=\"steps-progress-indicator\"\n                  className={progressIndicatorClasses}\n                  style={\n                    motionEnabled\n                      ? undefined\n                      : { transform: `scaleX(${resolvedProgress / 100})` }\n                  }\n                />\n              </span>\n              <span\n                data-slot=\"steps-progress-value\"\n                className={progressValueClasses}\n              >\n                {progressText}\n              </span>\n            </div>\n          ) : null}\n          <div\n            data-slot=\"steps-viewport\"\n            className={\n              orientation === \"horizontal\"\n                ? horizontalViewportClasses\n                : verticalViewportClasses\n            }\n          >\n            {/* Flex/list-none can suppress native list semantics in Safari. */}\n            <motionElement.ol\n              aria-label={ariaLabelledBy ? undefined : ariaLabel}\n              aria-labelledby={ariaLabelledBy}\n              role=\"list\"\n              data-slot=\"steps-list\"\n              data-orientation={orientation}\n              className={cn(\n                listBaseClasses,\n                listOrientationClasses[orientation],\n              )}\n              onPointerLeave={() => setHoveredValue(undefined)}\n            >\n              <AnimatePresence initial={false} mode=\"popLayout\">\n                {items.map((item, index) => {\n                  const current = item.id === currentValue;\n                  const status = getResolvedStatus({\n                    currentIndex,\n                    index,\n                    override: item.status,\n                  });\n                  const percentage =\n                    count === 0 ? 0 : ((index + 1) / count) * 100;\n                  const state: StepRenderState = {\n                    index,\n                    count,\n                    current,\n                    disabled: item.disabled === true,\n                    interactive,\n                    optional: item.optional === true,\n                    orientation,\n                    size,\n                    status,\n                    percentage,\n                  };\n\n                  return (\n                    <motionElement.li\n                      key={item.id}\n                      layout={motionEnabled ? \"position\" : false}\n                      initial={\n                        motionEnabled ? { opacity: 0, scale: 0.96 } : false\n                      }\n                      animate={\n                        motionEnabled ? { opacity: 1, scale: 1 } : undefined\n                      }\n                      exit={\n                        motionEnabled ? { opacity: 0, scale: 0.96 } : undefined\n                      }\n                      transition={motionEnabled ? transition : undefined}\n                      data-motion-enabled={motionEnabled ? \"true\" : undefined}\n                      data-motion-preset={motionPreset}\n                      data-reduced-motion={reducedMotion ? \"true\" : undefined}\n                      data-slot=\"steps-item\"\n                      data-current={current ? \"true\" : undefined}\n                      data-disabled={item.disabled ? \"true\" : undefined}\n                      data-optional={item.optional ? \"true\" : undefined}\n                      data-status={status}\n                      className={cn(\n                        itemBaseClasses,\n                        itemOrientationClasses[orientation],\n                      )}\n                    >\n                      {index < count - 1 ? (\n                        <span\n                          aria-hidden=\"true\"\n                          data-slot=\"steps-connector\"\n                          data-complete={\n                            status === \"complete\" ? \"true\" : undefined\n                          }\n                          className={cn(\n                            connectorBaseClasses,\n                            orientation === \"horizontal\"\n                              ? horizontalConnectorSizeClasses[size]\n                              : cn(\n                                  verticalConnectorSizeClasses[size],\n                                  status === \"complete\"\n                                    ? \"bg-primary/60\"\n                                    : \"bg-border\",\n                                ),\n                          )}\n                        >\n                          {orientation === \"horizontal\" ? (\n                            <span\n                              data-slot=\"steps-connector-line\"\n                              className={cn(\n                                horizontalConnectorLineClasses,\n                                status === \"complete\"\n                                  ? \"bg-primary/60\"\n                                  : undefined,\n                              )}\n                            />\n                          ) : null}\n                        </span>\n                      ) : null}\n                      {interactive ? (\n                        <motionElement.button\n                          type=\"button\"\n                          aria-current={current ? \"step\" : undefined}\n                          data-hovered={\n                            hoveredValue === item.id ? \"true\" : undefined\n                          }\n                          data-slot=\"steps-surface\"\n                          disabled={item.disabled}\n                          className={cn(\n                            surfaceClasses,\n                            surfaceOrientationClasses[orientation],\n                            interactiveSurfaceClasses,\n                          )}\n                          onClick={() =>\n                            selectValue(item.id, item.disabled === true)\n                          }\n                          onPointerEnter={(event) => {\n                            if (event.pointerType !== \"touch\") {\n                              setHoveredValue(\n                                item.disabled === true ? undefined : item.id,\n                              );\n                            }\n                          }}\n                        >\n                          {hoveredValue === item.id ? (\n                            <StepsHoverLayer\n                              layoutId={hoverLayerLayoutId}\n                              motionEnabled={motionEnabled}\n                              motionPreset={motionPreset}\n                              orientation={orientation}\n                              reducedMotion={reducedMotion}\n                              transition={hoverTransition}\n                            />\n                          ) : null}\n                          <StepContent\n                            currentMarkerLayoutId={currentMarkerLayoutId}\n                            index={index}\n                            item={item}\n                            motionEnabled={motionEnabled}\n                            motionPreset={motionPreset}\n                            reducedMotion={reducedMotion}\n                            renderItem={renderItem}\n                            state={state}\n                            transition={transition}\n                          />\n                        </motionElement.button>\n                      ) : (\n                        <div\n                          aria-current={current ? \"step\" : undefined}\n                          data-slot=\"steps-surface\"\n                          className={cn(\n                            surfaceClasses,\n                            surfaceOrientationClasses[orientation],\n                          )}\n                        >\n                          <StepContent\n                            currentMarkerLayoutId={currentMarkerLayoutId}\n                            index={index}\n                            item={item}\n                            motionEnabled={motionEnabled}\n                            motionPreset={motionPreset}\n                            reducedMotion={reducedMotion}\n                            renderItem={renderItem}\n                            state={state}\n                            transition={transition}\n                          />\n                        </div>\n                      )}\n                    </motionElement.li>\n                  );\n                })}\n              </AnimatePresence>\n            </motionElement.ol>\n          </div>\n        </div>\n      </LayoutGroup>\n    </MotionConfig>\n  );\n}\n\nfunction StepsHoverLayer({\n  layoutId,\n  motionEnabled,\n  motionPreset,\n  orientation,\n  reducedMotion,\n  transition,\n}: {\n  layoutId: string;\n  motionEnabled: boolean;\n  motionPreset: StepsMotionPreset;\n  orientation: StepsOrientation;\n  reducedMotion: boolean;\n  transition: Transition;\n}) {\n  const props = {\n    \"aria-hidden\": true,\n    \"data-layout-id\": layoutId,\n    \"data-motion-enabled\": motionEnabled ? \"true\" : undefined,\n    \"data-motion-preset\": motionPreset,\n    \"data-reduced-motion\": reducedMotion ? \"true\" : undefined,\n    \"data-slot\": \"steps-hover-layer\",\n    className: cn(hoverLayerClasses, hoverLayerOrientationClasses[orientation]),\n  } as const;\n\n  if (!motionEnabled) {\n    return <span {...props} />;\n  }\n\n  return (\n    <motionElement.span\n      {...props}\n      layoutId={layoutId}\n      transition={transition}\n    />\n  );\n}\n\nfunction StepContent<TData>({\n  currentMarkerLayoutId,\n  index,\n  item,\n  motionEnabled,\n  motionPreset,\n  reducedMotion,\n  renderItem,\n  state,\n  transition,\n}: {\n  currentMarkerLayoutId: string;\n  index: number;\n  item: StepItemData<TData>;\n  motionEnabled: boolean;\n  motionPreset: StepsMotionPreset;\n  reducedMotion: boolean;\n  renderItem?: (item: StepItemData<TData>, state: StepRenderState) => ReactNode;\n  state: StepRenderState;\n  transition: Transition;\n}) {\n  return (\n    <>\n      <span\n        aria-hidden=\"true\"\n        data-slot=\"steps-indicator\"\n        className={cn(\n          indicatorClasses,\n          indicatorSizeClasses[state.size],\n          indicatorStatusClasses[state.status],\n        )}\n      >\n        {state.current ? (\n          motionEnabled ? (\n            <motionElement.span\n              aria-hidden=\"true\"\n              layoutId={currentMarkerLayoutId}\n              transition={transition}\n              data-layout-id={currentMarkerLayoutId}\n              data-motion-enabled=\"true\"\n              data-motion-preset={motionPreset}\n              data-slot=\"steps-current-marker\"\n              className={currentMarkerClasses}\n            />\n          ) : (\n            <span\n              aria-hidden=\"true\"\n              data-layout-id={currentMarkerLayoutId}\n              data-motion-preset={motionPreset}\n              data-reduced-motion={reducedMotion ? \"true\" : undefined}\n              data-slot=\"steps-current-marker\"\n              className={currentMarkerClasses}\n            />\n          )\n        ) : null}\n        <DefaultIndicator index={index} item={item} status={state.status} />\n      </span>\n      <span\n        data-slot=\"steps-body\"\n        className={bodyOrientationClasses[state.orientation]}\n      >\n        {renderItem ? (\n          renderItem(item, state)\n        ) : (\n          <>\n            <span\n              data-slot=\"steps-label\"\n              className={cn(labelClasses, labelSizeClasses[state.size])}\n            >\n              {item.label}\n            </span>\n            {item.description ? (\n              <span\n                data-slot=\"steps-description\"\n                className={descriptionClasses}\n              >\n                {item.description}\n              </span>\n            ) : null}\n            {item.optional ? (\n              <span data-slot=\"steps-optional\" className={optionalClasses}>\n                Optional\n              </span>\n            ) : null}\n          </>\n        )}\n      </span>\n      <span className=\"sr-only\" data-slot=\"steps-status\">\n        {statusLabels[state.status]}\n        {state.current && state.status !== \"current\" ? \", current step\" : \"\"}\n      </span>\n    </>\n  );\n}\n\ntype StepsComponent = <TData = unknown>(\n  props: StepsProps<TData> & RefAttributes<HTMLDivElement>,\n) => ReactElement | null;\n\nexport const Steps = forwardRef(StepsInner) as StepsComponent;\n"
    },
    {
      "path": "packages/components/src/components/steps/steps-state.tsx",
      "type": "registry:file",
      "target": "~/components/dethink/components/steps/steps-state.tsx",
      "content": "import {\n  createContext,\n  useCallback,\n  useContext,\n  useEffect,\n  useMemo,\n  useRef,\n  useState,\n  type ReactNode,\n} from \"react\";\nimport type { StepItemData, StepsProps } from \"./steps\";\n\nexport interface UseStepsStateOptions<TData = unknown> {\n  items?: StepItemData<TData>[];\n  defaultItems?: StepItemData<TData>[];\n  value?: string;\n  defaultValue?: string;\n  onItemsChange?: (items: StepItemData<TData>[]) => void;\n  onValueChange?: (value: string) => void;\n  progressValue?: number;\n}\n\nexport interface StepsIndicatorProps<TData = unknown> {\n  items: StepItemData<TData>[];\n  value: string | undefined;\n  onValueChange: NonNullable<StepsProps<TData>[\"onValueChange\"]>;\n  progressValue: number;\n}\n\nexport interface StepsCurrentState<TData = unknown> {\n  currentStep: StepItemData<TData> | undefined;\n  currentIndex: number;\n  value: string | undefined;\n  isFirstStep: boolean;\n  isLastStep: boolean;\n}\n\nexport interface StepsNextState<TData = unknown> {\n  nextSteps: StepItemData<TData>[];\n  addNextStep: (step: StepItemData<TData>) => boolean;\n  insertNextStep: (index: number, step: StepItemData<TData>) => boolean;\n  removeNextStep: (id: string) => boolean;\n  replaceNextSteps: (steps: StepItemData<TData>[]) => boolean;\n  clearNextSteps: () => boolean;\n}\n\nexport interface StepsState<TData = unknown>\n  extends StepsCurrentState<TData>, StepsNextState<TData> {\n  items: StepItemData<TData>[];\n  count: number;\n  progress: number;\n  setValue: (value: string) => boolean;\n  stepsProps: StepsIndicatorProps<TData>;\n}\n\nexport interface StepsProviderProps<TData = unknown> {\n  children: ReactNode;\n  state: StepsState<TData>;\n}\n\nconst StepsStateContext = createContext<StepsState<unknown> | null>(null);\n\nfunction clampPercentage(value: number) {\n  if (!Number.isFinite(value)) {\n    return 0;\n  }\n\n  return Math.min(Math.max(value, 0), 100);\n}\n\nfunction getDuplicateStepIds<TData>(items: StepItemData<TData>[]) {\n  const seen = new Set<string>();\n  const duplicates = new Set<string>();\n\n  for (const item of items) {\n    if (seen.has(item.id)) {\n      duplicates.add(item.id);\n    }\n\n    seen.add(item.id);\n  }\n\n  return [...duplicates];\n}\n\nfunction warn(message: string) {\n  const nodeEnvironment = (\n    globalThis as typeof globalThis & {\n      process?: { env?: { NODE_ENV?: string } };\n    }\n  ).process?.env?.NODE_ENV;\n\n  if (nodeEnvironment !== \"production\") {\n    console.warn(`[Steps] ${message}`);\n  }\n}\n\nexport function useStepsState<TData = unknown>({\n  defaultItems = [],\n  defaultValue,\n  items,\n  onItemsChange,\n  onValueChange,\n  progressValue,\n  value,\n}: UseStepsStateOptions<TData> = {}): StepsState<TData> {\n  const itemsControlled = items !== undefined;\n  const valueControlled = value !== undefined;\n  const initialItems = items ?? defaultItems;\n  const [uncontrolledItems, setUncontrolledItems] =\n    useState<StepItemData<TData>[]>(defaultItems);\n  const [uncontrolledValue, setUncontrolledValue] = useState(\n    () => defaultValue ?? initialItems[0]?.id,\n  );\n  const resolvedItems = items ?? uncontrolledItems;\n  const resolvedValue = value ?? uncontrolledValue;\n  const mutationSnapshotRef = useRef({\n    actualItems: resolvedItems,\n    actualValue: resolvedValue,\n    items: resolvedItems,\n    value: resolvedValue,\n  });\n  const getMutationSnapshot = useCallback(() => {\n    const snapshot = mutationSnapshotRef.current;\n\n    if (\n      snapshot.actualItems === resolvedItems &&\n      snapshot.actualValue === resolvedValue\n    ) {\n      return snapshot;\n    }\n\n    const nextSnapshot = {\n      actualItems: resolvedItems,\n      actualValue: resolvedValue,\n      items: resolvedItems,\n      value: resolvedValue,\n    };\n    mutationSnapshotRef.current = nextSnapshot;\n    return nextSnapshot;\n  }, [resolvedItems, resolvedValue]);\n  const currentIndex = resolvedItems.findIndex(\n    (item) => item.id === resolvedValue,\n  );\n  const currentStep =\n    currentIndex >= 0 ? resolvedItems[currentIndex] : undefined;\n  const count = resolvedItems.length;\n  const duplicateIds = useMemo(\n    () => getDuplicateStepIds(resolvedItems),\n    [resolvedItems],\n  );\n  const duplicateIdSignature = duplicateIds.join(\",\");\n  const derivedProgress =\n    currentIndex >= 0 && count > 0 ? ((currentIndex + 1) / count) * 100 : 0;\n  const progress = clampPercentage(progressValue ?? derivedProgress);\n  const nextSteps = useMemo(\n    () => (currentIndex >= 0 ? resolvedItems.slice(currentIndex + 1) : []),\n    [currentIndex, resolvedItems],\n  );\n\n  // Accepted calls compose until React commits the consumer's actual state.\n  useEffect(() => {\n    mutationSnapshotRef.current = {\n      actualItems: resolvedItems,\n      actualValue: resolvedValue,\n      items: resolvedItems,\n      value: resolvedValue,\n    };\n  });\n\n  useEffect(() => {\n    if (duplicateIds.length > 0) {\n      warn(\n        `items must use stable unique ids. Duplicate ids: ${duplicateIds.join(\", \")}.`,\n      );\n    }\n  }, [duplicateIdSignature, duplicateIds]);\n\n  useEffect(() => {\n    if (resolvedValue !== undefined && count > 0 && currentIndex < 0) {\n      warn(\n        `current value \"${resolvedValue}\" is not present in items. Removing the current step is unsupported; keep it in the active branch until navigation changes.`,\n      );\n    }\n  }, [count, currentIndex, resolvedValue]);\n\n  const commitItems = useCallback(\n    (nextItems: StepItemData<TData>[]) => {\n      const duplicateIds = getDuplicateStepIds(nextItems);\n\n      if (duplicateIds.length > 0) {\n        warn(\n          `items must use stable unique ids. Duplicate ids: ${duplicateIds.join(\", \")}.`,\n        );\n        return false;\n      }\n\n      const snapshot = getMutationSnapshot();\n      const currentValue = snapshot.value;\n\n      if (\n        currentValue !== undefined &&\n        !nextItems.some((item) => item.id === currentValue)\n      ) {\n        warn(\n          `cannot remove the current step \"${currentValue}\". Change the current value before replacing its branch.`,\n        );\n        return false;\n      }\n\n      mutationSnapshotRef.current = { ...snapshot, items: nextItems };\n\n      if (!itemsControlled) {\n        setUncontrolledItems(nextItems);\n      }\n\n      onItemsChange?.(nextItems);\n      return true;\n    },\n    [getMutationSnapshot, itemsControlled, onItemsChange],\n  );\n\n  const setValue = useCallback(\n    (nextValue: string) => {\n      const snapshot = getMutationSnapshot();\n      const nextStep = snapshot.items.find((item) => item.id === nextValue);\n\n      if (!nextStep) {\n        warn(`cannot select unknown step \"${nextValue}\".`);\n        return false;\n      }\n\n      if (nextStep.disabled) {\n        return false;\n      }\n\n      if (nextValue === snapshot.value) {\n        return false;\n      }\n\n      mutationSnapshotRef.current = { ...snapshot, value: nextValue };\n\n      if (!valueControlled) {\n        setUncontrolledValue(nextValue);\n      }\n\n      onValueChange?.(nextValue);\n      return true;\n    },\n    [getMutationSnapshot, onValueChange, valueControlled],\n  );\n\n  const replaceNextSteps = useCallback(\n    (steps: StepItemData<TData>[]) => {\n      const snapshot = getMutationSnapshot();\n      const currentItems = snapshot.items;\n      const currentSnapshotIndex = currentItems.findIndex(\n        (item) => item.id === snapshot.value,\n      );\n\n      if (currentSnapshotIndex < 0) {\n        warn(\n          \"cannot change future steps because the current value is not present in items.\",\n        );\n        return false;\n      }\n\n      return commitItems([\n        ...currentItems.slice(0, currentSnapshotIndex + 1),\n        ...steps,\n      ]);\n    },\n    [commitItems, getMutationSnapshot],\n  );\n\n  const addNextStep = useCallback(\n    (step: StepItemData<TData>) => {\n      const snapshot = getMutationSnapshot();\n      const currentItems = snapshot.items;\n      const currentSnapshotIndex = currentItems.findIndex(\n        (item) => item.id === snapshot.value,\n      );\n      const currentNextSteps =\n        currentSnapshotIndex >= 0\n          ? currentItems.slice(currentSnapshotIndex + 1)\n          : [];\n\n      return replaceNextSteps([...currentNextSteps, step]);\n    },\n    [getMutationSnapshot, replaceNextSteps],\n  );\n\n  const insertNextStep = useCallback(\n    (index: number, step: StepItemData<TData>) => {\n      const snapshot = getMutationSnapshot();\n      const currentItems = snapshot.items;\n      const currentSnapshotIndex = currentItems.findIndex(\n        (item) => item.id === snapshot.value,\n      );\n      const currentNextSteps =\n        currentSnapshotIndex >= 0\n          ? currentItems.slice(currentSnapshotIndex + 1)\n          : [];\n\n      if (\n        !Number.isInteger(index) ||\n        index < 0 ||\n        index > currentNextSteps.length\n      ) {\n        warn(\n          `future step index ${index} is outside the valid range 0-${currentNextSteps.length}.`,\n        );\n        return false;\n      }\n\n      const updatedNextSteps = [...currentNextSteps];\n      updatedNextSteps.splice(index, 0, step);\n      return replaceNextSteps(updatedNextSteps);\n    },\n    [getMutationSnapshot, replaceNextSteps],\n  );\n\n  const removeNextStep = useCallback(\n    (id: string) => {\n      const snapshot = getMutationSnapshot();\n      const currentItems = snapshot.items;\n      const currentSnapshotIndex = currentItems.findIndex(\n        (item) => item.id === snapshot.value,\n      );\n      const currentNextSteps =\n        currentSnapshotIndex >= 0\n          ? currentItems.slice(currentSnapshotIndex + 1)\n          : [];\n      const nextIndex = currentNextSteps.findIndex((step) => step.id === id);\n\n      if (nextIndex < 0) {\n        warn(`cannot remove \"${id}\" because it is not a future step.`);\n        return false;\n      }\n\n      return replaceNextSteps(\n        currentNextSteps.filter((step) => step.id !== id),\n      );\n    },\n    [getMutationSnapshot, replaceNextSteps],\n  );\n\n  const clearNextSteps = useCallback(\n    () => replaceNextSteps([]),\n    [replaceNextSteps],\n  );\n\n  const onStepsValueChange = useCallback(\n    (nextValue: string) => {\n      setValue(nextValue);\n    },\n    [setValue],\n  );\n\n  const stepsProps = useMemo<StepsIndicatorProps<TData>>(\n    () => ({\n      items: resolvedItems,\n      onValueChange: onStepsValueChange,\n      progressValue: progress,\n      value: resolvedValue,\n    }),\n    [onStepsValueChange, progress, resolvedItems, resolvedValue],\n  );\n\n  return useMemo(\n    () => ({\n      addNextStep,\n      clearNextSteps,\n      count,\n      currentIndex,\n      currentStep,\n      insertNextStep,\n      isFirstStep: currentIndex === 0,\n      isLastStep: currentIndex >= 0 && currentIndex === count - 1,\n      items: resolvedItems,\n      nextSteps,\n      progress,\n      removeNextStep,\n      replaceNextSteps,\n      setValue,\n      stepsProps,\n      value: resolvedValue,\n    }),\n    [\n      addNextStep,\n      clearNextSteps,\n      count,\n      currentIndex,\n      currentStep,\n      insertNextStep,\n      nextSteps,\n      progress,\n      removeNextStep,\n      replaceNextSteps,\n      resolvedItems,\n      resolvedValue,\n      setValue,\n      stepsProps,\n    ],\n  );\n}\n\nexport function StepsProvider<TData = unknown>({\n  children,\n  state,\n}: StepsProviderProps<TData>) {\n  return (\n    <StepsStateContext.Provider value={state as unknown as StepsState<unknown>}>\n      {children}\n    </StepsStateContext.Provider>\n  );\n}\n\nexport function useSteps<TData = unknown>() {\n  const state = useContext(StepsStateContext);\n\n  if (!state) {\n    throw new Error(\"useSteps must be used within a StepsProvider.\");\n  }\n\n  return state as StepsState<TData>;\n}\n\nexport function useCurrentStep<TData = unknown>(): StepsCurrentState<TData> {\n  const { currentIndex, currentStep, isFirstStep, isLastStep, value } =\n    useSteps<TData>();\n\n  return useMemo(\n    () => ({\n      currentIndex,\n      currentStep,\n      isFirstStep,\n      isLastStep,\n      value,\n    }),\n    [currentIndex, currentStep, isFirstStep, isLastStep, value],\n  );\n}\n\nexport function useNextSteps<TData = unknown>(): StepsNextState<TData> {\n  const {\n    addNextStep,\n    clearNextSteps,\n    insertNextStep,\n    nextSteps,\n    removeNextStep,\n    replaceNextSteps,\n  } = useSteps<TData>();\n\n  return useMemo(\n    () => ({\n      addNextStep,\n      clearNextSteps,\n      insertNextStep,\n      nextSteps,\n      removeNextStep,\n      replaceNextSteps,\n    }),\n    [\n      addNextStep,\n      clearNextSteps,\n      insertNextStep,\n      nextSteps,\n      removeNextStep,\n      replaceNextSteps,\n    ],\n  );\n}\n"
    },
    {
      "path": "packages/components/src/components/steps/steps-panel.tsx",
      "type": "registry:file",
      "target": "~/components/dethink/components/steps/steps-panel.tsx",
      "content": "import {\n  Fragment,\n  forwardRef,\n  type ForwardedRef,\n  type HTMLAttributes,\n  type ReactElement,\n  type ReactNode,\n  type RefAttributes,\n} from \"react\";\nimport { cn } from \"../../utils/cn\";\nimport { useSteps } from \"./steps-state\";\nimport type { StepItemData } from \"./steps\";\n\nexport interface StepsPanelRenderContext<TData = unknown> {\n  step: StepItemData<TData>;\n  index: number;\n  count: number;\n  value: string;\n}\n\nexport interface StepsPanelProps<TData = unknown> extends Omit<\n  HTMLAttributes<HTMLDivElement>,\n  \"children\"\n> {\n  fallback?: ReactNode;\n  render: (context: StepsPanelRenderContext<TData>) => ReactNode;\n}\n\nfunction StepsPanelInner<TData>(\n  { className, fallback = null, render, ...props }: StepsPanelProps<TData>,\n  ref: ForwardedRef<HTMLDivElement>,\n) {\n  const { count, currentIndex, currentStep, value } = useSteps<TData>();\n\n  return (\n    <div\n      {...props}\n      ref={ref}\n      data-slot=\"steps-panel\"\n      data-step-value={currentStep?.id}\n      className={cn(\"min-w-0\", className)}\n    >\n      {currentStep && value !== undefined ? (\n        <Fragment key={currentStep.id}>\n          {render({\n            count,\n            index: currentIndex,\n            step: currentStep,\n            value,\n          })}\n        </Fragment>\n      ) : (\n        fallback\n      )}\n    </div>\n  );\n}\n\ntype StepsPanelComponent = <TData = unknown>(\n  props: StepsPanelProps<TData> & RefAttributes<HTMLDivElement>,\n) => ReactElement | null;\n\nexport const StepsPanel = forwardRef(StepsPanelInner) as StepsPanelComponent;\n"
    },
    {
      "path": "packages/components/src/components/steps/index.ts",
      "type": "registry:file",
      "target": "~/components/dethink/components/steps/index.ts",
      "content": "export {\n  Steps,\n  type StepItemData,\n  type StepRenderState,\n  type StepStatus,\n  type StepsMotionPreset,\n  type StepsOrientation,\n  type StepsProps,\n  type StepsSize,\n} from \"./steps\";\nexport {\n  StepsProvider,\n  useCurrentStep,\n  useNextSteps,\n  useSteps,\n  useStepsState,\n  type StepsCurrentState,\n  type StepsIndicatorProps,\n  type StepsNextState,\n  type StepsProviderProps,\n  type StepsState,\n  type UseStepsStateOptions,\n} from \"./steps-state\";\nexport {\n  StepsPanel,\n  type StepsPanelProps,\n  type StepsPanelRenderContext,\n} from \"./steps-panel\";\n"
    },
    {
      "path": "packages/components/src/utils/cn.ts",
      "type": "registry:file",
      "target": "~/components/dethink/utils/cn.ts",
      "content": "import { clsx, type ClassValue } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\nexport function cn(...inputs: ClassValue[]) {\n  return twMerge(clsx(inputs));\n}\n"
    }
  ]
}
