{
  "name": "PromptInput",
  "category": "AI",
  "subcategory": null,
  "description": "Multiline AI prompt textarea with submit + keyboard shortcuts.",
  "thumbnail": "/preview-cards/prompt-input.png",
  "hasDocsPage": true,
  "nugetPackage": "Lumeo",
  "files": [
    "UI/PromptInput/PromptInput.razor"
  ],
  "dependencies": [
    "icon",
    "spinner"
  ],
  "packageDependencies": [],
  "cssVars": [
    "--color-border",
    "--color-card",
    "--color-foreground",
    "--color-muted",
    "--color-muted-foreground",
    "--color-primary",
    "--color-primary-foreground",
    "--color-ring",
    "--radius"
  ],
  "api": {
    "name": "PromptInput",
    "category": "AI",
    "subcategory": null,
    "description": "Multiline AI prompt textarea with submit + keyboard shortcuts.",
    "nugetPackage": "Lumeo",
    "files": [
      "UI/PromptInput/PromptInput.razor"
    ],
    "namespace": "Lumeo",
    "inheritsFrom": null,
    "implements": [
      "IAsyncDisposable"
    ],
    "parameters": [
      {
        "name": "Value",
        "type": "string?",
        "default": null,
        "description": null,
        "isCascading": false,
        "captureUnmatched": false
      },
      {
        "name": "ValueChanged",
        "type": "EventCallback<string?>",
        "default": null,
        "description": null,
        "isCascading": false,
        "captureUnmatched": false
      },
      {
        "name": "Placeholder",
        "type": "string",
        "default": "\"Ask anything…\"",
        "description": null,
        "isCascading": false,
        "captureUnmatched": false
      },
      {
        "name": "MaxHeight",
        "type": "int",
        "default": "240",
        "description": null,
        "isCascading": false,
        "captureUnmatched": false
      },
      {
        "name": "MinHeight",
        "type": "int",
        "default": "56",
        "description": null,
        "isCascading": false,
        "captureUnmatched": false
      },
      {
        "name": "IsLoading",
        "type": "bool",
        "default": null,
        "description": null,
        "isCascading": false,
        "captureUnmatched": false
      },
      {
        "name": "OnSend",
        "type": "EventCallback<string>",
        "default": null,
        "description": null,
        "isCascading": false,
        "captureUnmatched": false
      },
      {
        "name": "DisableSendOnEmpty",
        "type": "bool",
        "default": "true",
        "description": null,
        "isCascading": false,
        "captureUnmatched": false
      },
      {
        "name": "LeadingContent",
        "type": "RenderFragment?",
        "default": null,
        "description": null,
        "isCascading": false,
        "captureUnmatched": false
      },
      {
        "name": "TrailingContent",
        "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": [
      {
        "name": "ValueChanged",
        "type": "EventCallback<string?>",
        "description": null
      },
      {
        "name": "OnSend",
        "type": "EventCallback<string>",
        "description": null
      }
    ],
    "enums": [],
    "records": [],
    "cssVars": [
      "--color-border",
      "--color-card",
      "--color-foreground",
      "--color-muted",
      "--color-muted-foreground",
      "--color-primary",
      "--color-primary-foreground",
      "--color-ring",
      "--radius"
    ],
    "examples": [
      {
        "title": "Basic",
        "code": "<PromptInput @bind-Value=\"_prompt\" OnSend=\"@HandleSend\" Placeholder=\"Ask anything…\" />\n\n@code {\n    private string? _prompt;\n    private void HandleSend(string text) { /* call AI API */ }\n}"
      },
      {
        "title": "Loading State",
        "code": "<PromptInput @bind-Value=\"_prompt\" IsLoading=\"@_isLoading\" OnSend=\"@HandleSend\" />\n\n@code {\n    private string? _prompt;\n    private bool _isLoading;\n\n    private async Task HandleSend(string text)\n    {\n        _isLoading = true;\n        await CallAiApi(text);\n        _isLoading = false;\n    }\n}"
      },
      {
        "title": "With Leading and Trailing Content",
        "code": "<PromptInput @bind-Value=\"_prompt\" OnSend=\"@HandleSend\" Placeholder=\"Message Claude…\">\n    <LeadingContent>\n        <Button Variant=\"Button.ButtonVariant.Ghost\" Size=\"Button.ButtonSize.Icon\" Class=\"h-7 w-7\">\n            <Blazicon Svg=\"Lucide.Paperclip\" class=\"h-4 w-4\" />\n        </Button>\n    </LeadingContent>\n    <TrailingContent>\n        <span class=\"text-xs text-muted-foreground\">Shift+Enter for newline</span>\n    </TrailingContent>\n</PromptInput>"
      }
    ],
    "subComponents": {},
    "parseFailed": false,
    "parseError": null
  },
  "$schema": "https://lumeo.nativ.sh/registry-component-schema.json",
  "key": "prompt-input",
  "docsUrl": "https://lumeo.nativ.sh/components/prompt-input",
  "sourceUrl": [
    {
      "file": "UI/PromptInput/PromptInput.razor",
      "url": "https://github.com/Brain2k-0005/Lumeo/tree/master/src/Lumeo/UI/PromptInput/PromptInput.razor"
    }
  ],
  "source": [
    {
      "path": "UI/PromptInput/PromptInput.razor",
      "content": "@namespace Lumeo\n@inject Services.ComponentInteropService Interop\n@implements IAsyncDisposable\n\n<div class=\"@RootClass\" @attributes=\"AdditionalAttributes\">\n    <textarea id=\"@_textareaId\"\n              class=\"w-full resize-none bg-transparent px-2 py-1.5 text-sm text-foreground placeholder:text-muted-foreground focus:outline-none disabled:cursor-not-allowed disabled:opacity-60\"\n              style=\"@TextareaStyle\"\n              placeholder=\"@Placeholder\"\n              rows=\"1\"\n              disabled=\"@IsLoading\"\n              value=\"@Value\"\n              @oninput=\"HandleInput\"\n              @onkeydown=\"HandleKeyDown\"\n              @onkeydown:preventDefault=\"_suppressNextKey\"></textarea>\n\n    <div class=\"mt-1 flex items-center justify-between gap-2\">\n        <div class=\"flex items-center gap-1 text-muted-foreground\">\n            @LeadingContent\n        </div>\n        <div class=\"flex items-center gap-2 text-xs text-muted-foreground\">\n            @TrailingContent\n            <button type=\"button\"\n                    class=\"@SendButtonClass\"\n                    disabled=\"@IsSendDisabled\"\n                    aria-label=\"Send\"\n                    @onclick=\"HandleSendClick\">\n                @if (IsLoading)\n                {\n                    <Spinner Size=\"Spinner.SpinnerSize.Sm\" Class=\"text-primary-foreground\" />\n                }\n                else\n                {\n                    <Icon Name=\"ArrowUp\" Size=\"Icon.IconSize.Sm\" />\n                }\n            </button>\n        </div>\n    </div>\n</div>\n\n@code {\n    [Parameter] public string? Value { get; set; }\n    [Parameter] public EventCallback<string?> ValueChanged { get; set; }\n    [Parameter] public string Placeholder { get; set; } = \"Ask anything…\";\n    [Parameter] public int MaxHeight { get; set; } = 240;\n    [Parameter] public int MinHeight { get; set; } = 56;\n    [Parameter] public bool IsLoading { get; set; }\n    [Parameter] public EventCallback<string> OnSend { get; set; }\n    [Parameter] public bool DisableSendOnEmpty { get; set; } = true;\n    [Parameter] public RenderFragment? LeadingContent { get; set; }\n    [Parameter] public RenderFragment? TrailingContent { 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 _textareaId = $\"prompt-input-{Guid.NewGuid():N}\";\n    private bool _autosizeReady;\n    private bool _suppressNextKey;\n\n    private string RootClass =>\n        $\"flex flex-col rounded-xl border border-border bg-card p-2 shadow-sm transition-colors focus-within:border-ring/60 focus-within:ring-[1.5px] focus-within:ring-ring/40 {Class}\".Trim();\n\n    private string TextareaStyle => $\"min-height: {MinHeight}px; max-height: {MaxHeight}px;\";\n\n    private string SendButtonClass =>\n        \"inline-flex h-8 w-8 items-center justify-center rounded-lg bg-primary text-primary-foreground transition-colors hover:bg-primary/90 disabled:pointer-events-none disabled:opacity-50\";\n\n    private bool IsSendDisabled =>\n        IsLoading || (DisableSendOnEmpty && string.IsNullOrWhiteSpace(Value));\n\n    protected override async Task OnAfterRenderAsync(bool firstRender)\n    {\n        if (!firstRender) return;\n        try\n        {\n            _autosizeReady = true;\n            await Interop.AiAutosize(_textareaId, MaxHeight);\n        }\n        catch (JSDisconnectedException) { }\n        catch (ObjectDisposedException) { }\n    }\n\n    private async Task HandleInput(ChangeEventArgs e)\n    {\n        Value = e.Value?.ToString();\n        await ValueChanged.InvokeAsync(Value);\n        if (_autosizeReady)\n        {\n            await Interop.AiAutosize(_textareaId, MaxHeight);\n        }\n    }\n\n    private async Task HandleKeyDown(Microsoft.AspNetCore.Components.Web.KeyboardEventArgs e)\n    {\n        // Suppress the native newline insertion for plain Enter — Shift+Enter passes through for multiline.\n        _suppressNextKey = e.Key == \"Enter\" && !e.ShiftKey && !e.CtrlKey && !e.AltKey && !e.MetaKey;\n        if (_suppressNextKey)\n        {\n            await TrySendAsync();\n        }\n    }\n\n    private Task HandleSendClick() => TrySendAsync();\n\n    private async Task TrySendAsync()\n    {\n        if (IsSendDisabled) return;\n        var current = Value ?? string.Empty;\n        await OnSend.InvokeAsync(current);\n    }\n\n    public async ValueTask DisposeAsync()\n    {\n        // No JS observer to tear down for autosize; reserved for future use.\n        await ValueTask.CompletedTask;\n    }\n}\n"
    }
  ],
  "slots": [
    {
      "name": "LeadingContent",
      "owner": "PromptInput",
      "type": "RenderFragment?",
      "description": null
    },
    {
      "name": "TrailingContent",
      "owner": "PromptInput",
      "type": "RenderFragment?",
      "description": null
    }
  ],
  "serviceDependencies": [
    {
      "service": "ComponentInteropService",
      "namespace": "Services"
    }
  ],
  "cascadingDependencies": [],
  "relatedComponents": [
    {
      "name": "Spinner",
      "reason": "used internally"
    },
    {
      "name": "Icon",
      "reason": "used internally"
    }
  ],
  "keyboardInteractions": [
    {
      "key": "Enter",
      "action": "HandleKeyDown handles Enter in PromptInput.razor"
    }
  ],
  "tests": [
    "tests/Lumeo.Tests/Components/PromptInput/PromptInputTests.cs"
  ],
  "mdSummary": "# PromptInput\n\nMultiline AI prompt textarea with submit + keyboard shortcuts.\n\n**Package:** `Lumeo` · **Category:** AI\n**Docs:** https://lumeo.nativ.sh/components/prompt-input\n\n## Parameters\n| Owner | Name | Type | Default | Description |\n|---|---|---|---|---|\n| PromptInput | Value | `string?` | `` |  |\n| PromptInput | ValueChanged | `EventCallback<string?>` | `` |  |\n| PromptInput | Placeholder | `string` | `\"Ask anything…\"` |  |\n| PromptInput | MaxHeight | `int` | `240` |  |\n| PromptInput | MinHeight | `int` | `56` |  |\n| PromptInput | IsLoading | `bool` | `` |  |\n| PromptInput | OnSend | `EventCallback<string>` | `` |  |\n| PromptInput | DisableSendOnEmpty | `bool` | `true` |  |\n| PromptInput | LeadingContent | `RenderFragment?` | `` |  |\n| PromptInput | TrailingContent | `RenderFragment?` | `` |  |\n| PromptInput | Class | `string?` | `` |  |\n| PromptInput | AdditionalAttributes | `Dictionary<string, object>?` | `` |  |\n\n## Slots\n- `LeadingContent` (PromptInput) — RenderFragment slot\n- `TrailingContent` (PromptInput) — RenderFragment slot\n\n## Events\n- `ValueChanged` (`EventCallback<string?>`)\n- `OnSend` (`EventCallback<string>`)\n\n## Sub-components\n- (none)\n\n## Cascading values it expects\n- (none)\n\n## Services it injects\n- `ComponentInteropService` (Services)\n\n## Keyboard\n- `Enter` — HandleKeyDown handles Enter in PromptInput.razor\n\n## Example\n```razor\n<PromptInput />\n```\n\n## CSS variables\n`--color-border`, `--color-card`, `--color-foreground`, `--color-muted`, `--color-muted-foreground`, `--color-primary`, `--color-primary-foreground`, `--color-ring`, `--radius`\n\n## Files\n- `UI/PromptInput/PromptInput.razor`\n"
}