Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
b65bf53
Merge pull request #214 from blazorblueprintui/develop
mathewtaylor Mar 6, 2026
c37345f
Merge pull request #233 from blazorblueprintui/develop
mathewtaylor Mar 11, 2026
ac9e0ae
Merge pull request #235 from blazorblueprintui/develop
mathewtaylor Mar 11, 2026
8067eec
Merge pull request #241 from blazorblueprintui/develop
mathewtaylor Mar 12, 2026
04b3350
Merge pull request #248 from blazorblueprintui/develop
mathewtaylor Mar 17, 2026
7347e01
Update sortable.js
edgett Mar 18, 2026
d3c8982
Merge pull request #1 from edgett/sortable-js-case-issue-fix
edgett Mar 18, 2026
ce177aa
Merge pull request #250 from edgett/main
mathewtaylor Mar 19, 2026
942e5ce
Merge pull request #258 from blazorblueprintui/develop
mathewtaylor Mar 20, 2026
2592475
Merge pull request #261 from blazorblueprintui/develop
mathewtaylor Mar 21, 2026
da1cbb2
Merge pull request #264 from blazorblueprintui/develop
mathewtaylor Mar 22, 2026
9d72c0a
Merge pull request #268 from blazorblueprintui/develop
mathewtaylor Mar 26, 2026
0db1abf
Merge pull request #270 from blazorblueprintui/develop
mathewtaylor Mar 26, 2026
2e2c36b
Merge pull request #272 from blazorblueprintui/develop
mathewtaylor Mar 26, 2026
711a60f
Merge pull request #274 from blazorblueprintui/develop
mathewtaylor Mar 27, 2026
507d924
Merge pull request #277 from blazorblueprintui/develop
mathewtaylor Mar 27, 2026
05719bf
Merge pull request #287 from blazorblueprintui/develop
mathewtaylor Apr 1, 2026
07afb5c
Merge pull request #289 from blazorblueprintui/develop
mathewtaylor Apr 3, 2026
9982377
Update README with out of box working missing code
mxmissile Apr 3, 2026
790100e
Merge pull request #290 from mxmissile/patch-1
mathewtaylor Apr 6, 2026
a716ef6
Merge pull request #292 from blazorblueprintui/develop
mathewtaylor Apr 7, 2026
238a56d
Merge pull request #298 from blazorblueprintui/develop
mathewtaylor Apr 10, 2026
754f952
fix: update source property from Avatar component
alanlecart Apr 16, 2026
27e3f97
Merge pull request #302 from alanlecart/main
mathewtaylor Apr 18, 2026
0208307
Added ItemsProvider to BbDataView. Added example in demo page.
djb-fnz Apr 21, 2026
72c69f2
Merge pull request #313 from blazorblueprintui/develop
mathewtaylor May 3, 2026
9c992af
Merge branch 'main' into feature/data-view-items-provider
djb-fnz May 7, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ builder.Services.AddBlazorBlueprintComponents();

```razor
@using BlazorBlueprint.Components
@using BlazorBlueprint.Primitives.Services
```

