Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
54 changes: 30 additions & 24 deletions src/BlazorUI/Bit.BlazorUI/Components/DropDown/BitDropDown.razor
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
@namespace Bit.BlazorUI
@inherits BitInputBase<string>
@inherits BitInputBase<string?>

<div @ref="RootElement"
id="@UniqueId"
style="@StyleBuilder.Value"
Expand Down Expand Up @@ -27,7 +28,7 @@
aria-haspopup="listbox"
aria-controls="@(IsOpen ? $"{_dropDownId}-list" : null)"
title="@Title"
@onclick="HandleClick">
@onclick="HandleOnClick">
<span class="text-container"
id="@_dropDownOptionId"
aria-live="polite"
Expand Down Expand Up @@ -56,6 +57,12 @@
}
}
</span>
@if (ShowClearButton && SelectedItems.Count > 0 && IsEnabled)
{
<span @onclick:stopPropagation @onclick="Clear" class="icon-container clear-btn">
<i class="bit-icon bit-icon--ChromeClose"></i>
</span>
}
<span class="icon-container">
@if (CaretDownTemplate is not null)
{
Expand Down Expand Up @@ -85,12 +92,11 @@
<label class="label">@Label</label>
}
<button class="close-btn"
type="button"
aria-label="Close"
aria-describedby="Close"
aria-hidden="Close"
title="Close"
@onclick="CloseCallout">
type="button"
aria-label="Close"
aria-describedby="Close"
aria-hidden="Close"
title="Close" @onclick="CloseCallout">
<span>
<i class="bit-icon bit-icon--ChromeClose"></i>
</span>
Expand All @@ -104,24 +110,24 @@
<i class="bit-icon bit-icon--Search" aria-hidden="true"></i>
</div>
<input @ref="_searchInputElement"
class="search-input"
type="text"
aria-label="Search text"
placeholder="@SearchBoxPlaceholder"
role="searchbox"
value="@_searchText"
@onfocusin="HandleSearchBoxFocusIn"
@onfocusout="HandleSearchBoxFocusOut"
@oninput="@HandleFilterChange" />
@onfocusin="HandleSearchBoxFocusIn"
@onfocusout="HandleSearchBoxFocusOut"
@oninput="@HandleFilterChange"
class="search-input"
type="text"
aria-label="Search text"
placeholder="@SearchBoxPlaceholder"
role="searchbox"
value="@_searchText" />
@if (_searchText.HasValue())
{
<div class="clear-btn-container">
<button class="clear-btn"
type="button"
aria-label="Clear text"
aria-hidden="true"
disabled="@(_searchText.HasNoValue())"
@onclick="HandleSearchBoxOnClear">
type="button"
aria-label="Clear text"
aria-hidden="true"
disabled="@(_searchText.HasNoValue())"
@onclick="HandleSearchBoxOnClear">
<span>
<i class="bit-icon bit-icon--Clear" aria-hidden="true"></i>
</span>
Expand Down Expand Up @@ -187,7 +193,7 @@
@if (IsMultiSelect)
{
<div style="@(dropDownItem.item.IsHidden ? "display: none" : string.Empty)" class="@GetCssClassForItem(dropDownItem.item)">
<label class="checkbox-container" @onclick="@((e) => HandleItemClick(dropDownItem.item))">
<label class="checkbox-container" @onclick="@((e) => HandleOnItemClick(dropDownItem.item))">
<div class="checkbox">
<i class="checkmark bit-icon bit-icon--Accept"
aria-hidden="true"
Expand Down Expand Up @@ -223,7 +229,7 @@
tabindex="@(dropDownItem.item.IsEnabled ? 0 : -1)"
title="@dropDownItem.item.Title"
aria-label="@dropDownItem.item.AriaLabel"
@onclick="()=> HandleItemClick(dropDownItem.item)">
@onclick="()=> HandleOnItemClick(dropDownItem.item)">
@if (ItemTemplate is not null)
{
@ItemTemplate(dropDownItem.item)
Expand Down
104 changes: 76 additions & 28 deletions src/BlazorUI/Bit.BlazorUI/Components/DropDown/BitDropDown.razor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ public partial class BitDropDown
private Virtualize<(int index, BitDropDownItem item)>? _virtualizeElement;
private ElementReference _searchInputElement;
private ElementReference _scrollWrapperElement;
private DotNetObjectReference<BitDropDown> _dotnetObj = default!;

[Inject] private IJSRuntime _js { get; set; } = default!;

Expand Down Expand Up @@ -210,6 +211,11 @@ public List<string> Values
/// </summary>
[Parameter] public RenderFragment<BitDropDownItem>? ItemTemplate { get; set; }

/// <summary>
/// Clear Button is shown when something is selected.
/// </summary>
[Parameter] public bool ShowClearButton { get; set; }

/// <summary>
/// Search box is enabled for the end user
/// </summary>
Expand Down Expand Up @@ -270,7 +276,6 @@ public List<BitDropDownItem> SelectedItems
_ = SelectedItemsChanged.InvokeAsync(value);
}
}

[Parameter] public EventCallback<List<BitDropDownItem>> SelectedItemsChanged { get; set; }

/// <summary>
Expand All @@ -284,16 +289,15 @@ public BitDropDownItem? SelectedItem
{
if (SelectedItems?.FirstOrDefault() == value) return;

SelectedItems.Clear();
SelectedItems?.Clear();
if (value is not null)
{
SelectedItems.Add(value);
SelectedItems?.Add(value);
}
ClassBuilder.Reset();
_ = SelectedItemChanged.InvokeAsync(value);
}
}

