AyCode.Blazor/AyCode.Blazor.Components/Components/CardViews/MgCardView.razor.cs

164 lines
4.7 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using Microsoft.AspNetCore.Components;
using Microsoft.JSInterop;
namespace AyCode.Blazor.Components.Components.CardViews;
/// <summary>
/// Generic card view component that displays items in a responsive CSS Grid layout
/// with optional pagination and scroll-to-item support.
/// </summary>
/// <typeparam name="TItem">The type of data item displayed in each card.</typeparam>
public partial class MgCardView<TItem> : ComponentBase
{
[Inject] private IJSRuntime JSRuntime { get; set; } = null!;
/// <summary>
/// The collection of items to display as cards.
/// </summary>
[Parameter, EditorRequired]
public IReadOnlyList<TItem> Data { get; set; } = [];
/// <summary>
/// Template for rendering each card's content.
/// </summary>
[Parameter, EditorRequired]
public RenderFragment<TItem> CardTemplate { get; set; } = null!;
/// <summary>
/// Fired when a card is clicked/tapped.
/// </summary>
[Parameter]
public EventCallback<TItem> OnCardClick { get; set; }
/// <summary>
/// Number of columns on extra-small screens (below 576px). Default: 1.
/// </summary>
[Parameter]
public int ColumnCountXs { get; set; } = 1;
/// <summary>
/// Number of columns on small screens (576768px). Default: 2.
/// </summary>
[Parameter]
public int ColumnCountSm { get; set; } = 2;
/// <summary>
/// Number of columns on medium+ screens (769px+). Default: 3.
/// </summary>
[Parameter]
public int ColumnCountLg { get; set; } = 3;
/// <summary>
/// Whether to show the pager below the cards. Default: false.
/// </summary>
[Parameter]
public bool ShowPager { get; set; }
/// <summary>
/// Number of items per page when paging is enabled. Default: 12.
/// </summary>
[Parameter]
public int PageSize { get; set; } = 12;
/// <summary>
/// Additional CSS class for the card view container.
/// </summary>
[Parameter]
public string? CssClass { get; set; }
/// <summary>
/// Additional CSS class applied to each individual card wrapper.
/// </summary>
[Parameter]
public string? CardCssClass { get; set; }
/// <summary>
/// Height of the card view container (e.g., "500px", "70vh"). When set, the component uses its own scroll area.
/// </summary>
[Parameter]
public string? Height { get; set; }
/// <summary>
/// Whether to show the filter panel above the cards. Default: false.
/// </summary>
[Parameter]
public bool ShowFilterPanel { get; set; }
/// <summary>
/// Custom content for the filter panel. Rendered above the card grid when ShowFilterPanel is true.
/// </summary>
[Parameter]
public RenderFragment? FilterPanel { get; set; }
/// <summary>
/// Item to scroll into view after render. Set to null to disable.
/// </summary>
[Parameter]
public TItem? ScrollToItem { get; set; }
/// <summary>
/// Key selector for identifying items (e.g., item => item.Id). Required when ScrollToItem is used.
/// </summary>
[Parameter]
public Func<TItem, object>? ItemKeySelector { get; set; }
private int _activePageIndex;
private object? _lastScrolledKey;
private string? ContainerStyle => Height is not null ? $"height: {Height};" : null;
private IReadOnlyList<TItem> 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;
}
/// <summary>
/// Generates a stable DOM element id for a card item using the key selector.
/// </summary>
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
}
}
}
}
}