diff --git a/AyCode.Blazor.Components/Components/CardViews/MgCardView.razor b/AyCode.Blazor.Components/Components/CardViews/MgCardView.razor index ad2de7e..b1c4d9a 100644 --- a/AyCode.Blazor.Components/Components/CardViews/MgCardView.razor +++ b/AyCode.Blazor.Components/Components/CardViews/MgCardView.razor @@ -1,31 +1,36 @@ @typeparam TItem -@if (ShowFilterPanel && FilterPanel is not null) -{ -
- @FilterPanel -
-} - -@if (Data is { Count: > 0 }) -{ -
- @foreach (var item in PagedItems) - { -
- @CardTemplate(item) -
- } -
- - @if (ShowPager && Data.Count > PageSize) +
+ @if (ShowFilterPanel && FilterPanel is not null) { - +
+ @FilterPanel +
} -} + + @if (Data is { Count: > 0 }) + { +
+
+ @foreach (var item in PagedItems) + { +
+ @CardTemplate(item) +
+ } +
+
+ + @if (ShowPager && Data.Count > PageSize) + { + + } + } +
diff --git a/AyCode.Blazor.Components/Components/CardViews/MgCardView.razor.cs b/AyCode.Blazor.Components/Components/CardViews/MgCardView.razor.cs index 9da93bf..e3e9133 100644 --- a/AyCode.Blazor.Components/Components/CardViews/MgCardView.razor.cs +++ b/AyCode.Blazor.Components/Components/CardViews/MgCardView.razor.cs @@ -1,15 +1,17 @@ using Microsoft.AspNetCore.Components; +using Microsoft.JSInterop; namespace AyCode.Blazor.Components.Components.CardViews; /// -/// Generic card view component that displays items in a responsive grid layout. -/// Uses DxGridLayout + DxLayoutBreakpoint for responsive column management -/// and DxPager for optional pagination. +/// Generic card view component that displays items in a responsive CSS Grid layout +/// with optional pagination and scroll-to-item support. /// /// The type of data item displayed in each card. public partial class MgCardView : ComponentBase { + [Inject] private IJSRuntime JSRuntime { get; set; } = null!; + /// /// The collection of items to display as cards. /// @@ -70,6 +72,12 @@ public partial class MgCardView : ComponentBase [Parameter] public string? CardCssClass { get; set; } + /// + /// Height of the card view container (e.g., "500px", "70vh"). When set, the component uses its own scroll area. + /// + [Parameter] + public string? Height { get; set; } + /// /// Whether to show the filter panel above the cards. Default: false. /// @@ -82,7 +90,22 @@ public partial class MgCardView : ComponentBase [Parameter] public RenderFragment? FilterPanel { get; set; } + /// + /// Item to scroll into view after render. Set to null to disable. + /// + [Parameter] + public TItem? ScrollToItem { get; set; } + + /// + /// Key selector for identifying items (e.g., item => item.Id). Required when ScrollToItem is used. + /// + [Parameter] + public Func? ItemKeySelector { get; set; } + private int _activePageIndex; + private object? _lastScrolledKey; + + private string? ContainerStyle => Height is not null ? $"height: {Height};" : null; private IReadOnlyList PagedItems { @@ -108,4 +131,33 @@ public partial class MgCardView : ComponentBase { _activePageIndex = newPageIndex; } + + /// + /// Generates a stable DOM element id for a card item using the key selector. + /// + private string? GetCardElementId(TItem item) + { + return ItemKeySelector is null ? null : $"mg-card-{ItemKeySelector(item)}"; + } + + protected override async Task OnAfterRenderAsync(bool firstRender) + { + if (ScrollToItem is not null && ItemKeySelector is not null) + { + var key = ItemKeySelector(ScrollToItem); + if (!Equals(key, _lastScrolledKey)) + { + _lastScrolledKey = key; + var elementId = $"mg-card-{key}"; + try + { + await JSRuntime.InvokeVoidAsync("MgCardView.scrollToElement", elementId); + } + catch (JSException) + { + // JS might not be loaded yet + } + } + } + } } diff --git a/AyCode.Blazor.Components/Components/CardViews/MgCardView.razor.css b/AyCode.Blazor.Components/Components/CardViews/MgCardView.razor.css index 949e96e..048e9cb 100644 --- a/AyCode.Blazor.Components/Components/CardViews/MgCardView.razor.css +++ b/AyCode.Blazor.Components/Components/CardViews/MgCardView.razor.css @@ -1,3 +1,15 @@ +.mg-card-view-container { + display: flex; + flex-direction: column; + overflow: hidden; +} + +.mg-card-scroll-area { + flex: 1; + overflow-y: auto; + padding-right: 4px; +} + .mg-card-grid { display: grid; gap: 1rem; diff --git a/AyCode.Blazor.Components/wwwroot/js/mgCardView.js b/AyCode.Blazor.Components/wwwroot/js/mgCardView.js new file mode 100644 index 0000000..7aa0512 --- /dev/null +++ b/AyCode.Blazor.Components/wwwroot/js/mgCardView.js @@ -0,0 +1,18 @@ +// MgCardView - Scroll handling +window.MgCardView = { + scrollToElement: function (elementId) { + const element = document.getElementById(elementId); + if (!element) return; + + // Find the closest scroll container + const scrollArea = element.closest('.mg-card-scroll-area'); + if (scrollArea) { + const containerRect = scrollArea.getBoundingClientRect(); + const elementRect = element.getBoundingClientRect(); + const offset = elementRect.top - containerRect.top - (containerRect.height - elementRect.height) / 2; + scrollArea.scrollBy({ top: offset, behavior: 'smooth' }); + } else { + element.scrollIntoView({ behavior: 'smooth', block: 'center' }); + } + } +};