Skip to content

Commit

Permalink
feat(components): add switch component (#31)
Browse files Browse the repository at this point in the history
* feat(components): initial implementation of the switch component

* chore(components): update the size styles of the switch component

* chore(components): set the size for the icons of the switch component

* chore(components): styles tweaks

* feat(components): derive the switch component from the boolean input base

* feat(components): derive the checkbox component from the boolean input base

* test(components): add tests for the switch component
  • Loading branch information
desmondinho authored Jul 6, 2024
1 parent b35d174 commit c31e296
Show file tree
Hide file tree
Showing 9 changed files with 537 additions and 49 deletions.
51 changes: 51 additions & 0 deletions src/LumexUI/Components/Bases/LumexBooleanInputBase.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
using System.Diagnostics.CodeAnalysis;

using Microsoft.AspNetCore.Components;

namespace LumexUI;

public abstract class LumexBooleanInputBase : LumexInputBase<bool>
{
/// <summary>
/// Gets or sets content to be rendered inside the input.
/// </summary>
[Parameter] public RenderFragment? ChildContent { get; set; }

/// <summary>
/// Gets the disabled state of the input.
/// Derived classes can override this to determine the input's disabled state.
/// </summary>
/// <returns>A <see cref="bool"/> value indicating whether the input is disabled.</returns>
protected internal virtual bool GetDisabledState() => Disabled;

/// <summary>
/// Gets the readonly state of the input.
/// Derived classes can override this to determine the input's readonly state.
/// </summary>
/// <returns>A <see cref="bool"/> value indicating whether the input is readonly.</returns>
protected internal virtual bool GetReadOnlyState() => ReadOnly;

/// <inheritdoc />
protected override bool TryParseValueFromString( string? value, [MaybeNullWhen( false )] out bool result )
{
throw new NotSupportedException(
$"This component does not parse string inputs. " +
$"Bind to the '{nameof( CurrentValue )}' property, not '{nameof( CurrentValueAsString )}'." );
}

/// <summary>
/// Handles the change event asynchronously.
/// Derived classes can override this to specify custom behavior when the input's value changes.
/// </summary>
/// <param name="args">The change event arguments.</param>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
protected virtual Task OnChangeAsync( ChangeEventArgs args )
{
if( GetDisabledState() || GetReadOnlyState() )
{
return Task.CompletedTask;
}

return SetCurrentValueAsync( (bool)args.Value! );
}
}
8 changes: 0 additions & 8 deletions src/LumexUI/Components/Bases/LumexInputBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,6 @@ public abstract class LumexInputBase<TValue> : LumexComponentBase
/// </remarks>
[Parameter] public ThemeColor Color { get; set; }

/// <summary>
/// Gets or sets the border radius of the input.
/// </summary>
/// <remarks>
/// The default is <see cref="Radius.Medium"/>
/// </remarks>
[Parameter] public Radius Radius { get; set; } = Radius.Medium;

/// <summary>
/// Gets or sets the size of the input.
/// </summary>
Expand Down
18 changes: 10 additions & 8 deletions src/LumexUI/Components/Checkbox/LumexCheckbox.razor
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
@namespace LumexUI
@inherits LumexInputBase<bool>
@inherits LumexBooleanInputBase

<LumexComponent As="label"
Class="@RootClass"
Style="@RootStyle"
data-checked="@_checked"
tabindex="1"
data-checked="@CurrentValue"
data-disabled="@GetDisabledState()"
data-readonly="@GetReadOnlyState()"
tabindex="0"
@attributes="@AdditionalAttributes">
<span class="@Utils.VisuallyHidden">
<input type="checkbox"
value="@true"
checked="@_checked"
disabled="@_disabled"
checked="@CurrentValue"
disabled="@GetDisabledState()"
@ref="@Element"
@attributes="@AdditionalAttributes"
@onchange="@OnChangeAsync" />
Expand Down Expand Up @@ -40,19 +42,19 @@
private void RenderCheckIcon( RenderTreeBuilder __builder )
{
var style = ElementStyle.Empty()
.Add( "transition", "stroke-dashoffset 0.15s linear 0.15s", when: _checked )
.Add( "transition", "stroke-dashoffset 0.15s linear 0.15s", when: CurrentValue )
.ToString();

<svg class="@IconClass" viewBox="0 0 17 18">
<polyline fill="none"
points="1 9 7 14 15 4"
stroke="currentColor"
stroke-dasharray="22"
stroke-dashoffset="@(_checked ? 44 : 66)"
stroke-dashoffset="@(CurrentValue ? 44 : 66)"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
style="@style" />
</svg>
}
}
}
45 changes: 12 additions & 33 deletions src/LumexUI/Components/Checkbox/LumexCheckbox.razor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,15 @@

namespace LumexUI;

