Blazor Web程序集(WASM)拨动开关

目录

介绍

设计

要求

Light主题

Dark主题

自定义主题

自定义用户界面

实现

Aria Compliance

切换用户界面

​编辑

组件样式表

主题

Light主题

Dark主题

Disabled

Primary Color Theming

自定义用户界面

编码

标记

后端代码

组件部件ID

组件配置

切换输入验证

键盘支持

用法

切换

切换输入

自定义布局

ColorSelect——Bonus组件

概括


介绍

这是Blazor文章系列中的第二篇,该文章专注于内置明暗主题的控件开发。查看第一篇文章,该文章为本篇文章和后续文章的主题支持奠定了基础。

设计

要求

该组件需要支持:

  • 在标准HTMLBlazor EditForm中使用
  • 控制状态:
    • Enabled
    • Focus
    • Hover
    • Checked
    • Disabled
    • Read-only
  • 控制部件的显示
    • Positioning
    • Visibility
    • 所有文本可更改和自定义标记
  • 类、样式和自定义属性
  • Aria compliance
  • 主题支持
  • C# Nullable合规性
  • 可跨多个项目重用
  • 如果不可避免,最少的JavaScript
  • 最新的BlazorCSS3编码技术
  • BEM —(块元素修饰符) CSS类命名约定

在各种Blazor第三方库中有许多不同的拨动开关设计。对于这个控件,我的外观基于标准的Windows外观。

Light主题

 

Dark主题

 

自定义主题

我们将探索两种不同的切换主题颜色的实现:

  1. 通过样式直接在HTML标签上应用CSS变量
  2. 使用预设的CSS类名称

下面是一个示例,我们为基础颜色和悬停颜色应用了红色原色方案。

 

自定义用户界面

在本文后面,我们将探讨如何使用六种不同的设计自定义外观。虽然每个组件的外观和动画都不同,但底层HTML保持不变。

实现

Aria Compliance

虽然没有具体的要求文档,但是,由于它基于input type="checkbox",我们可以遵守Dual-state requirements。也有一个例子

切换用户界面

 

拨动开关由三个部分控制:

  1. 标签/标题——通知用户选择的内容的文本
  2. Toggle——可点击的开/关开关
  3. 状态——指示切换状态的文本

对于自定义UI,我添加了第四部分。这不用于默认组件。渲染控件时,标记如下:

<div class="c-toggle">
    <div id="id_piLBO02PjEqg5wHdYvv4gA" class="c-toggle__label">
        Enabled and checked
    </div>
    <div class="c-toggle__container">
        <input type="checkbox" role="switch"
               id="id_VfH38gwO8UOP45Y_g7v5bg"
               class="c-toggle__pill"
               aria-labelledby="id_nB-HBiD9DUORGBYQXFU2sA"
               aria-checked="true"
               aria-readonly="false">
        <label class="c-toggle__thumb" 
               for="id_VfH38gwO8UOP45Y_g7v5bg"
               aria-labelledby="id_piLBO02PjEqg5wHdYvv4gA"
               data-label="On" data-label-on="On" data-label-off="Off"
               tabindex="0">
            <span class="c-toggle__thumb-inner"></span>
        </label>
        <span id="id_nB-HBiD9DUORGBYQXFU2sA"
              class="c-toggle__state-text">On</span>
    </div>
</div>

按类名细分:

  • c-toggle——根容器
  • c-toggle__label——标题标签
  • c-toggle__container——保持开关和状态。允许灵活定位
  • c-toggle__pill——保存组件的状态值。它用于HTML FormBlazor EditForm输入控制要求。readonlydisabledcheckedonchange事件跟踪的UI属性已连接到此标记上。
  • c-toggle__thumb——组件的默认UI::after选择器用于呈现打开和关闭状态的开关拇指位置。还有data-label(已删除状态)、data-label-on(打开状态文本)和data-label-off(关闭状态文本)用于自定义UI支持。tabindex属性用于告诉浏览器在哪里设置焦点。
  • c-toggle__thumb-inner——这不用于默认渲染,但用于自定义UI支持。
  • c-toggle__state-text——开启和关闭状态的文本标签。

组件样式表

