{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "steps",
  "title": "Steps",
  "description": "Shows where someone is in a process, across a line, down a column, as a bar with a count or as numbered rows, with done, current, upcoming and error states.",
  "dependencies": [
    "lucide-react",
    "motion"
  ],
  "registryDependencies": [
    "utils",
    "https://miseui.junsu4621.workers.dev/r/motion.json"
  ],
  "files": [
    {
      "path": "registry/miseui/ui/steps.tsx",
      "content": "import type * as React from \"react\"\r\nimport { SEGMENT_CLASS, StepConnector, StepMarker, TITLE_CLASS, stepState, type StepState } from \"@/registry/miseui/ui/steps-parts\"\r\nimport { cn } from \"@/registry/miseui/lib/utils\"\r\n\r\ntype StepsVariant = \"horizontal\" | \"vertical\" | \"compact\" | \"numbered\"\r\ntype StepsEffect = \"draw\"\r\n\r\ninterface StepItem {\r\n  title: React.ReactNode\r\n  description?: React.ReactNode\r\n  /** Marks this step as failed. It keeps its place in the order. */\r\n  error?: boolean\r\n}\r\n\r\ninterface StepsLabels {\r\n  done: string\r\n  error: string\r\n  /** The line above the bar in `compact`. */\r\n  count: (position: number, total: number) => string\r\n}\r\n\r\nconst DEFAULT_LABELS: StepsLabels = {\r\n  done: \"Completed\",\r\n  error: \"Error\",\r\n  count: (position, total) => `Step ${position} of ${total}`,\r\n}\r\n\r\ninterface ListProps {\r\n  steps: readonly StepItem[]\r\n  current: number\r\n  draw: boolean\r\n  labels: StepsLabels\r\n  onStepSelect?: (index: number) => void\r\n}\r\n\r\nconst spokenState = (state: StepState, labels: StepsLabels) =>\r\n  state === \"done\" || state === \"error\" ? <span className=\"sr-only\">{labels[state]}: </span> : null\r\n\r\n/** Only a finished step can be gone back to; a later one would skip the work in between. */\r\nfunction StepTitle({ index, state, step, labels, onStepSelect, className }: { index: number; state: StepState; step: StepItem; labels: StepsLabels; onStepSelect?: (index: number) => void; className?: string }) {\r\n  const text = (\r\n    <>\r\n      {spokenState(state, labels)}\r\n      {step.title}\r\n    </>\r\n  )\r\n  const classes = cn(\"block min-w-0 text-sm leading-5 break-words\", TITLE_CLASS[state], className)\r\n  if (state === \"done\" && onStepSelect) {\r\n    return (\r\n      <button\r\n        type=\"button\"\r\n        data-slot=\"steps-link\"\r\n        onClick={() => onStepSelect(index)}\r\n        className={cn(classes, \"rounded-sm text-left underline-offset-4 outline-none hover:underline focus-visible:ring-[3px] focus-visible:ring-ring/50 pointer-coarse:min-h-10\")}\r\n      >\r\n        {text}\r\n      </button>\r\n    )\r\n  }\r\n  return <span className={classes}>{text}</span>\r\n}\r\n\r\nfunction HorizontalList({ steps, current, draw, labels, onStepSelect }: ListProps) {\r\n  return (\r\n    <ol data-slot=\"steps-list\" className=\"grid min-w-0 auto-cols-fr grid-flow-col\">\r\n      {steps.map((step, index) => {\r\n        const state = stepState(index, current, step.error)\r\n        return (\r\n          <li key={index} aria-current={state === \"current\" ? \"step\" : undefined} className=\"relative grid min-w-0 justify-items-center gap-2 px-1 text-center\">\r\n            {index > 0 ? (\r\n              // Stops 4 px short of both markers, so neither needs an opaque fill that would have to match the surface behind it.\r\n              <StepConnector axis=\"x\" filled={index <= current} draw={draw} className=\"absolute top-[11px] right-[calc(50%+16px)] h-0.5 w-[calc(100%-32px)] rtl:right-auto rtl:left-[calc(50%+16px)]\" />\r\n            ) : null}\r\n            <StepMarker state={state} number={index + 1} />\r\n            <StepTitle {...{ index, state, step, labels, onStepSelect }} className=\"text-[13px]\" />\r\n          </li>\r\n        )\r\n      })}\r\n    </ol>\r\n  )\r\n}\r\n\r\nfunction VerticalList({ steps, current, draw, labels, onStepSelect }: ListProps) {\r\n  return (\r\n    <ol data-slot=\"steps-list\" className=\"grid min-w-0\">\r\n      {steps.map((step, index) => {\r\n        const state = stepState(index, current, step.error)\r\n        const last = index === steps.length - 1\r\n        return (\r\n          <li key={index} aria-current={state === \"current\" ? \"step\" : undefined} className=\"relative flex min-w-0 gap-3 pb-6 last:pb-0\">\r\n            {last ? null : <StepConnector axis=\"y\" filled={index < current} draw={draw} className=\"absolute top-7 bottom-1 left-[11px] w-0.5 rtl:right-[11px] rtl:left-auto\" />}\r\n            <StepMarker state={state} number={index + 1} />\r\n            <div className=\"grid min-w-0\">\r\n              <StepTitle {...{ index, state, step, labels, onStepSelect }} className=\"leading-6\" />\r\n              {step.description ? <p className=\"text-[13px] leading-5 text-muted-foreground\">{step.description}</p> : null}\r\n            </div>\r\n          </li>\r\n        )\r\n      })}\r\n    </ol>\r\n  )\r\n}\r\n\r\nfunction CompactList({ steps, current, labels }: ListProps) {\r\n  const position = Math.min(current, steps.length - 1)\r\n  const active = steps[position]\r\n  return (\r\n    <div className=\"grid min-w-0 gap-2\">\r\n      <p className=\"flex min-w-0 items-baseline justify-between gap-3 text-[13px] leading-5\">\r\n        <span className=\"min-w-0 truncate font-medium\">{active?.title}</span>\r\n        <span className=\"shrink-0 text-muted-foreground tabular-nums\">{labels.count(position + 1, steps.length)}</span>\r\n      </p>\r\n      <ol data-slot=\"steps-list\" className=\"grid auto-cols-fr grid-flow-col gap-1\">\r\n        {steps.map((step, index) => {\r\n          const state = stepState(index, current, step.error)\r\n          return (\r\n            <li key={index} aria-current={state === \"current\" ? \"step\" : undefined} className=\"min-w-0\">\r\n              <span aria-hidden=\"true\" data-slot=\"steps-segment\" data-state={state} className={cn(\"block h-1 rounded-full\", SEGMENT_CLASS[state])} />\r\n              <span className=\"sr-only\">\r\n                {spokenState(state, labels)}\r\n                {step.title}\r\n              </span>\r\n            </li>\r\n          )\r\n        })}\r\n      </ol>\r\n    </div>\r\n  )\r\n}\r\n\r\nfunction NumberedList({ steps, current, labels, onStepSelect }: ListProps) {\r\n  return (\r\n    <ol data-slot=\"steps-list\" className=\"grid min-w-0 divide-y border-y\">\r\n      {steps.map((step, index) => {\r\n        const state = stepState(index, current, step.error)\r\n        return (\r\n          <li key={index} aria-current={state === \"current\" ? \"step\" : undefined} className=\"flex min-w-0 gap-4 py-3\">\r\n            <span aria-hidden=\"true\" data-slot=\"steps-number\" className={cn(\"w-6 shrink-0 text-right text-[15px] leading-5 font-medium tabular-nums\", state === \"upcoming\" ? \"text-muted-foreground\" : TITLE_CLASS[state])}>\r\n              {index + 1}\r\n            </span>\r\n            <div className=\"grid min-w-0 gap-1\">\r\n              <StepTitle {...{ index, state, step, labels, onStepSelect }} />\r\n              {step.description ? <p className=\"text-[13px] leading-5 text-muted-foreground\">{step.description}</p> : null}\r\n            </div>\r\n          </li>\r\n        )\r\n      })}\r\n    </ol>\r\n  )\r\n}\r\n\r\nconst LIST: Record<StepsVariant, (props: ListProps) => React.ReactNode> = {\r\n  horizontal: HorizontalList,\r\n  vertical: VerticalList,\r\n  compact: CompactList,\r\n  numbered: NumberedList,\r\n}\r\n\r\ntype StepsProps = Omit<React.ComponentProps<\"nav\">, \"children\"> & {\r\n  steps: readonly StepItem[]\r\n  /** Index of the step in progress. Past the last index means everything is done. */\r\n  current?: number\r\n  /** `horizontal` markers on a line, `vertical` with room for details, `compact` a bar with a count, `numbered` ruled rows led by the number. */\r\n  variant?: StepsVariant\r\n  /** `draw` lets the connecting line grow when a step completes. Only `horizontal` and `vertical` have one. */\r\n  effect?: StepsEffect\r\n  /** Makes finished steps links back. */\r\n  onStepSelect?: (index: number) => void\r\n  labels?: Partial<StepsLabels>\r\n}\r\n\r\nfunction Steps({ steps, current = 0, variant = \"horizontal\", effect, onStepSelect, labels, className, ...props }: StepsProps) {\r\n  const List = LIST[variant]\r\n  return (\r\n    <nav data-slot=\"steps\" data-variant={variant} data-effect={effect} className={cn(\"w-full min-w-0\", className)} {...props}>\r\n      <List\r\n        steps={steps}\r\n        current={current}\r\n        draw={effect === \"draw\"}\r\n        labels={{ ...DEFAULT_LABELS, ...labels }}\r\n        onStepSelect={onStepSelect}\r\n      />\r\n    </nav>\r\n  )\r\n}\r\n\r\nexport { Steps, stepState, type StepsProps, type StepItem, type StepsVariant, type StepsEffect, type StepsLabels, type StepState }\r\n",
      "type": "registry:ui"
    },
    {
      "path": "registry/miseui/ui/steps-parts.tsx",
      "content": "import { AlertTriangle, Check } from \"lucide-react\"\r\nimport { motion, useReducedMotion } from \"motion/react\"\r\nimport { SPRING_DEFAULT } from \"@/registry/miseui/lib/motion\"\r\nimport { cn } from \"@/registry/miseui/lib/utils\"\r\n\r\ntype StepState = \"done\" | \"current\" | \"upcoming\" | \"error\"\r\n\r\nconst INSTANT = { duration: 0 } as const\r\n\r\n/** A step can say it failed; otherwise its place relative to `current` decides. */\r\nfunction stepState(index: number, current: number, error?: boolean): StepState {\r\n  if (error) return \"error\"\r\n  if (index < current) return \"done\"\r\n  return index === current ? \"current\" : \"upcoming\"\r\n}\r\n\r\n/**\r\n * Done is neutral on purpose. It is progress and not a success message, and in the accent it would compete with\r\n * the one step that needs attention now. Current gets the accent, so the eye lands there first.\r\n */\r\nconst MARKER_CLASS: Record<StepState, string> = {\r\n  done: \"bg-foreground text-background\",\r\n  current: \"bg-mise text-mise-foreground\",\r\n  upcoming: \"border border-input text-muted-foreground\",\r\n  error: \"bg-destructive text-white dark:text-background\",\r\n}\r\n\r\nconst TITLE_CLASS: Record<StepState, string> = {\r\n  done: \"text-foreground\",\r\n  current: \"font-medium text-foreground\",\r\n  upcoming: \"text-muted-foreground\",\r\n  error: \"font-medium text-destructive\",\r\n}\r\n\r\nconst SEGMENT_CLASS: Record<StepState, string> = {\r\n  done: \"bg-foreground\",\r\n  current: \"bg-mise\",\r\n  upcoming: \"bg-muted\",\r\n  error: \"bg-destructive\",\r\n}\r\n\r\nfunction StepMarker({ state, number }: { state: StepState; number: number }) {\r\n  const Glyph = state === \"done\" ? Check : state === \"error\" ? AlertTriangle : null\r\n  return (\r\n    <span\r\n      aria-hidden=\"true\"\r\n      data-slot=\"steps-marker\"\r\n      data-state={state}\r\n      className={cn(\"flex size-6 shrink-0 items-center justify-center rounded-full text-xs font-medium tabular-nums\", MARKER_CLASS[state])}\r\n    >\r\n      {Glyph ? <Glyph className=\"size-3.5\" strokeWidth={state === \"done\" ? 3 : 2} /> : number}\r\n    </span>\r\n  )\r\n}\r\n\r\ninterface ConnectorProps {\r\n  axis: \"x\" | \"y\"\r\n  /** The stretch of the journey already behind the person. */\r\n  filled: boolean\r\n  /** Draws the fill with a spring instead of placing it. */\r\n  draw: boolean\r\n  className?: string\r\n}\r\n\r\nfunction StepConnector({ axis, filled, draw, className }: ConnectorProps) {\r\n  const reduceMotion = useReducedMotion() ?? false\r\n  const scale = filled ? 1 : 0\r\n  return (\r\n    <span aria-hidden=\"true\" data-slot=\"steps-connector\" data-filled={filled ? \"true\" : \"false\"} className={cn(\"block overflow-hidden bg-border\", className)}>\r\n      <motion.span\r\n        className={cn(\"block size-full bg-foreground\", axis === \"x\" ? \"origin-left rtl:origin-right\" : \"origin-top\")}\r\n        initial={false}\r\n        animate={axis === \"x\" ? { scaleX: scale } : { scaleY: scale }}\r\n        transition={draw && !reduceMotion ? SPRING_DEFAULT : INSTANT}\r\n      />\r\n    </span>\r\n  )\r\n}\r\n\r\nexport { SEGMENT_CLASS, StepConnector, StepMarker, TITLE_CLASS, stepState, type StepState }\r\n",
      "type": "registry:ui"
    }
  ],
  "categories": [
    "navigation"
  ],
  "type": "registry:ui"
}