{
  "name": "Image",
  "category": "Data Display",
  "subcategory": "Cards & Layout",
  "description": "Image with lazy-loading, loading skeleton, and error fallback.",
  "thumbnail": "/preview-cards/image.png",
  "hasDocsPage": true,
  "nugetPackage": "Lumeo",
  "files": [
    "UI/Image/Image.razor",
    "UI/Image/ImageGallery.razor"
  ],
  "dependencies": [],
  "packageDependencies": [
    "Blazicons.Lucide"
  ],
  "cssVars": [
    "--color-muted",
    "--color-muted-foreground",
    "--radius"
  ],
  "api": {
    "name": "Image",
    "category": "Data Display",
    "subcategory": "Cards & Layout",
    "description": "Image with lazy-loading, loading skeleton, and error fallback.",
    "nugetPackage": "Lumeo",
    "files": [
      "UI/Image/Image.razor",
      "UI/Image/ImageGallery.razor"
    ],
    "namespace": "Lumeo",
    "inheritsFrom": null,
    "implements": [
      "IAsyncDisposable"
    ],
    "parameters": [
      {
        "name": "Src",
        "type": "string?",
        "default": null,
        "description": null,
        "isCascading": false,
        "captureUnmatched": false
      },
      {
        "name": "Alt",
        "type": "string?",
        "default": null,
        "description": null,
        "isCascading": false,
        "captureUnmatched": false
      },
      {
        "name": "Width",
        "type": "string?",
        "default": null,
        "description": null,
        "isCascading": false,
        "captureUnmatched": false
      },
      {
        "name": "Height",
        "type": "string?",
        "default": null,
        "description": null,
        "isCascading": false,
        "captureUnmatched": false
      },
      {
        "name": "Lazy",
        "type": "bool",
        "default": null,
        "description": null,
        "isCascading": false,
        "captureUnmatched": false
      },
      {
        "name": "Fallback",
        "type": "string?",
        "default": null,
        "description": null,
        "isCascading": false,
        "captureUnmatched": false
      },
      {
        "name": "Preview",
        "type": "bool",
        "default": null,
        "description": null,
        "isCascading": false,
        "captureUnmatched": false
      },
      {
        "name": "Zoomable",
        "type": "bool",
        "default": "false",
        "description": "When true, registers a two-finger pinch-zoom gesture on the image container so touch users can zoom in/out on the image directly (independent of the fullscreen Preview overlay). Sets touch-action: none on the wrapper so the browser doesn't intercept the gesture for native page zoom.",
        "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-muted",
      "--color-muted-foreground",
      "--radius"
    ],
    "examples": [
      {
        "title": "Basic Image",
        "code": "<Image Src=\"photo.jpg\" Alt=\"Sample\" Width=\"400\" Height=\"250\" />"
      },
      {
        "title": "With Preview",
        "code": "<Image Src=\"photo.jpg\" Preview=\"true\" />"
      },
      {
        "title": "Gallery",
        "code": "<ImageGallery Images=\"@_images\" Columns=\"4\" />"
      },
      {
        "title": "Pinch-to-Zoom",
        "code": "<Image Src=\"photo.jpg\" Zoomable=\"true\" Alt=\"Pinch to zoom on mobile\" />"
      }
    ],
    "subComponents": {
      "ImageGallery": {
        "componentName": "ImageGallery",
        "fileName": "ImageGallery.razor",
        "namespace": "Lumeo",
        "inheritsFrom": null,
        "implements": [
          "IAsyncDisposable"
        ],
        "parameters": [
          {
            "name": "Images",
            "type": "List<ImageItem>?",
            "default": null,
            "description": null,
            "isCascading": false,
            "captureUnmatched": false
          },
          {
            "name": "Columns",
            "type": "int",
            "default": "3",
            "description": null,
            "isCascading": false,
            "captureUnmatched": false
          },
          {
            "name": "Gap",
            "type": "string?",
            "default": null,
            "description": null,
            "isCascading": false,
            "captureUnmatched": false
          },
          {
            "name": "GesturesEnabled",
            "type": "bool",
            "default": "true",
            "description": "When true (default), enables horizontal swipe (prev/next image) and pinch-to-zoom gestures on the fullscreen preview overlay on touch devices.",
            "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": [
          {
            "name": "ImageItem",
            "signature": "ImageItem(string Src, string? Alt = null)",
            "description": null
          }
        ],
        "parseFailed": false,
        "parseError": null
      }
    },
    "parseFailed": false,
    "parseError": null
  },
  "$schema": "https://lumeo.nativ.sh/registry-component-schema.json",
  "key": "image",
  "docsUrl": "https://lumeo.nativ.sh/components/image",
  "sourceUrl": [
    {
      "file": "UI/Image/Image.razor",
      "url": "https://github.com/Brain2k-0005/Lumeo/tree/master/src/Lumeo/UI/Image/Image.razor"
    },
    {
      "file": "UI/Image/ImageGallery.razor",
      "url": "https://github.com/Brain2k-0005/Lumeo/tree/master/src/Lumeo/UI/Image/ImageGallery.razor"
    }
  ],
  "source": [
    {
      "path": "UI/Image/Image.razor",
      "content": "@namespace Lumeo\n@implements IAsyncDisposable\n\n<div class=\"@ContainerClass\" @attributes=\"AdditionalAttributes\" id=\"@(Zoomable ? _zoomContainerId : null)\">\n    @if (!_hasError)\n    {\n        <img src=\"@Src\" alt=\"@Alt\"\n             width=\"@Width\" height=\"@Height\"\n             loading=\"@(Lazy ? \"lazy\" : null)\"\n             class=\"@ImageClass\"\n             style=\"@ZoomStyle\"\n             @onclick=\"HandleClick\"\n             @onerror=\"HandleError\" />\n    }\n    else if (Fallback is not null)\n    {\n        <img src=\"@Fallback\" alt=\"@Alt\"\n             width=\"@Width\" height=\"@Height\"\n             class=\"@ImageClass\" />\n    }\n    else\n    {\n        <div class=\"@FallbackClass\" style=\"@FallbackStyle\">\n            <Blazicon Svg=\"Lucide.ImageOff\" class=\"h-8 w-8 text-muted-foreground\" />\n        </div>\n    }\n\n    @if (Preview)\n    {\n        <div class=\"absolute inset-0 flex items-center justify-center bg-black/0 hover:bg-black/20 transition-colors cursor-pointer opacity-0 hover:opacity-100\"\n             @onclick=\"HandleClick\">\n            <Blazicon Svg=\"Lucide.ZoomIn\" class=\"h-6 w-6 text-white drop-shadow-lg\" />\n        </div>\n    }\n</div>\n\n@if (_showPreview && Preview)\n{\n    <div class=\"fixed inset-0 z-50 bg-black/80 flex items-center justify-center animate-fade-in\" @onclick=\"ClosePreview\" @onkeydown=\"HandlePreviewKeyDown\" tabindex=\"-1\" id=\"@_previewId\">\n        <button type=\"button\" class=\"absolute top-4 right-4 text-white/80 hover:text-white transition-colors\" @onclick=\"ClosePreview\">\n            <Blazicon Svg=\"Lucide.X\" class=\"h-6 w-6\" />\n        </button>\n\n        <div class=\"flex items-center gap-2 absolute bottom-4 left-1/2 -translate-x-1/2\">\n            <button type=\"button\" class=\"text-white/80 hover:text-white p-1.5 rounded-md bg-white/10 hover:bg-white/20 transition-colors\" @onclick=\"ZoomOut\" @onclick:stopPropagation=\"true\">\n                <Blazicon Svg=\"Lucide.ZoomOut\" class=\"h-5 w-5\" />\n            </button>\n            <span class=\"text-white/80 text-sm min-w-[3rem] text-center\">@($\"{_zoom}%\")</span>\n            <button type=\"button\" class=\"text-white/80 hover:text-white p-1.5 rounded-md bg-white/10 hover:bg-white/20 transition-colors\" @onclick=\"ZoomIn\" @onclick:stopPropagation=\"true\">\n                <Blazicon Svg=\"Lucide.ZoomIn\" class=\"h-5 w-5\" />\n            </button>\n        </div>\n\n        <img src=\"@Src\" alt=\"@Alt\" class=\"max-h-[85vh] max-w-[90vw] object-contain transition-transform\" style=\"transform: scale(@(_zoom / 100.0))\" @onclick:stopPropagation=\"true\" />\n    </div>\n}\n\n@inject ComponentInteropService Interop\n\n@code {\n    [Parameter] public string? Src { get; set; }\n    [Parameter] public string? Alt { get; set; }\n    [Parameter] public string? Width { get; set; }\n    [Parameter] public string? Height { get; set; }\n    [Parameter] public bool Lazy { get; set; }\n    [Parameter] public string? Fallback { get; set; }\n    [Parameter] public bool Preview { get; set; }\n    /// <summary>\n    /// When true, registers a two-finger pinch-zoom gesture on the image container\n    /// so touch users can zoom in/out on the image directly (independent of the\n    /// fullscreen <see cref=\"Preview\"/> overlay). Sets <c>touch-action: none</c>\n    /// on the wrapper so the browser doesn't intercept the gesture for native page zoom.\n    /// </summary>\n    [Parameter] public bool Zoomable { get; set; } = false;\n    [Parameter] public string? Class { get; set; }\n    [Parameter(CaptureUnmatchedValues = true)] public Dictionary<string, object>? AdditionalAttributes { get; set; }\n\n    private bool _hasError;\n    private bool _showPreview;\n    private int _zoom = 100;\n    private readonly string _previewId = $\"image-preview-{Guid.NewGuid():N}\";\n\n    // Pinch-zoom state (only used when Zoomable=true).\n    private readonly string _zoomContainerId = $\"image-zoom-{Guid.NewGuid():N}\";\n    private bool _pinchRegistered;\n    private double _zoomScale = 1.0;\n    private const double MinZoomScale = 0.5;\n    private const double MaxZoomScale = 5.0;\n    private string? ZoomStyle => Zoomable && Math.Abs(_zoomScale - 1.0) > 0.0001\n        ? $\"transform: scale({_zoomScale.ToString(\"0.###\", System.Globalization.CultureInfo.InvariantCulture)}); transform-origin: center center;\"\n        : null;\n\n    private string ContainerClass => string.Join(\" \", new[] { \"relative inline-block overflow-hidden\", Class }.Where(s => !string.IsNullOrWhiteSpace(s)));\n    private string ImageClass => string.Join(\" \", new[] { \"object-cover\", Preview ? \"cursor-pointer\" : null }.Where(s => !string.IsNullOrWhiteSpace(s)));\n    private string FallbackClass => \"flex items-center justify-center bg-muted rounded-md\";\n    private string FallbackStyle\n    {\n        get\n        {\n            var w = Width is not null ? $\"width:{Width}px;\" : \"width:100%;\";\n            var h = Height is not null ? $\"height:{Height}px;\" : \"height:8rem;\";\n            return w + h;\n        }\n    }\n\n    private void HandleError() => _hasError = true;\n\n    private async Task HandleClick()\n    {\n        if (Preview && !_hasError)\n        {\n            _showPreview = true;\n            _zoom = 100;\n            await Interop.LockScroll();\n        }\n    }\n\n    private async Task ClosePreview()\n    {\n        _showPreview = false;\n        try { await Interop.UnlockScroll(); } catch (JSDisconnectedException) { }\n    }\n\n    private void ZoomIn()\n    {\n        if (_zoom < 300) _zoom += 25;\n    }\n\n    private void ZoomOut()\n    {\n        if (_zoom > 25) _zoom -= 25;\n    }\n\n    private async Task HandlePreviewKeyDown(KeyboardEventArgs e)\n    {\n        switch (e.Key)\n        {\n            case \"Escape\": await ClosePreview(); break;\n            case \"+\":\n            case \"=\": ZoomIn(); break;\n            case \"-\": ZoomOut(); break;\n        }\n    }\n\n    protected override async Task OnAfterRenderAsync(bool firstRender)\n    {\n        if (firstRender && Zoomable && !_pinchRegistered)\n        {\n            _pinchRegistered = true;\n            try\n            {\n                await Interop.RegisterPinchZoom(_zoomContainerId, HandlePinchZoom);\n            }\n            catch (JSDisconnectedException) { }\n        }\n    }\n\n    private Task HandlePinchZoom(double scaleDelta)\n    {\n        // Multiply incremental delta into the running zoom, then clamp.\n        var next = _zoomScale * scaleDelta;\n        if (next < MinZoomScale) next = MinZoomScale;\n        else if (next > MaxZoomScale) next = MaxZoomScale;\n        if (Math.Abs(next - _zoomScale) < 0.0001) return Task.CompletedTask;\n        _zoomScale = next;\n        // The pointermove callbacks fire at high frequency; StateHasChanged is\n        // cheap here because only the <img> inline style changes.\n        StateHasChanged();\n        return Task.CompletedTask;\n    }\n\n    public async ValueTask DisposeAsync()\n    {\n        if (_showPreview)\n        {\n            try { await Interop.UnlockScroll(); } catch (JSDisconnectedException) { }\n        }\n        if (_pinchRegistered)\n        {\n            try { await Interop.UnregisterPinchZoom(_zoomContainerId); } catch (JSDisconnectedException) { }\n        }\n    }\n}\n"
    },
    {
      "path": "UI/Image/ImageGallery.razor",
      "content": "@namespace Lumeo\n@implements IAsyncDisposable\n\n<div class=\"@CssClass\" @attributes=\"AdditionalAttributes\">\n    @if (Images is { Count: > 0 })\n    {\n        @foreach (var (image, index) in Images.Select((img, i) => (img, i)))\n        {\n            <div @key=\"image.Src\" class=\"overflow-hidden rounded-md cursor-pointer group\" @onclick=\"() => OpenPreview(index)\">\n                <img src=\"@image.Src\" alt=\"@image.Alt\" loading=\"lazy\"\n                     class=\"w-full h-full object-cover transition-transform group-hover:scale-105\" />\n            </div>\n        }\n    }\n</div>\n\n@if (_showPreview && Images is { Count: > 0 })\n{\n    <div class=\"fixed inset-0 z-50 bg-black/80 flex items-center justify-center animate-fade-in\" @onkeydown=\"HandleKeyDown\" tabindex=\"-1\" id=\"@_previewId\">\n        <button type=\"button\" class=\"absolute top-4 right-4 text-white/80 hover:text-white transition-colors z-10\" @onclick=\"ClosePreview\">\n            <Blazicon Svg=\"Lucide.X\" class=\"h-6 w-6\" />\n        </button>\n\n        @if (Images.Count > 1)\n        {\n            <button type=\"button\" class=\"absolute left-4 top-1/2 -translate-y-1/2 text-white/80 hover:text-white p-2 rounded-full bg-white/10 hover:bg-white/20 transition-colors\"\n                    @onclick=\"PrevImage\" @onclick:stopPropagation=\"true\">\n                <Blazicon Svg=\"Lucide.ChevronLeft\" class=\"h-6 w-6\" />\n            </button>\n            <button type=\"button\" class=\"absolute right-4 top-1/2 -translate-y-1/2 text-white/80 hover:text-white p-2 rounded-full bg-white/10 hover:bg-white/20 transition-colors\"\n                    @onclick=\"NextImage\" @onclick:stopPropagation=\"true\">\n                <Blazicon Svg=\"Lucide.ChevronRight\" class=\"h-6 w-6\" />\n            </button>\n        }\n\n        <div class=\"flex items-center gap-2 absolute bottom-4 left-1/2 -translate-x-1/2 z-10\">\n            <button type=\"button\" class=\"text-white/80 hover:text-white p-1.5 rounded-md bg-white/10 hover:bg-white/20 transition-colors\" @onclick=\"ZoomOut\" @onclick:stopPropagation=\"true\">\n                <Blazicon Svg=\"Lucide.ZoomOut\" class=\"h-5 w-5\" />\n            </button>\n            <span class=\"text-white/80 text-sm min-w-[3rem] text-center\">@($\"{_zoom}%\")</span>\n            <button type=\"button\" class=\"text-white/80 hover:text-white p-1.5 rounded-md bg-white/10 hover:bg-white/20 transition-colors\" @onclick=\"ZoomIn\" @onclick:stopPropagation=\"true\">\n                <Blazicon Svg=\"Lucide.ZoomIn\" class=\"h-5 w-5\" />\n            </button>\n            @if (Images.Count > 1)\n            {\n                <span class=\"text-white/60 text-sm ml-2\">@(_currentIndex + 1) / @Images.Count</span>\n            }\n        </div>\n\n        <img src=\"@Images[_currentIndex].Src\" alt=\"@Images[_currentIndex].Alt\"\n             class=\"max-h-[85vh] max-w-[90vw] object-contain transition-transform\"\n             style=\"transform: scale(@(_zoom / 100.0))\"\n             id=\"@_previewImageId\"\n             @onclick:stopPropagation=\"true\" />\n    </div>\n}\n\n@inject ComponentInteropService Interop\n\n@code {\n    [Parameter] public List<ImageItem>? Images { get; set; }\n    [Parameter] public int Columns { get; set; } = 3;\n    [Parameter] public string? Gap { get; set; }\n    /// <summary>\n    /// When true (default), enables horizontal swipe (prev/next image) and\n    /// pinch-to-zoom gestures on the fullscreen preview overlay on touch devices.\n    /// </summary>\n    [Parameter] public bool GesturesEnabled { get; set; } = true;\n    [Parameter] public string? Class { get; set; }\n    [Parameter(CaptureUnmatchedValues = true)] public Dictionary<string, object>? AdditionalAttributes { get; set; }\n\n    private bool _showPreview;\n    private int _currentIndex;\n    private int _zoom = 100;\n    private readonly string _previewId = $\"gallery-preview-{Guid.NewGuid():N}\";\n    private readonly string _previewImageId = $\"gallery-img-{Guid.NewGuid():N}\";\n\n    // Gesture state\n    private bool _pinchRegistered;\n    private bool _swipeRegistered;\n\n    private const int MinZoom = 25;\n    private const int MaxZoom = 300;\n\n    private string CssClass\n    {\n        get\n        {\n            var cols = Columns switch\n            {\n                1 => \"grid-cols-1\",\n                2 => \"grid-cols-2\",\n                3 => \"grid-cols-3\",\n                4 => \"grid-cols-4\",\n                5 => \"grid-cols-5\",\n                6 => \"grid-cols-6\",\n                _ => \"grid-cols-3\"\n            };\n            var gap = Gap ?? \"gap-2\";\n            return $\"grid {cols} {gap} {Class}\".Trim();\n        }\n    }\n\n    private async Task OpenPreview(int index)\n    {\n        _currentIndex = index;\n        _zoom = 100;\n        _showPreview = true;\n        await Interop.LockScroll();\n    }\n\n    private async Task ClosePreview()\n    {\n        _showPreview = false;\n        await UnregisterGesturesAsync();\n        _pinchRegistered = false;\n        _swipeRegistered = false;\n        try { await Interop.UnlockScroll(); } catch (JSDisconnectedException) { }\n    }\n\n    private void PrevImage()\n    {\n        if (Images is null) return;\n        _currentIndex = (_currentIndex - 1 + Images.Count) % Images.Count;\n        _zoom = 100;\n    }\n\n    private void NextImage()\n    {\n        if (Images is null) return;\n        _currentIndex = (_currentIndex + 1) % Images.Count;\n        _zoom = 100;\n    }\n\n    private void ZoomIn()\n    {\n        if (_zoom < MaxZoom) _zoom += 25;\n    }\n\n    private void ZoomOut()\n    {\n        if (_zoom > MinZoom) _zoom -= 25;\n    }\n\n    private async Task HandleKeyDown(KeyboardEventArgs e)\n    {\n        switch (e.Key)\n        {\n            case \"Escape\": await ClosePreview(); break;\n            case \"ArrowLeft\": PrevImage(); break;\n            case \"ArrowRight\": NextImage(); break;\n            case \"+\":\n            case \"=\": ZoomIn(); break;\n            case \"-\": ZoomOut(); break;\n        }\n    }\n\n    protected override async Task OnAfterRenderAsync(bool firstRender)\n    {\n        if (_showPreview && GesturesEnabled)\n        {\n            // Register pinch zoom on the preview image element.\n            if (!_pinchRegistered)\n            {\n                _pinchRegistered = true;\n                try\n                {\n                    await Interop.RegisterPinchZoom(_previewImageId, HandlePinchZoom);\n                }\n                catch (JSDisconnectedException) { }\n            }\n\n            // Register horizontal swipe on the preview overlay (for prev/next).\n            if (!_swipeRegistered && Images is { Count: > 1 })\n            {\n                _swipeRegistered = true;\n                try\n                {\n                    await Interop.RegisterGallerySwipe(_previewId, HandleGallerySwipe);\n                }\n                catch (JSDisconnectedException) { }\n            }\n        }\n    }\n\n    private Task HandlePinchZoom(double scaleDelta)\n    {\n        // scaleDelta is a multiplicative factor (currentDistance / previousDistance).\n        // Multiply into current zoom percentage and clamp to [MinZoom, MaxZoom].\n        var next = (int)Math.Round(_zoom * scaleDelta);\n        if (next < MinZoom) next = MinZoom;\n        else if (next > MaxZoom) next = MaxZoom;\n        if (next == _zoom) return Task.CompletedTask;\n        _zoom = next;\n        StateHasChanged();\n        return Task.CompletedTask;\n    }\n\n    private Task HandleGallerySwipe(string direction)\n    {\n        if (Images is null || Images.Count <= 1) return Task.CompletedTask;\n        if (direction == \"next\")\n            NextImage();\n        else\n            PrevImage();\n        StateHasChanged();\n        return Task.CompletedTask;\n    }\n\n    private async Task UnregisterGesturesAsync()\n    {\n        if (_pinchRegistered)\n        {\n            try { await Interop.UnregisterPinchZoom(_previewImageId); } catch (JSDisconnectedException) { }\n        }\n        if (_swipeRegistered)\n        {\n            try { await Interop.UnregisterGallerySwipe(_previewId); } catch (JSDisconnectedException) { }\n        }\n    }\n\n    public async ValueTask DisposeAsync()\n    {\n        if (_showPreview)\n        {\n            await UnregisterGesturesAsync();\n            try { await Interop.UnlockScroll(); } catch (JSDisconnectedException) { }\n        }\n    }\n\n    public record ImageItem(string Src, string? Alt = null);\n}\n"
    }
  ],
  "slots": [],
  "serviceDependencies": [
    {
      "service": "ComponentInteropService",
      "namespace": ""
    }
  ],
  "cascadingDependencies": [],
  "relatedComponents": [
    {
      "name": "ImageGallery",
      "reason": "sub-component"
    }
  ],
  "keyboardInteractions": [],
  "tests": [
    "tests/Lumeo.Tests.E2E/Visual/HomePageVisualTests.cs",
    "tests/Lumeo.Tests/Components/Image/ImageTests.cs"
  ],
  "mdSummary": "# Image\n\nImage with lazy-loading, loading skeleton, and error fallback.\n\n**Package:** `Lumeo` · **Category:** Data Display/Cards & Layout\n**Docs:** https://lumeo.nativ.sh/components/image\n\n## Parameters\n| Owner | Name | Type | Default | Description |\n|---|---|---|---|---|\n| Image | Src | `string?` | `` |  |\n| Image | Alt | `string?` | `` |  |\n| Image | Width | `string?` | `` |  |\n| Image | Height | `string?` | `` |  |\n| Image | Lazy | `bool` | `` |  |\n| Image | Fallback | `string?` | `` |  |\n| Image | Preview | `bool` | `` |  |\n| Image | Zoomable | `bool` | `false` | When true, registers a two-finger pinch-zoom gesture on the image container so touch users can zoom in/out on the image directly (independent of the fullscreen Preview overlay). Sets touch-action: none on the wrapper so the browser doesn't intercept the gesture for native page zoom. |\n| Image | Class | `string?` | `` |  |\n| Image | AdditionalAttributes | `Dictionary<string, object>?` | `` |  |\n| ImageGallery | Images | `List<ImageItem>?` | `` |  |\n| ImageGallery | Columns | `int` | `3` |  |\n| ImageGallery | Gap | `string?` | `` |  |\n| ImageGallery | GesturesEnabled | `bool` | `true` | When true (default), enables horizontal swipe (prev/next image) and pinch-to-zoom gestures on the fullscreen preview overlay on touch devices. |\n| ImageGallery | Class | `string?` | `` |  |\n| ImageGallery | AdditionalAttributes | `Dictionary<string, object>?` | `` |  |\n\n## Slots\n- (none)\n\n## Events\n- (none)\n\n## Sub-components\n- `ImageGallery`\n\n## Cascading values it expects\n- (none)\n\n## Services it injects\n- `ComponentInteropService` ()\n\n## Keyboard\n—\n\n## Example\n```razor\n<Image />\n```\n\n## CSS variables\n`--color-muted`, `--color-muted-foreground`, `--radius`\n\n## Files\n- `UI/Image/Image.razor`\n- `UI/Image/ImageGallery.razor`\n"
}