{
  "name": "Resizable",
  "category": "Layout",
  "subcategory": null,
  "description": "Draggable splitter for resizable panel layouts.",
  "thumbnail": "/preview-cards/resizable.png",
  "hasDocsPage": true,
  "nugetPackage": "Lumeo",
  "files": [
    "UI/Resizable/ResizableHandle.razor",
    "UI/Resizable/ResizablePanel.razor",
    "UI/Resizable/ResizablePanelGroup.razor"
  ],
  "dependencies": [],
  "packageDependencies": [
    "Blazicons.Lucide"
  ],
  "cssVars": [
    "--color-border",
    "--color-primary",
    "--color-ring",
    "--radius"
  ],
  "api": {
    "name": "Resizable",
    "category": "Layout",
    "subcategory": null,
    "description": "Draggable splitter for resizable panel layouts.",
    "nugetPackage": "Lumeo",
    "files": [
      "UI/Resizable/ResizableHandle.razor",
      "UI/Resizable/ResizablePanel.razor",
      "UI/Resizable/ResizablePanelGroup.razor"
    ],
    "namespace": "Lumeo",
    "inheritsFrom": null,
    "implements": [
      "IAsyncDisposable"
    ],
    "parameters": [
      {
        "name": "Context",
        "type": "ResizablePanelGroup.ResizablePanelGroupContext",
        "default": "default!",
        "description": null,
        "isCascading": true,
        "captureUnmatched": false
      },
      {
        "name": "PanelIndex",
        "type": "int",
        "default": null,
        "description": null,
        "isCascading": false,
        "captureUnmatched": false
      },
      {
        "name": "WithHandle",
        "type": "bool",
        "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": [],
    "cssVars": [
      "--color-border",
      "--color-primary",
      "--color-ring",
      "--radius"
    ],
    "examples": [
      {
        "title": "Horizontal",
        "code": "<ResizablePanelGroup Direction=\"ResizablePanelGroup.ResizableDirection.Horizontal\"\n                       DefaultSizes=\"@(new double[] { 50, 50 })\" Class=\"min-h-[200px] rounded-lg border\">\n    <ResizablePanel Order=\"0\">\n        <div class=\"flex h-full items-center justify-center p-6\">\n            <span class=\"font-semibold\">Panel One</span>\n        </div>\n    </ResizablePanel>\n    <ResizableHandle PanelIndex=\"0\" WithHandle=\"true\" />\n    <ResizablePanel Order=\"1\">\n        <div class=\"flex h-full items-center justify-center p-6\">\n            <span class=\"font-semibold\">Panel Two</span>\n        </div>\n    </ResizablePanel>\n</ResizablePanelGroup>"
      },
      {
        "title": "Vertical",
        "code": "<ResizablePanelGroup Direction=\"ResizablePanelGroup.ResizableDirection.Vertical\"\n                       DefaultSizes=\"@(new double[] { 40, 60 })\" Class=\"min-h-[300px] rounded-lg border\">\n    <ResizablePanel Order=\"0\">\n        <div class=\"flex h-full items-center justify-center p-6\">\n            <span class=\"font-semibold\">Top</span>\n        </div>\n    </ResizablePanel>\n    <ResizableHandle PanelIndex=\"0\" WithHandle=\"true\" />\n    <ResizablePanel Order=\"1\">\n        <div class=\"flex h-full items-center justify-center p-6\">\n            <span class=\"font-semibold\">Bottom</span>\n        </div>\n    </ResizablePanel>\n</ResizablePanelGroup>"
      },
      {
        "title": "Three Panels",
        "code": "<ResizablePanelGroup Direction=\"ResizablePanelGroup.ResizableDirection.Horizontal\"\n                       DefaultSizes=\"@(new double[] { 25, 50, 25 })\" Class=\"min-h-[200px] rounded-lg border\">\n    <ResizablePanel Order=\"0\">\n        <div class=\"flex h-full items-center justify-center p-6\">\n            <span class=\"font-semibold\">Sidebar</span>\n        </div>\n    </ResizablePanel>\n    <ResizableHandle PanelIndex=\"0\" WithHandle=\"true\" />\n    <ResizablePanel Order=\"1\">\n        <div class=\"flex h-full items-center justify-center p-6\">\n            <span class=\"font-semibold\">Content</span>\n        </div>\n    </ResizablePanel>\n    <ResizableHandle PanelIndex=\"1\" WithHandle=\"true\" />\n    <ResizablePanel Order=\"2\">\n        <div class=\"flex h-full items-center justify-center p-6\">\n            <span class=\"font-semibold\">Panel</span>\n        </div>\n    </ResizablePanel>\n</ResizablePanelGroup>"
      }
    ],
    "subComponents": {
      "ResizablePanel": {
        "componentName": "ResizablePanel",
        "fileName": "ResizablePanel.razor",
        "namespace": "Lumeo",
        "inheritsFrom": null,
        "implements": [],
        "parameters": [
          {
            "name": "Context",
            "type": "ResizablePanelGroup.ResizablePanelGroupContext",
            "default": "default!",
            "description": null,
            "isCascading": true,
            "captureUnmatched": false
          },
          {
            "name": "Order",
            "type": "int",
            "default": null,
            "description": null,
            "isCascading": false,
            "captureUnmatched": false
          },
          {
            "name": "DefaultSize",
            "type": "double",
            "default": "50",
            "description": null,
            "isCascading": false,
            "captureUnmatched": false
          },
          {
            "name": "MinSize",
            "type": "double",
            "default": "10",
            "description": null,
            "isCascading": false,
            "captureUnmatched": false
          },
          {
            "name": "MaxSize",
            "type": "double",
            "default": "90",
            "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
      },
      "ResizablePanelGroup": {
        "componentName": "ResizablePanelGroup",
        "fileName": "ResizablePanelGroup.razor",
        "namespace": "Lumeo",
        "inheritsFrom": null,
        "implements": [
          "IAsyncDisposable"
        ],
        "parameters": [
          {
            "name": "ChildContent",
            "type": "RenderFragment?",
            "default": null,
            "description": null,
            "isCascading": false,
            "captureUnmatched": false
          },
          {
            "name": "Direction",
            "type": "ResizableDirection",
            "default": "ResizableDirection.Horizontal",
            "description": null,
            "isCascading": false,
            "captureUnmatched": false
          },
          {
            "name": "DefaultSizes",
            "type": "double[]?",
            "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": [
          {
            "name": "ResizableDirection",
            "values": [
              "Horizontal",
              "Vertical"
            ],
            "description": null
          }
        ],
        "records": [
          {
            "name": "ResizablePanelGroupContext",
            "signature": "ResizablePanelGroupContext(\n        string GroupId,\n        ResizableDirection Direction,\n        EventCallback<(int PanelIndex, double Delta)> OnHandleDrag,\n        Func<int, double> GetPanelSize,\n        int PanelCount,\n        Action<int, double, double> RegisterPanel\n    )",
            "description": null
          }
        ],
        "parseFailed": false,
        "parseError": null
      }
    },
    "parseFailed": false,
    "parseError": null
  },
  "$schema": "https://lumeo.nativ.sh/registry-component-schema.json",
  "key": "resizable",
  "docsUrl": "https://lumeo.nativ.sh/components/resizable",
  "sourceUrl": [
    {
      "file": "UI/Resizable/ResizableHandle.razor",
      "url": "https://github.com/Brain2k-0005/Lumeo/tree/master/src/Lumeo/UI/Resizable/ResizableHandle.razor"
    },
    {
      "file": "UI/Resizable/ResizablePanel.razor",
      "url": "https://github.com/Brain2k-0005/Lumeo/tree/master/src/Lumeo/UI/Resizable/ResizablePanel.razor"
    },
    {
      "file": "UI/Resizable/ResizablePanelGroup.razor",
      "url": "https://github.com/Brain2k-0005/Lumeo/tree/master/src/Lumeo/UI/Resizable/ResizablePanelGroup.razor"
    }
  ],
  "source": [
    {
      "path": "UI/Resizable/ResizableHandle.razor",
      "content": "@namespace Lumeo\n@implements IAsyncDisposable\n\n<div id=\"@_handleId\"\n     role=\"separator\"\n     tabindex=\"0\"\n     class=\"@CssClass\"\n     data-panel-group-direction=\"@DirectionString\"\n     @attributes=\"AdditionalAttributes\">\n    @if (WithHandle)\n    {\n        @if (Context.Direction == ResizablePanelGroup.ResizableDirection.Horizontal)\n        {\n            <div class=\"z-10 flex h-4 w-3 items-center justify-center rounded-sm border bg-border\">\n                <Blazicon Svg=\"Lucide.GripVertical\" class=\"h-2.5 w-2.5\" />\n            </div>\n        }\n        else\n        {\n            <div class=\"z-10 flex h-3 w-4 items-center justify-center rounded-sm border bg-border\">\n                <Blazicon Svg=\"Lucide.GripHorizontal\" class=\"h-2.5 w-2.5\" />\n            </div>\n        }\n    }\n</div>\n\n@inject ComponentInteropService Interop\n\n@code {\n    [CascadingParameter] public ResizablePanelGroup.ResizablePanelGroupContext Context { get; set; } = default!;\n    [Parameter, EditorRequired] public int PanelIndex { get; set; }\n    [Parameter] public bool WithHandle { 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 _handleId = $\"resizable-handle-{Guid.NewGuid():N}\";\n\n    private string DirectionString =>\n        Context.Direction == ResizablePanelGroup.ResizableDirection.Horizontal ? \"horizontal\" : \"vertical\";\n\n    private string CssClass =>\n        Context.Direction == ResizablePanelGroup.ResizableDirection.Horizontal\n            ? $\"relative flex w-px items-center justify-center bg-border after:absolute after:inset-y-0 after:-left-1 after:-right-1 after:content-[''] hover:bg-primary/50 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring focus-visible:ring-offset-1 cursor-col-resize {Class}\".Trim()\n            : $\"relative flex h-px w-full items-center justify-center bg-border after:absolute after:inset-x-0 after:-top-1 after:-bottom-1 after:content-[''] hover:bg-primary/50 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring focus-visible:ring-offset-1 cursor-row-resize {Class}\".Trim();\n\n    protected override async Task OnAfterRenderAsync(bool firstRender)\n    {\n        if (!firstRender) return;\n        try\n        {\n            var direction = Context.Direction == ResizablePanelGroup.ResizableDirection.Horizontal\n                ? \"horizontal\"\n                : \"vertical\";\n\n            await Interop.RegisterResizeHandle(\n                _handleId,\n                direction,\n                async (delta) =>\n                {\n                    await Context.OnHandleDrag.InvokeAsync((PanelIndex, delta));\n                },\n                async () =>\n                {\n                    await InvokeAsync(StateHasChanged);\n                }\n            );\n        }\n        catch (JSDisconnectedException) { }\n        catch (ObjectDisposedException) { }\n    }\n\n    public async ValueTask DisposeAsync()\n    {\n        try\n        {\n            await Interop.UnregisterResizeHandle(_handleId);\n        }\n        catch (JSDisconnectedException) { }\n    }\n}\n"
    },
    {
      "path": "UI/Resizable/ResizablePanel.razor",
      "content": "@namespace Lumeo\n\n<div class=\"@CssClass\"\n     style=\"flex: @(Size.ToString(\"F2\", System.Globalization.CultureInfo.InvariantCulture)) 1 0; min-width: 0; min-height: 0; overflow: auto;\"\n     @attributes=\"AdditionalAttributes\">\n    @ChildContent\n</div>\n\n@code {\n    [CascadingParameter] public ResizablePanelGroup.ResizablePanelGroupContext Context { get; set; } = default!;\n    [Parameter] public int Order { get; set; }\n    [Parameter] public double DefaultSize { get; set; } = 50;\n    [Parameter] public double MinSize { get; set; } = 10;\n    [Parameter] public double MaxSize { get; set; } = 90;\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 double Size => Context?.GetPanelSize(Order) ?? DefaultSize;\n\n    private string CssClass => $\"relative {Class}\".Trim();\n\n    protected override void OnInitialized()\n    {\n        Context?.RegisterPanel?.Invoke(Order, MinSize, MaxSize);\n    }\n}\n"
    },
    {
      "path": "UI/Resizable/ResizablePanelGroup.razor",
      "content": "@namespace Lumeo\n@implements IAsyncDisposable\n\n@{\n    var context = new ResizablePanelGroupContext(\n        _groupId,\n        Direction,\n        EventCallback.Factory.Create<(int PanelIndex, double Delta)>(this, HandleDrag),\n        GetPanelSize,\n        _sizes.Count,\n        RegisterPanel\n    );\n}\n\n<div id=\"@_groupId\"\n     class=\"@CssClass\"\n     data-panel-group-direction=\"@DirectionString\"\n     @attributes=\"AdditionalAttributes\">\n    <CascadingValue Value=\"context\" IsFixed=\"false\">\n        @ChildContent\n    </CascadingValue>\n</div>\n\n@inject ComponentInteropService Interop\n\n@code {\n    [Parameter] public RenderFragment? ChildContent { get; set; }\n    [Parameter] public ResizableDirection Direction { get; set; } = ResizableDirection.Horizontal;\n    [Parameter] public double[]? DefaultSizes { 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 _groupId = $\"resizable-group-{Guid.NewGuid():N}\";\n    private List<double> _sizes = new();\n    private int _panelCount;\n    private readonly Dictionary<int, (double MinSize, double MaxSize)> _panelConstraints = new();\n\n    private string DirectionString => Direction == ResizableDirection.Horizontal ? \"horizontal\" : \"vertical\";\n\n    private string CssClass =>\n        Direction == ResizableDirection.Horizontal\n            ? $\"flex h-full w-full {Class}\".Trim()\n            : $\"flex h-full w-full flex-col {Class}\".Trim();\n\n    protected override void OnParametersSet()\n    {\n        if (DefaultSizes is { Length: > 0 })\n        {\n            if (_sizes.Count != DefaultSizes.Length)\n            {\n                _sizes = new List<double>(DefaultSizes);\n                _panelCount = DefaultSizes.Length;\n            }\n        }\n    }\n\n    private double GetPanelSize(int index)\n    {\n        if (index >= 0 && index < _sizes.Count)\n        {\n            return _sizes[index];\n        }\n\n        // Fallback: equal distribution\n        var count = _panelCount > 0 ? _panelCount : 1;\n        return 100.0 / count;\n    }\n\n    private void RegisterPanel(int order, double minSize, double maxSize)\n    {\n        _panelConstraints[order] = (minSize, maxSize);\n    }\n\n    private async Task HandleDrag((int PanelIndex, double Delta) args)\n    {\n        var (panelIndex, deltaPx) = args;\n\n        if (panelIndex < 0 || panelIndex + 1 >= _sizes.Count)\n            return;\n\n        // Convert delta from pixels to percentage of total group dimension\n        var dimension = Direction == ResizableDirection.Horizontal ? \"width\" : \"height\";\n        var totalSize = await Interop.GetElementDimension(_groupId, dimension);\n\n        if (totalSize <= 0)\n            return;\n\n        var deltaPercent = (deltaPx / totalSize) * 100.0;\n\n        var newLeft = _sizes[panelIndex] + deltaPercent;\n        var newRight = _sizes[panelIndex + 1] - deltaPercent;\n\n        // Enforce per-panel min/max constraints\n        var leftConstraints = _panelConstraints.TryGetValue(panelIndex, out var lc) ? lc : (MinSize: 0.0, MaxSize: 100.0);\n        var rightConstraints = _panelConstraints.TryGetValue(panelIndex + 1, out var rc) ? rc : (MinSize: 0.0, MaxSize: 100.0);\n\n        if (newLeft < leftConstraints.MinSize || newLeft > leftConstraints.MaxSize) return;\n        if (newRight < rightConstraints.MinSize || newRight > rightConstraints.MaxSize) return;\n\n        _sizes[panelIndex] = newLeft;\n        _sizes[panelIndex + 1] = newRight;\n\n        StateHasChanged();\n    }\n\n    public ValueTask DisposeAsync()\n    {\n        return ValueTask.CompletedTask;\n    }\n\n    public enum ResizableDirection { Horizontal, Vertical }\n\n    public record ResizablePanelGroupContext(\n        string GroupId,\n        ResizableDirection Direction,\n        EventCallback<(int PanelIndex, double Delta)> OnHandleDrag,\n        Func<int, double> GetPanelSize,\n        int PanelCount,\n        Action<int, double, double> RegisterPanel\n    );\n}\n"
    }
  ],
  "slots": [
    {
      "name": "ChildContent",
      "owner": "ResizablePanel",
      "type": "RenderFragment?",
      "description": null
    },
    {
      "name": "ChildContent",
      "owner": "ResizablePanelGroup",
      "type": "RenderFragment?",
      "description": null
    }
  ],
  "serviceDependencies": [
    {
      "service": "ComponentInteropService",
      "namespace": ""
    }
  ],
  "cascadingDependencies": [
    {
      "name": "Context",
      "type": "ResizablePanelGroup.ResizablePanelGroupContext",
      "required": true
    }
  ],
  "relatedComponents": [
    {
      "name": "ResizableHandle",
      "reason": "sub-component"
    },
    {
      "name": "ResizablePanel",
      "reason": "sub-component"
    },
    {
      "name": "ResizablePanelGroup",
      "reason": "sub-component"
    }
  ],
  "keyboardInteractions": [],
  "tests": [
    "tests/Lumeo.Docs.Tests/Helpers/DocsTestContext.cs",
    "tests/Lumeo.Tests/Components/Resizable/ResizableTests.cs",
    "tests/Lumeo.Tests/Contract/ComponentContractTests.cs"
  ],
  "mdSummary": "# Resizable\n\nDraggable splitter for resizable panel layouts.\n\n**Package:** `Lumeo` · **Category:** Layout\n**Docs:** https://lumeo.nativ.sh/components/resizable\n\n## Parameters\n| Owner | Name | Type | Default | Description |\n|---|---|---|---|---|\n| Resizable | Context | `ResizablePanelGroup.ResizablePanelGroupContext` | `default!` |  |\n| Resizable | PanelIndex | `int` | `` |  |\n| Resizable | WithHandle | `bool` | `` |  |\n| Resizable | Class | `string?` | `` |  |\n| Resizable | AdditionalAttributes | `Dictionary<string, object>?` | `` |  |\n| ResizablePanel | Context | `ResizablePanelGroup.ResizablePanelGroupContext` | `default!` |  |\n| ResizablePanel | Order | `int` | `` |  |\n| ResizablePanel | DefaultSize | `double` | `50` |  |\n| ResizablePanel | MinSize | `double` | `10` |  |\n| ResizablePanel | MaxSize | `double` | `90` |  |\n| ResizablePanel | ChildContent | `RenderFragment?` | `` |  |\n| ResizablePanel | Class | `string?` | `` |  |\n| ResizablePanel | AdditionalAttributes | `Dictionary<string, object>?` | `` |  |\n| ResizablePanelGroup | ChildContent | `RenderFragment?` | `` |  |\n| ResizablePanelGroup | Direction | `ResizableDirection` | `ResizableDirection.Horizontal` |  |\n| ResizablePanelGroup | DefaultSizes | `double[]?` | `` |  |\n| ResizablePanelGroup | Class | `string?` | `` |  |\n| ResizablePanelGroup | AdditionalAttributes | `Dictionary<string, object>?` | `` |  |\n\n## Slots\n- `ChildContent` (ResizablePanel) — RenderFragment slot\n- `ChildContent` (ResizablePanelGroup) — RenderFragment slot\n\n## Events\n- (none)\n\n## Sub-components\n- `ResizableHandle`, `ResizablePanel`, `ResizablePanelGroup`\n\n## Cascading values it expects\n- `Context` (`ResizablePanelGroup.ResizablePanelGroupContext`)\n\n## Services it injects\n- `ComponentInteropService` ()\n\n## Keyboard\n—\n\n## Example\n```razor\n<Resizable />\n```\n\n## CSS variables\n`--color-border`, `--color-primary`, `--color-ring`, `--radius`\n\n## Files\n- `UI/Resizable/ResizableHandle.razor`\n- `UI/Resizable/ResizablePanel.razor`\n- `UI/Resizable/ResizablePanelGroup.razor`\n"
}