Toggle开关实现为两个独立的组件——一个用于Blazor EditForm,另一个用于一般用途。为了提高灵活性并减少重复,没有使用CSS Isolation,但Razor类库(RCL)中包含一个默认样式表。要使用,我们需要包含在index.html标头中。

<link href="_content/Blazor.Toggle/css/styles.css" rel="stylesheet" />

样式表的顺序对于CSS特殊性要求很重要:

  1. 基本CSS框架——例如:Bootstrap
  2. 库样式表
  3. 应用程序样式表

因此,例如,对于演示项目,使用以下内容:

<link href="css/bootstrap/bootstrap.min.css" rel="stylesheet" />
<link href="_content/Blazor.Toggle/css/styles.css" rel="stylesheet" />
<link href="ToggleDemo.styles.css" rel="stylesheet" />
<link href="css/app.css" rel="stylesheet" />

主题

使用CSS变量来启用在LightDark模式之间的切换。查看之前的文章,了解这是如何实现的。

我尝试不使用特定于组件的CSS变量名称,而是使用通用名称,以便将来与其他控件一起使用。

Light主题

--primary-fill: #0078D4;
--primary-fill-hover: #006CBE;
--primary-foreground: #FFFFFF;
--neutral-fill: #EDEDED;
--neutral-fill-hover: #E5E5E5;
--neutral-outline: #646464;
--neutral-outline-hover: #3B3B3B;
--neutral-foreground: #2B2B2B;
--neutral-focus-visual: 0 0 0 0.25rem rgba(13, 110, 253, 0.25);
--neutral-shadow-visual:  0 10px 20px -8px #3B3B3B;
--neutral-border: #BEBEBE;
--neutral-color1: #767676;
--neutral-color2: #FFFFFF;
--neutral-background1: #FFFFFF;
--neutral-background2: #F7F7F7;
--icon-backkground: #b6d2e8;

Dark主题

--primary-fill: #006CBE;
--primary-fill-hover: #0078D4;
--primary-foreground: #FFFFFF;
--neutral-fill: #363636;
--neutral-fill-hover: #3D3D3D;
--neutral-outline: #646464;
--neutral-outline-hover: #8A8A8A;
--neutral-foreground: #F5F5F5;
--neutral-focus-visual: 0 0 0 0.25rem rgba(120,120,120,0.25);
--neutral-shadow-visual: 0 10px 20px -8px #8A8A8A;
--neutral-border: #323232;
--neutral-color1: #8D8D8D;
--neutral-color2: #929292;
--neutral-background1: #202020;
--neutral-background2: #424242;
--icon-backkground: #0066B4

Disabled

对于禁用状态,不透明度用于减少使用的CSS变量的数量。

--disabled-opacity: 0.3;

Primary Color Theming

包括一些替代颜色。

.c-toggle__primary-indigo {
    --primary-fill: #6610f2;
    --primary-fill-hover: #580ED1;
}

.c-toggle__primary-purple {
    --primary-fill: #6f42c1;
    --primary-fill-hover: #633BAD;
}

.c-toggle__primary-red {
    --primary-fill: #dc3545;
    --primary-fill-hover: #C72F3E;
}

.c-toggle__primary-orange {
    --primary-fill: #fd7e14;
    --primary-fill-hover: #EB7512;
}

.c-toggle__primary-green {
    --primary-fill: #198754;
    --primary-fill-hover: #177A4C;
}

作为演示代码的一部分,我演示了如何使用两种常用方法添加自己的自定义主题颜色:使用StylesClasses

自定义用户界面

演示项目中还包含六个自定义UI,除了CSS类之外,它们都使用相同的组件属性。CSS类用于应用更改。

 

正如您从上面的屏幕截图中看到的那样,ToggleToggleInput组件的渲染标记是相同的,因此自定义UI CSS将与两者无缝协作。

这是渲染Toggle组件的代码:

<div class="@CssSection">

    <h3>Standard <b>Toggle</b> Component</h3>

    <div class="o-custom__sections">
        @foreach (CustomToggleModel model in Toggles)
        {
          <section class="o-section__custom">
                <Toggle Value="@true"
                        Class="@model.CssClass"
                        OnText="@model.YesChoice"
                        OffText="@model.NoChoice" />
          </section>
        }
    </div>
