Skip to content

Commit 8197e48

Browse files
authored
[Autocomplete] Add a ShowProgressIndicator parameter + Breaking change (#4042)
1 parent b8b0347 commit 8197e48

File tree

7 files changed

+87
-11
lines changed

7 files changed

+87
-11
lines changed

examples/Demo/Shared/Microsoft.FluentUI.AspNetCore.Components.xml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6065,6 +6065,13 @@
60656065
Gets or sets whether the dropdown is shown when there are no items.
60666066
</summary>
60676067
</member>
6068+
<member name="P:Microsoft.FluentUI.AspNetCore.Components.FluentAutocomplete`1.ShowProgressIndicator">
6069+
<summary>
6070+
Gets or sets whether the component will display a progress indicator while fetching data.
6071+
A progress ring will be shown ad the end of the component, when the <see cref="P:Microsoft.FluentUI.AspNetCore.Components.FluentAutocomplete`1.OnOptionsSearch"/> is invoked.
6072+
You can customize the progress indicator by using the <see cref="P:Microsoft.FluentUI.AspNetCore.Components.FluentAutocomplete`1.HeaderContent"/> or <see cref="P:Microsoft.FluentUI.AspNetCore.Components.FluentAutocomplete`1.FooterContent"/> parameters: see <see cref="P:Microsoft.FluentUI.AspNetCore.Components.HeaderFooterContent`1.InProgress"/>.
6073+
</summary>
6074+
</member>
60686075
<member name="P:Microsoft.FluentUI.AspNetCore.Components.FluentAutocomplete`1.Virtualize">
60696076
<summary>
60706077
If true, the options list will be rendered with virtualization. This is normally used in conjunction with
@@ -6177,6 +6184,19 @@
61776184
<member name="M:Microsoft.FluentUI.AspNetCore.Components.FluentAutocomplete`1.MustBeClosed">
61786185
<summary />
61796186
</member>
6187+
<member name="T:Microsoft.FluentUI.AspNetCore.Components.HeaderFooterContent`1">
6188+
<summary />
6189+
</member>
6190+
<member name="P:Microsoft.FluentUI.AspNetCore.Components.HeaderFooterContent`1.InProgress">
6191+
<summary>
6192+
Gets a value indicating whether the operation is currently in progress.
6193+
</summary>
6194+
</member>
6195+
<member name="P:Microsoft.FluentUI.AspNetCore.Components.HeaderFooterContent`1.Items">
6196+
<summary>
6197+
Gets the items to display in the header or footer.
6198+
</summary>
6199+
</member>
61806200
<member name="P:Microsoft.FluentUI.AspNetCore.Components.FluentCombobox`1.LibraryConfiguration">
61816201
<summary />
61826202
</member>

examples/Demo/Shared/Pages/List/Autocomplete/Examples/AutocompleteCustomized.razor

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@
55
TOption="Person"
66
Width="100%"
77
Placeholder="search"
8-
OnOptionsSearch="@OnSearch"
8+
OnOptionsSearch="@OnSearchAsync"
9+
ShowProgressIndicator="@ShowProgressIndicator"
910
MaximumSelectedOptions="3"
1011
KeepOpen="true"
1112
OptionText="@(item => item.FirstName)"
@@ -48,11 +49,12 @@
4849
Style="padding: 8px; font-size: 11px; border-bottom: 1px solid var(--neutral-fill-stealth-hover);">
4950
Suggested contacts
5051
</FluentLabel>
52+
<FluentProgress Style="@($"visibility: {(context.InProgress ? "visible" : "collapse")}")" />
5153
</HeaderContent>
5254

5355
@* Content display at the bottom of the Popup area *@
5456
<FooterContent>
55-
@if (!context.Any())
57+
@if (!context.Items.Any())
5658
{
5759
<FluentLabel Style="font-size: 11px; text-align: center; width: 200px;">
5860
No results found
@@ -65,13 +67,23 @@
6567
<b>Selected</b>: @(String.Join(" - ", SelectedItems.Select(i => i.LastName)))
6668
</p>
6769

70+
<p>
71+
<FluentSwitch @bind-Value="@ShowProgressIndicator" Label="ShowProgressIndicator" />
72+
</p>
73+
6874
@code
6975
{
76+
bool ShowProgressIndicator = false;
7077
FluentAutocomplete<Person> ContactList = default!;
7178
IEnumerable<Person> SelectedItems = Array.Empty<Person>();
7279

73-
private void OnSearch(OptionsSearchEventArgs<Person> e)
80+
private async Task OnSearchAsync(OptionsSearchEventArgs<Person> e)
7481
{
82+
if (ShowProgressIndicator)
83+
{
84+
await Task.Delay(500); // Simulate a delay for the search operation
85+
}
86+
7587
e.Items = Data.People.Where(i => i.LastName.StartsWith(e.Text, StringComparison.OrdinalIgnoreCase) ||
7688
i.FirstName.StartsWith(e.Text, StringComparison.OrdinalIgnoreCase))
7789
.OrderBy(i => i.LastName);

src/Core/Components/List/FluentAutocomplete.razor

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,11 @@
7171
}
7272
@if (!Disabled && !ReadOnly)
7373
{
74+
if (ShowProgressIndicator && _inProgress)
75+
{
76+
<FluentProgressRing style="width: 16px; height: 16px;" Slot="end" />
77+
}
78+
7479
if (this.SelectedOptions?.Any() == true || !string.IsNullOrEmpty(ValueText) || this.SelectedOption is not null)
7580
{
7681
if (IconDismiss != null)
@@ -122,7 +127,7 @@
122127
Shadow="ElevationShadow.Flyout">
123128
@if (HeaderContent != null)
124129
{
125-
@HeaderContent(Items ?? Array.Empty<TOption>())
130+
@HeaderContent(new HeaderFooterContent<TOption>(Items, _inProgress))
126131
}
127132

128133
<div id="@IdPopup" role="listbox" style="@ListStyleValue" tabindex="0">
@@ -148,7 +153,7 @@
148153

149154
@if (FooterContent != null)
150155
{
151-
@FooterContent(Items ?? Array.Empty<TOption>())
156+
@FooterContent(new HeaderFooterContent<TOption>(Items, _inProgress))
152157
}
153158
</FluentAnchoredRegion>
154159
}

src/Core/Components/List/FluentAutocomplete.razor.cs

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ public partial class FluentAutocomplete<TOption> : ListComponentBase<TOption> wh
2626
private Virtualize<TOption>? VirtualizationContainer { get; set; }
2727
private readonly Debounce _debounce = new();
2828
private bool _shouldRender = true;
29+
private bool _inProgress;
2930

3031
/// <summary>
3132
/// Initializes a new instance of the <see cref="FluentAutocomplete{TOption}"/> class.
@@ -142,13 +143,13 @@ public override string? Value
142143
/// Gets or sets the header content, placed at the top of the popup panel.
143144
/// </summary>
144145
[Parameter]
145-
public RenderFragment<IEnumerable<TOption>>? HeaderContent { get; set; }
146+
public RenderFragment<HeaderFooterContent<TOption>>? HeaderContent { get; set; }
146147

147148
/// <summary>
148149
/// Gets or sets the footer content, placed at the bottom of the popup panel.
149150
/// </summary>
150151
[Parameter]
151-
public RenderFragment<IEnumerable<TOption>>? FooterContent { get; set; }
152+
public RenderFragment<HeaderFooterContent<TOption>>? FooterContent { get; set; }
152153

153154
/// <summary>
154155
/// Gets or sets the title and Aria-Label for the Scroll to previous button.
@@ -180,6 +181,14 @@ public override string? Value
180181
[Parameter]
181182
public bool ShowOverlayOnEmptyResults { get; set; } = true;
182183

184+
/// <summary>
185+
/// Gets or sets whether the component will display a progress indicator while fetching data.
186+
/// A progress ring will be shown ad the end of the component, when the <see cref="OnOptionsSearch"/> is invoked.
187+
/// You can customize the progress indicator by using the <see cref="HeaderContent"/> or <see cref="FooterContent"/> parameters: see <see cref="HeaderFooterContent{TOption}.InProgress"/>.
188+
/// </summary>
189+
[Parameter]
190+
public bool ShowProgressIndicator { get; set; }
191+
183192
/// <summary>
184193
/// If true, the options list will be rendered with virtualization. This is normally used in conjunction with
185194
/// 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)
288297
return;
289298
}
290299

300+
_inProgress = true;
301+
StateHasChanged();
302+
291303
_shouldRender = false;
292304

293305
ValueText = e.Value?.ToString() ?? string.Empty;
@@ -309,7 +321,7 @@ protected override async Task InputHandlerAsync(ChangeEventArgs e)
309321
}
310322
else
311323
{
312-
await this.InvokeOptionsSearchAsync();
324+
await InvokeOptionsSearchAsync();
313325
}
314326
}
315327

@@ -320,6 +332,8 @@ protected override async Task InputHandlerAsync(ChangeEventArgs e)
320332
/// <returns></returns>
321333
public async Task InvokeOptionsSearchAsync()
322334
{
335+
_inProgress = true;
336+
323337
var args = new OptionsSearchEventArgs<TOption>()
324338
{
325339
Items = Items ?? Array.Empty<TOption>(),
@@ -339,6 +353,7 @@ public async Task InvokeOptionsSearchAsync()
339353
await VirtualizationContainer.RefreshDataAsync();
340354
}
341355

356+
_inProgress = false;
342357
await RenderComponentAsync();
343358
}
344359

@@ -691,3 +706,23 @@ private bool MustBeClosed()
691706
return false;
692707
}
693708
}
709+
710+
/// <summary />
711+
public class HeaderFooterContent<TOption>
712+
{
713+
internal HeaderFooterContent(IEnumerable<TOption>? items, bool inProgress)
714+
{
715+
Items = items ?? Array.Empty<TOption>();
716+
InProgress = inProgress;
717+
}
718+
719+
/// <summary>
720+
/// Gets a value indicating whether the operation is currently in progress.
721+
/// </summary>
722+
public bool InProgress { get; init; }
723+
724+
/// <summary>
725+
/// Gets the items to display in the header or footer.
726+
/// </summary>
727+
public IEnumerable<TOption> Items { get; init; }
728+
}

