diff --git a/examples/Demo/Shared/Microsoft.FluentUI.AspNetCore.Components.xml b/examples/Demo/Shared/Microsoft.FluentUI.AspNetCore.Components.xml index a2614a05f5..feb5474744 100644 --- a/examples/Demo/Shared/Microsoft.FluentUI.AspNetCore.Components.xml +++ b/examples/Demo/Shared/Microsoft.FluentUI.AspNetCore.Components.xml @@ -6065,6 +6065,13 @@ Gets or sets whether the dropdown is shown when there are no items. + + + Gets or sets whether the component will display a progress indicator while fetching data. + A progress ring will be shown ad the end of the component, when the is invoked. + You can customize the progress indicator by using the or parameters: see . + + If true, the options list will be rendered with virtualization. This is normally used in conjunction with @@ -6177,6 +6184,19 @@ + + + + + + Gets a value indicating whether the operation is currently in progress. + + + + + Gets the items to display in the header or footer. + + diff --git a/examples/Demo/Shared/Pages/List/Autocomplete/Examples/AutocompleteCustomized.razor b/examples/Demo/Shared/Pages/List/Autocomplete/Examples/AutocompleteCustomized.razor index 9315d477f1..d7b77e5fb1 100644 --- a/examples/Demo/Shared/Pages/List/Autocomplete/Examples/AutocompleteCustomized.razor +++ b/examples/Demo/Shared/Pages/List/Autocomplete/Examples/AutocompleteCustomized.razor @@ -5,7 +5,8 @@ TOption="Person" Width="100%" Placeholder="search" - OnOptionsSearch="@OnSearch" + OnOptionsSearch="@OnSearchAsync" + ShowProgressIndicator="@ShowProgressIndicator" MaximumSelectedOptions="3" KeepOpen="true" OptionText="@(item => item.FirstName)" @@ -48,11 +49,12 @@ Style="padding: 8px; font-size: 11px; border-bottom: 1px solid var(--neutral-fill-stealth-hover);"> Suggested contacts + @* Content display at the bottom of the Popup area *@ - @if (!context.Any()) + @if (!context.Items.Any()) { No results found @@ -65,13 +67,23 @@ Selected: @(String.Join(" - ", SelectedItems.Select(i => i.LastName)))

+

+ +

+ @code { + bool ShowProgressIndicator = false; FluentAutocomplete ContactList = default!; IEnumerable SelectedItems = Array.Empty(); - private void OnSearch(OptionsSearchEventArgs e) + private async Task OnSearchAsync(OptionsSearchEventArgs e) { + if (ShowProgressIndicator) + { + await Task.Delay(500); // Simulate a delay for the search operation + } + e.Items = Data.People.Where(i => i.LastName.StartsWith(e.Text, StringComparison.OrdinalIgnoreCase) || i.FirstName.StartsWith(e.Text, StringComparison.OrdinalIgnoreCase)) .OrderBy(i => i.LastName); diff --git a/src/Core/Components/List/FluentAutocomplete.razor b/src/Core/Components/List/FluentAutocomplete.razor index f6499e6ff7..4bb93343c3 100644 --- a/src/Core/Components/List/FluentAutocomplete.razor +++ b/src/Core/Components/List/FluentAutocomplete.razor @@ -71,6 +71,11 @@ } @if (!Disabled && !ReadOnly) { + if (ShowProgressIndicator && _inProgress) + { + + } + if (this.SelectedOptions?.Any() == true || !string.IsNullOrEmpty(ValueText) || this.SelectedOption is not null) { if (IconDismiss != null) @@ -122,7 +127,7 @@ Shadow="ElevationShadow.Flyout"> @if (HeaderContent != null) { - @HeaderContent(Items ?? Array.Empty()) + @HeaderContent(new HeaderFooterContent(Items, _inProgress)) }
@@ -148,7 +153,7 @@ @if (FooterContent != null) { - @FooterContent(Items ?? Array.Empty()) + @FooterContent(new HeaderFooterContent(Items, _inProgress)) } } diff --git a/src/Core/Components/List/FluentAutocomplete.razor.cs b/src/Core/Components/List/FluentAutocomplete.razor.cs index 9cbe1cfef5..bcd41b1455 100644 --- a/src/Core/Components/List/FluentAutocomplete.razor.cs +++ b/src/Core/Components/List/FluentAutocomplete.razor.cs @@ -26,6 +26,7 @@ public partial class FluentAutocomplete : ListComponentBase wh private Virtualize? VirtualizationContainer { get; set; } private readonly Debounce _debounce = new(); private bool _shouldRender = true; + private bool _inProgress; /// /// Initializes a new instance of the class. @@ -142,13 +143,13 @@ public override string? Value /// Gets or sets the header content, placed at the top of the popup panel. /// [Parameter] - public RenderFragment>? HeaderContent { get; set; } + public RenderFragment>? HeaderContent { get; set; } /// /// Gets or sets the footer content, placed at the bottom of the popup panel. /// [Parameter] - public RenderFragment>? FooterContent { get; set; } + public RenderFragment>? FooterContent { get; set; } /// /// Gets or sets the title and Aria-Label for the Scroll to previous button. @@ -180,6 +181,14 @@ public override string? Value [Parameter] public bool ShowOverlayOnEmptyResults { get; set; } = true; + /// + /// Gets or sets whether the component will display a progress indicator while fetching data. + /// A progress ring will be shown ad the end of the component, when the is invoked. + /// You can customize the progress indicator by using the or parameters: see . + /// + [Parameter] + public bool ShowProgressIndicator { get; set; } + /// /// If true, the options list will be rendered with virtualization. This is normally used in conjunction with /// scrolling and causes the option list to fetch and render only the data around the current scroll viewport. @@ -288,6 +297,9 @@ protected override async Task InputHandlerAsync(ChangeEventArgs e) return; } + _inProgress = true; + StateHasChanged(); + _shouldRender = false; ValueText = e.Value?.ToString() ?? string.Empty; @@ -309,7 +321,7 @@ protected override async Task InputHandlerAsync(ChangeEventArgs e) } else { - await this.InvokeOptionsSearchAsync(); + await InvokeOptionsSearchAsync(); } } @@ -320,6 +332,8 @@ protected override async Task InputHandlerAsync(ChangeEventArgs e) /// public async Task InvokeOptionsSearchAsync() { + _inProgress = true; + var args = new OptionsSearchEventArgs() { Items = Items ?? Array.Empty(), @@ -339,6 +353,7 @@ public async Task InvokeOptionsSearchAsync() await VirtualizationContainer.RefreshDataAsync(); } + _inProgress = false; await RenderComponentAsync(); } @@ -691,3 +706,23 @@ private bool MustBeClosed() return false; } } + +/// +public class HeaderFooterContent +{ + internal HeaderFooterContent(IEnumerable? items, bool inProgress) + { + Items = items ?? Array.Empty(); + InProgress = inProgress; + } + + /// + /// Gets a value indicating whether the operation is currently in progress. + /// + public bool InProgress { get; init; } + + /// + /// Gets the items to display in the header or footer. + /// + public IEnumerable Items { get; init; } +} diff --git a/src/Core/Resources/TimeAgoResource.Designer.cs b/src/Core/Resources/TimeAgoResource.Designer.cs index 8dd0f1ec8c..45f9000a54 100644 --- a/src/Core/Resources/TimeAgoResource.Designer.cs +++ b/src/Core/Resources/TimeAgoResource.Designer.cs @@ -19,7 +19,7 @@ namespace Microsoft.FluentUI.AspNetCore.Components.Resources { // class via a tool like ResGen or Visual Studio. // To add or remove a member, edit your .ResX file then rerun ResGen // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "18.0.0.0")] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] internal class TimeAgoResource { diff --git a/tests/Core/DataGrid/FluentDataGridIsFixedTests.razor b/tests/Core/DataGrid/FluentDataGridIsFixedTests.razor index e29315c110..2ca5253f44 100644 --- a/tests/Core/DataGrid/FluentDataGridIsFixedTests.razor +++ b/tests/Core/DataGrid/FluentDataGridIsFixedTests.razor @@ -47,6 +47,8 @@ [Fact] public async Task FluentDataGrid_IsFixed_True_Allows_Data_Changes_Without_Automatic_Refresh() { + await Task.CompletedTask; + // Arrange var items = GetCustomers().AsQueryable(); diff --git a/tests/Core/List/FluentAutocompleteTests.razor b/tests/Core/List/FluentAutocompleteTests.razor index a1b5d21f19..292a424ef4 100644 --- a/tests/Core/List/FluentAutocompleteTests.razor +++ b/tests/Core/List/FluentAutocompleteTests.razor @@ -74,6 +74,7 @@ async Task OnSearchValueChanged(OptionsSearchEventArgs e) { + await Task.CompletedTask; e.Items = customers; if (!string.IsNullOrWhiteSpace(externalSearchString)) e.Items = customers.Where(x => x.Name.Contains(externalSearchString)); @@ -164,7 +165,7 @@ // Add a HeaderContent template parameters.Add(p => p.HeaderContent, context => { - return $"
Please, select an item
"; + return $"
Please, select an item
"; }); // Add an Item template @@ -176,7 +177,7 @@ // Add a FooterContent template parameters.Add(p => p.FooterContent, context => { - return $"
{context.Count()} items found
"; + return $""; }); parameters.Add(p => p.Items, Customers.Get()); @@ -475,6 +476,7 @@ [Fact] public async Task FluentAutocomplete_MultipleEqualsFalse() { + await Task.CompletedTask; Customer? SelectedItem = Customers.Get().First(); // Arrange