{
  "name": "Transfer",
  "category": "Drag & Drop",
  "subcategory": null,
  "description": "Dual-list transfer picker — left/right with arrows.",
  "thumbnail": "/preview-cards/transfer.png",
  "hasDocsPage": true,
  "nugetPackage": "Lumeo",
  "files": [
    "UI/Transfer/Transfer.razor"
  ],
  "dependencies": [
    "checkbox",
    "list"
  ],
  "packageDependencies": [
    "Blazicons.Lucide"
  ],
  "cssVars": [
    "--color-accent",
    "--color-accent-foreground",
    "--color-background",
    "--color-border",
    "--color-input",
    "--color-muted",
    "--color-muted-foreground",
    "--color-ring",
    "--radius"
  ],
  "api": {
    "name": "Transfer",
    "category": "Drag & Drop",
    "subcategory": null,
    "description": "Dual-list transfer picker — left/right with arrows.",
    "nugetPackage": "Lumeo",
    "files": [
      "UI/Transfer/Transfer.razor"
    ],
    "namespace": "Lumeo",
    "inheritsFrom": null,
    "implements": [],
    "parameters": [
      {
        "name": "SourceItems",
        "type": "List<TransferItem>?",
        "default": null,
        "description": null,
        "isCascading": false,
        "captureUnmatched": false
      },
      {
        "name": "SourceItemsChanged",
        "type": "EventCallback<List<TransferItem>>",
        "default": null,
        "description": null,
        "isCascading": false,
        "captureUnmatched": false
      },
      {
        "name": "TargetItems",
        "type": "List<TransferItem>?",
        "default": null,
        "description": null,
        "isCascading": false,
        "captureUnmatched": false
      },
      {
        "name": "TargetItemsChanged",
        "type": "EventCallback<List<TransferItem>>",
        "default": null,
        "description": null,
        "isCascading": false,
        "captureUnmatched": false
      },
      {
        "name": "SourceTitle",
        "type": "string?",
        "default": null,
        "description": null,
        "isCascading": false,
        "captureUnmatched": false
      },
      {
        "name": "TargetTitle",
        "type": "string?",
        "default": null,
        "description": null,
        "isCascading": false,
        "captureUnmatched": false
      },
      {
        "name": "OnChange",
        "type": "EventCallback<TransferChangeEventArgs>",
        "default": null,
        "description": null,
        "isCascading": false,
        "captureUnmatched": false
      },
      {
        "name": "ShowSearch",
        "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": [
      {
        "name": "SourceItemsChanged",
        "type": "EventCallback<List<TransferItem>>",
        "description": null
      },
      {
        "name": "TargetItemsChanged",
        "type": "EventCallback<List<TransferItem>>",
        "description": null
      },
      {
        "name": "OnChange",
        "type": "EventCallback<TransferChangeEventArgs>",
        "description": null
      }
    ],
    "enums": [],
    "records": [
      {
        "name": "TransferItem",
        "signature": "TransferItem(string Label, string Value)",
        "description": null
      },
      {
        "name": "TransferChangeEventArgs",
        "signature": "TransferChangeEventArgs(string Direction, List<TransferItem> MovedItems)",
        "description": null
      }
    ],
    "cssVars": [
      "--color-accent",
      "--color-accent-foreground",
      "--color-background",
      "--color-border",
      "--color-input",
      "--color-muted",
      "--color-muted-foreground",
      "--color-ring",
      "--radius"
    ],
    "examples": [
      {
        "title": "Basic Transfer",
        "code": "<Transfer @bind-SourceItems=\"_source\" @bind-TargetItems=\"_target\"\n          SourceTitle=\"Available\" TargetTitle=\"Selected\" />"
      },
      {
        "title": "OnChange — interactive",
        "code": "<Transfer @bind-SourceItems=\"_source\" @bind-TargetItems=\"_target\"\n          OnChange=\"@(args => _lastChange = $\"Moved {args.MovedItems.Count} item(s) → {(args.Direction == \"\"right\"\" ? \"\"Selected\"\" : \"\"Available\"\")}\")\"\n          SourceTitle=\"Available\" TargetTitle=\"Selected\" />\n@if (_lastChange is not null)\n{\n    <Lumeo.Text Size=\"sm\" Color=\"muted\">Last change: @_lastChange</Lumeo.Text>\n}\n\n@code { private string? _lastChange; }"
      },
      {
        "title": "With Search",
        "code": "<Transfer @bind-SourceItems=\"_source\" @bind-TargetItems=\"_target\"\n          SourceTitle=\"Users\" TargetTitle=\"Team\" ShowSearch=\"true\" />"
      }
    ],
    "subComponents": {},
    "parseFailed": false,
    "parseError": null
  },
  "$schema": "https://lumeo.nativ.sh/registry-component-schema.json",
  "key": "transfer",
  "docsUrl": "https://lumeo.nativ.sh/components/transfer",
  "sourceUrl": [
    {
      "file": "UI/Transfer/Transfer.razor",
      "url": "https://github.com/Brain2k-0005/Lumeo/tree/master/src/Lumeo/UI/Transfer/Transfer.razor"
    }
  ],
  "source": [
    {
      "path": "UI/Transfer/Transfer.razor",
      "content": "@namespace Lumeo\n@inject Lumeo.Services.Localization.ILumeoLocalizer L\n\n<div class=\"@CssClass\" @attributes=\"AdditionalAttributes\">\n    @* Source panel *@\n    <div class=\"flex-1 border border-border/40 rounded-lg overflow-hidden\">\n        <div class=\"px-3 py-2 border-b border-border/40 bg-muted/30\">\n            <div class=\"flex items-center justify-between\">\n                <span class=\"text-sm font-medium\">@(SourceTitle ?? L[\"Transfer.SourceHeader\"])</span>\n                <span class=\"text-xs text-muted-foreground\">@_selectedSource.Count / @(SourceItems?.Count ?? 0)</span>\n            </div>\n        </div>\n        @if (ShowSearch)\n        {\n            <div class=\"px-2 py-1.5 border-b border-border/40\">\n                <input type=\"text\" placeholder=\"@L[\"Combobox.SearchPlaceholder\"]\"\n                       class=\"flex h-7 w-full rounded-md border border-input bg-transparent px-2 py-1 text-xs placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-[1.5px] focus-visible:ring-ring\"\n                       value=\"@_sourceSearch\"\n                       @oninput=\"OnSourceSearch\" />\n            </div>\n        }\n        <div class=\"max-h-64 overflow-y-auto p-1\">\n            @if (FilteredSourceItems is { Count: > 0 })\n            {\n                @foreach (var item in FilteredSourceItems)\n                {\n                    <label @key=\"item.Value\" class=\"flex items-center gap-2 px-2 py-1.5 rounded-md cursor-pointer hover:bg-accent/50 transition-colors text-sm\">\n                        <Checkbox Checked=\"@_selectedSource.Contains(item.Value)\" CheckedChanged=\"b => ToggleSourceItem(item.Value, b)\" />\n                        <span class=\"truncate\">@item.Label</span>\n                    </label>\n                }\n            }\n            else\n            {\n                <div class=\"text-center text-xs text-muted-foreground py-4\">@L[\"Transfer.NoItems\"]</div>\n            }\n        </div>\n    </div>\n\n    @* Transfer buttons *@\n    <div class=\"flex flex-col items-center justify-center gap-1.5\">\n        <button type=\"button\" class=\"@ButtonClass\" @onclick=\"MoveToTarget\" disabled=\"@(_selectedSource.Count == 0)\">\n            <Blazicon Svg=\"Lucide.ChevronRight\" class=\"h-4 w-4\" />\n        </button>\n        <button type=\"button\" class=\"@ButtonClass\" @onclick=\"MoveToSource\" disabled=\"@(_selectedTarget.Count == 0)\">\n            <Blazicon Svg=\"Lucide.ChevronLeft\" class=\"h-4 w-4\" />\n        </button>\n    </div>\n\n    @* Target panel *@\n    <div class=\"flex-1 border border-border/40 rounded-lg overflow-hidden\">\n        <div class=\"px-3 py-2 border-b border-border/40 bg-muted/30\">\n            <div class=\"flex items-center justify-between\">\n                <span class=\"text-sm font-medium\">@(TargetTitle ?? L[\"Transfer.TargetHeader\"])</span>\n                <span class=\"text-xs text-muted-foreground\">@_selectedTarget.Count / @(TargetItems?.Count ?? 0)</span>\n            </div>\n        </div>\n        @if (ShowSearch)\n        {\n            <div class=\"px-2 py-1.5 border-b border-border/40\">\n                <input type=\"text\" placeholder=\"@L[\"Combobox.SearchPlaceholder\"]\"\n                       class=\"flex h-7 w-full rounded-md border border-input bg-transparent px-2 py-1 text-xs placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-[1.5px] focus-visible:ring-ring\"\n                       value=\"@_targetSearch\"\n                       @oninput=\"OnTargetSearch\" />\n            </div>\n        }\n        <div class=\"max-h-64 overflow-y-auto p-1\">\n            @if (FilteredTargetItems is { Count: > 0 })\n            {\n                @foreach (var item in FilteredTargetItems)\n                {\n                    <label @key=\"item.Value\" class=\"flex items-center gap-2 px-2 py-1.5 rounded-md cursor-pointer hover:bg-accent/50 transition-colors text-sm\">\n                        <Checkbox Checked=\"@_selectedTarget.Contains(item.Value)\" CheckedChanged=\"b => ToggleTargetItem(item.Value, b)\" />\n                        <span class=\"truncate\">@item.Label</span>\n                    </label>\n                }\n            }\n            else\n            {\n                <div class=\"text-center text-xs text-muted-foreground py-4\">@L[\"Transfer.NoItems\"]</div>\n            }\n        </div>\n    </div>\n</div>\n\n@code {\n    [Parameter] public List<TransferItem>? SourceItems { get; set; }\n    [Parameter] public EventCallback<List<TransferItem>> SourceItemsChanged { get; set; }\n    [Parameter] public List<TransferItem>? TargetItems { get; set; }\n    [Parameter] public EventCallback<List<TransferItem>> TargetItemsChanged { get; set; }\n    [Parameter] public string? SourceTitle { get; set; }\n    [Parameter] public string? TargetTitle { get; set; }\n    [Parameter] public EventCallback<TransferChangeEventArgs> OnChange { get; set; }\n    [Parameter] public bool ShowSearch { get; set; }\n    [Parameter] public string? Class { get; set; }\n    [Parameter(CaptureUnmatchedValues = true)] public Dictionary<string, object>? AdditionalAttributes { get; set; }\n\n    private readonly HashSet<string> _selectedSource = [];\n    private readonly HashSet<string> _selectedTarget = [];\n    private string _sourceSearch = \"\";\n    private string _targetSearch = \"\";\n\n    private string CssClass => $\"flex items-start gap-3 {Class}\".Trim();\n\n    private const string ButtonClass = \"inline-flex items-center justify-center h-8 w-8 rounded-md border border-input bg-background hover:bg-accent hover:text-accent-foreground transition-colors disabled:opacity-50 disabled:cursor-not-allowed\";\n\n    private List<TransferItem> FilteredSourceItems =>\n        string.IsNullOrWhiteSpace(_sourceSearch)\n            ? SourceItems ?? []\n            : (SourceItems ?? []).Where(i => i.Label.Contains(_sourceSearch, StringComparison.OrdinalIgnoreCase)).ToList();\n\n    private List<TransferItem> FilteredTargetItems =>\n        string.IsNullOrWhiteSpace(_targetSearch)\n            ? TargetItems ?? []\n            : (TargetItems ?? []).Where(i => i.Label.Contains(_targetSearch, StringComparison.OrdinalIgnoreCase)).ToList();\n\n    private void OnSourceSearch(ChangeEventArgs e) => _sourceSearch = e.Value?.ToString() ?? \"\";\n    private void OnTargetSearch(ChangeEventArgs e) => _targetSearch = e.Value?.ToString() ?? \"\";\n\n    private void ToggleSourceItem(string value, bool selected)\n    {\n        if (selected) _selectedSource.Add(value);\n        else _selectedSource.Remove(value);\n    }\n\n    private void ToggleTargetItem(string value, bool selected)\n    {\n        if (selected) _selectedTarget.Add(value);\n        else _selectedTarget.Remove(value);\n    }\n\n    private async Task MoveToTarget()\n    {\n        var source = SourceItems ?? [];\n        var target = TargetItems ?? [];\n        var moved = source.Where(i => _selectedSource.Contains(i.Value)).ToList();\n        var remaining = source.Where(i => !_selectedSource.Contains(i.Value)).ToList();\n        target = [..target, ..moved];\n\n        SourceItems = remaining;\n        TargetItems = target;\n        _selectedSource.Clear();\n\n        await SourceItemsChanged.InvokeAsync(SourceItems);\n        await TargetItemsChanged.InvokeAsync(TargetItems);\n        await OnChange.InvokeAsync(new TransferChangeEventArgs(\"right\", moved));\n    }\n\n    private async Task MoveToSource()\n    {\n        var source = SourceItems ?? [];\n        var target = TargetItems ?? [];\n        var moved = target.Where(i => _selectedTarget.Contains(i.Value)).ToList();\n        var remaining = target.Where(i => !_selectedTarget.Contains(i.Value)).ToList();\n        source = [..source, ..moved];\n\n        SourceItems = source;\n        TargetItems = remaining;\n        _selectedTarget.Clear();\n\n        await SourceItemsChanged.InvokeAsync(SourceItems);\n        await TargetItemsChanged.InvokeAsync(TargetItems);\n        await OnChange.InvokeAsync(new TransferChangeEventArgs(\"left\", moved));\n    }\n\n    public record TransferItem(string Label, string Value);\n    public record TransferChangeEventArgs(string Direction, List<TransferItem> MovedItems);\n}\n"
    }
  ],
  "slots": [],
  "serviceDependencies": [
    {
      "service": "ILumeoLocalizer",
      "namespace": "Lumeo.Services.Localization"
    }
  ],
  "cascadingDependencies": [],
  "relatedComponents": [
    {
      "name": "Checkbox",
      "reason": "used internally"
    },
    {
      "name": "List",
      "reason": "used internally"
    }
  ],
  "keyboardInteractions": [],
  "tests": [
    "tests/Lumeo.Tests/Components/Transfer/TransferTests.cs"
  ],
  "mdSummary": "# Transfer\n\nDual-list transfer picker — left/right with arrows.\n\n**Package:** `Lumeo` · **Category:** Drag & Drop\n**Docs:** https://lumeo.nativ.sh/components/transfer\n\n## Parameters\n| Owner | Name | Type | Default | Description |\n|---|---|---|---|---|\n| Transfer | SourceItems | `List<TransferItem>?` | `` |  |\n| Transfer | SourceItemsChanged | `EventCallback<List<TransferItem>>` | `` |  |\n| Transfer | TargetItems | `List<TransferItem>?` | `` |  |\n| Transfer | TargetItemsChanged | `EventCallback<List<TransferItem>>` | `` |  |\n| Transfer | SourceTitle | `string?` | `` |  |\n| Transfer | TargetTitle | `string?` | `` |  |\n| Transfer | OnChange | `EventCallback<TransferChangeEventArgs>` | `` |  |\n| Transfer | ShowSearch | `bool` | `` |  |\n| Transfer | Class | `string?` | `` |  |\n| Transfer | AdditionalAttributes | `Dictionary<string, object>?` | `` |  |\n\n## Slots\n- (none)\n\n## Events\n- `SourceItemsChanged` (`EventCallback<List<TransferItem>>`)\n- `TargetItemsChanged` (`EventCallback<List<TransferItem>>`)\n- `OnChange` (`EventCallback<TransferChangeEventArgs>`)\n\n## Sub-components\n- (none)\n\n## Cascading values it expects\n- (none)\n\n## Services it injects\n- `ILumeoLocalizer` (Lumeo.Services.Localization)\n\n## Keyboard\n—\n\n## Example\n```razor\n<Transfer />\n```\n\n## CSS variables\n`--color-accent`, `--color-accent-foreground`, `--color-background`, `--color-border`, `--color-input`, `--color-muted`, `--color-muted-foreground`, `--color-ring`, `--radius`\n\n## Files\n- `UI/Transfer/Transfer.razor`\n"
}