</div>

这是渲染ToggleInput组件的代码:

<EditForm class="@CssSection" Model="Toggles">

    <h3>EditForm <b>ToggleInput</b> Component</h3>

    <div class="o-custom__sections">
        @foreach (CustomToggleModel model in Toggles)
        {
            <section class="o-section__custom">
                <ToggleInput @bind-Value="model.IsChecked"
                        Class="@model.CssClass"
                        OnText="@model.YesChoice"
                        OffText="@model.NoChoice" />
            </section>
        }
    </div>
</EditForm>

用于分配CSS类和默认选项和名称的C#代码:

#region BEM

private readonly string CssSection
    = "o-section".JoinName("body");

#endregion

#region Fields

    private const string NoChoiceValue = "No";
    private const string YesChoiceValue = "Yes";

#endregion

#region Properties

private List<CustomToggleModel> Toggles { get; } = new()
{
// [..trimmed..]
    new ()
    {
        NoChoice = NoChoiceValue,
        YesChoice = YesChoiceValue,
        CssClass = "custom__toggle
                    custom__toggle-2
                    custom__toggle--position"
    },
// [..trimmed..]
};

注意:以上自定义UI选自Free Frontend网站。值得一试,因为他们有很多可供选择。

编码

标记

Razor非常不言自明:

  1. 每个部分的定位都可以通过属性和方法进行选择:HasLabelHasOnOffLabel()Position
  2. 标题标签通过LabelContent支持自定义内容。
  3. OnOff状态文本通过OnTextOffText属性设置。活动状态是使用StateLabel私有属性设置的。
  4. 组件UI disableenabled>and <checked>UI属性通过input标签上privatepublic的组件属性设置。

<div class="@Classname" style="@Style">
    @if (HasLabel())
    {
        <div id="@LabelId" class="@CSS.Label">
            @if (LabelContent is not null)
            {
                @LabelContent
            }
            else
            {
                @Label
            }
        </div>
    }
    <div class="@CSS.Container">
        @if (Position == TogglePosition.Left)
        {
            <input type="checkbox" role="switch"
                   id="@PillId" class="@CSS.Pill"
                   aria-labelledby="@StateLabelId"
                   aria-checked="@Value.ToString().ToLower()"
                   aria-readonly="@Disabled.ToString().ToLower()"
                   checked="@Value" disabled="@Disabled"
                   onchange="@OnChange"
                   @onclick:preventDefault="@ReadOnly"/>

            <label class="@CSS.Thumb" for="@PillId"
                   aria-labelledby="@LabelId"
                   data-label="@StateLabel"
                   data-label-on="@OnText"
                   data-label-off="@OffText"
                   tabindex="0"
                   @onkeydown="@(OnKeyDownAsync)"
                   @onkeydown:preventDefault="true"
                   @onkeydown:stopPropagation="true">
                <span class="@CSS.ThumbInner"></span>
            </label>

            @if (HasOnOffLabel())
            {
                <span id="@StateLabelId" class="@CSS.State">
                    @StateLabel
                </span>
            }
        }
        else
        {
            @if (HasOnOffLabel())
            {
                <span id="@StateLabelId" class="@CSS.State">
                    @StateLabel
                </span>
            }

            <input type="checkbox" role="switch"
                   id="@PillId" class="@CSS.Pill"
                   aria-labelledby="@StateLabelId"
                   aria-checked="@Value.ToString().ToLower()"
                   aria-readonly="@Disabled.ToString().ToLower()"
                   checked="@Value" disabled="@Disabled"
                   onchange="@OnChange"
                   @onclick:preventDefault="@ReadOnly"/>

            <label class="@CSS.Thumb" for="@PillId"
                   aria-labelledby="@LabelId"
                   data-label="@StateLabel"
                   data-label-on="@OnText"
                   data-label-off="@OffText"
                   tabindex="0"
                   @onkeydown="@(OnKeyDownAsync)"
                   @onkeydown:preventDefault="true"
                   @onkeydown:stopPropagation="true">
                <span class="@CSS.ThumbInner"></span>
            </label>
        }
    </div>
