using Microsoft.AspNetCore.Components; using Microsoft.JSInterop; namespace AyCode.Blazor.Components.Components.CardViews; /// /// 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. /// [Parameter, EditorRequired] public IReadOnlyList Data { get; set; } = []; /// /// Template for rendering each card's content. /// [Parameter, EditorRequired] public RenderFragment CardTemplate { get; set; } = null!; /// /// Fired when a card is clicked/tapped. /// [Parameter] public EventCallback OnCardClick { get; set; } /// /// Number of columns on extra-small screens (below 576px). Default: 1. /// [Parameter] public int ColumnCountXs { get; set; } = 1; /// /// Number of columns on small screens (576–768px). Default: 2. /// [Parameter] public int ColumnCountSm { get; set; } = 2; /// /// Number of columns on medium+ screens (769px+). Default: 3. /// [Parameter] public int ColumnCountLg { get; set; } = 3; /// /// Whether to show the pager below the cards. Default: false. /// [Parameter] public bool ShowPager { get; set; } /// /// Number of items per page when paging is enabled. Default: 12. /// [Parameter] public int PageSize { get; set; } = 12; /// /// Additional CSS class for the card view container. /// [Parameter] public string? CssClass { get; set; } /// /// Additional CSS class applied to each individual card wrapper. /// [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. /// [Parameter] public bool ShowFilterPanel { get; set; } /// /// Custom content for the filter panel. Rendered above the card grid when ShowFilterPanel is true. /// [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 { get { if (!ShowPager) return Data; return Data .Skip(_activePageIndex * PageSize) .Take(PageSize) .ToList(); } } private async Task OnCardClickInternal(TItem item) { if (OnCardClick.HasDelegate) await OnCardClick.InvokeAsync(item); } private void OnActivePageIndexChanged(int newPageIndex) { _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 } } } } }