diff --git a/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/CustomCSS.css b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/CustomCSS.css
new file mode 100644
index 0000000000..09c44e83e8
--- /dev/null
+++ b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/CustomCSS.css
@@ -0,0 +1,29 @@
+#datagrid-container {
+ height: calc(100% - 3rem);
+ min-height: 8rem;
+ overflow-x: auto;
+ overflow-y: hidden;
+}
+
+article {
+ min-height: 32rem;
+ max-height: 80dvh;
+}
+
+.demo-section-content {
+ height: calc(100% - 10rem);
+}
+
+.demo-section-example {
+ min-height: 135px !important;
+ height: 100%;
+}
+
+fluent-tabs {
+ height: 100%;
+}
+
+#tab-example-autoitemsperpage-panel {
+ height: 100% !important;
+ max-height: calc(100% - 2rem) !important;
+}
diff --git a/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridAutoFit.razor b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridAutoFit.razor
new file mode 100644
index 0000000000..2ff7d08f3f
--- /dev/null
+++ b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridAutoFit.razor
@@ -0,0 +1,57 @@
+@using System.ComponentModel.DataAnnotations
+
+
+
+ With auto-fit
+
+
+
+
+
+
+
+
+ Without auto-fit
+
+
+
+
+
+
+
+
+
+@code {
+ public class Person
+ {
+ public Person(int personId, string name, DateOnly birthDate, string bio)
+ {
+ PersonId = personId;
+ Name = name;
+ BirthDate = birthDate;
+ Bio = bio;
+ }
+
+ [Display(Name = "Identity")]
+ public int PersonId { get; set; }
+
+ [Display(Name = "Name")]
+ public string Name { get; set; }
+
+ [Display(Name = "Birth date")]
+ public DateOnly BirthDate { get; set; }
+
+ [Display(Name = "Biography")]
+ public string Bio { get; set; }
+ }
+
+ IQueryable people = new[]
+ {
+ new Person(10895, "Jean Martin", new DateOnly(1825, 11, 29), "Born on November 29, 1825, in Paris, France, is renowned as the founder of modern neurology."),
+ new Person(10944, "António Langa", new DateOnly(1972, 5, 15), "Born on May 15, 1972, in Columbia, South Carolina, is a distinguished former professional basketball player."),
+ new Person(11203, "Julie Smith", new DateOnly(1944, 11, 25), "Born on November 25, 1944, in Annapolis, Maryland, is an acclaimed American mystery writer celebrated for her rich storytelling."),
+ new Person(11205, "Nur Sari", new DateOnly(1922, 4, 27), "Nur Sari is a fictional character known for her extraordinary contributions to the field of renewable energy."),
+ new Person(11898, "Jose Hernandez", new DateOnly(1962, 8, 7), "Born on August 7, 1962, in French Camp, California, is a Mexican-American engineer."),
+ new Person(12130, "Kenji Sato", new DateOnly(2004, 1, 9), ""),
+ }.AsQueryable();
+}
diff --git a/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridAutoItemsPerPage.razor b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridAutoItemsPerPage.razor
new file mode 100644
index 0000000000..0c143485d8
--- /dev/null
+++ b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridAutoItemsPerPage.razor
@@ -0,0 +1,31 @@
+@using static FluentUI.Demo.SampleData.Olympics2024
+
+@inject IJSRuntime JSRuntime
+
+
+
+
+
+@code {
+
+ DataGridRowSize rowSize = DataGridRowSize.Small;
+ IQueryable? items;
+ PaginationState pagination = new PaginationState { ItemsPerPage = 10 };
+
+ protected override void OnInitialized() =>
+ items = SampleData.Olympics2024.Countries.AsQueryable();
+
+}
+
diff --git a/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridColumnHeaderGeneration.razor b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridColumnHeaderGeneration.razor
new file mode 100644
index 0000000000..827db84e4a
--- /dev/null
+++ b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridColumnHeaderGeneration.razor
@@ -0,0 +1,37 @@
+@using System.ComponentModel.DataAnnotations
+
+
+
+
+
+
+@code {
+ public class Person
+ {
+ public Person(int personId, string name, DateOnly birthDate)
+ {
+ PersonId = personId;
+ Name = name;
+ BirthDate = birthDate;
+ }
+
+ [Display(Name="Identity")]
+ public int PersonId { get; set; }
+
+ [Display(Name = "Full _name")]
+ public string Name { get; set; }
+
+ [Display(Name = "Birth date")]
+ public DateOnly BirthDate { get; set; }
+ }
+
+ IQueryable people = new[]
+ {
+ new Person(10895, "Jean Martin", new DateOnly(1985, 3, 16)),
+ new Person(10944, "António Langa", new DateOnly(1991, 12, 1)),
+ new Person(11203, "Julie Smith", new DateOnly(1958, 10, 10)),
+ new Person(11205, "Nur Sari", new DateOnly(1922, 4, 27)),
+ new Person(11898, "Jose Hernandez", new DateOnly(2011, 5, 3)),
+ new Person(12130, "Kenji Sato", new DateOnly(2004, 1, 9)),
+ }.AsQueryable();
+}
\ No newline at end of file
diff --git a/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridColumnKeyGridSort.razor b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridColumnKeyGridSort.razor
new file mode 100644
index 0000000000..19609c8d41
--- /dev/null
+++ b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridColumnKeyGridSort.razor
@@ -0,0 +1,50 @@
+
+
+ @context.Properties["firstname"]
+
+
+
+ @context.Properties["lastname"]
+
+
+
+@code {
+ private ColumnKeyGridSort _firstNameSort = new ColumnKeyGridSort(
+ "firstname",
+ (queryable, sortAscending) =>
+ {
+ if (sortAscending)
+ {
+ return queryable.OrderBy(x => x.Properties["firstname"]);
+ }
+ else
+ {
+ return queryable.OrderByDescending(x => x.Properties["firstname"]);
+ }
+ }
+ );
+
+ private ColumnKeyGridSort _lastNameSort = new ColumnKeyGridSort(
+ "lastname",
+ (queryable, sortAscending) =>
+ {
+ if (sortAscending)
+ {
+ return queryable.OrderBy(x => x.Properties["lastname"]);
+ }
+ else
+ {
+ return queryable.OrderByDescending(x => x.Properties["lastname"]);
+ }
+ }
+ );
+
+ private static readonly IQueryable _gridData = new GridRow[] {
+ new(new Dictionary{ { "firstname", "Tom" }, { "lastname", "Cruise" } }),
+ new(new Dictionary{ { "firstname", "Dolly" }, { "lastname", "Parton" } }),
+ new(new Dictionary{ { "firstname", "Nicole" }, { "lastname", "Kidmon" } }),
+ new(new Dictionary{ { "firstname", "James" }, { "lastname", "Bond" } }),
+ }.AsQueryable();
+
+ public record GridRow(Dictionary Properties);
+}
diff --git a/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridCustomComparer.razor b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridCustomComparer.razor
new file mode 100644
index 0000000000..f6ef843bc8
--- /dev/null
+++ b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridCustomComparer.razor
@@ -0,0 +1,147 @@
+@using static FluentUI.Demo.SampleData.Olympics2024
+
+@if (altcolor)
+{
+
+
+}
+
+
+
+
+ Discrete
+ Exact
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ @*
+
+
+
+
+
+
+ @(context.Name)
+
+ *@
+
+
+
+
+
+
+
+
+
+ There are @(pagination.TotalItemCount ?? 0) rows
+
+
+ This is page @(pagination.CurrentPageIndex + 1) out of a total of @(pagination.LastPageIndex + 1) pages
+
+
+
+
+
+
+@code {
+ bool altcolor = false;
+ IQueryable? items;
+ PaginationState pagination = new PaginationState { ItemsPerPage = 10 };
+ string nameFilter = string.Empty;
+ DataGridResizeType? _resizeType = null;
+ bool _showActionsMenu;
+ bool _useMenuService = true;
+ bool _resizeColumnOnAllRows = true;
+
+ GridSort rankSort = GridSort
+ .ByDescending(x => x.Medals.Gold)
+ .ThenDescending(x => x.Medals.Silver)
+ .ThenDescending(x => x.Medals.Bronze);
+
+ // Uncomment line below when using the TemplateColumn example for the country _name
+ //GridSort nameSort = GridSort.ByAscending(x => x.Name, StringLengthComparer.Instance);
+
+
+ IQueryable? FilteredItems => items?.Where(x => x.Name.Contains(nameFilter, StringComparison.CurrentCultureIgnoreCase));
+
+ protected override void OnInitialized()
+ {
+ items = SampleData.Olympics2024.Countries.AsQueryable();
+ }
+
+ private void HandleCountryFilter(ChangeEventArgs args)
+ {
+ if (args.Value is string value)
+ {
+ nameFilter = value;
+ }
+ }
+
+ private void HandleClear()
+ {
+ if (string.IsNullOrWhiteSpace(nameFilter))
+ {
+ nameFilter = string.Empty;
+ }
+ }
+
+ private void HandleRowFocus(FluentDataGridRow row)
+ {
+ Console.WriteLine($"[Custom comparer] Row focused: {row.Item?.Name}");
+ }
+
+ public class StringLengthComparer : IComparer
+ {
+ public static readonly StringLengthComparer Instance = new StringLengthComparer();
+
+ public int Compare(string? x, string? y)
+ {
+ if (x is null)
+ {
+ return y is null ? 0 : -1;
+ }
+
+ if (y is null)
+ {
+ return 1;
+ }
+
+ return x.Length.CompareTo(y.Length);
+ }
+ }
+}
diff --git a/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridCustomComparer.razor.css b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridCustomComparer.razor.css
new file mode 100644
index 0000000000..ae1eb8b29e
--- /dev/null
+++ b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridCustomComparer.razor.css
@@ -0,0 +1,6 @@
+/* Ensure all the flags are the same size, and centered */
+.flag {
+ height: 1rem;
+ margin: auto;
+ border: 1px solid var(--neutral-layer-3);
+}
\ No newline at end of file
diff --git a/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridCustomGridSort.razor b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridCustomGridSort.razor
new file mode 100644
index 0000000000..0ac0b567df
--- /dev/null
+++ b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridCustomGridSort.razor
@@ -0,0 +1,41 @@
+
+
+
+
+ @context.Group
+
+
+
+
+Keep numbers always sorted ascending inside the group when sorting by group
+
+
+
+
+
+
+
+
+
+@code {
+ GridSort groupRank = GridSort
+ .ByAscending(x => x.Group)
+ .ThenAscending(x => x.Number);
+
+ GridSort groupRankNumberAlwaysAscending = GridSort
+ .ByAscending(x => x.Group)
+ .ThenAlwaysAscending(x => x.Number);
+
+ private static readonly IQueryable _gridData = new GridRow[] {
+ new(2, "B"),
+ new(1, "A"),
+ new(4, "B"),
+ new(3, "A")
+ }.AsQueryable();
+
+ public class GridRow(int number, string group)
+ {
+ public int Number { get; } = number;
+ public string Group { get; } = group;
+ }
+}
diff --git a/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridCustomHeader.razor b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridCustomHeader.razor
new file mode 100644
index 0000000000..40319a05b1
--- /dev/null
+++ b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridCustomHeader.razor
@@ -0,0 +1,51 @@
+@using Microsoft.FluentUI.AspNetCore.Components
+
+
+
+
+
+
+ @context.Title
+
+
+
+
+
+
+
+
+ Birth date
+
+
+
+
+
+@code {
+ public class Person
+ {
+ public Person(int personId, string name, DateOnly birthDate)
+ {
+ PersonId = personId;
+ Name = name;
+ BirthDate = birthDate;
+ }
+
+ public int PersonId { get; set; }
+
+ public string Name { get; set; }
+
+ public DateOnly BirthDate { get; set; }
+ }
+
+ IQueryable people = new[]
+ {
+ new Person(10895, "Jean Martin", new DateOnly(1985, 3, 16)),
+ new Person(10944, "António Langa", new DateOnly(1991, 12, 1)),
+ new Person(11203, "Julie Smith", new DateOnly(1958, 10, 10)),
+ new Person(11205, "Nur Sari", new DateOnly(1922, 4, 27)),
+ new Person(11898, "Jose Hernandez", new DateOnly(2011, 5, 3)),
+ new Person(12130, "Kenji Sato", new DateOnly(2004, 1, 9)),
+ }.AsQueryable();
+}
diff --git a/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridCustomPaging.razor b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridCustomPaging.razor
new file mode 100644
index 0000000000..87f53dc909
--- /dev/null
+++ b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridCustomPaging.razor
@@ -0,0 +1,52 @@
+@using static FluentUI.Demo.SampleData.Olympics2024
+
+
+
+
+ Page:
+ @if (pagination.TotalItemCount.HasValue)
+ {
+ for (var pageIndex = 0; pageIndex <= pagination.LastPageIndex; pageIndex++)
+ {
+ var capturedIndex = pageIndex;
+ GoToPageAsync(capturedIndex))"
+ Appearance="@PageButtonAppearance(capturedIndex)"
+ aria-current="@AriaCurrentValue(capturedIndex)"
+ aria-label="@AriaLabel(capturedIndex + 1)">
+ @(capturedIndex + 1)
+
+ }
+ }
+
+
+@code {
+ PaginationState pagination = new PaginationState { ItemsPerPage = 10 };
+ IQueryable? items;
+
+ protected override void OnInitialized()
+ {
+ items = SampleData.Olympics2024.Countries.AsQueryable();
+ pagination.TotalItemCountChanged += (sender, eventArgs) => StateHasChanged();
+ }
+
+ private async Task GoToPageAsync(int pageIndex)
+ {
+ await pagination.SetCurrentPageIndexAsync(pageIndex);
+ }
+
+ private ButtonAppearance PageButtonAppearance(int pageIndex)
+ => pagination.CurrentPageIndex == pageIndex ? ButtonAppearance.Primary : ButtonAppearance.Default;
+
+ private string? AriaCurrentValue(int pageIndex)
+ => pagination.CurrentPageIndex == pageIndex ? "page" : null;
+
+ private string AriaLabel(int pageIndex)
+ => $"Go to page {pageIndex}";
+}
diff --git a/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridCustomPaging.razor.css b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridCustomPaging.razor.css
new file mode 100644
index 0000000000..05342039fa
--- /dev/null
+++ b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridCustomPaging.razor.css
@@ -0,0 +1,36 @@
+/* Fix height and enable scrolling */
+.grid {
+ overflow-y: auto;
+}
+
+
+/* Style the custom page links*/
+.page-buttons {
+ margin: 1rem 0;
+ align-items: center;
+}
+
+ .page-buttons button {
+ background: #d6d7d8;
+ color: black;
+ padding: 0.25rem 0.75rem;
+ border-radius: 0.4rem;
+ transition: transform 0.3s ease-out;
+ margin: 0.25rem;
+ }
+
+ .page-buttons button:active {
+ background: #a7c1ff !important;
+ color: white;
+ transform: scale(0.95) translateY(-0.15rem);
+ transition-duration: 0.05s;
+ }
+
+ .page-buttons button:hover:not(.current) {
+ background: #c0c9dc;
+ }
+
+ .page-buttons button.current {
+ background: #3771f4;
+ color: white;
+ }
diff --git a/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridDynamicColumns.razor b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridDynamicColumns.razor
new file mode 100644
index 0000000000..735e695a7b
--- /dev/null
+++ b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridDynamicColumns.razor
@@ -0,0 +1,32 @@
+@using static FluentUI.Demo.SampleData.People
+
+
+ Show:
+ Name
+ Birth date
+
+
+
+
+
+ @if (showName)
+ {
+
+ @context.LastName, @context.FirstName
+
+ }
+ @if (showBirthDate)
+ {
+
+ }
+
+
+
+@code {
+ bool showName = true;
+ bool showBirthDate = false;
+}
diff --git a/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridGettingStarted.razor b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridGettingStarted.razor
new file mode 100644
index 0000000000..e39f649cea
--- /dev/null
+++ b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridGettingStarted.razor
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
+
+
+@code {
+ PaginationState pagination = new PaginationState() { ItemsPerPage = 2 };
+
+ record Person(int PersonId, string Name, DateOnly BirthDate);
+
+ IQueryable people = new[]
+ {
+ new Person(10895, "Jean Martin", new DateOnly(1985, 3, 16)),
+ new Person(10944, "António Langa", new DateOnly(1991, 12, 1)),
+ new Person(11203, "Julie Smith", new DateOnly(1958, 10, 10)),
+ new Person(11205, "Nur Sari", new DateOnly(1922, 4, 27)),
+ new Person(11898, "Jose Hernandez", new DateOnly(2011, 5, 3)),
+ new Person(12130, "Kenji Sato", new DateOnly(2004, 1, 9)),
+ }.AsQueryable();
+
+ private RenderFragment template = @;
+}
diff --git a/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridManual.razor b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridManual.razor
new file mode 100644
index 0000000000..d5c300b952
--- /dev/null
+++ b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridManual.razor
@@ -0,0 +1,19 @@
+
+
+ Column 1
+ Column 2
+
+
+ 1.1
+ 1.2
+
+
+ 2.1
+ 2.2
+
+
diff --git a/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridMultilineText.razor b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridMultilineText.razor
new file mode 100644
index 0000000000..bf68608e07
--- /dev/null
+++ b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridMultilineText.razor
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+@code {
+ record Person(int PersonId, string Name, string Description);
+
+ IQueryable people = new[]
+ {
+ new Person(10895, "Jean Martin", "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."),
+ new Person(10944, "António Langa", "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."),
+ new Person(11203, "Julie Smith","Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."),
+ }.AsQueryable();
+}
diff --git a/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridNotVirtualizedLoadingAndEmpty.razor b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridNotVirtualizedLoadingAndEmpty.razor
new file mode 100644
index 0000000000..cdb34f370d
--- /dev/null
+++ b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridNotVirtualizedLoadingAndEmpty.razor
@@ -0,0 +1,76 @@
+
+
+
+
+
+
+
+
+
+
+
+ Nothing to see here. Carry on!
+
+
+
+
+
+
+
+Simulate data loading
+
+
+@code {
+ FluentDataGrid? grid;
+ FluentSwitch? _clearToggle;
+
+ bool _clearItems = false;
+
+ public record SampleGridData(string Item1, string Item2, string Item3, string Item4);
+ IQueryable? items = Enumerable.Empty().AsQueryable();
+
+ private IQueryable GenerateSampleGridData(int size)
+ {
+ SampleGridData[] data = new SampleGridData[size];
+
+ for (int i = 0; i < size; i++)
+ {
+ data[i] = new SampleGridData($"This {i}-1", $"is {i}-2", $"some {i}-3", $"data {i}-4");
+ }
+ return data.AsQueryable();
+ }
+
+ protected override void OnInitialized()
+ {
+ items = GenerateSampleGridData(100);
+ }
+
+ private async Task SimulateDataLoading()
+ {
+ _clearItems = false;
+
+ grid?.SetLoadingState(true);
+ items = null;
+
+ await Task.Delay(1500);
+
+ items = GenerateSampleGridData(100);
+ grid?.SetLoadingState(false);
+ }
+
+ private void ToggleItems()
+ {
+ if (_clearItems)
+ {
+ items = null;
+ }
+ else
+ {
+ items = GenerateSampleGridData(100);
+ }
+ }
+}
diff --git a/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridRemoteData.razor b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridRemoteData.razor
new file mode 100644
index 0000000000..c507ecb3a9
--- /dev/null
+++ b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridRemoteData.razor
@@ -0,0 +1,57 @@
+@using FluentUI.Demo.SampleData
+
+@inject HttpClient Http
+@inject NavigationManager NavManager
+
+
+
+Total: @numResults results found
+
+@code {
+ GridItemsProvider foodRecallProvider = default!;
+ int? numResults;
+
+ protected override async Task OnInitializedAsync()
+ {
+ // Define the GridRowsDataProvider. Its job is to convert QuickGrid's GridRowsDataProviderRequest into a query against
+ // an arbitrary data source. In this example, we need to translate query parameters into the particular URL format
+ // supported by the external JSON API. It's only possible to perform whatever sorting/filtering/etc is supported
+ // by the external API.
+ foodRecallProvider = async req =>
+ {
+ var url = NavManager.GetUriWithQueryParameters("https://api.fda.gov/food/enforcement.json", new Dictionary
+ {
+ { "skip", req.StartIndex },
+ { "limit", req.Count },
+ });
+
+ var response = await Http.GetFromJsonAsync(url, req.CancellationToken);
+
+
+ return GridItemsProviderResult.From(
+ items: response!.Results,
+ totalItemCount: response!.Meta.Results.Total);
+ };
+
+ // Display the number of results just for information. This is completely separate from the grid.
+ numResults = (await Http.GetFromJsonAsync("https://api.fda.gov/food/enforcement.json"))!.Meta.Results.Total;
+ }
+}
diff --git a/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridRemoteData2.razor b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridRemoteData2.razor
new file mode 100644
index 0000000000..d5da2ab45c
--- /dev/null
+++ b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridRemoteData2.razor
@@ -0,0 +1,103 @@
+@using FluentUI.Demo.SampleData
+
+@inject HttpClient Http
+@inject NavigationManager NavManager
+
+
+
+
+
+
+
+
+
+
+
+ Clear
+
+
+ Search
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+@code {
+
+ FluentDataGrid dataGrid = default!;
+ IQueryable foodRecallItems = default!;
+ bool loading = true;
+ PaginationState pagination = new PaginationState { ItemsPerPage = 10 };
+ string? _stateFilter = "NY";
+
+ protected async Task RefreshItemsAsync(GridItemsProviderRequest req)
+ {
+ loading = true;
+ await InvokeAsync(StateHasChanged);
+
+ var filters = new Dictionary
+ {
+ { "skip", req.StartIndex },
+ { "limit", req.Count },
+ };
+
+ if (!string.IsNullOrWhiteSpace(_stateFilter))
+ filters.Add("search", $"state:{_stateFilter}");
+
+ var s = req.GetSortByProperties().FirstOrDefault();
+ if (req.SortByColumn != null && !string.IsNullOrEmpty(s.PropertyName))
+ {
+ filters.Add("sort", s.PropertyName + (s.Direction == DataGridSortDirection.Ascending ? ":asc" : ":desc"));
+ }
+
+ var url = NavManager.GetUriWithQueryParameters("https://api.fda.gov/food/enforcement.json", filters);
+
+ var response = await Http.GetFromJsonAsync(url);
+
+ foodRecallItems = response!.Results.AsQueryable();
+ await pagination.SetTotalItemCountAsync(response!.Meta.Results.Total);
+
+ loading = false;
+ await InvokeAsync(StateHasChanged);
+
+ }
+
+ public void ClearFilters()
+ {
+ _stateFilter = null;
+ }
+
+ public async Task DataGridRefreshDataAsync()
+ {
+ await dataGrid.RefreshDataAsync(true);
+ }
+}
diff --git a/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridTableScrollbars.razor b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridTableScrollbars.razor
new file mode 100644
index 0000000000..0958218f1d
--- /dev/null
+++ b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridTableScrollbars.razor
@@ -0,0 +1,20 @@
+
+
+@code {
+ record Person(int PersonId, string Name, string Description);
+
+ IQueryable people = new[]
+ {
+ new Person(10895, "Jean Martin", "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."),
+ new Person(10944, "António Langa", "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."),
+ new Person(11203, "Julie Smith","Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."),
+ }.AsQueryable();
+}
diff --git a/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridTemplateColumns.razor b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridTemplateColumns.razor
new file mode 100644
index 0000000000..6f7f54e12b
--- /dev/null
+++ b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridTemplateColumns.razor
@@ -0,0 +1,26 @@
+@using static FluentUI.Demo.SampleData.People
+
+
+
+ @context.LastName, @context.FirstName
+
+
+ Bonus(context))">Regular
+ DoubleBonus(context))">Double
+
+
+
+
+@message
+
+@code {
+ string message = string.Empty;
+
+ GridSort sortByName = GridSort
+ .ByAscending(p => p.LastName)
+ .ThenAscending(p => p.FirstName);
+
+ void Bonus(Person p) => message = $"You want to give {p.FirstName} {p.LastName} a regular bonus";
+
+ void DoubleBonus(Person p) => message = $"You want to give {p.FirstName} {p.LastName} a double bonus";
+}
diff --git a/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridTypical.razor b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridTypical.razor
index d8032f99a6..02e77a470b 100644
--- a/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridTypical.razor
+++ b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridTypical.razor
@@ -21,7 +21,20 @@
-
+
+
+
+
+
@@ -109,11 +122,20 @@
private async Task HandleCloseFilterAsync(KeyboardEventArgs args)
{
+ if (grid is null)
+ {
+ return;
+ }
+
if (args.Key == "Escape")
{
+ if (string.IsNullOrEmpty(nameFilter))
+ {
+ await grid.CloseColumnOptionsAsync();
+ }
nameFilter = string.Empty;
}
- if (args.Key == "Enter" && grid is not null)
+ if (args.Key == "Enter")
{
await grid.CloseColumnOptionsAsync();
}
diff --git a/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridVirtualize.razor b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridVirtualize.razor
new file mode 100644
index 0000000000..e2f41ce173
--- /dev/null
+++ b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridVirtualize.razor
@@ -0,0 +1,83 @@
+
+
+
+
+
+
+
+
+
+ Nothing to see here. Carry on!
+
+
+
+ Loading...
+
+
+
+
+
+
+
+
+Simulate data loading
+
+@code {
+ FluentDataGrid? grid;
+ FluentSwitch? _clearToggle;
+
+ bool _clearItems = false;
+ public record SampleGridData(string Item1, string Item2, string Item3, string Item4);
+
+ IQueryable? items = Enumerable.Empty().AsQueryable();
+
+ private IQueryable GenerateSampleGridData(int size)
+ {
+ SampleGridData[] data = new SampleGridData[size];
+
+ for (int i = 0; i < size; i++)
+ {
+ data[i] = new SampleGridData($"value {i}-1", $"value {i}-2", $"value {i}-3", $"value {i}-4");
+ }
+ return data.AsQueryable();
+ }
+ protected override void OnInitialized()
+ {
+ items = GenerateSampleGridData(5000);
+ }
+
+ private void ToggleItems()
+ {
+ if (_clearItems)
+ {
+ items = null;
+ }
+ else
+ {
+ items = GenerateSampleGridData(5000);
+ }
+ }
+
+ private async Task SimulateDataLoading()
+ {
+ _clearItems = false;
+
+ items = null;
+ grid?.SetLoadingState(true);
+
+ await Task.Delay(1500);
+
+ items = GenerateSampleGridData(5000);
+ grid?.SetLoadingState(false);
+ }
+}
diff --git a/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/FluentDataGrid.md b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/FluentDataGrid.md
index e631693acd..6f9e8fc701 100644
--- a/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/FluentDataGrid.md
+++ b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/FluentDataGrid.md
@@ -5,7 +5,113 @@ route: /DataGrid
# DataGrid
-Overview page and examples to follow
+The DataGrid actually comprises several components and used to display tabular data. The `` component is the most important
+component and is the one normally put onto a page. Internally it uses the`` and `` components to build up
+the grid. Technically it is possible to use these components manually, but that is not the recommended way of working with the DataGrid.
+
+## Rendering
+The DataGrid uses standard HTML table elements for rendering the grid. It supports 2 different display modes through the `DisplayMode`
+parameter: `DataGridDisplayMode.Grid` (default) and `DataGridDisplayMode.Table`.
+- With the `Grid` mode, the `GridTableColumns` parameter can be
+used to specify column widths in fractions. It basically provides an HTML table element with a `display: grid;` style.
+- With the `Table` mode, it uses standard HTML table elements and rendering. Column widths are best specified through the `Width` parameter on the columns.
+
+> [!NOTE]
+Specifically when using `Virtualize`, it is **highly recommended** to use the `Table` display mode as the `Grid` mode can exhibit odd scrolling behavior.
+
+
+## Accessibility
+
+You can use the Arrow keys to navigate through a DataGrid. When a header cell is focused and the column is sortable, you can use the
+Tab key to select the sort button. Pressing the Enter key will toggle the sorting direction. Pressing Shift + s
+removes the column sorting and restores the default/start situation with regards to sorting. *You cannot remove the default grid sorting with
+this key combination*.
+
+When a header cell is focused and the column allows setting options, you can use the Tab key to select the options button. Pressing
+the Enter key then will toggle the options popover. Pressing Esc closes the popover .
+
+When a grid allows resizing of the columns, you can use the + and - keys to resize the column the focused header belongs
+to. Incrementing/decrementing width is done in steps of 10 pixels at a time. You can reset to the original initial column widths by pressing
+Shift + r.
+
+When a row cell is focused, the grid contains a `SelectColumn` (with `SelectFromEntireRow` parameter set to `true` (default)), you can use the Enter key to select or unselect the current
+row.
+
+## Sorting
+
+The DataGrid supports sorting by clicking on the column headers (when the `HeaderCellAsButtonWithMenu` parameter is `false` (default)). The default sort direction is ascending. Clicking on the same column header
+again will toggle the sort direction. When `HeaderCellAsButtonWithMenu` is true, a menu will be used to get trigger the sorting action.
+
+A sort can be removed by right clicking (or by pressing Shift + r) on the header column (with exception of
+the default sort).
+
+## Row size
+
+The DataGrid offers a `RowSize` parameter which allows you to use different preset row heights. The value uses the `DataGridRowSize`
+enumeration for its type. When using `Virtualize`, the `ItemSize` value **must** still be used to indicate the row height.
+
+## Change strings used in the UI
+
+The DataGrid has a number of strings that are used in the UI. These can be changed by leveraging the built-in [localization](/localization) functionality.
+The following values can be localized:
+
+- DataGrid_OptionsMenu
+- DataGrid_ResizeDiscrete
+- DataGrid_ResizeExact
+- DataGrid_ResizeGrow
+- DataGrid_ResizeMenu
+- DataGrid_ResizeReset
+- DataGrid_ResizeShrink
+- DataGrid_ResizeSubmit
+- DataGrid_SelectColumn_AllChecked
+- DataGrid_SelectColumn_AllIndeterminate
+- DataGrid_SelectColumn_AllUnchecked
+- DataGrid_SelectColumn_RowChecked
+- DataGrid_SelectColumn_RowUnchecked
+- DataGrid_SortMenu
+- DataGrid_SortMenuAscending
+- DataGrid_SortMenuDescending
+
+
+
+## Using the DataGrid component with EF Core
+
+If you want to use the `FluentDataGrid` with data provided through EF Core, you need to install an additional package so the grid knows how to resolve queries asynchronously for efficiency.
+
+### Installation
+Install the package by running the command:
+
+```cshtml
+dotnet add package Microsoft.FluentUI.AspNetCore.Components.DataGrid.EntityFrameworkAdapterCopy
+```
+
+### Usage
+In your `Program.cs` file you need to add the following after the `builder.Services.AddFluentUIComponents();` line:
+
+```csharp
+builder.Services.AddDataGridEntityFrameworkAdapter();Copy
+```
+
+## Using the DataGrid component with OData
+
+*Added in 4.11.0*
+
+If you want to use the `FluentDataGrid` with data provided through OData, you need to install an additional package so the grid knows how to resolve queries asynchronously for efficiency.
+
+### Installation
+Install the package by running the command:
+
+```cshtml
+dotnet add package Microsoft.FluentUI.AspNetCore.Components.DataGrid.ODataAdapterCopy
+```
+
+### Usage
+In your `Program.cs` file you need to add the following after the `builder.Services.AddFluentUIComponents();` line:
+
+```csharp
+builder.Services.AddDataGridODataAdapter();
+```
+
## Typical usage
Here is an example of a data grid that uses in-memory data and enables features including pagination, sorting, filtering, column options, row highlighting and column resizing.
@@ -25,8 +131,34 @@ Pressing enter finishes the filter action by the current input to filter on and
The resize options UI is using a customized string for the label ('Width (+/- 10px)' instead of the normal 'Column width'). This is done through
the custom localizer which is registered in the Server project's `Program.cs` file.
-{{ DataGridVirtualization }}
+## Examples
+The following examples show how to use the DataGrid component in different scenarios:
+
+### Basics
+- [Getting started](/DataGrid/GettingStarted)
+- [Typical grid usage](/DataGrid/Typical)
+
+### Layout
+- [Loading and empty content](/DataGrid/LoadingAndEmptyContent)
+- [Auto fit columns](/DataGrid/AutoFit)
+- [Auto items per page](/DataGrid/AutoItemsPerPage)
+- [Custom paging](/DataGrid/CustomPaging)
+- [Multi line text in cells](/DataGrid/MultiLine)
+- [Table with scrollbars](/DataGrid/Scrollbars)
-{{ DataGridTypical }}
+### Sorting
+- [Sorting](/DataGrid/Sorting)
+- [Custom comparer for sorting](/DataGrid/CustomComparerSort)
+
+### Columns
+- [Single/Multi select](/DataGrid/SingleMultiSelect)
+- [Dynamic columns](/DataGrid/DynamicColumns)]
+- [Header generation](/DataGrid/HeaderGeneration)
+- [Template columns](/DataGrid/TemplateColumns)
+- [Template columns 2](/DataGrid/TemplateColumns2)
-{{ DataGridMultiSelect }}
+### Advanced
+- [Custom comparer](/DataGrid/CustomComparerSort)
+- [Virtualized grid](/DataGrid/Virtualize)
+- [Remote data](/DataGrid/RemoteData)
+- [Manual grid](/DataGrid/Manual)
diff --git a/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Pages/DataGridAutoFitPage.md b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Pages/DataGridAutoFitPage.md
new file mode 100644
index 0000000000..fd0c5a3afa
--- /dev/null
+++ b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Pages/DataGridAutoFitPage.md
@@ -0,0 +1,25 @@
+---
+title: Auto fit columns
+route: /DataGrid/AutoFit
+---
+
+# Auto fit columns
+The example and code below show what you need to add to one of your Blazor page components to implement auto-fit.
+
+
+The `AutoFit` parameter is used to automatically adjust the column widths to fit the content. It only runs on
+the first render and does not update when the content changes.
+
+
+The column widths are calculated with the process below:
+
+- Loop through the columns and find the biggest width of each cell of the column
+- Build the `GridTemplateColumns` string using the `fr` unit
+
+
+This does not work when `Virtualization` is turned on. The `GridTemplateColumns` parameter is ignored when `AutoFit` is set to `true`.
+
+This is untested in MAUI.
+
+
+{{ DataGridAutoFit }} }}
diff --git a/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Pages/DataGridAutoItemsPerPagePage.md b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Pages/DataGridAutoItemsPerPagePage.md
new file mode 100644
index 0000000000..7370822ac1
--- /dev/null
+++ b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Pages/DataGridAutoItemsPerPagePage.md
@@ -0,0 +1,54 @@
+---
+title: Auto items per page
+route: /DataGrid/AutoItemsPerPage
+---
+
+# Auto items per page
+
+The example and code below show what you need to get auto items per page functionality for the pagination of a datagrid.
+
+Resize the page vertically to see the number of rows being displayed per page adapt to the available height.
+
+The `AutoItemsPerPage` parameter must be set to true and obviously `Pagination` must be used as well for this to work.
+
+Also, the DataGrid **container** must have styling that makes it automatically adapt to the available height.
+
+An example of how that can be done for this demo site layout is shown in the `
+```
+
+{{ DataGridAutoItemsPerPage Files=Code:DataGridAutoItemsPerPage.razor;CustomCSS.css:CustomCSS.css}}
+
diff --git a/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Pages/DataGridColumnHeaderGenerationPage.md b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Pages/DataGridColumnHeaderGenerationPage.md
new file mode 100644
index 0000000000..d719ef9b4e
--- /dev/null
+++ b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Pages/DataGridColumnHeaderGenerationPage.md
@@ -0,0 +1,25 @@
+---
+title: Header generation
+route: /DataGrid/HeaderGeneration
+---
+
+
+# Header generation
+
+## Use a DisplayAttribute
+The DataGrid can generate column headers by using the `System.ComponentModel.DataAnnotations.DisplayAttribute` on properties
+shown in the grid.
+
+See the 'Razor' tab on how these attributes have been applied to the class properties.
+
+{{ DataGridColumnHeaderGeneration}}
+
+The DataGrid can display custom column header titles by setting the `HeaderCellTitleTemplate` property on columns
+shown in the grid.
+
+## Custom headers
+{{ DataGridCustomHeader} }
+
+See the 'Razor' tab on how these properties have been applied to the columns.
+
+{{ DataGridCustomHeader }}
diff --git a/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Pages/DataGridCustomComparerPage.md b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Pages/DataGridCustomComparerPage.md
new file mode 100644
index 0000000000..598b504551
--- /dev/null
+++ b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Pages/DataGridCustomComparerPage.md
@@ -0,0 +1,25 @@
+---
+title: Custom comparer for sorting
+route: /DataGrid/CustomComparerSort
+---
+
+# Custom comparer for sorting
+
+Here a custom comparer is being used to sort counties by the length of their name. The code has examples for both
+`PropertyColumn` and `TemplateColumn` implementations (see the Razor tab).
+
+For this example the code for the comparer is placed in the `DataGridCustomComparer.razor.cs` file but it
+could of course be placed in its own file.
+
+For the paginator, this example also shows how to use the `SummaryTemplate` and `PaginationTextTemplate` parameters.
+
+This example also shows using an `OnRowFocus` event callback to detect which row the cursor is over. By setting `ShowHover`
+to true, the current row will be highlighted. By default the system will use the designated hover color for this but you can specify an alternative
+color by setting the `--datagrid-hover-color` CSS variable. See the Razor tab for how this is done in this example.
+
+To show how the resize handle can be altered, when choosing to use the alternate hover color, the handle color is set to a different value.
+
+[!Note]: once a value has been selected for the Resize type, it cannot be set to null again. You need to refresh the page to start with a null value again.
+
+
+{{ DataGridCustomComparer Files=Code:DataGridCustomComparer.razor;CSS:DataGridCustomComparer.razor.css }} }}
diff --git a/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Pages/DataGridCustomGridSortPage.md b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Pages/DataGridCustomGridSortPage.md
new file mode 100644
index 0000000000..1724b58fae
--- /dev/null
+++ b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Pages/DataGridCustomGridSortPage.md
@@ -0,0 +1,22 @@
+---
+title: Custom sort
+route: /DataGrid/CustomSort
+---
+
+# Custom sort
+
+## Sort by Rank
+Here is an example that demonstrates some of the sort order specification that are available in the GridSort
class.
+
+{{ DataGridCustomGridSort }}
+
+## Column Key Sort
+In some instances, your dataset may not have a property on the class that can be used for sorting.
+In this instance, you can supply a ColumnKeyGridSort
that allows you to specify the name of your column and supply the logic for sorting.
+
+{{ DataGridColumnKeyGridSort } }}
+
+## Documentation
+
+{{ API Type=GridSort }}
+
diff --git a/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Pages/DataGridCustomPagingPage.md b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Pages/DataGridCustomPagingPage.md
new file mode 100644
index 0000000000..1ceef6fd00
--- /dev/null
+++ b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Pages/DataGridCustomPagingPage.md
@@ -0,0 +1,11 @@
+---
+title: Custom Paging
+route: /DataGrid/CustomPaging
+---
+
+# Custom Paging
+
+## Custom paging UI
+You can customize the appearance of `Paginator` by supplying a `SummaryTemplate`. If you want further customization, you can create your own alternative UI that works with `PaginationState`. Example:
+
+{{ DataGridCustomPaging Files=Code:DataGridCustomPaging.razor;CSS:DataGridCustomPaging.razor.css }}
diff --git a/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Pages/DataGridDynamicColumnsPage.md b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Pages/DataGridDynamicColumnsPage.md
new file mode 100644
index 0000000000..6a1fbfb39b
--- /dev/null
+++ b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Pages/DataGridDynamicColumnsPage.md
@@ -0,0 +1,12 @@
+---
+title: Dynamic columns
+route: /DataGrid/DynamicColumns
+---
+
+# Dynamic columns
+
+You can make columns appear conditionally using normal Razor logic such as `@if`. Example:
+
+Also, in this example the column's Width parameter is being set instead of specifying all widths for all columns in the `GridTemplateColumn` parameter.
+
+{{ DataGridDynamicColumns }}
diff --git a/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Pages/DataGridGettingStartedPage.md b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Pages/DataGridGettingStartedPage.md
new file mode 100644
index 0000000000..f2737cf090
--- /dev/null
+++ b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Pages/DataGridGettingStartedPage.md
@@ -0,0 +1,12 @@
+---
+title: Getting started
+route: /DataGrid/GettingStarted
+---
+
+# Getting started
+
+The example and code below show what you need to add to one of your Blazor page components to render a very simple grid (with sortable columns).
+
+It also shows how you can add pagination to your grid. In fact there are two paginators used here. One at the top and one at the bottom of the grid. They automatically stay synchronized. The top paginator does not show the total count summary.
+
+{{ DataGridGettingStarted }}
diff --git a/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Pages/DataGridManualPage.md b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Pages/DataGridManualPage.md
new file mode 100644
index 0000000000..aad30e1162
--- /dev/null
+++ b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Pages/DataGridManualPage.md
@@ -0,0 +1,10 @@
+---
+title: Manual grid
+route: /DataGrid/ManualDataGrid
+---
+
+# Manual grid
+
+Although it is possible to create a data grid declaratively, we advise to not use this way of working with a DataGrid. Things like setting headers and specifying sorting are much harder and sometimes not possible to achieve. Best result will be achieved when using `DisplayMode="DataGridDisplayMode.Table"`.
+
+{{ DataGridManual }}
diff --git a/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Pages/DataGridMultiSelectPage.md b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Pages/DataGridMultiSelectPage.md
new file mode 100644
index 0000000000..771d8a5e30
--- /dev/null
+++ b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Pages/DataGridMultiSelectPage.md
@@ -0,0 +1,52 @@
+---
+title: Multi select
+route: /DataGrid/MultiSelect
+---
+
+# Multi select
+
+
+## Multi select
+
+The same example, adding a `SelectColumn`, to allow multi-select rows.
+
+To utilize the **SelectColumn** feature in the Fluent DataGrid, there are two approaches available:
+
+**Automatic Management via `SelectedItems`**
+- Provide a list of data via the `Items` property.
+- Let the grid handle selected rows entirely through the `SelectedItems` property.
+
+**Manual Management via `Property` and `OnSelect`:**
+- Control how selected lines are saved manually.
+- Utilize the `Property`, `OnSelect`, and `SelectAll` attributes.
+
+This method offers more flexibility but requires additional configuration, making it particularly useful when using `Virtualize` or directly managing a custom `IsSelected` property.
+
+> By default the Fluent Design System recommends to only use the checkbox to indicate selected rows.
+> It is possible to change this behavior by using a CSS style like this to set a background on selected rows:
+>
+> ```css
+> .fluent-data-grid-row:has([row-selected]) > td {
+> background-color: var(--neutral-fill-stealth-hover)
+> }
+> ```
+
+{{ DataGridMultiSelect } }}
+
+
+
+Using this `SelectColumn`, you can customize the checkboxes by using `ChildContent` to define the contents of the selection for each row of the grid;
+or `SelectAllTemplate` to customize the header of this column.
+If you don't want the user to be able to interact (click and change) on the SelectAll header, you can set the `SelectAllDisabled="true"` attribute.
+
+
+Example:
+```razor
+
+ @(context.AllSelected == true ? "✅" : context.AllSelected == null ? "➖" : "⬜")
+
+
+ @(SelectedItems.Contains(context) ? "✅" : " ") @@* Using SelectedItems *@@
+ @(context.Selected ? "✅" : " ") @@* Using Property and OnSelect *@@
+
+```
diff --git a/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Pages/DataGridMultilineTextPage.md b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Pages/DataGridMultilineTextPage.md
new file mode 100644
index 0000000000..7c19d816cb
--- /dev/null
+++ b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Pages/DataGridMultilineTextPage.md
@@ -0,0 +1,10 @@
+---
+title: Multi line text
+route: /DataGrid/MultilineText
+---
+
+# Multi line text content
+
+Set the grid parameter `MultiLine` to true when you have cells in your data that will take up more than a single line.
+
+{{ DataGridMultilineText }}
diff --git a/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Pages/DataGridNotVirtualizedLoadingAndEmptyPage.md b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Pages/DataGridNotVirtualizedLoadingAndEmptyPage.md
new file mode 100644
index 0000000000..6ff18c05ab
--- /dev/null
+++ b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Pages/DataGridNotVirtualizedLoadingAndEmptyPage.md
@@ -0,0 +1,12 @@
+---
+title: Using LoadingContent and EmptyContent
+route: /DataGrid/NotVirtualizedLoadingAndEmpty
+---
+
+# Using LoadingContent and EmptyContent
+
+This example shows the usage of the templated EmptyContent
and LoadingContent
parameters.
+
+Use the switch and the button at the bottom to toggle between loading and empty content.
+
+{{ DataGridNotVirtualizedLoadingAndEmpty } }}
diff --git a/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Pages/DataGridRemoteDataPage.md b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Pages/DataGridRemoteDataPage.md
new file mode 100644
index 0000000000..e926dd495d
--- /dev/null
+++ b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Pages/DataGridRemoteDataPage.md
@@ -0,0 +1,41 @@
+---
+title: Remote data
+route: /DataGrid/RemoteData
+---
+
+# Remote data
+
+## Remote data
+
+If you're using Blazor WebAssembly, it's very common to fetch data from a JSON API on a server. If you want to
+fetch only the data that's needed for the current page/viewport and apply any sorting or filtering rules on the
+server, you can use the `ItemsProvider` parameter.
+
+You can also use `ItemsProvider` with Blazor Server if it needs to query an external endpoint, or in any
+other case where your requirements aren't covered by an `IQueryable`.
+
+To do this, supply a callback matching the `GridItemsProvider<TGridItem>` delegate type, where `TGridItem`
+is the type of data displayed in the grid. Your callback will be given a parameter of type `GridItemsProviderRequest<TGridItem>`
+which specifies the start index, maximum row count, and sort order of data to return. As well as returning the matching items, you need
+to return a `totalItemCount` so that paging or virtualization can work.
+
+Here is an example of connecting a grid to the public [OpenFDA Food Enforcement database](https://open.fda.gov/apis/food/enforcement/).
+
+This grid is using a 'sticky' header (i.e. the header is always visible when scrolling). The buttons in the last column disappear under the header when scrolling.
+In this example they don't really do anything more than writing a message in the console log'
+
+The second column has a custom `Style` parameter set and applied to it. The 4th column has its `Tooltip`
+parameter set to true. This will show the full content of the cell when hovering over it. See the 'Razor' tab for how these
+parameters have been applied.
+
+{{ DataGridRemoteData }}
+
+## Remote data with RefreshItems
+
+If the external endpoint controls filtering, paging and sorting you can use `Items` combined with `RefreshItems`.
+
+The method defined in `RefreshItems` will be called once, and only once, if there is a change in the pagination or ordering.
+
+Meanwhile, you can control the filtering with elements present on the page itself and force a call to `RefreshItems` with the force option in the RefreshDataAsync.
+
+{{ DataGridRemoteData2 }}
diff --git a/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Pages/DataGridTableScrollbarsPage.md b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Pages/DataGridTableScrollbarsPage.md
new file mode 100644
index 0000000000..9b5335b66e
--- /dev/null
+++ b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Pages/DataGridTableScrollbarsPage.md
@@ -0,0 +1,13 @@
+---
+title: Table scrollbars
+route: /DataGrid/TableScrollbars
+---
+
+# Table scrollbars
+
+Example of using an outside `div` and the `Style` parameter to achieve a table like DataGrid with infinite horizontal scrollbars to display all content on all devices.
+
+If you use this in combination with a sticky header, the style of the header will be lost for the columns that are rendered out of the view initially.
+You can fix this by adding the following `Style` to your data grid: `Style="min-width: max-content;"`
+
+{{ DataGridTableScrollbars }}
diff --git a/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Pages/DataGridTemplateColumns2Page.md b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Pages/DataGridTemplateColumns2Page.md
new file mode 100644
index 0000000000..678d61b656
--- /dev/null
+++ b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Pages/DataGridTemplateColumns2Page.md
@@ -0,0 +1,11 @@
+---
+title: More template columns
+route: /DataGrid/TemplateColumns2
+---
+
+# More template columns
+
+## Template columns 2
+`TemplateColumn` uses arbitrary Razor fragments to supply contents for its cells. It can't infer the column's title or sort order automatically.
+
+{{ DataGridTemplateColumns2 }}
diff --git a/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Pages/DataGridTemplateColumnsPage.md b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Pages/DataGridTemplateColumnsPage.md
new file mode 100644
index 0000000000..201063460a
--- /dev/null
+++ b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Pages/DataGridTemplateColumnsPage.md
@@ -0,0 +1,11 @@
+---
+title: Template columns
+route: /DataGrid/TemplateColumns
+---
+
+# Template columns
+
+## Template columns
+`TemplateColumn` uses arbitrary Razor fragments to supply contents for its cells. It can't infer the column's title or sort order automatically.
+
+{{ DataGridTemplateColumns }}
diff --git a/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Pages/DataGridTypicalPage.md b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Pages/DataGridTypicalPage.md
new file mode 100644
index 0000000000..9b3474dcb9
--- /dev/null
+++ b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Pages/DataGridTypicalPage.md
@@ -0,0 +1,21 @@
+---
+title: Typical usage
+route: /DataGrid/Typical
+---
+
+# Typical usage
+
+Here is an example of a data grid that uses in-memory data and enables features including pagination, sorting, filtering, column options, row highlighting and column resizing.
+
+All columns, except 'Bronze', have a `Tooltip` parameter value of `true`.
+When using this for a `TemplateColumn` (like 'Rank' here), you need to also supply a value for the `TooltipText` parameter. No value given equals no tooltip shown.
+When using this for a `PropertyColumn`, a value for the `TooltipText` is not required. By default, the value given for `Property`
+will be re-used for the tooltip. If you do supply a value for `TooltipText` its outcome will be used instead.
+
+`TooltipText` is a lambda function that takes the current item as input and returns the text to show in the tooltip (and `aria-label`).
+Look at the Razor tab to see how this is done and how it can be customized.
+
+The Country filter option can be used to quickly filter the list of countries shown. Pressing the ESC key just closes the option popup without changing the filtering currently being used.
+Pressing enter finishes the filter action by the current input to filter on and closes the option popup.
+
+{{ DataGridTypical Files=Code:DataGridTypical.razor;CSS:DataGridTypical.razor.css }} }}
diff --git a/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Pages/DataGridVirtualizePage.md b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Pages/DataGridVirtualizePage.md
new file mode 100644
index 0000000000..0b298035f6
--- /dev/null
+++ b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Pages/DataGridVirtualizePage.md
@@ -0,0 +1,19 @@
+---
+title: Virtualize
+route: /DataGrid/Virtualize
+---
+# Virtualize
+
+It can be expensive both to fetch and to render large numbers of items. If the amount of data you're
+displaying might be large, you should use either paging or virtualization.
+
+Virtualization provides the appearance of continuous scrolling through an arbitrarily-large data set,
+while only needing to fetch and render the rows that are currently in the scroll viewport. This can provide
+excellent performance even when the data set is vast. FluentDataGrid's virtualization feature is built on Blazor's
+built-in [Virtualize component](https://docs.microsoft.com/en-us/aspnet/core/blazor/components/virtualization?view=aspnetcore-6.0),
+so it shares the same capabilities, requirements, and limitations.
+
+Enabling virtualization is just a matter of passing `Virtualize="true"`. For it to work
+properly and reliably, every row rendered must have the same known height. **This is handled by the `FluentDataGrid` code**
+
+{{ DataGridVirtualize }}
diff --git a/examples/Tools/FluentUI.Demo.SampleData/FoodProductRecalls.cs b/examples/Tools/FluentUI.Demo.SampleData/FoodProductRecalls.cs
index 5045497f47..aa369694a3 100644
--- a/examples/Tools/FluentUI.Demo.SampleData/FoodProductRecalls.cs
+++ b/examples/Tools/FluentUI.Demo.SampleData/FoodProductRecalls.cs
@@ -15,12 +15,12 @@ namespace FluentUI.Demo.SampleData;
public class RemoteFoodProductRecalls
{
private const string FDA_API = "https://api.fda.gov/food/enforcement.json";
- private static readonly HttpClient _client = new HttpClient();
+ private static readonly HttpClient _client = new();
///
/// Options for JSON serialization and deserialization.
///
- private static readonly JsonSerializerOptions JsonOptions = new JsonSerializerOptions
+ private static readonly JsonSerializerOptions JsonOptions = new()
{
PropertyNameCaseInsensitive = true,
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
@@ -47,7 +47,7 @@ public class FoodProductRecalls
public Meta Meta { get; set; } = new();
///
- public IEnumerable Results { get; set; } = Array.Empty();
+ public IEnumerable Results { get; set; } = [];
}
///
@@ -115,3 +115,51 @@ public class MetaResults
public int Total { get; set; }
}
}
+
+#nullable disable
+///
+/// Represents the data returned by https://open.fda.gov/apis/food/enforcement/
+/// This is a subset of the fields available on that API
+///
+public class FoodRecall
+{
+ ///
+ public string Event_Id { get; set; }
+ ///
+ public string Status { get; set; }
+ ///
+ public string City { get; set; }
+ ///
+ public string State { get; set; }
+ ///
+ public string Recalling_Firm { get; set; }
+ ///
+ public string Termination_Date { get; set; }
+}
+
+///
+/// Represents the result of a food recall query from the FDA API.
+///
+public class FoodRecallQueryResult
+{
+ ///
+ public Metadata Meta { get; set; }
+ ///
+ public FoodRecall[] Results { get; set; }
+
+ ///
+ public class Metadata
+ {
+ ///
+ public ResultsInfo Results { get; set; }
+ }
+
+ ///
+ public class ResultsInfo
+ {
+ ///
+ public int Total { get; set; }
+ }
+}
+
+#nullable enable
diff --git a/spelling.dic b/spelling.dic
index aa81caf720..9affac6c7b 100644
--- a/spelling.dic
+++ b/spelling.dic
@@ -100,3 +100,6 @@ colspan
Voituron
firstname
lastname
+oninput
+altcolor
+wdelta
diff --git a/src/Core/Components/Button/FluentButton.razor.css b/src/Core/Components/Button/FluentButton.razor.css
index 1559a8a5ba..e2703e80e7 100644
--- a/src/Core/Components/Button/FluentButton.razor.css
+++ b/src/Core/Components/Button/FluentButton.razor.css
@@ -21,3 +21,7 @@ fluent-button svg[slot="end"],
fluent-button fluent-spinner[slot="end"] {
margin-inline-start: 8px;
}
+
+fluent-button[disabled] svg {
+ opacity: 0.4;
+}
diff --git a/src/Core/Components/DataGrid/Columns/ColumnBase.razor.cs b/src/Core/Components/DataGrid/Columns/ColumnBase.razor.cs
index cb8f5edb01..888493e58c 100644
--- a/src/Core/Components/DataGrid/Columns/ColumnBase.razor.cs
+++ b/src/Core/Components/DataGrid/Columns/ColumnBase.razor.cs
@@ -18,7 +18,6 @@ namespace Microsoft.FluentUI.AspNetCore.Components;
/// The type of data represented by each row in the grid.
public abstract partial class ColumnBase
{
- private bool _isMenuOpen;
private static readonly string[] KEYBOARD_MENU_SELECT_KEYS = ["Enter", "NumpadEnter"];
private readonly string _columnId = Identifier.NewId();
private FluentMenu? _menu;
@@ -286,14 +285,21 @@ private async Task HandleColumnHeaderClickedAsync()
var hasSorting = Sortable is true || IsDefaultSortColumn;
var hasResize = Grid.ResizableColumns;
var hasOptions = ColumnOptions is not null;
- var hasMultiple = (hasSorting && hasResize) || (hasSorting && hasOptions) || (hasResize && hasOptions);
+ var hasMultiple = (Convert.ToInt32(hasSorting) + Convert.ToInt32(hasResize) + Convert.ToInt32(hasOptions)) > 1;
+ var hideMenu = (hasSorting ^ hasResize ^ hasOptions) && !(hasSorting && hasResize && hasOptions);
+
+ if (_menu is not null && hideMenu)
+ {
+ await _menu.CloseMenuAsync();
+ }
if (hasMultiple)
{
- _isMenuOpen = !_isMenuOpen;
- StateHasChanged();
+ return;
+ //StateHasChanged();
}
- else if (hasSorting)
+
+ if (hasSorting)
{
await Grid.SortByColumnAsync(this);
}
@@ -312,8 +318,10 @@ private async Task HandleSortMenuKeyDownAsync(KeyboardEventArgs args)
if (KEYBOARD_MENU_SELECT_KEYS.Contains(args.Key, StringComparer.OrdinalIgnoreCase))
{
await Grid.SortByColumnAsync(this);
- StateHasChanged();
- _isMenuOpen = false;
+ if (_menu is not null)
+ {
+ await _menu.CloseMenuAsync();
+ }
}
}
@@ -322,7 +330,10 @@ private async Task HandleResizeMenuKeyDownAsync(KeyboardEventArgs args)
if (KEYBOARD_MENU_SELECT_KEYS.Contains(args.Key, StringComparer.OrdinalIgnoreCase))
{
await Grid.ShowColumnResizeAsync(this);
- _isMenuOpen = false;
+ if (_menu is not null)
+ {
+ await _menu.CloseMenuAsync();
+ }
}
}
@@ -331,7 +342,10 @@ private async Task HandleOptionsMenuKeyDownAsync(KeyboardEventArgs args)
if (KEYBOARD_MENU_SELECT_KEYS.Contains(args.Key, StringComparer.OrdinalIgnoreCase))
{
await Grid.ShowColumnOptionsAsync(this);
- _isMenuOpen = false;
+ if (_menu is not null)
+ {
+ await _menu.CloseMenuAsync();
+ }
}
}
diff --git a/src/Core/Components/DataGrid/Columns/ColumnKeyGridSort.cs b/src/Core/Components/DataGrid/Columns/ColumnKeyGridSort.cs
index c2c8adc050..e310f6f0fb 100644
--- a/src/Core/Components/DataGrid/Columns/ColumnKeyGridSort.cs
+++ b/src/Core/Components/DataGrid/Columns/ColumnKeyGridSort.cs
@@ -11,11 +11,12 @@ namespace Microsoft.FluentUI.AspNetCore.Components;
public sealed class ColumnKeyGridSort : IGridSort
{
private readonly string _columnKey;
- private readonly Func, bool, IOrderedQueryable> _sortFunction;
+ private readonly Func, bool, IOrderedQueryable>? _sortFunction;
- internal ColumnKeyGridSort(
+ ///
+ public ColumnKeyGridSort(
string columnKey,
- Func, bool, IOrderedQueryable> sortFunction)
+ Func, bool, IOrderedQueryable>? sortFunction = null)
{
_columnKey = columnKey;
_sortFunction = sortFunction;
@@ -29,13 +30,13 @@ internal ColumnKeyGridSort(
/// /// The ordered collection
public IOrderedQueryable Apply(IQueryable queryable, bool ascending)
{
- //if (_sortFunction != null)
- //{
+ if (_sortFunction != null)
+ {
return _sortFunction(queryable, ascending);
- //}
+ }
// If no sort is provided, apply a sort that has no affect in order to be able to return an IOrderedQueryable
- //return queryable.OrderBy(x => 0);
+ return queryable.OrderBy(x => 0);
}
///
diff --git a/src/Core/Components/DataGrid/FluentDataGrid.razor.cs b/src/Core/Components/DataGrid/FluentDataGrid.razor.cs
index d8f73f3625..e003389fdb 100644
--- a/src/Core/Components/DataGrid/FluentDataGrid.razor.cs
+++ b/src/Core/Components/DataGrid/FluentDataGrid.razor.cs
@@ -451,7 +451,7 @@ protected override async Task OnAfterRenderAsync(bool firstRender)
// Import the JavaScript module
await JSModule.ImportJavaScriptModuleAsync(JAVASCRIPT_FILE);
- await JSModule.ObjectReference.InvokeAsync("init", _gridReference, AutoFocus);
+ await JSModule.ObjectReference.InvokeAsync("Microsoft.FluentUI.Blazor.DataGrid.Initialize", _gridReference, AutoFocus);
if (AutoItemsPerPage)
{
@@ -1025,6 +1025,11 @@ public async Task OnKeyDownAsync(FluentKeyCodeEventArgs args)
await ResetColumnWidthsAsync();
}
+ if (args.ShiftKey && args.Key == KeyCode.KeyS)
+ {
+ await RemoveSortByColumnAsync();
+ }
+
if (string.Equals(args.Value, "-", StringComparison.Ordinal))
{
await SetColumnWidthDiscreteAsync(columnIndex: null, -10);
diff --git a/src/Core/Components/DataGrid/GridItemsProviderResult.cs b/src/Core/Components/DataGrid/GridItemsProviderResult.cs
index 34a9c7c83b..cc0e70704b 100644
--- a/src/Core/Components/DataGrid/GridItemsProviderResult.cs
+++ b/src/Core/Components/DataGrid/GridItemsProviderResult.cs
@@ -27,7 +27,7 @@ public readonly struct GridItemsProviderResult
///
/// Provides convenience methods for constructing instances.
///
-internal static class GridItemsProviderResult
+public static class GridItemsProviderResult
{
// This is just to provide generic type inference, so you don't have to specify TGridItem yet again.
diff --git a/src/Core/Components/Pagination/FluentPaginator.razor b/src/Core/Components/Pagination/FluentPaginator.razor
index 2c39d9d36c..c926fec6a3 100644
--- a/src/Core/Components/Pagination/FluentPaginator.razor
+++ b/src/Core/Components/Pagination/FluentPaginator.razor
@@ -22,12 +22,12 @@
Disabled="@(!CanGoBack || Disabled)"
Title="@Localizer[Localization.LanguageResource.Paginator_GoFirstPage]"
aria-label="@Localizer[Localization.LanguageResource.Paginator_GoFirstPage]"
- IconStart="@(new CoreIcons.Regular.Size20.ChevronDoubleLeft())" />
+ IconStart="@(new CoreIcons.Regular.Size20.ChevronDoubleLeft().WithColor(Color.Primary))" />
+ IconStart="@(new CoreIcons.Regular.Size20.ChevronLeft().WithColor(Color.Primary))" />