164 lines
4.7 KiB
C#
164 lines
4.7 KiB
C#
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 (576–768px). 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
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|