</div> 

ToggleToggleInputinput组件之间的关键Razor代码差异是标签。

对于Toggle组件:

<input type="checkbox" role="switch"
       id="@PillId" class="@CSS.Pill"
       aria-labelledby="@StateLabelId"
       aria-checked="@Value.ToString().ToLower()"
       aria-readonly="@Disabled.ToString().ToLower()"
       checked="@Value" disabled="@Disabled"
       onchange="@OnChange" @onclick:preventDefault="@ReadOnly"/>

使用Toggle组件,我们手动绑定到Value属性并挂钩到onchange事件。当onchange事件触发时,我们手动更新Value属性并调用StateHasChanged()更新UI

对于ToggleInput组件:

<input type="checkbox" role="switch"
       id="@PillId" class="@CSS.Pill"
       aria-labelledby="@StateLabelId"
       aria-checked="@CurrentValue.ToString().ToLower()"
       aria-readonly="@Disabled.ToString().ToLower()"
       @bind=CurrentValue disabled="@Disabled"
       @onclick:preventDefault="@ReadOnly"/>

使用ToggleInput,我们可以双向绑定到基InputBae<T>类中,当值发生变化时,基类将处理UI刷新。

后端代码

我将专注于关键逻辑并跳过属性。属性是直截了当的。

组件部件ID

AriaHTML标记要求各个部分相互指向。示例:aria-labelledby, & for。我们只需要在组件创建时设置一次。

protected override void OnInitialized()
{
    _labelId = GetUniqueId();
    _pillId = GetUniqueId();
    _stateLabelId = GetUniqueId();

    if (DefaultValue)
        Value = true;

    base.OnInitialized();
}

辅助方法用于唯一ID。可以在Blazor.Common库中找到。

在组件基类中:

public string GetUniqueId()
    => "id_" + Guid.NewGuid().ToShortString();

扩展方法:

public static class GuidExtensions
{
    public static string ToShortString(this Guid guid)
        => Convert.ToBase64String(guid
                     .ToByteArray())
                     .Replace('+', '-').Replace('/', '_')[..22];
}

组件配置

CSS类名称用于根据属性集配置UI

private string Classname
{
    get
    {
        CssBuilder builder = new CssBuilder(CSS.Root);

        if (Disabled)
            builder.AddClass(CSS.Modifier.Disabled);

        if (InlineLabel)
            builder.AddClass(CSS.Modifier.InlineLabel);

        if (!HasOnOffLabel())
            builder.AddClass(CSS.Modifier.NoOnOffLabel);

        if (!string.IsNullOrEmpty(Class))
            builder.AddClass(Class);

        return builder.Build();
    }
}

我正在使用来自Ed Charbeneau · GitHub的帮助类来构建CSS类——清洁代码的必备品!

切换输入验证

ToggleInputInputBase<TValue>解析Value和验证所需的额外代码:

protected override bool TryParseValueFromString
(
    string? value,
    out bool result,
    out string validationErrorMessage
)
{
    if (bool.TryParse(value, out bool parsedValue))
    {
        result = parsedValue;
        validationErrorMessage = string.Empty;
        return true;
    }

    result = default;
    validationErrorMessage = $"The {FieldIdentifier.FieldName} field is not valid.";
    return false;
}

键盘支持

Aria对键盘支持的要求只是space键。但是,我还添加了对enter键的支持。

首先,我们需要挂钩onkeydown事件。我们还需要使用按键:

<label class="@CSS.Thumb" for="@PillId" aria-labelledby="@LabelId"
       data-label="@StateLabel" data-label-on="@OnText" data-label-off="@OffText"
       tabindex="0"
       @onkeydown="@(OnKeyDownAsync)"
       @onkeydown:preventDefault="true"
       @onkeydown:stopPropagation="true">

现在我们可以处理事件了。Toggle组件,我们手动处理Value change事件。对于ToggleInputInputBase<TValue>基类处理change事件,因此我们需要以不同的方式处理它。

对于Toggle组件:

private void OnKeyDownAsync(KeyboardEventArgs arg)
{
    switch (arg.Code)
    {
        case "Space":
        case "Enter":
            OnChange();
            break;
    }
}