**3. Add CSS** to your `App.razor` `<head>`:
Expand Down
4 changes: 2 additions & 2 deletions V3-MIGRATION-GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -1368,11 +1368,11 @@ A new component that renders child `Avatar` components with overlapping negative
```razor
<BbAvatarGroup>
<BbAvatar>
<BbAvatarImage Src="/user1.jpg" Alt="User 1" />
<BbAvatarImage Source="/user1.jpg" Alt="User 1" />
<BbAvatarFallback>U1</BbAvatarFallback>
</BbAvatar>
<BbAvatar>
<BbAvatarImage Src="/user2.jpg" Alt="User 2" />
<BbAvatarImage Source="/user2.jpg" Alt="User 2" />
<BbAvatarFallback>U2</BbAvatarFallback>
</BbAvatar>
</BbAvatarGroup>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
@using BlazorBlueprint.Primitives.DataView
@using BlazorBlueprint.Primitives.Table

<BbDataView TItem="Person" ItemsProvider="@LoadPeopleAsync" InitialPageSize="5">
<ListTemplate Context="person">
<BbCard Class="flex items-center gap-4 p-4 shadow-none hover:bg-accent/50 transition-colors">
<BbAvatar>
<BbAvatarFallback>@GetInitials(person.Name)</BbAvatarFallback>
</BbAvatar>
<div class="flex-1 min-w-0">
<p class="text-sm font-semibold truncate">@person.Name</p>
<BbTypographyMuted Class="truncate">@person.Email</BbTypographyMuted>
<BbTypographyMuted Class="text-xs">@person.Department · @person.Role</BbTypographyMuted>
</div>
<BbBadge Variant="@(person.Status == "Active" ? BadgeVariant.Default : BadgeVariant.Outline)">
@person.Status
</BbBadge>
</BbCard>
</ListTemplate>
<Fields>
<BbDataViewColumn TItem="Person" TValue="string" Property="@(p => p.Name)" Header="Name" Sortable Filterable />
<BbDataViewColumn TItem="Person" TValue="string" Property="@(p => p.Email)" Header="Email" Filterable />
<BbDataViewColumn TItem="Person" TValue="string" Property="@(p => p.Department)" Header="Department" Sortable Filterable />
<BbDataViewColumn TItem="Person" TValue="string" Property="@(p => p.Role)" Header="Role" Sortable Filterable />
</Fields>
</BbDataView>

@code {
private List<Person> asyncPeople = MockDataService.GeneratePersons(200);

private async ValueTask<DataViewResult<Person>> LoadPeopleAsync(DataViewRequest request)
{
// Simulate latency from a real server
await Task.Delay(300, request.CancellationToken);

IEnumerable<Person> query = asyncPeople;

// Apply search
if (!string.IsNullOrWhiteSpace(request.SearchText))
{
var search = request.SearchText;
query = query.Where(p =>
p.Name.Contains(search, StringComparison.OrdinalIgnoreCase) ||
p.Email.Contains(search, StringComparison.OrdinalIgnoreCase) ||
p.Department.Contains(search, StringComparison.OrdinalIgnoreCase) ||
p.Role.Contains(search, StringComparison.OrdinalIgnoreCase));
}

// Apply sorting
if (!string.IsNullOrEmpty(request.SortField) && request.SortDirection != SortDirection.None)
{
var asc = request.SortDirection == SortDirection.Ascending;
query = request.SortField switch
{
"name" => asc ? query.OrderBy(p => p.Name) : query.OrderByDescending(p => p.Name),
"department" => asc ? query.OrderBy(p => p.Department) : query.OrderByDescending(p => p.Department),
"role" => asc ? query.OrderBy(p => p.Role) : query.OrderByDescending(p => p.Role),
_ => query
};
}

// Apply paging after filtering/sorting so TotalItemCount reflects the filtered set
var materialized = query.ToList();
var items = materialized
.Skip(request.StartIndex)
.Take(request.Count ?? materialized.Count)
.ToList();

return new DataViewResult<Person>
{
Items = items,
TotalItemCount = materialized.Count
};
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
@page "/components/dataview"
@using BlazorBlueprint.Demo.Services
@using BlazorBlueprint.Primitives.DataView
@using BlazorBlueprint.Primitives.Table
@inject MockDataService MockDataService

<PageTitle>Data View Component - Blazor Blueprint</PageTitle>
Expand Down Expand Up @@ -431,6 +433,45 @@
<CodeBlock Source="Components/DataView/infinite-scroll.txt" />
</div>

<!-- ── Async Data Loading (ItemsProvider) ─────────────────────────────── -->
<div class="space-y-4">
<div class="space-y-2">
<h2 class="text-2xl font-semibold">Async Data Loading</h2>
<p class="text-sm text-muted-foreground">
Use <code class="bg-muted px-1 py-0.5 rounded">ItemsProvider</code> for server-side data fetching.
The view passes the current pagination, sort, and search state and the provider returns the
matching page plus a total count. The built-in loading overlay is shown automatically while
the request is in flight, and rapid search keystrokes cancel the previous request. Provide
either <code class="bg-muted px-1 py-0.5 rounded">Data</code> or
<code class="bg-muted px-1 py-0.5 rounded">ItemsProvider</code>, not both.
</p>
</div>
<BbDataView TItem="Person" ItemsProvider="@LoadPeopleAsync" InitialPageSize="5">
<ListTemplate Context="person">
<BbCard Class="flex items-center gap-4 p-4 shadow-none hover:bg-accent/50 transition-colors">
<BbAvatar>
<BbAvatarFallback>@GetInitials(person.Name)</BbAvatarFallback>
</BbAvatar>
<div class="flex-1 min-w-0">
<p class="text-sm font-semibold truncate">@person.Name</p>
<BbTypographyMuted Class="truncate">@person.Email</BbTypographyMuted>
<BbTypographyMuted Class="text-xs">@person.Department · @person.Role</BbTypographyMuted>
</div>
<BbBadge Variant="@(person.Status == "Active" ? BadgeVariant.Default : BadgeVariant.Outline)">
@person.Status
</BbBadge>
</BbCard>
</ListTemplate>
<Fields>
<BbDataViewColumn TItem="Person" TValue="string" Property="@(p => p.Name)" Header="Name" Sortable Filterable />
<BbDataViewColumn TItem="Person" TValue="string" Property="@(p => p.Email)" Header="Email" Filterable />
<BbDataViewColumn TItem="Person" TValue="string" Property="@(p => p.Department)" Header="Department" Sortable Filterable />
<BbDataViewColumn TItem="Person" TValue="string" Property="@(p => p.Role)" Header="Role" Sortable Filterable />
</Fields>
</BbDataView>
<CodeBlock Source="Components/DataView/items-provider.txt" />
</div>

<!-- ── Loading State ──────────────────────────────────────────────────── -->
<div class="space-y-4">
<div class="space-y-2">
Expand Down Expand Up @@ -525,8 +566,13 @@
</div>
<div class="space-y-4">
<ApiReference ComponentName="DataView">
<ApiParameter Name="Data" Type="IEnumerable&lt;TItem&gt;">
The data source for the view.
<ApiParameter Name="Data" Type="IEnumerable&lt;TItem&gt;?">
In-memory data source for the view. Mutually exclusive with ItemsProvider.
</ApiParameter>
<ApiParameter Name="ItemsProvider" Type="DataViewItemsProvider&lt;TItem&gt;?">
Async delegate for server-side data fetching. Receives the current pagination,
sort, search, and a cancellation token and returns the matching page plus a total
count. Mutually exclusive with Data.
</ApiParameter>
<ApiParameter Name="ListTemplate" Type="RenderFragment&lt;TItem&gt;?" Default="null">
Template used to render each item in list layout. When set without GridTemplate the
Expand Down Expand Up @@ -653,12 +699,14 @@
private List<Person> people = new();
private List<Person> emptyPeople = new();
private List<Product> products = new();
private List<Person> asyncPeople = new();
private bool isLoading;

protected override void OnInitialized()
{
people = MockDataService.GeneratePersons(500);
products = MockDataService.GenerateProducts(60);
asyncPeople = MockDataService.GeneratePersons(200);
}

private static string GetInitials(string name)
Expand All @@ -668,4 +716,43 @@
? $"{parts[0][0]}{parts[^1][0]}"
: name.Length > 0 ? name[0].ToString() : "?";
}

private async ValueTask<DataViewResult<Person>> LoadPeopleAsync(DataViewRequest request)
{
IEnumerable<Person> query = asyncPeople;

if (!string.IsNullOrWhiteSpace(request.SearchText))
{
var search = request.SearchText;
query = query.Where(p =>
p.Name.Contains(search, StringComparison.OrdinalIgnoreCase) ||
p.Email.Contains(search, StringComparison.OrdinalIgnoreCase) ||
p.Department.Contains(search, StringComparison.OrdinalIgnoreCase) ||
p.Role.Contains(search, StringComparison.OrdinalIgnoreCase));
}

if (!string.IsNullOrEmpty(request.SortField) && request.SortDirection != SortDirection.None)
{
var asc = request.SortDirection == SortDirection.Ascending;
query = request.SortField switch
{
"name" => asc ? query.OrderBy(p => p.Name) : query.OrderByDescending(p => p.Name),
"department" => asc ? query.OrderBy(p => p.Department) : query.OrderByDescending(p => p.Department),
"role" => asc ? query.OrderBy(p => p.Role) : query.OrderByDescending(p => p.Role),
_ => query
};
}

var materialized = query.ToList();
var items = materialized
.Skip(request.StartIndex)
.Take(request.Count ?? materialized.Count)
.ToList();

return new DataViewResult<Person>
{
Items = items,
TotalItemCount = materialized.Count
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@
}

@* ── Content ─────────────────────────────────────────────────── *@
@if (IsLoading)
@if (IsLoading || _isProviderLoading)
{
@if (LoadingTemplate != null)
{
Expand Down Expand Up @@ -191,7 +191,7 @@
}

@* ── Pagination – hidden in infinite scroll mode ─────────────── *@
@if (ShowPagination && !EnableInfiniteScroll && !IsLoading && _filteredSortedData.Count > 0)
@if (ShowPagination && !EnableInfiniteScroll && !IsLoading && !_isProviderLoading && _filteredSortedData.Count > 0)
{
<BbPagination State="@_paginationState"
PageSizes="@PageSizes"
Expand Down
Loading