{
  "name": "Sheet",
  "category": "Overlay",
  "subcategory": null,
  "description": "Slide-in side panel from left/right/top/bottom.",
  "thumbnail": "/preview-cards/sheet.png",
  "hasDocsPage": true,
  "nugetPackage": "Lumeo",
  "files": [
    "UI/Sheet/Sheet.razor",
    "UI/Sheet/SheetClose.razor",
    "UI/Sheet/SheetContent.razor",
    "UI/Sheet/SheetDescription.razor",
    "UI/Sheet/SheetFooter.razor",
    "UI/Sheet/SheetHeader.razor",
    "UI/Sheet/SheetTitle.razor",
    "UI/Sheet/SheetTrigger.razor"
  ],
  "dependencies": [],
  "packageDependencies": [
    "Blazicons.Lucide"
  ],
  "cssVars": [
    "--color-background",
    "--color-foreground",
    "--color-muted",
    "--color-muted-foreground",
    "--radius"
  ],
  "api": {
    "name": "Sheet",
    "category": "Overlay",
    "subcategory": null,
    "description": "Slide-in side panel from left/right/top/bottom.",
    "nugetPackage": "Lumeo",
    "files": [
      "UI/Sheet/Sheet.razor",
      "UI/Sheet/SheetClose.razor",
      "UI/Sheet/SheetContent.razor",
      "UI/Sheet/SheetDescription.razor",
      "UI/Sheet/SheetFooter.razor",
      "UI/Sheet/SheetHeader.razor",
      "UI/Sheet/SheetTitle.razor",
      "UI/Sheet/SheetTrigger.razor"
    ],
    "namespace": "Lumeo",
    "inheritsFrom": null,
    "implements": [],
    "parameters": [
      {
        "name": "ChildContent",
        "type": "RenderFragment?",
        "default": null,
        "description": null,
        "isCascading": false,
        "captureUnmatched": false
      },
      {
        "name": "Open",
        "type": "bool",
        "default": null,
        "description": null,
        "isCascading": false,
        "captureUnmatched": false
      },
      {
        "name": "OpenChanged",
        "type": "EventCallback<bool>",
        "default": null,
        "description": null,
        "isCascading": false,
        "captureUnmatched": false
      },
      {
        "name": "IsOpen",
        "type": "bool",
        "default": null,
        "description": null,
        "isCascading": false,
        "captureUnmatched": false
      },
      {
        "name": "IsOpenChanged",
        "type": "EventCallback<bool>",
        "default": null,
        "description": null,
        "isCascading": false,
        "captureUnmatched": false
      }
    ],
    "events": [
      {
        "name": "OpenChanged",
        "type": "EventCallback<bool>",
        "description": null
      },
      {
        "name": "IsOpenChanged",
        "type": "EventCallback<bool>",
        "description": null
      }
    ],
    "enums": [],
    "records": [
      {
        "name": "SheetContext",
        "signature": "SheetContext(bool IsOpen, EventCallback<bool> SetOpen, string TitleId, string DescriptionId)",
        "description": null
      }
    ],
    "cssVars": [
      "--color-background",
      "--color-foreground",
      "--color-muted",
      "--color-muted-foreground",
      "--radius"
    ],
    "examples": [
      {
        "title": "Default (Right)",
        "code": "<Sheet @bind-Open=\"_isOpen\">\n    <SheetTrigger>\n        <Button Variant=\"Button.ButtonVariant.Outline\">Open Sheet</Button>\n    </SheetTrigger>\n    <SheetContent>\n        <SheetHeader>\n            <SheetTitle>Edit Profile</SheetTitle>\n            <SheetDescription>Make changes to your profile here.</SheetDescription>\n        </SheetHeader>\n        <Grid Columns=\"4\" Gap=\"4\" Class=\"items-center py-4\">\n            <Label Class=\"text-right\">Name</Label>\n            <Input Class=\"col-span-3\" Value=\"John Doe\" />\n        </Grid>\n        <SheetFooter>\n            <SheetClose>\n                <Button>Save Changes</Button>\n            </SheetClose>\n        </SheetFooter>\n    </SheetContent>\n</Sheet>\n\n@code {\n    private bool _isOpen;\n}"
      },
      {
        "title": "Side Variations",
        "code": "<Sheet @bind-Open=\"_isTopOpen\">\n    <SheetTrigger>\n        <Button Variant=\"Button.ButtonVariant.Outline\">Top</Button>\n    </SheetTrigger>\n    <SheetContent Side=\"SheetContent.SheetSide.Top\">\n        <SheetHeader>\n            <SheetTitle>Top Sheet</SheetTitle>\n            <SheetDescription>This sheet slides in from the top.</SheetDescription>\n        </SheetHeader>\n    </SheetContent>\n</Sheet>\n\n<Sheet @bind-Open=\"_isLeftOpen\">\n    <SheetTrigger>\n        <Button Variant=\"Button.ButtonVariant.Outline\">Left</Button>\n    </SheetTrigger>\n    <SheetContent Side=\"SheetContent.SheetSide.Left\">\n        <SheetHeader>\n            <SheetTitle>Left Sheet</SheetTitle>\n            <SheetDescription>This sheet slides in from the left.</SheetDescription>\n        </SheetHeader>\n    </SheetContent>\n</Sheet>"
      },
      {
        "title": "Sizes",
        "code": "@foreach (var size in Enum.GetValues<SheetContent.SheetSize>())\n{\n    <Sheet @bind-Open=\"@_sheetSizeOpen[size]\">\n        <SheetTrigger>\n            <Button Variant=\"Button.ButtonVariant.Outline\">@size</Button>\n        </SheetTrigger>\n        <SheetContent Size=\"@size\">\n            <SheetHeader>\n                <SheetTitle>@size Sheet</SheetTitle>\n                <SheetDescription>This sheet uses Size=\"SheetContent.SheetSize.@size\"</SheetDescription>\n            </SheetHeader>\n            <Stack Gap=\"4\" Class=\"py-4\">\n                <Lumeo.Text Size=\"sm\" Color=\"muted\">Sheet content goes here.</Lumeo.Text>\n            </Stack>\n        </SheetContent>\n    </Sheet>\n}\n\n@code {\n    private Dictionary<SheetContent.SheetSize, bool> _sheetSizeOpen =\n        Enum.GetValues<SheetContent.SheetSize>().ToDictionary(s => s, _ => false);\n}"
      },
      {
        "title": "Prevent Close",
        "code": "<Sheet @bind-Open=\"_preventSheetOpen\">\n    <SheetTrigger>\n        <Button Variant=\"Button.ButtonVariant.Outline\">Open Protected Sheet</Button>\n    </SheetTrigger>\n    <SheetContent PreventClose=\"true\">\n        <SheetHeader>\n            <SheetTitle>Protected Sheet</SheetTitle>\n            <SheetDescription>This sheet cannot be dismissed by clicking outside.</SheetDescription>\n        </SheetHeader>\n        <Stack Gap=\"4\" Class=\"py-4\">\n            <Button OnClick=\"@(() => _preventSheetOpen = false)\">Close Sheet</Button>\n        </Stack>\n    </SheetContent>\n</Sheet>\n\n@code {\n    private bool _preventSheetOpen;\n}"
      }
    ],
    "subComponents": {
      "SheetClose": {
        "componentName": "SheetClose",
        "fileName": "SheetClose.razor",
        "namespace": "Lumeo",
        "inheritsFrom": null,
        "implements": [],
        "parameters": [
          {
            "name": "Context",
            "type": "Sheet.SheetContext",
            "default": "default!",
            "description": null,
            "isCascading": true,
            "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
      },
      "SheetContent": {
        "componentName": "SheetContent",
        "fileName": "SheetContent.razor",
        "namespace": "Lumeo",
        "inheritsFrom": null,
        "implements": [
          "IAsyncDisposable"
        ],
        "parameters": [
          {
            "name": "Context",
            "type": "Sheet.SheetContext",
            "default": "default!",
            "description": null,
            "isCascading": true,
            "captureUnmatched": false
          },
          {
            "name": "Shell",
            "type": "OverlayShellMarker?",
            "default": null,
            "description": null,
            "isCascading": true,
            "captureUnmatched": false
          },
          {
            "name": "ChildContent",
            "type": "RenderFragment?",
            "default": null,
            "description": null,
            "isCascading": false,
            "captureUnmatched": false
          },
          {
            "name": "Side",
            "type": "SheetSide",
            "default": "SheetSide.Right",
            "description": null,
            "isCascading": false,
            "captureUnmatched": false
          },
          {
            "name": "Size",
            "type": "SheetSize",
            "default": "SheetSize.Default",
            "description": null,
            "isCascading": false,
            "captureUnmatched": false
          },
          {
            "name": "Animation",
            "type": "SheetAnimation",
            "default": "SheetAnimation.Slide",
            "description": "How the sheet enters/leaves. Slide (default) animates from the configured Side. Fade uses a simple opacity tween — useful in dense forms where popovers and animations compete. None disables the animation entirely.",
            "isCascading": false,
            "captureUnmatched": false
          },
          {
            "name": "PreventClose",
            "type": "bool",
            "default": null,
            "description": null,
            "isCascading": false,
            "captureUnmatched": false
          },
          {
            "name": "SwipeToClose",
            "type": "bool",
            "default": "false",
            "description": "When true, the sheet can be dismissed by swiping in the direction opposite to its Side (e.g. swipe-down on a Bottom sheet). Default false to avoid conflicts with scrollable content. Wire to true only when the sheet's content fits without scrolling.",
            "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": "SheetSide",
            "values": [
              "Top",
              "Right",
              "Bottom",
              "Left"
            ],
            "description": null
          },
          {
            "name": "SheetSize",
            "values": [
              "Sm",
              "Default",
              "Lg",
              "Xl",
              "Full"
            ],
            "description": null
          },
          {
            "name": "SheetAnimation",
            "values": [
              "Slide",
              "Fade",
              "None"
            ],
            "description": null
          }
        ],
        "records": [],
        "parseFailed": false,
        "parseError": null
      },
      "SheetDescription": {
        "componentName": "SheetDescription",
        "fileName": "SheetDescription.razor",
        "namespace": "Lumeo",
        "inheritsFrom": null,
        "implements": [],
        "parameters": [
          {
            "name": "Context",
            "type": "Sheet.SheetContext",
            "default": "default!",
            "description": null,
            "isCascading": true,
            "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
      },
      "SheetFooter": {
        "componentName": "SheetFooter",
        "fileName": "SheetFooter.razor",
        "namespace": "Lumeo",
        "inheritsFrom": null,
        "implements": [],
        "parameters": [
          {
            "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
      },
      "SheetHeader": {
        "componentName": "SheetHeader",
        "fileName": "SheetHeader.razor",
        "namespace": "Lumeo",
        "inheritsFrom": null,
        "implements": [],
        "parameters": [
          {
            "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
      },
      "SheetTitle": {
        "componentName": "SheetTitle",
        "fileName": "SheetTitle.razor",
        "namespace": "Lumeo",
        "inheritsFrom": null,
        "implements": [],
        "parameters": [
          {
            "name": "Context",
            "type": "Sheet.SheetContext",
            "default": "default!",
            "description": null,
            "isCascading": true,
            "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
      },
      "SheetTrigger": {
        "componentName": "SheetTrigger",
        "fileName": "SheetTrigger.razor",
        "namespace": "Lumeo",
        "inheritsFrom": null,
        "implements": [],
        "parameters": [
          {
            "name": "Context",
            "type": "Sheet.SheetContext",
            "default": "default!",
            "description": null,
            "isCascading": true,
            "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": "sheet",
  "docsUrl": "https://lumeo.nativ.sh/components/sheet",
  "sourceUrl": [
    {
      "file": "UI/Sheet/Sheet.razor",
      "url": "https://github.com/Brain2k-0005/Lumeo/tree/master/src/Lumeo/UI/Sheet/Sheet.razor"
    },
    {
      "file": "UI/Sheet/SheetClose.razor",
      "url": "https://github.com/Brain2k-0005/Lumeo/tree/master/src/Lumeo/UI/Sheet/SheetClose.razor"
    },
    {
      "file": "UI/Sheet/SheetContent.razor",
      "url": "https://github.com/Brain2k-0005/Lumeo/tree/master/src/Lumeo/UI/Sheet/SheetContent.razor"
    },
    {
      "file": "UI/Sheet/SheetDescription.razor",
      "url": "https://github.com/Brain2k-0005/Lumeo/tree/master/src/Lumeo/UI/Sheet/SheetDescription.razor"
    },
    {
      "file": "UI/Sheet/SheetFooter.razor",
      "url": "https://github.com/Brain2k-0005/Lumeo/tree/master/src/Lumeo/UI/Sheet/SheetFooter.razor"
    },
    {
      "file": "UI/Sheet/SheetHeader.razor",
      "url": "https://github.com/Brain2k-0005/Lumeo/tree/master/src/Lumeo/UI/Sheet/SheetHeader.razor"
    },
    {
      "file": "UI/Sheet/SheetTitle.razor",
      "url": "https://github.com/Brain2k-0005/Lumeo/tree/master/src/Lumeo/UI/Sheet/SheetTitle.razor"
    },
    {
      "file": "UI/Sheet/SheetTrigger.razor",
      "url": "https://github.com/Brain2k-0005/Lumeo/tree/master/src/Lumeo/UI/Sheet/SheetTrigger.razor"
    }
  ],
  "source": [
    {
      "path": "UI/Sheet/Sheet.razor",
      "content": "@namespace Lumeo\n\n@{\n    var context = new SheetContext(Open, EventCallback.Factory.Create<bool>(this, SetOpen), _titleId, _descriptionId);\n}\n\n<CascadingValue Value=\"context\" IsFixed=\"false\">\n    @ChildContent\n</CascadingValue>\n\n@code {\n    [Parameter] public RenderFragment? ChildContent { get; set; }\n    [Parameter] public bool Open { get; set; }\n    [Parameter] public EventCallback<bool> OpenChanged { get; set; }\n\n    [Obsolete(\"Use Open instead. IsOpen will be removed in a future release.\")]\n    [Parameter] public bool IsOpen { get => Open; set => Open = value; }\n\n    [Obsolete(\"Use OpenChanged instead. IsOpenChanged will be removed in a future release.\")]\n    [Parameter] public EventCallback<bool> IsOpenChanged { get => OpenChanged; set => OpenChanged = value; }\n\n    private readonly string _titleId = $\"sheet-title-{Guid.NewGuid():N}\";\n    private readonly string _descriptionId = $\"sheet-desc-{Guid.NewGuid():N}\";\n\n    private async Task SetOpen(bool value)\n    {\n        Open = value;\n        await OpenChanged.InvokeAsync(value);\n    }\n\n    public record SheetContext(bool IsOpen, EventCallback<bool> SetOpen, string TitleId, string DescriptionId);\n}\n"
    },
    {
      "path": "UI/Sheet/SheetClose.razor",
      "content": "@namespace Lumeo\n\n<div class=\"@CssClass\" @onclick=\"Close\" @attributes=\"AdditionalAttributes\">\n    @ChildContent\n</div>\n\n@code {\n    [CascadingParameter] public Sheet.SheetContext Context { get; set; } = default!;\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 => $\"inline-flex {Class}\".Trim();\n\n    private async Task Close() => await Context.SetOpen.InvokeAsync(false);\n}\n"
    },
    {
      "path": "UI/Sheet/SheetContent.razor",
      "content": "@namespace Lumeo\n@using Lumeo.Services\n@implements IAsyncDisposable\n\n@if (Shell is not null)\n{\n    @* OverlayProvider already rendered the SheetContent shell around our parent\n       component — rendering another shell here would stack a second backdrop +\n       sliding panel on top. Pass the children straight through to the host\n       shell instead. Side / Size / PreventClose / Class on this nested element\n       are intentionally ignored; the provider controls those via OverlayOptions. *@\n    @ChildContent\n}\nelse if (Context.IsOpen)\n{\n    <div class=\"fixed inset-0 z-50\">\n        <div class=\"fixed inset-0 bg-black/80 animate-fade-in\" @onclick=\"HandleBackdropClick\"></div>\n        <div id=\"@_contentId\" class=\"@CssClass\" role=\"dialog\" aria-modal=\"true\" aria-labelledby=\"@Context.TitleId\" aria-describedby=\"@Context.DescriptionId\" @onkeydown=\"HandleKeyDown\" tabindex=\"-1\" @attributes=\"AdditionalAttributes\">\n            @ChildContent\n            @if (!PreventClose)\n            {\n                <button class=\"absolute right-4 top-4 rounded-sm opacity-70 transition-opacity hover:opacity-100 focus:outline-none\" @onclick=\"Close\">\n                    <Blazicon Svg=\"Lucide.X\" class=\"h-4 w-4\" />\n                    <span class=\"sr-only\">@L[\"Dialog.Close\"]</span>\n                </button>\n            }\n        </div>\n    </div>\n}\n\n@inject ComponentInteropService Interop\n@inject Lumeo.Services.Localization.ILumeoLocalizer L\n\n@code {\n    [CascadingParameter] public Sheet.SheetContext Context { get; set; } = default!;\n    [CascadingParameter] public OverlayShellMarker? Shell { get; set; }\n    [Parameter] public RenderFragment? ChildContent { get; set; }\n    [Parameter] public SheetSide Side { get; set; } = SheetSide.Right;\n    [Parameter] public SheetSize Size { get; set; } = SheetSize.Default;\n    /// <summary>How the sheet enters/leaves. <c>Slide</c> (default) animates\n    /// from the configured <see cref=\"Side\"/>. <c>Fade</c> uses a simple\n    /// opacity tween — useful in dense forms where popovers and animations\n    /// compete. <c>None</c> disables the animation entirely.</summary>\n    [Parameter] public SheetAnimation Animation { get; set; } = SheetAnimation.Slide;\n    [Parameter] public bool PreventClose { get; set; }\n    /// <summary>When true, the sheet can be dismissed by swiping in the\n    /// direction opposite to its Side (e.g. swipe-down on a Bottom sheet).\n    /// Default false to avoid conflicts with scrollable content. Wire to\n    /// true only when the sheet's content fits without scrolling.</summary>\n    [Parameter] public bool SwipeToClose { get; set; } = false;\n    [Parameter] public string? Class { get; set; }\n    [Parameter(CaptureUnmatchedValues = true)] public Dictionary<string, object>? AdditionalAttributes { get; set; }\n\n    private readonly string _contentId = $\"sheet-{Guid.NewGuid():N}\";\n    private bool _wasOpen;\n\n    // Slide-animation transform trap is fixed via JS interop:\n    // Interop.AttachOverlaySlideEnd registers a native animationend listener\n    // that filters strictly on slide-in-from-* AND on event.target === this\n    // element (so descendant animations like Calendar's animate-zoom-in\n    // don't trigger us), then sets inline `transform: none`. Inline style\n    // beats the keyframe fill-mode forwarding — guarantees no containing\n    // block trap for popovers (Select / DatePicker / Combobox).\n    private string CssClass => $\"fixed z-50 flex flex-col gap-4 bg-background p-6 shadow-lg {PositionClasses} {AnimationClass} {Class}\".Trim();\n\n    private string SizeClass => Side is SheetSide.Left or SheetSide.Right\n        ? Size switch\n        {\n            SheetSize.Sm => \"sm:max-w-xs\",\n            SheetSize.Lg => \"sm:max-w-lg\",\n            SheetSize.Xl => \"sm:max-w-xl\",\n            SheetSize.Full => \"sm:max-w-full\",\n            _ => \"sm:max-w-sm\"\n        }\n        : \"\";\n\n    private string PositionClasses => Side switch\n    {\n        SheetSide.Top => \"inset-x-0 top-0\",\n        SheetSide.Bottom => \"inset-x-0 bottom-0\",\n        SheetSide.Left => $\"inset-y-0 left-0 h-full w-3/4 {SizeClass}\",\n        SheetSide.Right => $\"inset-y-0 right-0 h-full w-3/4 {SizeClass}\",\n        _ => $\"inset-y-0 right-0 h-full w-3/4 {SizeClass}\"\n    };\n\n    // Animation=Slide is paired with the JS-interop AttachOverlaySlideEnd\n    // (see OnAfterRenderAsync) which clears the inline transform once the\n    // slide animation completes — this defeats the `animation-fill-mode:\n    // both` identity-matrix trap that would otherwise create a containing\n    // block for fixed-positioned descendants. Fade / None don't use a\n    // transform at all, so no JS cleanup is needed.\n    private string AnimationClass => Animation switch\n    {\n        SheetAnimation.None => \"\",\n        SheetAnimation.Fade => \"animate-fade-in\",\n        SheetAnimation.Slide => Side switch\n        {\n            SheetSide.Top => \"animate-slide-in-from-top\",\n            SheetSide.Bottom => \"animate-slide-in-from-bottom\",\n            SheetSide.Left => \"animate-slide-in-from-left\",\n            SheetSide.Right => \"animate-slide-in-from-right\",\n            _ => \"animate-slide-in-from-right\"\n        },\n        _ => \"\"\n    };\n\n    // Kept for backward-compat with tests / consumers that read SideClasses.\n    private string SideClasses => $\"{PositionClasses} {AnimationClass}\".Trim();\n\n    // Swipe direction is the direction of the gesture that should dismiss the sheet:\n    // a Right-anchored sheet flies out to the right when the user swipes right, etc.\n    // Drawer uses the same convention — see DrawerContent.SwipeDirection.\n    private string SwipeDirection => Side switch\n    {\n        SheetSide.Top => \"up\",\n        SheetSide.Right => \"right\",\n        SheetSide.Bottom => \"down\",\n        SheetSide.Left => \"left\",\n        _ => \"right\"\n    };\n\n    protected override async Task OnAfterRenderAsync(bool firstRender)\n    {\n        // When passthrough mode is active the host SheetContent owns scroll lock\n        // + focus trap; this nested instance is a no-op for lifecycle effects.\n        if (Shell is not null) return;\n\n        if (Context.IsOpen && !_wasOpen)\n        {\n            await Interop.LockScroll();\n            await Interop.SetupFocusTrap(_contentId);\n            // Native animationend listener sets inline transform:none after\n            // the slide animation completes (filtered on slide-in-from-* so\n            // descendant zoom-in / fade-in events don't trigger us). Inline\n            // style beats the keyframe fill-mode forwarding — guarantees no\n            // containing-block trap for popovers.\n            if (Animation == SheetAnimation.Slide)\n            {\n                await Interop.AttachOverlaySlideEnd(_contentId);\n            }\n            // Touch swipe-to-dismiss (WCAG 2.5.1 / mobile UX parity with Drawer).\n            // Reuses the Drawer JS helper as-is — it already handles all four\n            // directions and matches the Sheet's Side parameter 1:1.\n            // Gated on SwipeToClose (default false) because Sheets commonly\n            // contain scrollable content, and a downward scroll on a Bottom\n            // sheet would otherwise be misread as a dismiss gesture.\n            if (!PreventClose && SwipeToClose)\n            {\n                await Interop.RegisterDrawerSwipe(_contentId, SwipeDirection, Close);\n            }\n            _wasOpen = true;\n        }\n        else if (!Context.IsOpen && _wasOpen)\n        {\n            await Cleanup();\n            _wasOpen = false;\n        }\n    }\n\n\n    private async Task Close() => await Context.SetOpen.InvokeAsync(false);\n\n    private async Task HandleBackdropClick()\n    {\n        if (!PreventClose) await Close();\n    }\n\n    private async Task HandleKeyDown(KeyboardEventArgs e)\n    {\n        if (e.Key == \"Escape\" && !PreventClose) await Close();\n    }\n\n    private async Task Cleanup()\n    {\n        try\n        {\n            await Interop.UnregisterDrawerSwipe(_contentId);\n            await Interop.RemoveFocusTrap(_contentId);\n            await Interop.UnlockScroll();\n        }\n        catch (JSDisconnectedException) { }\n    }\n\n    public async ValueTask DisposeAsync()\n    {\n        if (_wasOpen) await Cleanup();\n    }\n\n    public enum SheetSide { Top, Right, Bottom, Left }\n    public enum SheetSize { Sm, Default, Lg, Xl, Full }\n    public enum SheetAnimation { Slide, Fade, None }\n}\n"
    },
    {
      "path": "UI/Sheet/SheetDescription.razor",
      "content": "@namespace Lumeo\n\n<p id=\"@Context.DescriptionId\" class=\"@CssClass\" @attributes=\"AdditionalAttributes\">\n    @ChildContent\n</p>\n\n@code {\n    [CascadingParameter] public Sheet.SheetContext Context { get; set; } = default!;\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 => $\"text-sm text-muted-foreground {Class}\".Trim();\n}\n"
    },
    {
      "path": "UI/Sheet/SheetFooter.razor",
      "content": "@namespace Lumeo\n\n<div class=\"@CssClass\" @attributes=\"AdditionalAttributes\">\n    @ChildContent\n</div>\n\n@code {\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 => $\"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2 {Class}\".Trim();\n}\n"
    },
    {
      "path": "UI/Sheet/SheetHeader.razor",
      "content": "@namespace Lumeo\n\n<div class=\"@CssClass\" @attributes=\"AdditionalAttributes\">\n    @ChildContent\n</div>\n\n@code {\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 => $\"flex flex-col space-y-2 text-center sm:text-left {Class}\".Trim();\n}\n"
    },
    {
      "path": "UI/Sheet/SheetTitle.razor",
      "content": "@namespace Lumeo\n\n<h2 id=\"@Context.TitleId\" class=\"@CssClass\" @attributes=\"AdditionalAttributes\">\n    @ChildContent\n</h2>\n\n@code {\n    [CascadingParameter] public Sheet.SheetContext Context { get; set; } = default!;\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 => $\"text-lg font-semibold text-foreground {Class}\".Trim();\n}\n"
    },
    {
      "path": "UI/Sheet/SheetTrigger.razor",
      "content": "@namespace Lumeo\n\n<div class=\"@CssClass\" role=\"button\" aria-expanded=\"@Context.IsOpen\" @onclick=\"Open\" @attributes=\"AdditionalAttributes\">\n    @ChildContent\n</div>\n\n@code {\n    [CascadingParameter] public Sheet.SheetContext Context { get; set; } = default!;\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 => $\"inline-flex {Class}\".Trim();\n\n    private async Task Open() => await Context.SetOpen.InvokeAsync(true);\n}\n"
    }
  ],
  "slots": [
    {
      "name": "ChildContent",
      "owner": "Sheet",
      "type": "RenderFragment?",
      "description": null
    },
    {
      "name": "ChildContent",
      "owner": "SheetClose",
      "type": "RenderFragment?",
      "description": null
    },
    {
      "name": "ChildContent",
      "owner": "SheetContent",
      "type": "RenderFragment?",
      "description": null
    },
    {
      "name": "ChildContent",
      "owner": "SheetDescription",
      "type": "RenderFragment?",
      "description": null
    },
    {
      "name": "ChildContent",
      "owner": "SheetFooter",
      "type": "RenderFragment?",
      "description": null
    },
    {
      "name": "ChildContent",
      "owner": "SheetHeader",
      "type": "RenderFragment?",
      "description": null
    },
    {
      "name": "ChildContent",
      "owner": "SheetTitle",
      "type": "RenderFragment?",
      "description": null
    },
    {
      "name": "ChildContent",
      "owner": "SheetTrigger",
      "type": "RenderFragment?",
      "description": null
    }
  ],
  "serviceDependencies": [
    {
      "service": "ComponentInteropService",
      "namespace": ""
    },
    {
      "service": "ILumeoLocalizer",
      "namespace": "Lumeo.Services.Localization"
    }
  ],
  "cascadingDependencies": [
    {
      "name": "Context",
      "type": "Sheet.SheetContext",
      "required": true
    },
    {
      "name": "Shell",
      "type": "OverlayShellMarker?",
      "required": false
    }
  ],
  "relatedComponents": [
    {
      "name": "SheetClose",
      "reason": "sub-component"
    },
    {
      "name": "SheetContent",
      "reason": "sub-component"
    },
    {
      "name": "SheetDescription",
      "reason": "sub-component"
    },
    {
      "name": "SheetFooter",
      "reason": "sub-component"
    },
    {
      "name": "SheetHeader",
      "reason": "sub-component"
    },
    {
      "name": "SheetTitle",
      "reason": "sub-component"
    },
    {
      "name": "SheetTrigger",
      "reason": "sub-component"
    }
  ],
  "keyboardInteractions": [
    {
      "key": "Escape",
      "action": "HandleKeyDown handles Escape in SheetContent.razor"
    }
  ],
  "tests": [
    "tests/Lumeo.Tests/Components/Sheet/SheetTests.cs",
    "tests/Lumeo.Tests/Contract/ComponentContractTests.cs"
  ],
  "mdSummary": "# Sheet\n\nSlide-in side panel from left/right/top/bottom.\n\n**Package:** `Lumeo` · **Category:** Overlay\n**Docs:** https://lumeo.nativ.sh/components/sheet\n\n## Parameters\n| Owner | Name | Type | Default | Description |\n|---|---|---|---|---|\n| Sheet | ChildContent | `RenderFragment?` | `` |  |\n| Sheet | Open | `bool` | `` |  |\n| Sheet | OpenChanged | `EventCallback<bool>` | `` |  |\n| Sheet | IsOpen | `bool` | `` |  |\n| Sheet | IsOpenChanged | `EventCallback<bool>` | `` |  |\n| SheetClose | Context | `Sheet.SheetContext` | `default!` |  |\n| SheetClose | ChildContent | `RenderFragment?` | `` |  |\n| SheetClose | Class | `string?` | `` |  |\n| SheetClose | AdditionalAttributes | `Dictionary<string, object>?` | `` |  |\n| SheetContent | Context | `Sheet.SheetContext` | `default!` |  |\n| SheetContent | Shell | `OverlayShellMarker?` | `` |  |\n| SheetContent | ChildContent | `RenderFragment?` | `` |  |\n| SheetContent | Side | `SheetSide` | `SheetSide.Right` |  |\n| SheetContent | Size | `SheetSize` | `SheetSize.Default` |  |\n| SheetContent | Animation | `SheetAnimation` | `SheetAnimation.Slide` | How the sheet enters/leaves. Slide (default) animates from the configured Side. Fade uses a simple opacity tween — useful in dense forms where popovers and animations compete. None disables the animation entirely. |\n| SheetContent | PreventClose | `bool` | `` |  |\n| SheetContent | SwipeToClose | `bool` | `false` | When true, the sheet can be dismissed by swiping in the direction opposite to its Side (e.g. swipe-down on a Bottom sheet). Default false to avoid conflicts with scrollable content. Wire to true only when the sheet's content fits without scrolling. |\n| SheetContent | Class | `string?` | `` |  |\n| SheetContent | AdditionalAttributes | `Dictionary<string, object>?` | `` |  |\n| SheetDescription | Context | `Sheet.SheetContext` | `default!` |  |\n| SheetDescription | ChildContent | `RenderFragment?` | `` |  |\n| SheetDescription | Class | `string?` | `` |  |\n| SheetDescription | AdditionalAttributes | `Dictionary<string, object>?` | `` |  |\n| SheetFooter | ChildContent | `RenderFragment?` | `` |  |\n| SheetFooter | Class | `string?` | `` |  |\n| SheetFooter | AdditionalAttributes | `Dictionary<string, object>?` | `` |  |\n| SheetHeader | ChildContent | `RenderFragment?` | `` |  |\n| SheetHeader | Class | `string?` | `` |  |\n| SheetHeader | AdditionalAttributes | `Dictionary<string, object>?` | `` |  |\n| SheetTitle | Context | `Sheet.SheetContext` | `default!` |  |\n| SheetTitle | ChildContent | `RenderFragment?` | `` |  |\n| SheetTitle | Class | `string?` | `` |  |\n| SheetTitle | AdditionalAttributes | `Dictionary<string, object>?` | `` |  |\n| SheetTrigger | Context | `Sheet.SheetContext` | `default!` |  |\n| SheetTrigger | ChildContent | `RenderFragment?` | `` |  |\n| SheetTrigger | Class | `string?` | `` |  |\n| SheetTrigger | AdditionalAttributes | `Dictionary<string, object>?` | `` |  |\n\n## Slots\n- `ChildContent` (Sheet) — RenderFragment slot\n- `ChildContent` (SheetClose) — RenderFragment slot\n- `ChildContent` (SheetContent) — RenderFragment slot\n- `ChildContent` (SheetDescription) — RenderFragment slot\n- `ChildContent` (SheetFooter) — RenderFragment slot\n- `ChildContent` (SheetHeader) — RenderFragment slot\n- `ChildContent` (SheetTitle) — RenderFragment slot\n- `ChildContent` (SheetTrigger) — RenderFragment slot\n\n## Events\n- `OpenChanged` (`EventCallback<bool>`)\n- `IsOpenChanged` (`EventCallback<bool>`)\n\n## Sub-components\n- `SheetClose`, `SheetContent`, `SheetDescription`, `SheetFooter`, `SheetHeader`, `SheetTitle`, `SheetTrigger`\n\n## Cascading values it expects\n- `Context` (`Sheet.SheetContext`)\n- `Shell` (`OverlayShellMarker?`)\n\n## Services it injects\n- `ComponentInteropService` ()\n- `ILumeoLocalizer` (Lumeo.Services.Localization)\n\n## Keyboard\n- `Escape` — HandleKeyDown handles Escape in SheetContent.razor\n\n## Example\n```razor\n<Sheet />\n```\n\n## CSS variables\n`--color-background`, `--color-foreground`, `--color-muted`, `--color-muted-foreground`, `--radius`\n\n## Files\n- `UI/Sheet/Sheet.razor`\n- `UI/Sheet/SheetClose.razor`\n- `UI/Sheet/SheetContent.razor`\n- `UI/Sheet/SheetDescription.razor`\n- `UI/Sheet/SheetFooter.razor`\n- `UI/Sheet/SheetHeader.razor`\n- `UI/Sheet/SheetTitle.razor`\n- `UI/Sheet/SheetTrigger.razor`\n"
}