[Parameter] public EventCallback<BitDropDownItem?> SelectedItemChanged { get; set; }

/// <summary>
Expand Down Expand Up @@ -327,6 +331,8 @@ public void CloseCalloutBeforeAnotherCalloutIsOpened()
IsOpen = false;
}



protected override string RootElementClass => "bit-drp";

protected override void RegisterComponentClasses()
Expand Down Expand Up @@ -355,10 +361,9 @@ protected override void OnInitialized()
Items = new();
}

if (SelectedItems is null)
{
SelectedItems = new();
}
SelectedItems ??= new();

_dotnetObj = DotNetObjectReference.Create(this);

base.OnInitialized();
}
Expand Down Expand Up @@ -417,45 +422,40 @@ private async Task CloseCallout()
if (IsEnabled is false) return;
if (IsOpenHasBeenSet && IsOpenChanged.HasDelegate is false) return;

var obj = DotNetObjectReference.Create(this);
await _js.InvokeVoidAsync("BitDropDown.toggleDropDownCallout",
obj, UniqueId, _dropDownId, _dropDownCalloutId, _dropDownOverlayId, _scrollWrapperElement, DropDirection, IsOpen, IsResponsiveModeEnabled, IsRtl);
if (IsOpen is false) return;

await ToggleCallout();

IsOpen = false;
StateHasChanged();
}

private async Task HandleClick(MouseEventArgs e)
private async Task HandleOnClick(MouseEventArgs e)
{
if (IsEnabled is false) return;
if (IsOpenHasBeenSet && IsOpenChanged.HasDelegate is false) return;

var obj = DotNetObjectReference.Create(this);
await _js.InvokeVoidAsync("BitDropDown.toggleDropDownCallout", obj, UniqueId, _dropDownId, _dropDownCalloutId, _dropDownOverlayId, _scrollWrapperElement, DropDirection, IsOpen, IsResponsiveModeEnabled, IsRtl);
await ToggleCallout();

IsOpen = !IsOpen;
await OnClick.InvokeAsync(e);
await FocusOnSearchBox();
}

