diff --git a/examples/Demo/FluentUI.Demo.Client/Documentation/Components/Checkbox/Examples/CheckboxAppearances.razor b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/Checkbox/Examples/CheckboxAppearances.razor new file mode 100644 index 0000000000..e4cfd9aa89 --- /dev/null +++ b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/Checkbox/Examples/CheckboxAppearances.razor @@ -0,0 +1,7 @@ +
+ + + + + +
diff --git a/examples/Demo/FluentUI.Demo.Client/Documentation/Components/Checkbox/Examples/CheckboxDisabled.razor b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/Checkbox/Examples/CheckboxDisabled.razor new file mode 100644 index 0000000000..99b18f0db5 --- /dev/null +++ b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/Checkbox/Examples/CheckboxDisabled.razor @@ -0,0 +1,7 @@ +
+ + + + + +
diff --git a/examples/Demo/FluentUI.Demo.Client/Documentation/Components/Checkbox/Examples/CheckboxIndeterminate.razor b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/Checkbox/Examples/CheckboxIndeterminate.razor new file mode 100644 index 0000000000..438316625a --- /dev/null +++ b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/Checkbox/Examples/CheckboxIndeterminate.razor @@ -0,0 +1,4 @@ +
+
+ +
diff --git a/examples/Demo/FluentUI.Demo.Client/Documentation/Components/Checkbox/Examples/CheckboxSizes.razor b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/Checkbox/Examples/CheckboxSizes.razor new file mode 100644 index 0000000000..dcbf6a4d4f --- /dev/null +++ b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/Checkbox/Examples/CheckboxSizes.razor @@ -0,0 +1,21 @@ +
+ + + + + + + + + +
+ + + + + + + + + +
diff --git a/examples/Demo/FluentUI.Demo.Client/Documentation/Components/Checkbox/Examples/CheckboxThreeStates.razor b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/Checkbox/Examples/CheckboxThreeStates.razor new file mode 100644 index 0000000000..54b1856419 --- /dev/null +++ b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/Checkbox/Examples/CheckboxThreeStates.razor @@ -0,0 +1,2 @@ +
+
diff --git a/examples/Demo/FluentUI.Demo.Client/Documentation/Components/Checkbox/FluentCheckbox.md b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/Checkbox/FluentCheckbox.md new file mode 100644 index 0000000000..1e3bf0cde4 --- /dev/null +++ b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/Checkbox/FluentCheckbox.md @@ -0,0 +1,53 @@ +--- +title: Checkbox +route: /Checkbox +--- + +# Checkbox + +A **FluentCheckbox** component enables a user to select or deselect an option. +It's typically used to capture a boolean value. + +## Appearance + +The apparent style of a checkbox can be changed by setting the `Shape` property, but also by setting the `Size` property. + +You can also add a label to the checkbox by setting the `Label` property. +The label will be automatically positioned next to the checkbox. + +We recommend using a spacing of 24px between checkboxes and other components. + +{{ CheckboxAppearances }} + +### Size + +The size of the checkbox can be adjusted using the `Size` property. The available sizes are: + +- `Medium`: The default size. +- `Large`: A larger size for the checkbox. + +{{ CheckboxSizes }} + +### Indeterminate + +The `FluentCheckbox` component supports an indeterminate state, which can be useful for scenarios where a checkbox represents a mixed or partial selection. +The indeterminate state is visually distinct from the checked and unchecked states. + +To set the checkbox to the indeterminate state, use the `Indeterminate` property. + +{{ CheckboxIndeterminate }} + +## Three-State Checkbox + +The `FluentCheckbox` component supports a three-state mode, which allows the checkbox to have an additional indeterminate state. This can be useful for scenarios where a checkbox represents a mixed or partial selection. + +To enable the three-state mode, set the `ThreeState` property to `true`. You can also control the order of the states using the `ThreeStateOrderUncheckToIntermediate` property. + +- `ThreeState`: Enables the three-state mode. +- `ThreeStateOrderUncheckToIntermediate`: Controls the order of the states. If set to `true`, the order will be Unchecked -> Intermediate -> Checked. If set to `false` (default), the order will be Unchecked -> Checked -> Intermediate. + +{{ CheckboxThreeStates }} + +## API FluentCheckbox + +{{ API Type=FluentCheckbox }} diff --git a/examples/Demo/FluentUI.Demo.Client/FluentUI.Demo.Client.csproj b/examples/Demo/FluentUI.Demo.Client/FluentUI.Demo.Client.csproj index 146e6fb5f1..52567f4b1b 100644 --- a/examples/Demo/FluentUI.Demo.Client/FluentUI.Demo.Client.csproj +++ b/examples/Demo/FluentUI.Demo.Client/FluentUI.Demo.Client.csproj @@ -1,4 +1,4 @@ - + net9.0 diff --git a/src/Core/Components/Checkbox/FluentCheckbox.razor b/src/Core/Components/Checkbox/FluentCheckbox.razor new file mode 100644 index 0000000000..52575c4a21 --- /dev/null +++ b/src/Core/Components/Checkbox/FluentCheckbox.razor @@ -0,0 +1,43 @@ +@namespace Microsoft.FluentUI.AspNetCore.Components +@using Microsoft.FluentUI.AspNetCore.Components.Extensions +@inherits FluentInputBase + + + @if (HasLabel()) + { + + } + + + + @if (StartTemplate != null) + { +
+ @StartTemplate +
+ } + + @if (EndTemplate != null) + { +
+ @EndTemplate +
+ } +
+
diff --git a/src/Core/Components/Checkbox/FluentCheckbox.razor.cs b/src/Core/Components/Checkbox/FluentCheckbox.razor.cs new file mode 100644 index 0000000000..7d848a2ca8 --- /dev/null +++ b/src/Core/Components/Checkbox/FluentCheckbox.razor.cs @@ -0,0 +1,126 @@ +// ------------------------------------------------------------------------ +// MIT License - Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------------------ + +using System.Diagnostics.CodeAnalysis; +using System.Reflection.Metadata; +using Microsoft.AspNetCore.Components; +using Microsoft.FluentUI.AspNetCore.Components.Utilities; +using Microsoft.JSInterop; + +namespace Microsoft.FluentUI.AspNetCore.Components; + +/// +/// The FluentCheckbox component is used to render a checkbox input... +/// Test WIP +/// +public partial class FluentCheckbox : FluentInputBase, IFluentComponentElementBase +{ + private const string JAVASCRIPT_FILE = FluentJSModule.JAVASCRIPT_ROOT + "Checkbox/FluentCheckbox.razor.js"; + + /// + [Parameter] + public ElementReference Element { get; set; } + + /// + /// Gets or sets the checked state of the checkbox + /// + [Parameter] + public bool? Checked { get; set; } + + /// + /// Gets or sets the indeterminate state of the checkbox + /// + [Parameter] + public bool? Indeterminate { get; set; } + + /// + /// Gets or sets a value indicating whether the CheckBox will allow three check states rather than two. + /// + [Parameter] + public bool ThreeState { get; set; } + + /// + /// Gets or sets a value indicating the order of the three states of the CheckBox. + /// False(by default), the order is Unchecked -> Checked -> Intermediate. + /// True: the order is Unchecked -> Intermediate -> Checked. + /// + [Parameter] + public bool ThreeStateChecked { get; set; } + + /// + /// Gets or sets the shape of the checkbox + /// + [Parameter] + public CheckboxShape Shape { get; set; } = CheckboxShape.Square; + + /// + /// Gets or sets the content to prefix the input component. + /// + [Parameter] + public virtual RenderFragment? StartTemplate { get; set; } + + /// + /// Gets or sets the content to suffix the input component. + /// + [Parameter] + public virtual RenderFragment? EndTemplate { get; set; } + + /// + /// Gets or sets the size of the checkbox. See + /// + [Parameter] + public CheckboxSize Size { get; set; } = CheckboxSize.Medium; + + /// + protected override void OnParametersSet() + { + base.OnParametersSet(); + if (HasLabel()) + { + // When the id is not provided, generate a unique id. This allow to use the label for. + Id ??= $"checkbox-{Identifier.NewId()}"; + } + } + + /// + /// Parses a string to create the . + /// + /// The string value to be parsed. + /// The result to inject into the Value. + /// If the value could not be parsed, provides a validation error message. + /// True if the value could be parsed; otherwise false. + protected override bool TryParseValueFromString(string? value, [MaybeNullWhen(false)] out bool? result, [NotNullWhen(false)] out string? validationErrorMessage) + { + if (bool.TryParse(value, out var parsedValue)) + { + result = parsedValue; + validationErrorMessage = null; + return true; + } + + result = null; + validationErrorMessage = "The provided value is not a valid boolean."; + return false; + } + /// + protected override async Task OnAfterRenderAsync(bool firstRender) + { + if (firstRender) + { + if (Indeterminate.HasValue && Indeterminate == true) + { + await SetElementToIndeterminateAsync(); + } + } + } + + private async Task SetElementToIndeterminateAsync() + { + var jsModule = await JSModule.ImportJavaScriptModuleAsync(JAVASCRIPT_FILE); + await jsModule.InvokeVoidAsync("Microsoft.FluentUI.Blazor.TextInput.ObserveAttributeChanges", Element); + } + + private bool HasLabel() + => !string.IsNullOrEmpty(Label) || LabelTemplate is not null; +} diff --git a/src/Core/Components/Checkbox/FluentCheckbox.razor.ts b/src/Core/Components/Checkbox/FluentCheckbox.razor.ts new file mode 100644 index 0000000000..e1cad5bb06 --- /dev/null +++ b/src/Core/Components/Checkbox/FluentCheckbox.razor.ts @@ -0,0 +1,27 @@ +export namespace Microsoft.FluentUI.Blazor.Checkbox { + + /** + * Observe the changes in the ‘value’ attribute to update the element's ‘value’ property. + * Wait for this PR to delete the code. + * https://github.com/microsoft/fluentui/pull/33144 + */ + export function ObserveAttributeChanges(element: HTMLElement): void { + const observer = new MutationObserver((mutationsList) => { + for (let mutation of mutationsList) { + if (mutation.type === "attributes" && mutation.attributeName === "value") { + const newValue = element.getAttribute("value"); + const field = element as any; + if (newValue !== field.value) { + field.value = newValue; + } + } + } + }); + + observer.observe(element, { attributes: true }); + } + + export function SetIndeterminate(element: any ): void { + element.indeterminate = true; + } +} diff --git a/src/Core/Enums/CheckboxShape.cs b/src/Core/Enums/CheckboxShape.cs new file mode 100644 index 0000000000..aed04084fa --- /dev/null +++ b/src/Core/Enums/CheckboxShape.cs @@ -0,0 +1,25 @@ +// ------------------------------------------------------------------------ +// MIT License - Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------------------ + +using System.ComponentModel; + +namespace Microsoft.FluentUI.AspNetCore.Components; + +/// +/// The visual appearance of the . +/// +public enum CheckboxShape +{ + /// + /// The default appearance. The border is square. + /// + [Description("square")] + Square, + + /// + /// The appearance where the border is circular. + /// + [Description("circular")] + Circular, +} diff --git a/src/Core/Enums/CheckboxSize.cs b/src/Core/Enums/CheckboxSize.cs new file mode 100644 index 0000000000..71af64b5e8 --- /dev/null +++ b/src/Core/Enums/CheckboxSize.cs @@ -0,0 +1,25 @@ +// ------------------------------------------------------------------------ +// MIT License - Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------------------ + +using System.ComponentModel; + +namespace Microsoft.FluentUI.AspNetCore.Components; + +/// +/// Indicates the size of the . +/// +public enum CheckboxSize +{ + /// + /// Medium size. + /// + [Description("medium")] + Medium, + + /// + /// Large size. + /// + [Description("large")] + Large, +} diff --git a/src/Core/Events/CheckboxChangeEventArgs.cs b/src/Core/Events/CheckboxChangeEventArgs.cs new file mode 100644 index 0000000000..522e996c0b --- /dev/null +++ b/src/Core/Events/CheckboxChangeEventArgs.cs @@ -0,0 +1,21 @@ +// ------------------------------------------------------------------------ +// MIT License - Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------------------ + +namespace Microsoft.FluentUI.AspNetCore.Components; + +/// +/// Event arguments for the Checkbox change event. +/// +public class CheckboxChangeEventArgs : EventArgs +{ + /// + /// Gets or sets the checked state of the checkbox + /// + public bool? Checked { get; set; } + + /// + /// Gets or sets the indeterminate state of the checkbox + /// + public bool? Indeterminate { get; set; } +}