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
}
}
}
}
}