{
  "name": "Scrollspy",
  "category": "Navigation",
  "subcategory": null,
  "description": "Highlights the nav item matching the current scroll section.",
  "thumbnail": "/preview-cards/scrollspy.png",
  "hasDocsPage": true,
  "nugetPackage": "Lumeo",
  "files": [
    "UI/Scrollspy/Scrollspy.razor",
    "UI/Scrollspy/ScrollspyLink.razor",
    "UI/Scrollspy/ScrollspySection.razor"
  ],
  "dependencies": [],
  "packageDependencies": [],
  "cssVars": [
    "--color-foreground"
  ],
  "api": {
    "name": "Scrollspy",
    "category": "Navigation",
    "subcategory": null,
    "description": "Highlights the nav item matching the current scroll section.",
    "nugetPackage": "Lumeo",
    "files": [
      "UI/Scrollspy/Scrollspy.razor",
      "UI/Scrollspy/ScrollspyLink.razor",
      "UI/Scrollspy/ScrollspySection.razor"
    ],
    "namespace": "Lumeo",
    "inheritsFrom": null,
    "implements": [
      "IAsyncDisposable"
    ],
    "parameters": [
      {
        "name": "ChildContent",
        "type": "RenderFragment?",
        "default": null,
        "description": null,
        "isCascading": false,
        "captureUnmatched": false
      },
      {
        "name": "Offset",
        "type": "int",
        "default": null,
        "description": null,
        "isCascading": false,
        "captureUnmatched": false
      },
      {
        "name": "Smooth",
        "type": "bool",
        "default": "true",
        "description": null,
        "isCascading": false,
        "captureUnmatched": false
      },
      {
        "name": "ActiveId",
        "type": "string?",
        "default": null,
        "description": null,
        "isCascading": false,
        "captureUnmatched": false
      },
      {
        "name": "ActiveIdChanged",
        "type": "EventCallback<string>",
        "default": null,
        "description": null,
        "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": "ActiveIdChanged",
        "type": "EventCallback<string>",
        "description": null
      }
    ],
    "enums": [],
    "records": [
      {
        "name": "ScrollspyContext",
        "signature": "ScrollspyContext(string? ActiveId, EventCallback<string> OnNavigate)",
        "description": null
      }
    ],
    "cssVars": [
      "--color-foreground"
    ],
    "examples": [
      {
        "title": "Vertical Scrollspy",
        "code": "<Scrollspy Class=\"flex gap-4\" @bind-ActiveId=\"_active\">\n    <nav class=\"flex flex-col gap-1 w-40 shrink-0\">\n        <ScrollspyLink Target=\"section-1\"\n                       Class=\"px-3 py-1.5 rounded-md text-sm font-medium text-muted-foreground\n                              data-[active=true]:bg-primary data-[active=true]:text-primary-foreground\">\n            Section 1\n        </ScrollspyLink>\n        <ScrollspyLink Target=\"section-2\" Class=\"...\">Section 2</ScrollspyLink>\n        <ScrollspyLink Target=\"section-3\" Class=\"...\">Section 3</ScrollspyLink>\n    </nav>\n    <div class=\"flex-1 overflow-y-auto h-[400px] rounded-md border border-border/40\">\n        <ScrollspySection Id=\"section-1\" Class=\"p-6 min-h-[300px]\">\n            <h3>Section 1</h3>\n            <p>Content for section 1...</p>\n        </ScrollspySection>\n        <ScrollspySection Id=\"section-2\" Class=\"p-6 min-h-[300px]\">\n            <h3>Section 2</h3>\n            <p>Content for section 2...</p>\n        </ScrollspySection>\n        <ScrollspySection Id=\"section-3\" Class=\"p-6 min-h-[300px]\">\n            <h3>Section 3</h3>\n            <p>Content for section 3...</p>\n        </ScrollspySection>\n    </div>\n</Scrollspy>\n\n@code {\n    private string? _active;\n}"
      },
      {
        "title": "Horizontal Nav",
        "code": "<Scrollspy Class=\"flex flex-col\" @bind-ActiveId=\"_active\">\n    <nav class=\"flex gap-1 mb-4 border-b pb-2\">\n        <ScrollspyLink Target=\"section-1\"\n                       Class=\"px-3 py-1.5 rounded-md text-sm font-medium text-muted-foreground\n                              data-[active=true]:bg-primary data-[active=true]:text-primary-foreground\">\n            Section 1\n        </ScrollspyLink>\n        <ScrollspyLink Target=\"section-2\" Class=\"...\">Section 2</ScrollspyLink>\n    </nav>\n    <div class=\"overflow-y-auto h-[400px] rounded-md border border-border/40\">\n        <ScrollspySection Id=\"section-1\" Class=\"p-6 min-h-[300px]\">\n            <h3>Section 1</h3>\n            <p>Content...</p>\n        </ScrollspySection>\n        <ScrollspySection Id=\"section-2\" Class=\"p-6 min-h-[300px]\">\n            <h3>Section 2</h3>\n            <p>Content...</p>\n        </ScrollspySection>\n    </div>\n</Scrollspy>\n\n@code {\n    private string? _active;\n}"
      }
    ],
    "subComponents": {
      "ScrollspyLink": {
        "componentName": "ScrollspyLink",
        "fileName": "ScrollspyLink.razor",
        "namespace": "Lumeo",
        "inheritsFrom": null,
        "implements": [],
        "parameters": [
          {
            "name": "Context",
            "type": "Scrollspy.ScrollspyContext?",
            "default": null,
            "description": null,
            "isCascading": true,
            "captureUnmatched": false
          },
          {
            "name": "Target",
            "type": "string",
            "default": "\"\"",
            "description": null,
            "isCascading": false,
            "captureUnmatched": false
          },
          {
            "name": "ChildContent",
            "type": "RenderFragment?",
            "default": null,
            "description": null,
            "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": [],
        "records": [],
        "parseFailed": false,
        "parseError": null
      },
      "ScrollspySection": {
        "componentName": "ScrollspySection",
        "fileName": "ScrollspySection.razor",
        "namespace": "Lumeo",
        "inheritsFrom": null,
        "implements": [],
        "parameters": [
          {
            "name": "Id",
            "type": "string",
            "default": "\"\"",
            "description": null,
            "isCascading": false,
            "captureUnmatched": false
          },
          {
            "name": "ChildContent",
            "type": "RenderFragment?",
            "default": null,
            "description": null,
            "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": [],
        "records": [],
        "parseFailed": false,
        "parseError": null
      }
    },
    "parseFailed": false,
    "parseError": null
  },
  "$schema": "https://lumeo.nativ.sh/registry-component-schema.json",
  "key": "scrollspy",
  "docsUrl": "https://lumeo.nativ.sh/components/scrollspy",
  "sourceUrl": [
    {
      "file": "UI/Scrollspy/Scrollspy.razor",
      "url": "https://github.com/Brain2k-0005/Lumeo/tree/master/src/Lumeo/UI/Scrollspy/Scrollspy.razor"
    },
    {
      "file": "UI/Scrollspy/ScrollspyLink.razor",
      "url": "https://github.com/Brain2k-0005/Lumeo/tree/master/src/Lumeo/UI/Scrollspy/ScrollspyLink.razor"
    },
    {
      "file": "UI/Scrollspy/ScrollspySection.razor",
      "url": "https://github.com/Brain2k-0005/Lumeo/tree/master/src/Lumeo/UI/Scrollspy/ScrollspySection.razor"
    }
  ],
  "source": [
    {
      "path": "UI/Scrollspy/Scrollspy.razor",
      "content": "@namespace Lumeo\n@inject ComponentInteropService Interop\n@implements IAsyncDisposable\n\n<div id=\"@_containerId\" data-slot=\"scrollspy\" class=\"@CssClass\" @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 Offset { get; set; }\n    [Parameter] public bool Smooth { get; set; } = true;\n    [Parameter] public string? ActiveId { get; set; }\n    [Parameter] public EventCallback<string> ActiveIdChanged { get; set; }\n    [Parameter] public string? Class { get; set; }\n    [Parameter(CaptureUnmatchedValues = true)] public Dictionary<string, object>? AdditionalAttributes { get; set; }\n\n    private readonly string _containerId = $\"scrollspy-{Guid.NewGuid():N}\";\n    private ScrollspyContext _context = default!;\n    private string? _currentActiveId;\n\n    private string CssClass => $\"relative {Class}\".Trim();\n\n    protected override void OnInitialized()\n    {\n        _currentActiveId = ActiveId;\n        _context = new ScrollspyContext(_currentActiveId, EventCallback.Factory.Create<string>(this, OnNavigate));\n    }\n\n    protected override async Task OnAfterRenderAsync(bool firstRender)\n    {\n        if (!firstRender) return;\n        try\n        {\n            await Interop.RegisterScrollspy(_containerId, Offset, Smooth, OnScrollspyUpdate);\n        }\n        catch (JSDisconnectedException) { }\n        catch (ObjectDisposedException) { }\n    }\n\n    private async Task OnScrollspyUpdate(string? activeId)\n    {\n        if (_currentActiveId != activeId)\n        {\n            _currentActiveId = activeId;\n            _context = new ScrollspyContext(_currentActiveId, EventCallback.Factory.Create<string>(this, OnNavigate));\n            await ActiveIdChanged.InvokeAsync(activeId ?? string.Empty);\n            await InvokeAsync(StateHasChanged);\n        }\n    }\n\n    private async Task OnNavigate(string sectionId)\n    {\n        await Interop.ScrollspyScrollTo(_containerId, sectionId, Smooth);\n    }\n\n    public async ValueTask DisposeAsync()\n    {\n        try\n        {\n            await Interop.UnregisterScrollspy(_containerId);\n        }\n        catch (JSDisconnectedException) { }\n    }\n\n    public record ScrollspyContext(string? ActiveId, EventCallback<string> OnNavigate);\n}\n"
    },
    {
      "path": "UI/Scrollspy/ScrollspyLink.razor",
      "content": "@namespace Lumeo\n\n<button data-slot=\"scrollspy-link\"\n        data-active=\"@(Context?.ActiveId == Target ? \"true\" : null)\"\n        class=\"@CssClass\"\n        @onclick=\"HandleClick\"\n        @attributes=\"AdditionalAttributes\">\n    @ChildContent\n</button>\n\n@code {\n    [CascadingParameter] public Scrollspy.ScrollspyContext? Context { get; set; }\n    [Parameter, EditorRequired] public string Target { get; set; } = \"\";\n    [Parameter] public RenderFragment? ChildContent { get; set; }\n    [Parameter] public string? Class { get; set; }\n    [Parameter(CaptureUnmatchedValues = true)] public Dictionary<string, object>? AdditionalAttributes { get; set; }\n\n    private string CssClass => $\"{Class}\".Trim();\n\n    private async Task HandleClick()\n    {\n        if (Context is not null)\n        {\n            await Context.OnNavigate.InvokeAsync(Target);\n        }\n    }\n}\n"
    },
    {
      "path": "UI/Scrollspy/ScrollspySection.razor",
      "content": "@namespace Lumeo\n\n<div id=\"@Id\" data-scrollspy-section class=\"@CssClass\" @attributes=\"AdditionalAttributes\">\n    @ChildContent\n</div>\n\n@code {\n    [Parameter, EditorRequired] public string Id { get; set; } = \"\";\n    [Parameter] public RenderFragment? ChildContent { get; set; }\n    [Parameter] public string? Class { get; set; }\n    [Parameter(CaptureUnmatchedValues = true)] public Dictionary<string, object>? AdditionalAttributes { get; set; }\n\n    private string CssClass => $\"{Class}\".Trim();\n}\n"
    }
  ],
  "slots": [
    {
      "name": "ChildContent",
      "owner": "Scrollspy",
      "type": "RenderFragment?",
      "description": null
    },
    {
      "name": "ChildContent",
      "owner": "ScrollspyLink",
      "type": "RenderFragment?",
      "description": null
    },
    {
      "name": "ChildContent",
      "owner": "ScrollspySection",
      "type": "RenderFragment?",
      "description": null
    }
  ],
  "serviceDependencies": [
    {
      "service": "ComponentInteropService",
      "namespace": ""
    }
  ],
  "cascadingDependencies": [
    {
      "name": "Context",
      "type": "Scrollspy.ScrollspyContext?",
      "required": false
    }
  ],
  "relatedComponents": [
    {
      "name": "ScrollspyLink",
      "reason": "sub-component"
    },
    {
      "name": "ScrollspySection",
      "reason": "sub-component"
    }
  ],
  "keyboardInteractions": [],
  "tests": [
    "tests/Lumeo.Docs.Tests/Helpers/DocsTestContext.cs",
    "tests/Lumeo.Tests/Components/Scrollspy/ScrollspyTests.cs",
    "tests/Lumeo.Tests/Services/ComponentInteropServiceTests.cs",
    "tests/Lumeo.Tests/Services/Interop/InteropAdapterTests.cs",
    "tests/Lumeo.Tests/Services/StrictInteropTests.cs"
  ],
  "mdSummary": "# Scrollspy\n\nHighlights the nav item matching the current scroll section.\n\n**Package:** `Lumeo` · **Category:** Navigation\n**Docs:** https://lumeo.nativ.sh/components/scrollspy\n\n## Parameters\n| Owner | Name | Type | Default | Description |\n|---|---|---|---|---|\n| Scrollspy | ChildContent | `RenderFragment?` | `` |  |\n| Scrollspy | Offset | `int` | `` |  |\n| Scrollspy | Smooth | `bool` | `true` |  |\n| Scrollspy | ActiveId | `string?` | `` |  |\n| Scrollspy | ActiveIdChanged | `EventCallback<string>` | `` |  |\n| Scrollspy | Class | `string?` | `` |  |\n| Scrollspy | AdditionalAttributes | `Dictionary<string, object>?` | `` |  |\n| ScrollspyLink | Context | `Scrollspy.ScrollspyContext?` | `` |  |\n| ScrollspyLink | Target | `string` | `\"\"` |  |\n| ScrollspyLink | ChildContent | `RenderFragment?` | `` |  |\n| ScrollspyLink | Class | `string?` | `` |  |\n| ScrollspyLink | AdditionalAttributes | `Dictionary<string, object>?` | `` |  |\n| ScrollspySection | Id | `string` | `\"\"` |  |\n| ScrollspySection | ChildContent | `RenderFragment?` | `` |  |\n| ScrollspySection | Class | `string?` | `` |  |\n| ScrollspySection | AdditionalAttributes | `Dictionary<string, object>?` | `` |  |\n\n## Slots\n- `ChildContent` (Scrollspy) — RenderFragment slot\n- `ChildContent` (ScrollspyLink) — RenderFragment slot\n- `ChildContent` (ScrollspySection) — RenderFragment slot\n\n## Events\n- `ActiveIdChanged` (`EventCallback<string>`)\n\n## Sub-components\n- `ScrollspyLink`, `ScrollspySection`\n\n## Cascading values it expects\n- `Context` (`Scrollspy.ScrollspyContext?`)\n\n## Services it injects\n- `ComponentInteropService` ()\n\n## Keyboard\n—\n\n## Example\n```razor\n<Scrollspy />\n```\n\n## CSS variables\n`--color-foreground`\n\n## Files\n- `UI/Scrollspy/Scrollspy.razor`\n- `UI/Scrollspy/ScrollspyLink.razor`\n- `UI/Scrollspy/ScrollspySection.razor`\n"
}