public partial class LumexCheckbox : LumexInputBase<bool>, ISlotComponent<CheckboxSlots>
public partial class LumexCheckbox : LumexBooleanInputBase, ISlotComponent<CheckboxSlots>
{
/// <summary>
/// Gets or sets content to be rendered inside the checkbox.
/// Gets or sets the border radius of the checkbox.
/// </summary>
[Parameter] public RenderFragment? ChildContent { get; set; }
/// <remarks>
/// The default is <see cref="Radius.Medium"/>
/// </remarks>
[Parameter] public Radius Radius { get; set; } = Radius.Medium;

/// <summary>
/// Gets or sets the icon to be used for indicating a checked state of the checkbox.
Expand Down Expand Up @@ -42,10 +45,6 @@ public partial class LumexCheckbox : LumexInputBase<bool>, ISlotComponent<Checkb

private readonly RenderFragment _renderCheckIcon;

private bool _checked;
private bool _disabled;
private bool _readonly;

/// <summary>
/// Initializes a new instance of the <see cref="LumexCheckbox"/>.
/// </summary>
Expand All @@ -59,7 +58,7 @@ public override async Task SetParametersAsync( ParameterView parameters )
{
await base.SetParametersAsync( parameters );

Color = parameters.TryGetValue<ThemeColor>( nameof( Color ), out var color )
Color = parameters.TryGetValue<ThemeColor>( nameof( Color ), out var color )
? color
: Context?.Owner.Color ?? ThemeColor.Primary;

Expand All @@ -78,30 +77,10 @@ public override async Task SetParametersAsync( ParameterView parameters )
}

/// <inheritdoc />
protected override void OnParametersSet()
{
_checked = CurrentValue;
_disabled = Disabled || ( Context?.Owner.Disabled ?? false );
_readonly = ReadOnly || ( Context?.Owner.ReadOnly ?? false );
}
protected internal override bool GetDisabledState() =>
Disabled || ( Context?.Owner.Disabled ?? false );

/// <inheritdoc />
protected override bool TryParseValueFromString( string? value, out bool result )
{
throw new NotSupportedException(
$"This component does not parse string inputs. " +
$"Bind to the '{nameof( CurrentValue )}' property, not '{nameof( CurrentValueAsString )}'." );
}

internal bool GetDisabledState() => _disabled;

private Task OnChangeAsync( ChangeEventArgs args )
{
if( _disabled || _readonly )
{
return Task.CompletedTask;
}

return SetCurrentValueAsync( (bool)args.Value! );
}
}
protected internal override bool GetReadOnlyState() =>
ReadOnly || ( Context?.Owner.ReadOnly ?? false );
}
47 changes: 47 additions & 0 deletions src/LumexUI/Components/Switch/LumexSwitch.razor
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
@namespace LumexUI
@inherits LumexBooleanInputBase

<LumexComponent As="label"
Class="@RootClass"
Style="@RootStyle"
data-checked="@CurrentValue"
data-disabled="@GetDisabledState()"
data-readonly="@GetReadOnlyState()"
tabindex="0"
@attributes="@AdditionalAttributes">
<span class="@Utils.VisuallyHidden">
<input type="checkbox"
value="@true"
checked="@CurrentValue"
disabled="@GetDisabledState()"
@ref="@Element"
@attributes="@AdditionalAttributes"
@onchange="@OnChangeAsync" />
</span>

<span class="@WrapperClass">
@if( !string.IsNullOrEmpty( StartIcon ) )
{
<LumexIcon Icon="@StartIcon" Size="@(new("100%"))" Class="@StartIconClass" />
}

<span class="@ThumbClass">
@if( !string.IsNullOrEmpty( ThumbIcon ) )
{
<LumexIcon Icon="@ThumbIcon" Size="@(new("100%"))" Class="@ThumbIconClass" />
}
</span>

@if( !string.IsNullOrEmpty( EndIcon ) )
{
<LumexIcon Icon="@EndIcon" Size="@(new("100%"))" Class="@EndIconClass" />
}
</span>

@if( ChildContent is not null )
{
<span class="@LabelClass">
@ChildContent
</span>
}
</LumexComponent>
59 changes: 59 additions & 0 deletions src/LumexUI/Components/Switch/LumexSwitch.razor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// Copyright (c) LumexUI 2024
// LumexUI licenses this file to you under the MIT license
// See the license here https://github.com/LumexUI/lumexui/blob/main/LICENSE

using LumexUI.Common;
using LumexUI.Styles;

using Microsoft.AspNetCore.Components;

namespace LumexUI;

public partial class LumexSwitch : LumexBooleanInputBase, ISlotComponent<SwitchSlots>
{
/// <summary>
/// Gets or sets the icon to be used for indicating a checked state of the switch.
/// </summary>
[Parameter] public string? ThumbIcon { get; set; }

/// <summary>
/// Gets or sets the icon to be rendered before the switch.
/// </summary>
[Parameter] public string? StartIcon { get; set; }

/// <summary>
/// Gets or sets the icon to be rendered after the switch.
/// </summary>
[Parameter] public string? EndIcon { get; set; }

/// <summary>
/// Gets or sets the CSS class names for the switch slots.
/// </summary>
[Parameter] public SwitchSlots? Classes { get; set; }

private protected override string? RootClass =>
TwMerge.Merge( Switch.GetStyles( this ) );

private string? WrapperClass =>
TwMerge.Merge( Switch.GetWrapperStyles( this ) );

private string? ThumbClass =>
TwMerge.Merge( Switch.GetThumbStyles( this ) );

private string? ThumbIconClass =>
TwMerge.Merge( Switch.GetThumbIconStyles( this ) );

private string? StartIconClass =>
TwMerge.Merge( Switch.GetStartIconStyles( this ) );

private string? EndIconClass =>
TwMerge.Merge( Switch.GetEndIconStyles( this ) );

private string? LabelClass =>
TwMerge.Merge( Switch.GetLabelStyles( this ) );

public LumexSwitch()
{
Color = ThemeColor.Primary;
}
}
17 changes: 17 additions & 0 deletions src/LumexUI/Components/Switch/SwitchSlots.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using System.Diagnostics.CodeAnalysis;

using LumexUI.Common;

namespace LumexUI;

[ExcludeFromCodeCoverage]
public class SwitchSlots : ISlot
{
public string? Root { get; set; }
public string? Wrapper { get; set; }
public string? Thumb { get; set; }
public string? StartIcon { get; set; }
public string? EndIcon { get; set; }
public string? ThumbIcon { get; set; }
public string? Label { get; set; }
}
Loading

0 comments on commit c31e296

Please sign in to comment.