private void OnChange()
{
    // check is here for browsers that do not manage the input disabled state
    if (_disabled)
        return;

    Value = !Value;

    InvokeAsync(async () => await ValueChanged.InvokeAsync(Value));
}

对于ToggleInput组件:

private void OnKeyDownAsync(KeyboardEventArgs arg)
{
    //Console.WriteLine($"** KEY: {arg.Code} | {arg.Key}");

    switch (arg.Code)
    {
        case "Space":
        case "Enter":
            CurrentValue = !CurrentValue;
            break;
    }
}

用法

切换

示例项目有四个示例:

  1. 主要主题的基本用法
  2. 主要主题的EditForm用法
  3. 自定义布局
  4. 自定义用户界面设计

 

基本的:

<Toggle Label="Enabled and checked"
        DefaultValue="true"
        OnText="On" OffText="Off"
        Style="@CustomStyle"/>

自定义标签:

<Toggle InlineLabel="true"
        OnText="On" OffText="Off"
        Class="@CustomCss"
        ValueChanged=@OnCheckedAsync>
    <LabelContent>
        Custom inline label 
    </LabelContent>
</Toggle>

切换输入

ToggleInput如果没有包含在EditForm组件中,则会抛出异常。

<EditForm class="o-editform"
          Model=MyModel
          OnValidSubmit="@HandleValidSubmit">
    <ToggleInput @bind-Value=MyModel.BoundChecked2
                 Class="@CustomCss"
                 Label="Are you sure?"
                 InlineLabel="true"
                 OnText="Yes"
                 OffText="No" />
</EditForm>

自定义布局

有一个显示自定义布局的设置演示。

 

在这里,我们在LabelContent中设置图标、标题和选定状态,并使用display: flex来定位元素:

<ToggleInput @bind-Value="model.IsChecked"
             Class="@CssSenderItem"
             Position="TogglePosition.Right"
             OnText="@model.YesChoice"
             OffText="@model.NoChoice"
             Disabled="@NotificationsDisabled">
    <LabelContent>
            @if (!string.IsNullOrEmpty(model.IconType))
            {
                <box-icon name='@model.IconName'
                          type='@model.IconType'
                          class="@CssSenderItemIcon">
                </box-icon>
            }
            else
            {
                <box-icon name='@model.IconName'
                          class="@CssSenderItemIcon">
                </box-icon>
            }
            <div class="@CssSenderItemText">
                <span class="@CssSenderItemTitle">
                    @model.Title
                </span>
                <span class="@CssSenderItemState">
                    @GetStateLabel(model.IsChecked)
                </span>
            </div>
    </LabelContent>
</ToggleInput>

这是用于布局和调整部件大小的CSS

.c-setting {
    display: flex;
    flex-direction: column;
    gap: 0.5em;
}

.c-sender__item {
    display: flex;
    align-items: center;
}

.c-sender__item .c-toggle__label {
    display: flex;
    flex-direction: row;
    flex-grow: 1;
}

.c-sender__item-text {
    display: flex;
    flex-direction: column;
    line-height: 1.35;
}

.c-sender__item-title {
    font-size: 1.15em;
}

.c-sender__item-state {
    font-size: 0.85em;
}

ColorSelect——Bonus组件

为了演示Primary Color Theming,可以使用标准Select标签下拉菜单。相反,我想要一些更可定制的东西,显示正在选择的颜色。快速的Code Pen搜索并找到了这个纯CSS选择框,我对其进行了改编并变成了带有浅色和深色主题的Blazor组件。

 

概括

我们已经采取了谦逊的input type="checkbox",并把它变成一个Toggle开关的正常和EditForm使用。在您自己的项目中使用很容易实现,只需几个属性和一个事件。

我们还添加了浅色、深色和原色主题支持。我们还看到了如何自定义部件的布局。最后,我们还演示了如何将它提升到一个新的水平,将炫酷的动画自定义UI

作为bonus,包括一个ColorSelect控制。

下载代码并查看它的实际效果。

https://www.codeproject.com/Articles/5324473/Blazor-Web-Assembly-WASM-Toggle-Switch

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值