-
Notifications
You must be signed in to change notification settings - Fork 394
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[FluentCheckbox] Add 3-states (check/uncheck/indeterminate) (#1022)
* First version * Update using a isUpdating flag * Fix events * Update the CheckState property * Add ShowIndeterminate property * Add Unit Tests * Update js path --------- Co-authored-by: Vincent Baaij <[email protected]>
- Loading branch information
Showing
15 changed files
with
385 additions
and
47 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
40 changes: 17 additions & 23 deletions
40
examples/Demo/Shared/Pages/Checkbox/Examples/CheckboxDefault.razor
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,28 +1,22 @@ | ||
<h4>Standard</h4> | ||
<p>Without a label:</p> | ||
<FluentCheckbox @bind-Value=value1></FluentCheckbox> | ||
<h4>Horizontal</h4> | ||
<FluentStack> | ||
<FluentCheckbox @bind-Value="@value1" Label="Apples" /> | ||
<FluentCheckbox @bind-Value="@value2" Disabled="true" Label="Bananas (disabled)" /> | ||
<FluentCheckbox @bind-Value="@value3" Label="Oranges" /> | ||
</FluentStack> | ||
|
||
<p>With a label: </p> | ||
<FluentCheckbox @bind-Value=value2 Label="With a label:"></FluentCheckbox> | ||
<br /> | ||
<br /> | ||
|
||
<h4>Checked</h4> | ||
<FluentCheckbox @bind-Value=value3></FluentCheckbox> | ||
<h4>Vertical</h4> | ||
<FluentStack Orientation="Orientation.Vertical"> | ||
<FluentCheckbox @bind-Value="@value1">Apples</FluentCheckbox> | ||
<FluentCheckbox @bind-Value="@value2" Disabled="true">Bananas (disabled)</FluentCheckbox> | ||
<FluentCheckbox @bind-Value="@value3">Oranges</FluentCheckbox> | ||
</FluentStack> | ||
|
||
<!-- Required --> | ||
<h4>Required</h4> | ||
<FluentCheckbox Required="true" @bind-Value=value4></FluentCheckbox> | ||
|
||
<!-- Disabled --> | ||
<h4>Disabled</h4> | ||
<FluentCheckbox Disabled="true" @bind-Value=value5></FluentCheckbox> | ||
<FluentCheckbox Disabled="true" @bind-Value=value6>label</FluentCheckbox> | ||
<FluentCheckbox Disabled="true" @bind-Value=value7>Checked</FluentCheckbox> | ||
|
||
<h4>Inline</h4> | ||
<FluentCheckbox @bind-Value=value8>Apples</FluentCheckbox> | ||
<FluentCheckbox @bind-Value=value9>Bananas</FluentCheckbox> | ||
<FluentCheckbox @bind-Value=value10>Honeydew</FluentCheckbox> | ||
<FluentCheckbox @bind-Value=value11>Oranges</FluentCheckbox> | ||
@code { | ||
bool value1, value2, value3 = true, value4, value5, value6, value7 = true, value8 = true, value9 = true, value10, value11 = true; | ||
bool value1 = true; | ||
bool value2 = true; | ||
bool value3; | ||
} |
25 changes: 25 additions & 0 deletions
25
examples/Demo/Shared/Pages/Checkbox/Examples/CheckboxThreeState.razor
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
<FluentStack Style="margin: 20px;"> | ||
<FluentCheckbox Id="check1" ThreeState="true" @bind-Value="@value1" @bind-CheckState="@state1" Label="ThreeState = true" Style="min-width: 250px;" /> | ||
<div> | ||
Value = @value1 - CheckState = @(state1?.ToString() ?? "null (Indeterminate)") | ||
</div> | ||
</FluentStack> | ||
|
||
<FluentStack Style="margin: 20px;"> | ||
<FluentCheckbox Id="check2" ThreeState="false" @bind-Value="@value2" Label="ThreeState = false" Style="min-width: 250px;" /> | ||
<div> | ||
Value = @value2 | ||
</div> | ||
</FluentStack> | ||
|
||
<FluentStack Style="margin: 20px;"> | ||
<FluentCheckbox Id="check3" ThreeState="true" @bind-Value="@value3" @bind-CheckState="@state3" ShowIndeterminate="false" Label="ShowIndeterminate = false" Style="min-width: 250px;" /> | ||
<div> | ||
Value = @value3 - CheckState = @(state3?.ToString() ?? "null (Indeterminate)") | ||
</div> | ||
</FluentStack> | ||
|
||
@code { | ||
bool value1, value2, value3; | ||
bool? state1 = false, state3 = null; | ||
} |
11 changes: 0 additions & 11 deletions
11
examples/Demo/Shared/Pages/Checkbox/Examples/CheckboxVertical.razor
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,21 +1,189 @@ | ||
using System.Diagnostics.CodeAnalysis; | ||
using Microsoft.AspNetCore.Components; | ||
using Microsoft.JSInterop; | ||
|
||
namespace Microsoft.FluentUI.AspNetCore.Components; | ||
public partial class FluentCheckbox : FluentInputBase<bool> | ||
{ | ||
private const bool VALUE_FOR_INDETERMINATE = false; | ||
private bool _intermediate = false; | ||
private bool? _checkState = false; | ||
private const string JAVASCRIPT_FILE = "./_content/Microsoft.FluentUI.AspNetCore.Components/Components/Checkbox/FluentCheckbox.razor.js"; | ||
|
||
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(CheckboxChangeEventArgs))] | ||
public FluentCheckbox() | ||
{ | ||
Id = Identifier.NewId(); | ||
} | ||
|
||
/// <summary /> | ||
[Inject] | ||
private IJSRuntime JSRuntime { get; set; } = default!; | ||
|
||
/// <summary /> | ||
private IJSObjectReference? Module { get; set; } | ||
|
||
/// <summary> | ||
/// Gets or sets the content to be rendered inside the component. | ||
/// </summary> | ||
[Parameter] | ||
public RenderFragment? ChildContent { get; set; } | ||
|
||
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(CheckboxChangeEventArgs))] | ||
/// <summary> | ||
/// Gets or sets a value indicating whether the CheckBox will allow three check states rather than two. | ||
/// </summary> | ||
[Parameter] | ||
public bool ThreeState { get; set; } = false; | ||
|
||
public FluentCheckbox() | ||
/// <summary> | ||
/// Gets or sets a value indicating whether the user can display the indeterminate state by clicking the CheckBox. | ||
/// If this is not the case, the checkbox can be started in the indeterminate state, but the user cannot activate it with the mouse. | ||
/// Default is true. | ||
/// </summary> | ||
[Parameter] | ||
public bool ShowIndeterminate { get; set; } = true; | ||
|
||
/// <summary> | ||
/// Gets or sets the state of the CheckBox: true, false or null. | ||
/// </summary> | ||
[Parameter] | ||
public bool? CheckState | ||
{ | ||
get => _checkState; | ||
set | ||
{ | ||
if (!ThreeState) | ||
{ | ||
throw new ArgumentException("Set the `ThreeState` attribute to True to use this `CheckState` property."); | ||
} | ||
|
||
if (_checkState != value) | ||
{ | ||
_checkState = value; | ||
_ = SetCurrentAndIntermediate(value); | ||
} | ||
} | ||
} | ||
|
||
[Parameter] | ||
public EventCallback<bool?> CheckStateChanged { get; set; } | ||
|
||
/// <summary /> | ||
private async Task SetCurrentAndIntermediate(bool? value) | ||
{ | ||
switch (value) | ||
{ | ||
// Checked | ||
case true: | ||
await SetCurrentValue(true); | ||
await SetIntermediateAsync(false); | ||
break; | ||
|
||
// Unchecked | ||
case false: | ||
await SetCurrentValue(false); | ||
await SetIntermediateAsync(false); | ||
break; | ||
|
||
// Indeterminate | ||
default: | ||
await SetCurrentValue(VALUE_FOR_INDETERMINATE); | ||
await SetIntermediateAsync(true); | ||
break; | ||
} | ||
} | ||
|
||
/// <summary /> | ||
private async Task SetIntermediateAsync(bool intermediate) | ||
{ | ||
// Force the Indeterminate state to be set. | ||
// Each time the user clicks the checkbox, the Indeterminate state is reset to false. | ||
Module ??= await JSRuntime.InvokeAsync<IJSObjectReference>("import", JAVASCRIPT_FILE); | ||
await Module.InvokeVoidAsync("setFluentCheckBoxIndeterminate", Id, intermediate, Value); | ||
|
||
_intermediate = intermediate; | ||
} | ||
|
||
/// <summary /> | ||
private async Task SetCurrentCheckState(bool newChecked) | ||
{ | ||
// Value: False -> True -> False -> False | ||
// Intermediate: False -> False -> True -> False | ||
// --------------------------------------------- | ||
// Uncheck -> Check -> Indeterminate -> Uncheck | ||
|
||
// Uncheck -> Check | ||
if (newChecked && !_intermediate) | ||
{ | ||
await SetCurrentAndIntermediate(true); | ||
await UpdateAndRaiseCheckStateEvent(true); | ||
} | ||
|
||
// Check -> Indeterminate (or Uncheck is ShowIndeterminate is false) | ||
else if (!newChecked && !_intermediate) | ||
{ | ||
if (ShowIndeterminate) | ||
{ | ||
await SetCurrentAndIntermediate(null); | ||
await UpdateAndRaiseCheckStateEvent(null); | ||
} | ||
else | ||
{ | ||
await SetCurrentAndIntermediate(false); | ||
await UpdateAndRaiseCheckStateEvent(false); | ||
} | ||
} | ||
|
||
// Indeterminate -> Uncheck | ||
else if (newChecked && _intermediate) | ||
{ | ||
await SetCurrentAndIntermediate(false); | ||
await UpdateAndRaiseCheckStateEvent(false); | ||
} | ||
|
||
// | ||
else | ||
{ | ||
await SetCurrentAndIntermediate(false); | ||
await UpdateAndRaiseCheckStateEvent(false); | ||
} | ||
} | ||
|
||
/// <summary /> | ||
private async Task OnCheckedChangeHandlerAsync(CheckboxChangeEventArgs e) | ||
{ | ||
if (e.Checked == null && e.Indeterminate == null) | ||
{ | ||
return; | ||
} | ||
|
||
if (ThreeState) | ||
{ | ||
await SetCurrentCheckState(e.Checked ?? false); | ||
} | ||
else | ||
{ | ||
await SetCurrentValue(e.Checked ?? false); | ||
await SetIntermediateAsync(false); | ||
await UpdateAndRaiseCheckStateEvent(e.Checked ?? false); | ||
} | ||
} | ||
|
||
/// <summary /> | ||
private async Task UpdateAndRaiseCheckStateEvent(bool? value) | ||
{ | ||
if (_checkState != value) | ||
{ | ||
_checkState = value; | ||
|
||
if (CheckStateChanged.HasDelegate) | ||
{ | ||
await CheckStateChanged.InvokeAsync(value); | ||
} | ||
} | ||
} | ||
|
||
/// <summary /> | ||
protected override bool TryParseValueFromString(string? value, out bool result, [NotNullWhen(false)] out string? validationErrorMessage) => throw new NotSupportedException($"This component does not parse string inputs. Bind to the '{nameof(CurrentValue)}' property, not '{nameof(CurrentValueAsString)}'."); | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
export function setFluentCheckBoxIndeterminate(id, indeterminate, checked) { | ||
var item = document.getElementById(id); | ||
if (!!item) { | ||
item.isUpdating = true; | ||
|
||
// Need to update Checked before Indeterminate | ||
item.checked = checked; | ||
item.indeterminate = indeterminate; | ||
|
||
item.isUpdating = false; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.