private async Task HandleItemClick(BitDropDownItem selectedItem)
private async Task HandleOnItemClick(BitDropDownItem selectedItem)
{
if (selectedItem.ItemType != BitDropDownItemType.Normal) return;

if (IsEnabled is false || selectedItem.IsEnabled is false) return;

if (IsOpenHasBeenSet && IsOpenChanged.HasDelegate is false) return;

if (IsMultiSelect &&
ValuesHasBeenSet &&
ValuesChanged.HasDelegate is false) return;

if (IsMultiSelect is false &&
ValueHasBeenSet &&
ValueChanged.HasDelegate is false) return;

if (IsMultiSelect)
{
if (_isValuesChanged is false) _isValuesChanged = true;
if (ValuesHasBeenSet && ValuesChanged.HasDelegate is false) return;

if (_isValuesChanged is false)
{
_isValuesChanged = true;
}

selectedItem.IsSelected = !selectedItem.IsSelected;
if (selectedItem.IsSelected)
Expand All @@ -476,6 +476,7 @@ private async Task HandleItemClick(BitDropDownItem selectedItem)
}
else
{
if (ValueHasBeenSet && ValueChanged.HasDelegate is false) return;

var oldSelectedItem = SelectedItems.SingleOrDefault();
var isSameItemSelected = oldSelectedItem == selectedItem;
Expand All @@ -492,8 +493,9 @@ private async Task HandleItemClick(BitDropDownItem selectedItem)

_text = selectedItem.Text;
CurrentValueAsString = selectedItem.Value;
var obj = DotNetObjectReference.Create(this);
await _js.InvokeVoidAsync("BitDropDown.toggleDropDownCallout", obj, UniqueId, _dropDownId, _dropDownCalloutId, _dropDownOverlayId, _scrollWrapperElement, DropDirection, IsOpen, IsResponsiveModeEnabled, IsRtl);

await ToggleCallout();

IsOpen = false;
await ClearSearchBox();

Expand Down Expand Up @@ -655,6 +657,52 @@ private async Task SearchVirtualized()
await _virtualizeElement.RefreshDataAsync();
}

private async Task Clear(MouseEventArgs e)
{
if (IsEnabled is false) return;

if (IsMultiSelect)
{
if (ValuesHasBeenSet && ValuesChanged.HasDelegate is false) return;

SelectedItems.ForEach(i => i.IsSelected = false);
SelectedItems.Clear();
ClassBuilder.Reset();

Values = new();
await SelectedItemsChanged.InvokeAsync(SelectedItems);
}
else
{
if (ValueHasBeenSet && ValueChanged.HasDelegate is false) return;

if (SelectedItems.Count > 0)
{
var currentSelectedItem = SelectedItems.SingleOrDefault();
currentSelectedItem!.IsSelected = false;

SelectedItems.Clear();
ClassBuilder.Reset();
}

Value = string.Empty;
await SelectedItemChanged.InvokeAsync(SelectedItem);
}

_text = string.Empty;
CurrentValueAsString = null;

StateHasChanged();
}

private async Task ToggleCallout()
{
await _js.InvokeVoidAsync("BitDropDown.toggleDropDownCallout",
_dotnetObj, UniqueId, _dropDownId, _dropDownCalloutId, _dropDownOverlayId, _scrollWrapperElement,
DropDirection, IsOpen, IsResponsiveModeEnabled, IsRtl);
}


// Gets called both by RefreshDataCoreAsync and directly by the Virtualize child component during scrolling
private async ValueTask<ItemsProviderResult<(int index, BitDropDownItem item)>> ProvideVirtualizedItems(ItemsProviderRequest request)
{
Expand Down
12 changes: 12 additions & 0 deletions src/BlazorUI/Bit.BlazorUI/Components/DropDown/BitDropDown.scss
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,18 @@
pointer-events: none;
}
}

.clear-btn {
right: spacing(3.5);
width: spacing(3.75);
height: spacing(3.75);
display: flex;
justify-content: center;

&:hover {
background-color: $color-neutrals-gray20;
}
}
}

.items-wrapper {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
@inject HttpClient HttpClient
@inject NavigationManager NavManager

<ComponentDemo ComponentName="DropDown" ComponentDescription="A dropdown is a list in which the selected item is always visible while other items are visible on demand by clicking a dropdown button. Dropdowns are typically used for forms."
ComponentParameters="componentParameters" EnumParameters="enumParameters">
<ComponentDemo ComponentName="DropDown" ComponentParameters="componentParameters"
ComponentDescription="A dropdown is a list in which the selected item is always visible while other items are visible on demand by clicking a dropdown button. Dropdowns are typically used for forms.">
<ComponentExampleBox Title="BitDropDown" HTMLSourceCode="@example1HTMLCode" CSharpSourceCode="@example1CSharpCode" ExampleId="example1">
<ExamplePreview>
<div class="example-desc">To create a dropdown you can use a BitDropDown component.</div>
Expand Down Expand Up @@ -325,4 +325,32 @@
</BitDropDown>
</ExamplePreview>
</ComponentExampleBox>

<ComponentExampleBox Title="Clear button" HTMLSourceCode="@example13HTMLCode" CSharpSourceCode="@example13CSharpCode" ExampleId="example13">
<ExamplePreview>
<div class="example-desc">Select an item for the Clear button to show.</div>
<div>
<BitDropDown ShowClearButton="true"
Label="Basic Uncontrolled"
Items="GetDropdownItems()"
Placeholder="Select an option"
Style="width: 100%; max-width: 290px; margin: 20px 0 20px 0"
@bind-Value="SelectedValue">
</BitDropDown>
<div>Value: @SelectedValue</div>
<br>
<hr />
<br>
<BitDropDown ShowClearButton="true"
Label="Multi-select uncontrolled"
Items="GetDropdownItems()"
Placeholder="Select options"
IsMultiSelect="true"
Style="width: 100%; max-width: 290px; margin-bottom: 20px;"
@bind-Values="SelectedValues">
</BitDropDown>
<div>Values: @string.Join(',', SelectedValues)</div>
</div>
</ExamplePreview>
</ComponentExampleBox>
</ComponentDemo>
Loading