{
  "name": "Steps",
  "category": "Data Display",
  "subcategory": "Lists & Trees",
  "description": "Numbered step indicator for wizards and progress flows.",
  "thumbnail": "/preview-cards/steps.png",
  "hasDocsPage": true,
  "nugetPackage": "Lumeo",
  "files": [
    "UI/Steps/Steps.razor",
    "UI/Steps/StepsItem.razor"
  ],
  "dependencies": [],
  "packageDependencies": [
    "Blazicons.Lucide"
  ],
  "cssVars": [
    "--color-background",
    "--color-border",
    "--color-destructive",
    "--color-foreground",
    "--color-muted",
    "--color-muted-foreground",
    "--color-primary",
    "--color-primary-foreground",
    "--color-ring",
    "--radius"
  ],
  "api": {
    "name": "Steps",
    "category": "Data Display",
    "subcategory": "Lists & Trees",
    "description": "Numbered step indicator for wizards and progress flows.",
    "nugetPackage": "Lumeo",
    "files": [
      "UI/Steps/Steps.razor",
      "UI/Steps/StepsItem.razor"
    ],
    "namespace": "Lumeo",
    "inheritsFrom": null,
    "implements": [],
    "parameters": [
      {
        "name": "ChildContent",
        "type": "RenderFragment?",
        "default": null,
        "description": null,
        "isCascading": false,
        "captureUnmatched": false
      },
      {
        "name": "CurrentStep",
        "type": "int",
        "default": null,
        "description": null,
        "isCascading": false,
        "captureUnmatched": false
      },
      {
        "name": "CurrentStepChanged",
        "type": "EventCallback<int>",
        "default": null,
        "description": null,
        "isCascading": false,
        "captureUnmatched": false
      },
      {
        "name": "Clickable",
        "type": "bool",
        "default": null,
        "description": null,
        "isCascading": false,
        "captureUnmatched": false
      },
      {
        "name": "Orientation",
        "type": "StepsOrientation",
        "default": "StepsOrientation.Horizontal",
        "description": null,
        "isCascading": false,
        "captureUnmatched": false
      },
      {
        "name": "Animated",
        "type": "bool",
        "default": null,
        "description": "When true, enables opt-in motion: the current step's indicator pulses, connector lines draw in on advance, and retract on reverse. Default is false (no animation, original behaviour).",
        "isCascading": false,
        "captureUnmatched": false
      },
      {
        "name": "Class",
        "type": "string?",
        "default": null,
        "description": null,
        "isCascading": false,
        "captureUnmatched": false
      },
      {
        "name": "AdditionalAttributes",
        "type": "Dictionary<string, object>?",
        "default": null,
        "description": null,
        "isCascading": false,
        "captureUnmatched": true
      }
    ],
    "events": [
      {
        "name": "CurrentStepChanged",
        "type": "EventCallback<int>",
        "description": null
      }
    ],
    "enums": [
      {
        "name": "StepsOrientation",
        "values": [
          "Horizontal",
          "Vertical"
        ],
        "description": null
      },
      {
        "name": "StepsDirection",
        "values": [
          "Forward",
          "Backward"
        ],
        "description": null
      }
    ],
    "records": [
      {
        "name": "StepsContext",
        "signature": "StepsContext(int CurrentStep, bool Clickable, EventCallback<int> OnStepClick, StepsOrientation Orientation, bool Animated, StepsDirection Direction, int ReplayKey, Steps Parent)",
        "description": null
      }
    ],
    "cssVars": [
      "--color-background",
      "--color-border",
      "--color-destructive",
      "--color-foreground",
      "--color-muted",
      "--color-muted-foreground",
      "--color-primary",
      "--color-primary-foreground",
      "--color-ring",
      "--radius"
    ],
    "examples": [
      {
        "title": "Basic Steps",
        "code": "<Steps CurrentStep=\"1\">\n    <StepsItem Title=\"Account\" Description=\"Create your account\" />\n    <StepsItem Title=\"Profile\" Description=\"Set up your profile\" />\n    <StepsItem Title=\"Complete\" Description=\"All done\" />\n</Steps>"
      },
      {
        "title": "Interactive Steps",
        "code": "<Steps @bind-CurrentStep=\"_step\" Clickable=\"true\">\n    <StepsItem Title=\"Details\" />\n    <StepsItem Title=\"Payment\" />\n    <StepsItem Title=\"Review\" />\n    <StepsItem Title=\"Confirm\" />\n</Steps>"
      },
      {
        "title": "Vertical Steps",
        "code": "<Steps CurrentStep=\"2\" Orientation=\"Steps.StepsOrientation.Vertical\">\n    <StepsItem Title=\"Sign Up\" Description=\"Create an account\" />\n    <StepsItem Title=\"Verify Email\" Description=\"Confirm your email\" />\n    <StepsItem Title=\"Set Password\" Description=\"Choose a password\" />\n    <StepsItem Title=\"Done\" Description=\"Start using the app\" />\n</Steps>"
      },
      {
        "title": "Orientation (interactive)",
        "code": "<Segmented @bind-Value=\"_orientation\">\n    <SegmentedItem Value=\"@Steps.StepsOrientation.Horizontal\">Horizontal</SegmentedItem>\n    <SegmentedItem Value=\"@Steps.StepsOrientation.Vertical\">Vertical</SegmentedItem>\n</Segmented>\n<Steps CurrentStep=\"1\" Orientation=\"@_orientation\">\n    <StepsItem Title=\"Account\" Description=\"Create your account\" />\n    <StepsItem Title=\"Profile\" Description=\"Set up your profile\" />\n    <StepsItem Title=\"Payment\" Description=\"Add billing info\" />\n    <StepsItem Title=\"Done\" Description=\"All finished\" />\n</Steps>\n\n@code { private Steps.StepsOrientation _orientation = Steps.StepsOrientation.Horizontal; }"
      }
    ],
    "subComponents": {
      "StepsItem": {
        "componentName": "StepsItem",
        "fileName": "StepsItem.razor",
        "namespace": "Lumeo",
        "inheritsFrom": null,
        "implements": [],
        "parameters": [
          {
            "name": "Context",
            "type": "Steps.StepsContext?",
            "default": null,
            "description": null,
            "isCascading": true,
            "captureUnmatched": false
          },
          {
            "name": "Title",
            "type": "string?",
            "default": null,
            "description": null,
            "isCascading": false,
            "captureUnmatched": false
          },
          {
            "name": "Description",
            "type": "string?",
            "default": null,
            "description": null,
            "isCascading": false,
            "captureUnmatched": false
          },
          {
            "name": "Status",
            "type": "StepsItemStatus",
            "default": "StepsItemStatus.Default",
            "description": null,
            "isCascading": false,
            "captureUnmatched": false
          },
          {
            "name": "IconContent",
            "type": "RenderFragment?",
            "default": null,
            "description": "Icon slot. Named IconContent to avoid shadowing the &lt;Icon&gt; component.",
            "isCascading": false,
            "captureUnmatched": false
          },
          {
            "name": "Class",
            "type": "string?",
            "default": null,
            "description": null,
            "isCascading": false,
            "captureUnmatched": false
          },
          {
            "name": "AdditionalAttributes",
            "type": "Dictionary<string, object>?",
            "default": null,
            "description": null,
            "isCascading": false,
            "captureUnmatched": true
          }
        ],
        "events": [],
        "enums": [
          {
            "name": "StepStatus",
            "values": [
              "Completed",
              "Current",
              "Future"
            ],
            "description": null
          },
          {
            "name": "StepsItemStatus",
            "values": [
              "Default",
              "Error"
            ],
            "description": null
          }
        ],
        "records": [],
        "parseFailed": false,
        "parseError": null
      }
    },
    "parseFailed": false,
    "parseError": null
  },
  "$schema": "https://lumeo.nativ.sh/registry-component-schema.json",
  "key": "steps",
  "docsUrl": "https://lumeo.nativ.sh/components/steps",
  "sourceUrl": [
    {
      "file": "UI/Steps/Steps.razor",
      "url": "https://github.com/Brain2k-0005/Lumeo/tree/master/src/Lumeo/UI/Steps/Steps.razor"
    },
    {
      "file": "UI/Steps/StepsItem.razor",
      "url": "https://github.com/Brain2k-0005/Lumeo/tree/master/src/Lumeo/UI/Steps/StepsItem.razor"
    }
  ],
  "source": [
    {
      "path": "UI/Steps/Steps.razor",
      "content": "@namespace Lumeo\n\n@{\n    var context = new StepsContext(CurrentStep, Clickable, EventCallback.Factory.Create<int>(this, OnStepClick), Orientation, Animated, _direction, _replayKey, this);\n    _index = 0;\n}\n\n<div class=\"@CssClass\" role=\"list\" @attributes=\"AdditionalAttributes\">\n    <CascadingValue Value=\"context\" IsFixed=\"false\">\n        @ChildContent\n    </CascadingValue>\n</div>\n\n@code {\n    [Parameter] public RenderFragment? ChildContent { get; set; }\n    [Parameter] public int CurrentStep { get; set; }\n    [Parameter] public EventCallback<int> CurrentStepChanged { get; set; }\n    [Parameter] public bool Clickable { get; set; }\n    [Parameter] public StepsOrientation Orientation { get; set; } = StepsOrientation.Horizontal;\n\n    /// <summary>\n    /// When true, enables opt-in motion: the current step's indicator pulses,\n    /// connector lines draw in on advance, and retract on reverse. Default is\n    /// <c>false</c> (no animation, original behaviour).\n    /// </summary>\n    [Parameter] public bool Animated { get; set; }\n\n    [Parameter] public string? Class { get; set; }\n    [Parameter(CaptureUnmatchedValues = true)] public Dictionary<string, object>? AdditionalAttributes { get; set; }\n\n    private int _index;\n\n    // Direction tracking for opt-in Animated mode. `_direction` drives which\n    // keyframe the connector plays (forward draw-in vs. backward unwind).\n    // `_replayKey` is bumped every time CurrentStep actually changes so that\n    // `@key` on the connector forces the animation to re-fire on each click.\n    private int _lastCurrentStep;\n    private StepsDirection _direction = StepsDirection.Forward;\n    private int _replayKey;\n    private bool _initialized;\n\n    internal int GetNextIndex() => _index++;\n\n    protected override void OnParametersSet()\n    {\n        if (!_initialized)\n        {\n            _lastCurrentStep = CurrentStep;\n            _initialized = true;\n            return;\n        }\n\n        if (CurrentStep != _lastCurrentStep)\n        {\n            _direction = CurrentStep < _lastCurrentStep ? StepsDirection.Backward : StepsDirection.Forward;\n            _lastCurrentStep = CurrentStep;\n            _replayKey++;\n        }\n    }\n\n    private string CssClass => Orientation switch\n    {\n        StepsOrientation.Vertical => $\"flex flex-col gap-0 {Class}\".Trim(),\n        _ => $\"flex flex-row items-start {Class}\".Trim()\n    };\n\n    private async Task OnStepClick(int step)\n    {\n        if (Clickable)\n        {\n            CurrentStep = step;\n            await CurrentStepChanged.InvokeAsync(step);\n        }\n    }\n\n    public enum StepsOrientation { Horizontal, Vertical }\n    public enum StepsDirection { Forward, Backward }\n\n    public record StepsContext(int CurrentStep, bool Clickable, EventCallback<int> OnStepClick, StepsOrientation Orientation, bool Animated, StepsDirection Direction, int ReplayKey, Steps Parent);\n}\n"
    },
    {
      "path": "UI/Steps/StepsItem.razor",
      "content": "@namespace Lumeo\n\n@{\n    var status = GetStatus();\n    var isHorizontal = Context?.Orientation != Steps.StepsOrientation.Vertical;\n    var isError = Status == StepsItemStatus.Error && status == StepStatus.Current;\n}\n\n@if (isHorizontal)\n{\n    <div class=\"@HorizontalCssClass\" role=\"listitem\" @attributes=\"AdditionalAttributes\">\n        <div class=\"flex flex-col items-center\">\n            <button type=\"button\"\n                    class=\"@GetIndicatorClasses(status, isError)\"\n                    disabled=\"@(!IsClickable)\"\n                    @onclick=\"HandleClick\">\n                @if (IconContent is not null)\n                {\n                    @IconContent\n                }\n                else if (isError)\n                {\n                    <Blazicon Svg=\"Lucide.X\" class=\"h-4 w-4\" />\n                }\n                else if (status == StepStatus.Completed)\n                {\n                    <Blazicon Svg=\"Lucide.Check\" class=\"h-4 w-4\" />\n                }\n                else\n                {\n                    <span>@(_index + 1)</span>\n                }\n            </button>\n            <div class=\"mt-2 text-center\">\n                @if (!string.IsNullOrEmpty(Title))\n                {\n                    <p class=\"@GetTitleClasses(status, isError)\">@Title</p>\n                }\n                @if (!string.IsNullOrEmpty(Description))\n                {\n                    <p class=\"text-xs text-muted-foreground\">@Description</p>\n                }\n            </div>\n        </div>\n        <div class=\"mx-2 mt-4 h-px flex-1 @GetLineClasses(status)\" @key=\"@($\"line-h-{_index}-{status}\")\"></div>\n    </div>\n}\nelse\n{\n    <div class=\"@VerticalCssClass\" role=\"listitem\" @attributes=\"AdditionalAttributes\">\n        <div class=\"flex flex-col items-center\">\n            <button type=\"button\"\n                    class=\"@GetIndicatorClasses(status, isError)\"\n                    disabled=\"@(!IsClickable)\"\n                    @onclick=\"HandleClick\">\n                @if (IconContent is not null)\n                {\n                    @IconContent\n                }\n                else if (isError)\n                {\n                    <Blazicon Svg=\"Lucide.X\" class=\"h-4 w-4\" />\n                }\n                else if (status == StepStatus.Completed)\n                {\n                    <Blazicon Svg=\"Lucide.Check\" class=\"h-4 w-4\" />\n                }\n                else\n                {\n                    <span>@(_index + 1)</span>\n                }\n            </button>\n            <div class=\"my-1 w-px flex-1 @GetLineClasses(status)\" @key=\"@($\"line-v-{_index}-{status}\")\"></div>\n        </div>\n        <div class=\"ml-3 pb-6\">\n            @if (!string.IsNullOrEmpty(Title))\n            {\n                <p class=\"@GetTitleClasses(status, isError)\">@Title</p>\n            }\n            @if (!string.IsNullOrEmpty(Description))\n            {\n                <p class=\"text-xs text-muted-foreground\">@Description</p>\n            }\n        </div>\n    </div>\n}\n\n@code {\n    [CascadingParameter] public Steps.StepsContext? Context { get; set; }\n    [Parameter] public string? Title { get; set; }\n    [Parameter] public string? Description { get; set; }\n    [Parameter] public StepsItemStatus Status { get; set; } = StepsItemStatus.Default;\n\n    /// <summary>Icon slot. Named <c>IconContent</c> to avoid shadowing the <c>&lt;Icon&gt;</c> component.</summary>\n    [Parameter] public RenderFragment? IconContent { get; set; }\n\n    [Parameter] public string? Class { get; set; }\n    [Parameter(CaptureUnmatchedValues = true)] public Dictionary<string, object>? AdditionalAttributes { get; set; }\n\n    private int _index;\n\n    protected override void OnParametersSet()\n    {\n        if (Context?.Parent is not null)\n        {\n            _index = Context.Parent.GetNextIndex();\n        }\n    }\n\n    private bool IsClickable => Context?.Clickable == true;\n    private bool IsAnimated => Context?.Animated == true;\n\n    private StepStatus GetStatus()\n    {\n        var current = Context?.CurrentStep ?? 0;\n        if (_index < current) return StepStatus.Completed;\n        if (_index == current) return StepStatus.Current;\n        return StepStatus.Future;\n    }\n\n    private string GetIndicatorClasses(StepStatus status, bool isError)\n    {\n        var baseClasses = isError\n            ? \"flex h-8 w-8 shrink-0 items-center justify-center rounded-full border-2 border-destructive bg-background text-destructive\"\n            : status switch\n            {\n                StepStatus.Completed => \"flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-primary text-primary-foreground\",\n                StepStatus.Current => \"flex h-8 w-8 shrink-0 items-center justify-center rounded-full border-2 border-primary bg-background text-sm font-semibold text-primary\",\n                _ => \"flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-muted text-sm text-muted-foreground\"\n            };\n\n        // Only the Current (active) indicator pulses, and only when not in an error state.\n        if (IsAnimated && status == StepStatus.Current && !isError)\n        {\n            baseClasses += \" lumeo-steps-pulse\";\n        }\n\n        baseClasses += IsClickable\n            ? \" cursor-pointer focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2\"\n            : \" cursor-default\";\n\n        return baseClasses;\n    }\n\n    private string GetTitleClasses(StepStatus status, bool isError) =>\n        isError\n            ? \"text-sm font-semibold text-destructive\"\n            : status switch\n            {\n                StepStatus.Completed => \"text-sm font-medium text-foreground\",\n                StepStatus.Current => \"text-sm font-semibold text-foreground\",\n                _ => \"text-sm text-muted-foreground\"\n            };\n\n    private string GetLineClasses(StepStatus status)\n    {\n        var defaultColor = status switch\n        {\n            StepStatus.Completed => \"bg-primary\",\n            _ => \"bg-border\"\n        };\n\n        if (!IsAnimated)\n        {\n            return defaultColor;\n        }\n\n        var isVertical = Context?.Orientation == Steps.StepsOrientation.Vertical;\n        var isBackward = Context?.Direction == Steps.StepsDirection.Backward;\n\n        // Only animate the SINGLE connector that just transitioned state.\n        // Forward: connector at _index becomes completed when CurrentStep advances past it.\n        // Backward: connector at _index == new CurrentStep was just un-completed.\n        var current = Context?.CurrentStep ?? 0;\n\n        // Forward draw-in: this connector is now completed AND was just lit up\n        // (i.e. its index equals the new-current-1 — the one we just crossed).\n        if (!isBackward && status == StepStatus.Completed && _index == current - 1)\n        {\n            return $\"{defaultColor} \" + (isVertical ? \"lumeo-steps-line-draw-y\" : \"lumeo-steps-line-draw-x\");\n        }\n\n        // Backward unwind: connector at new CurrentStep was completed, now pending.\n        // Force bg-primary during the animation so the user sees a coloured\n        // line retracting (otherwise it's a bg-border grey line shrinking\n        // — invisible against the muted background).\n        if (isBackward && status != StepStatus.Completed && _index == current)\n        {\n            return \"bg-primary \" + (isVertical ? \"lumeo-steps-line-unwind-y\" : \"lumeo-steps-line-unwind-x\");\n        }\n\n        return defaultColor;\n    }\n\n    private async Task HandleClick()\n    {\n        if (Context is not null && IsClickable)\n        {\n            await Context.OnStepClick.InvokeAsync(_index);\n        }\n    }\n\n    private string HorizontalCssClass => $\"flex flex-1 flex-row last:flex-initial {Class}\".Trim();\n    private string VerticalCssClass => $\"flex flex-row {Class}\".Trim();\n\n    private enum StepStatus { Completed, Current, Future }\n\n    public enum StepsItemStatus { Default, Error }\n}\n"
    }
  ],
  "slots": [
    {
      "name": "ChildContent",
      "owner": "Steps",
      "type": "RenderFragment?",
      "description": null
    },
    {
      "name": "IconContent",
      "owner": "StepsItem",
      "type": "RenderFragment?",
      "description": "Icon slot. Named IconContent to avoid shadowing the &lt;Icon&gt; component."
    }
  ],
  "serviceDependencies": [],
  "cascadingDependencies": [
    {
      "name": "Context",
      "type": "Steps.StepsContext?",
      "required": false
    }
  ],
  "relatedComponents": [
    {
      "name": "StepsItem",
      "reason": "sub-component"
    }
  ],
  "keyboardInteractions": [],
  "tests": [
    "tests/Lumeo.Tests/Components/Steps/StepsAnimatedTests.cs",
    "tests/Lumeo.Tests/Components/Steps/StepsDirectionTests.cs",
    "tests/Lumeo.Tests/Components/Steps/StepsTests.cs",
    "tests/Lumeo.Tests/Components/Tour/TourTests.cs"
  ],
  "mdSummary": "# Steps\n\nNumbered step indicator for wizards and progress flows.\n\n**Package:** `Lumeo` · **Category:** Data Display/Lists & Trees\n**Docs:** https://lumeo.nativ.sh/components/steps\n\n## Parameters\n| Owner | Name | Type | Default | Description |\n|---|---|---|---|---|\n| Steps | ChildContent | `RenderFragment?` | `` |  |\n| Steps | CurrentStep | `int` | `` |  |\n| Steps | CurrentStepChanged | `EventCallback<int>` | `` |  |\n| Steps | Clickable | `bool` | `` |  |\n| Steps | Orientation | `StepsOrientation` | `StepsOrientation.Horizontal` |  |\n| Steps | Animated | `bool` | `` | When true, enables opt-in motion: the current step's indicator pulses, connector lines draw in on advance, and retract on reverse. Default is false (no animation, original behaviour). |\n| Steps | Class | `string?` | `` |  |\n| Steps | AdditionalAttributes | `Dictionary<string, object>?` | `` |  |\n| StepsItem | Context | `Steps.StepsContext?` | `` |  |\n| StepsItem | Title | `string?` | `` |  |\n| StepsItem | Description | `string?` | `` |  |\n| StepsItem | Status | `StepsItemStatus` | `StepsItemStatus.Default` |  |\n| StepsItem | IconContent | `RenderFragment?` | `` | Icon slot. Named IconContent to avoid shadowing the &lt;Icon&gt; component. |\n| StepsItem | Class | `string?` | `` |  |\n| StepsItem | AdditionalAttributes | `Dictionary<string, object>?` | `` |  |\n\n## Slots\n- `ChildContent` (Steps) — RenderFragment slot\n- `IconContent` (StepsItem) — Icon slot. Named IconContent to avoid shadowing the &lt;Icon&gt; component.\n\n## Events\n- `CurrentStepChanged` (`EventCallback<int>`)\n\n## Sub-components\n- `StepsItem`\n\n## Cascading values it expects\n- `Context` (`Steps.StepsContext?`)\n\n## Services it injects\n- (none)\n\n## Keyboard\n—\n\n## Example\n```razor\n<Steps />\n```\n\n## CSS variables\n`--color-background`, `--color-border`, `--color-destructive`, `--color-foreground`, `--color-muted`, `--color-muted-foreground`, `--color-primary`, `--color-primary-foreground`, `--color-ring`, `--radius`\n\n## Files\n- `UI/Steps/Steps.razor`\n- `UI/Steps/StepsItem.razor`\n"
}