src/Core/Resources/TimeAgoResource.Designer.cs

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tests/Core/DataGrid/FluentDataGridIsFixedTests.razor

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@
4747
[Fact]
4848
public async Task FluentDataGrid_IsFixed_True_Allows_Data_Changes_Without_Automatic_Refresh()
4949
{
50+
await Task.CompletedTask;
51+
5052
// Arrange
5153
var items = GetCustomers().AsQueryable();
5254

tests/Core/List/FluentAutocompleteTests.razor

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@
7474

7575
async Task OnSearchValueChanged(OptionsSearchEventArgs<Customer> e)
7676
{
77+
await Task.CompletedTask;
7778
e.Items = customers;
7879
if (!string.IsNullOrWhiteSpace(externalSearchString))
7980
e.Items = customers.Where(x => x.Name.Contains(externalSearchString));
@@ -164,7 +165,7 @@
164165
// Add a HeaderContent template
165166
parameters.Add(p => p.HeaderContent, context =>
166167
{
167-
return $"<header>Please, select an item</header>";
168+
return $"<header>Please, select an item</header>";
168169
});
169170

170171
// Add an Item template
@@ -176,7 +177,7 @@
176177
// Add a FooterContent template
177178
parameters.Add(p => p.FooterContent, context =>
178179
{
179-
return $"<footer>{context.Count()} items found</footer>";
180+
return $"<footer>{context.Items.Count()} items found</footer>";
180181
});
181182

182183
parameters.Add(p => p.Items, Customers.Get());
@@ -475,6 +476,7 @@
475476
[Fact]
476477
public async Task FluentAutocomplete_MultipleEqualsFalse()
477478
{
479+
await Task.CompletedTask;
478480
Customer? SelectedItem = Customers.Get().First();
479481

480482
// Arrange

0 commit comments

Comments
 (0)