diff --git a/src/BootstrapBlazor.Server/Components/Samples/ClockPickers.razor b/src/BootstrapBlazor.Server/Components/Samples/ClockPickers.razor index 16ea62038cb..b8f95155b93 100644 --- a/src/BootstrapBlazor.Server/Components/Samples/ClockPickers.razor +++ b/src/BootstrapBlazor.Server/Components/Samples/ClockPickers.razor @@ -4,4 +4,35 @@

@Localizer["Description"]

+ +
+
+
+ +
+
+
+ +
+
+
+ +
+ +
+
+ + +
+ +
+
+ + +
+ +
+
+ + diff --git a/src/BootstrapBlazor.Server/Components/Samples/ClockPickers.razor.cs b/src/BootstrapBlazor.Server/Components/Samples/ClockPickers.razor.cs index f0de99d6e3b..d18d0978853 100644 --- a/src/BootstrapBlazor.Server/Components/Samples/ClockPickers.razor.cs +++ b/src/BootstrapBlazor.Server/Components/Samples/ClockPickers.razor.cs @@ -13,4 +13,49 @@ public partial class ClockPickers [NotNull] private IStringLocalizer? Localizer { get; set; } + private TimeSpan Value { get; set; } = DateTime.Now - DateTime.Today; + + private TimeSpan SecondValue { get; set; } = TimeSpan.FromHours(12.5); + + private TimeSpan MinuteValue { get; set; } = TimeSpan.FromHours(12); + + private TimeSpan ScaleValue { get; set; } = TimeSpan.FromHours(12.5); + + private bool _autoSwitch = false; + + private AttributeItem[] GetAttributes() => + [ + new() + { + Name = nameof(ClockPicker.IsAutoSwitch), + Description = Localizer["IsAutoSwitchAttr"], + Type = "bool", + ValueList = "true / false", + DefaultValue = "true" + }, + new() + { + Name = nameof(ClockPicker.ShowClockScale), + Description = Localizer["ShowClockScaleAttr"], + Type = "bool", + ValueList = "true / false", + DefaultValue = "false" + }, + new() + { + Name = nameof(ClockPicker.ShowMinute), + Description = Localizer["ShowMinuteAttr"], + Type = "bool", + ValueList = "true / false", + DefaultValue = "true" + }, + new() + { + Name = nameof(ClockPicker.ShowSecond), + Description = Localizer["ShowSecondAttr"], + Type = "bool", + ValueList = "true / false", + DefaultValue = "true" + } + ]; } diff --git a/src/BootstrapBlazor.Server/Locales/en-US.json b/src/BootstrapBlazor.Server/Locales/en-US.json index 2492159f160..05e7d4bc90c 100644 --- a/src/BootstrapBlazor.Server/Locales/en-US.json +++ b/src/BootstrapBlazor.Server/Locales/en-US.json @@ -2469,6 +2469,23 @@ "TimeTitle": "Data is bound in both directions", "TimeIntro": "Click the confirm button to select the box value consistent with the text box value" }, + "BootstrapBlazor.Server.Components.Samples.ClockPickers": { + "Title": "ClockPicker", + "Description": "Select a time by dragging the watch needle", + "BindValueTitle": "bind", + "BindValueIntro": "By settingIsAutoSwitch=\"false\" to disable automatic switching of hour, minute, and stopwatch dial functions", + "AutoSwitchText": "Whether to automatically switch the dial", + "HasSecondsTitle": "Do not set the number of seconds", + "HasSecondsIntro": "By settingShowSecond=\"false\" to not display the second hand dial", + "ShowMinuteTitle": "Do not set minutes", + "ShowMinuteIntro": "By settingShowMinute=\"false\"to not display the minute dial", + "ShowClockScaleTitle": "Display dial scale", + "ShowClockScaleIntro": "By settingShowClockScale=\"true\" to display the dial scale", + "IsAutoSwitchAttr": "Does it automatically switch between hours, minutes, and seconds", + "ShowClockScaleAttr": "Is the dial scale displayed", + "ShowMinuteAttr": "Is the minute displayed", + "ShowSecondAttr": "Is seconds displayed" + }, "BootstrapBlazor.Server.Components.Samples.Editors": { "EditorsTitle": "Editor", "EditorsDescription": "Convert the entered text into html code snippets", diff --git a/src/BootstrapBlazor.Server/Locales/zh-CN.json b/src/BootstrapBlazor.Server/Locales/zh-CN.json index 4b4b04e5361..80c5a7ce7ed 100644 --- a/src/BootstrapBlazor.Server/Locales/zh-CN.json +++ b/src/BootstrapBlazor.Server/Locales/zh-CN.json @@ -2469,6 +2469,23 @@ "TimeTitle": "数据双向绑定", "TimeIntro": "点击确认按钮时间选择框值与文本框值一致" }, + "BootstrapBlazor.Server.Components.Samples.ClockPickers": { + "Title": "ClockPicker 时间选择器", + "Description": "通过拖动表针选择时间", + "BindValueTitle": "数据双向绑定", + "BindValueIntro": "通过设置 IsAutoSwitch=\"false\" 禁止小时、分钟、秒表盘自动切换功能", + "AutoSwitchText": "是否自动切换表盘", + "HasSecondsTitle": "不设置秒数", + "HasSecondsIntro": "通过设置 ShowSecond=\"false\" 不显示秒针表盘", + "ShowMinuteTitle": "不设置分钟", + "ShowMinuteIntro": "通过设置 ShowMinute=\"false\" 不显示分针表盘", + "ShowClockScaleTitle": "显示表盘刻度", + "ShowClockScaleIntro": "通过设置 ShowClockScale=\"true\" 显示表盘刻度", + "IsAutoSwitchAttr": "是否自动切换 小时、分钟、秒 自动切换", + "ShowClockScaleAttr": "是否显示表盘刻度", + "ShowMinuteAttr": "是否显示分钟", + "ShowSecondAttr": "是否显示秒" + }, "BootstrapBlazor.Server.Components.Samples.Editors": { "EditorsTitle": "Editor 富文本框", "EditorsDescription": "将输入的文字转化为 html 代码片段", diff --git a/src/BootstrapBlazor.Server/wwwroot/css/site.css b/src/BootstrapBlazor.Server/wwwroot/css/site.css index 10bc68fdea1..e0329986d9a 100644 --- a/src/BootstrapBlazor.Server/wwwroot/css/site.css +++ b/src/BootstrapBlazor.Server/wwwroot/css/site.css @@ -253,6 +253,13 @@ code { margin-bottom: 1rem; } +.custom-clock { + border: 1px solid var(--bs-border-color); + padding: .5rem; + border-radius: var(--bs-border-radius); + width: 320px; +} + @media (min-width: 768px) { :root { --bs-header-height: 50px; diff --git a/src/BootstrapBlazor/Components/ClockPicker/ClockPicker.razor b/src/BootstrapBlazor/Components/ClockPicker/ClockPicker.razor index b198ed9162d..b97a01bd042 100644 --- a/src/BootstrapBlazor/Components/ClockPicker/ClockPicker.razor +++ b/src/BootstrapBlazor/Components/ClockPicker/ClockPicker.razor @@ -10,9 +10,23 @@ } @HourValue : - @Value.Minutes.ToString("D2") + @if(ShowMinute) + { + @Value.Minutes.ToString("D2") + } + else + { + @Value.Minutes.ToString("D2") + } : - @Value.Seconds.ToString("D2") + @if(ShowMinute && ShowSecond) + { + @Value.Seconds.ToString("D2") + } + else + { + @Value.Seconds.ToString("D2") + }
@if (ShowClockScale) diff --git a/src/BootstrapBlazor/Components/ClockPicker/ClockPicker.razor.cs b/src/BootstrapBlazor/Components/ClockPicker/ClockPicker.razor.cs index b24209fb77d..1c8dc81bf54 100644 --- a/src/BootstrapBlazor/Components/ClockPicker/ClockPicker.razor.cs +++ b/src/BootstrapBlazor/Components/ClockPicker/ClockPicker.razor.cs @@ -19,37 +19,38 @@ public partial class ClockPicker .Build(); /// - /// 是否显示表盘刻度 默认 false + /// 获得/设置 是否显示表盘刻度 默认 false /// [Parameter] public bool ShowClockScale { get; set; } /// - /// 是否显示秒 默认 true + /// 获得/设置 是否显示秒 默认 true /// [Parameter] public bool ShowSecond { get; set; } = true; /// - /// 是否显示分钟 默认 true + /// 获得/设置 是否显示分钟 默认 true /// [Parameter] public bool ShowMinute { get; set; } = true; /// - /// 是否自动切换 小时、分钟、秒 自动切换 默认 true + /// 获得/设置 是否自动切换 小时、分钟、秒 自动切换 默认 true /// [Parameter] public bool IsAutoSwitch { get; set; } = true; [CascadingParameter] + [NotNull] private DatePickerBody? DatePicker { get; set; } [Inject] [NotNull] private IStringLocalizer? Localizer { get; set; } - private string? CurrentDateString => DatePicker?.Value.ToString(DatePicker.DateFormat); + private string? CurrentDateString => DatePicker.Value.ToString(DatePicker.DateFormat); /// /// is hour or min or sec mode @@ -118,24 +119,20 @@ private void SetTimePeriod(int hour) } /// - /// 设置小时调用此方法 + /// JSInvoke 调用此方法 /// [JSInvokable] public void SetTime(int hour, int minute, int second) { if (IsAutoSwitch) { - switch (Mode) + if (Mode == TimeMode.Hour && ShowMinute) + { + Mode = TimeMode.Minute; + } + else if (Mode == TimeMode.Minute && ShowSecond) { - case TimeMode.Hour: - Mode = TimeMode.Minute; - break; - case TimeMode.Minute: - Mode = TimeMode.Second; - break; - case TimeMode.Second: - default: - break; + Mode = TimeMode.Second; } } @@ -167,7 +164,7 @@ private static int GetSafeHour(int val) private void SwitchView() { Mode = TimeMode.Hour; - DatePicker?.SwitchDateView(); + DatePicker.SwitchDateView(); } private enum TimeMode diff --git a/src/BootstrapBlazor/Components/ClockPicker/ClockPicker.razor.scss b/src/BootstrapBlazor/Components/ClockPicker/ClockPicker.razor.scss index 041f903bab8..897481faed2 100644 --- a/src/BootstrapBlazor/Components/ClockPicker/ClockPicker.razor.scss +++ b/src/BootstrapBlazor/Components/ClockPicker/ClockPicker.razor.scss @@ -18,7 +18,6 @@ --bb-time-footer-btn-hover-bg-color: #409eff; --bb-time-footer-btn-active-color: #409eff; --bb-time-footer-btn-active-border-color: #409eff; - width: var(--bb-picker-panel-body-width); &.dragging { user-select: none; diff --git a/src/BootstrapBlazor/Components/DateTimePicker/DatePickerBody.razor b/src/BootstrapBlazor/Components/DateTimePicker/DatePickerBody.razor index 3b9eb302af7..b760aa7c447 100644 --- a/src/BootstrapBlazor/Components/DateTimePicker/DatePickerBody.razor +++ b/src/BootstrapBlazor/Components/DateTimePicker/DatePickerBody.razor @@ -147,7 +147,7 @@ @ChildContent - diff --git a/src/BootstrapBlazor/Components/DateTimePicker/DatePickerBody.razor.scss b/src/BootstrapBlazor/Components/DateTimePicker/DatePickerBody.razor.scss index 30ff785182d..547f9385a0e 100644 --- a/src/BootstrapBlazor/Components/DateTimePicker/DatePickerBody.razor.scss +++ b/src/BootstrapBlazor/Components/DateTimePicker/DatePickerBody.razor.scss @@ -214,6 +214,10 @@ } } } + + .clock-panel-body { + width: var(--bb-picker-panel-body-width); + } } .popover-body { diff --git a/src/BootstrapBlazor/Locales/en.json b/src/BootstrapBlazor/Locales/en.json index 990fbf8383c..80f7b19df8f 100644 --- a/src/BootstrapBlazor/Locales/en.json +++ b/src/BootstrapBlazor/Locales/en.json @@ -352,7 +352,7 @@ "AscText": "Ascending", "DescText": "Descending" }, - "BootstrapBlazor.Components.TimePickerPanel": { + "BootstrapBlazor.Components.ClockPicker": { "AMText": "AM", "PMText": "PM" } diff --git a/src/BootstrapBlazor/Locales/zh.json b/src/BootstrapBlazor/Locales/zh.json index bac545d9090..77c64ea4d93 100644 --- a/src/BootstrapBlazor/Locales/zh.json +++ b/src/BootstrapBlazor/Locales/zh.json @@ -352,7 +352,7 @@ "AscText": "升序", "DescText": "降序" }, - "BootstrapBlazor.Components.TimePickerPanel": { + "BootstrapBlazor.Components.ClockPicker": { "AMText": "上午", "PMText": "下午" } diff --git a/test/UnitTest/Components/ClockPickerTest.cs b/test/UnitTest/Components/ClockPickerTest.cs new file mode 100644 index 00000000000..14d87cdf375 --- /dev/null +++ b/test/UnitTest/Components/ClockPickerTest.cs @@ -0,0 +1,200 @@ +// Copyright (c) Argo Zhang (argo@163.com). All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// Website: https://www.blazor.zone or https://argozhang.github.io/ + +namespace UnitTest.Components; + +public class ClockPickerTest : BootstrapBlazorTestBase +{ + [Fact] + public async Task IsAutoSwitch_Ok() + { + var cut = Context.RenderComponent(pb => + { + pb.Add(a => a.Value, TimeSpan.FromHours(12.5)); + pb.Add(a => a.IsAutoSwitch, true); + }); + + // 当前表盘时小时 + cut.Contains("data-bb-mode=\"Hour\""); + + // 模拟 JSInvoke 调用 SetTime 方法 + await cut.InvokeAsync(() => + { + cut.Instance.SetTime(11, 0, 0); + }); + cut.Contains("data-bb-mode=\"Minute\""); + + // 模拟 JSInvoke 调用 SetTime 方法 + await cut.InvokeAsync(() => + { + cut.Instance.SetTime(11, 0, 0); + }); + cut.Contains("data-bb-mode=\"Second\""); + } + + [Fact] + public async Task ShowMinute_Ok() + { + var cut = Context.RenderComponent(pb => + { + pb.Add(a => a.Value, TimeSpan.FromHours(12.5)); + pb.Add(a => a.IsAutoSwitch, true); + pb.Add(a => a.ShowMinute, false); + }); + + // 当前表盘时小时 + cut.Contains("data-bb-mode=\"Hour\""); + + // 模拟 JSInvoke 调用 SetTime 方法 + await cut.InvokeAsync(() => + { + cut.Instance.SetTime(11, 0, 0); + }); + cut.Contains("data-bb-mode=\"Hour\""); + } + + [Fact] + public async Task ShowSecond_Ok() + { + var cut = Context.RenderComponent(pb => + { + pb.Add(a => a.Value, TimeSpan.FromHours(12.5)); + pb.Add(a => a.IsAutoSwitch, true); + pb.Add(a => a.ShowSecond, false); + }); + + // 当前表盘时小时 + cut.Contains("data-bb-mode=\"Hour\""); + + // 模拟 JSInvoke 调用 SetTime 方法 + await cut.InvokeAsync(() => + { + cut.Instance.SetTime(11, 0, 0); + }); + cut.Contains("data-bb-mode=\"Minute\""); + + // 模拟 JSInvoke 调用 SetTime 方法 + await cut.InvokeAsync(() => + { + cut.Instance.SetTime(11, 0, 0); + }); + cut.Contains("data-bb-mode=\"Minute\""); + } + + [Fact] + public async Task SwitchView_Ok() + { + var cut = Context.RenderComponent(pb => + { + pb.Add(a => a.Value, TimeSpan.FromHours(12.5)); + }); + var span = cut.Find(".hour"); + await cut.InvokeAsync(() => + { + span.Click(); + }); + cut.Contains("data-bb-mode=\"Hour\""); + + span = cut.Find(".minute"); + await cut.InvokeAsync(() => + { + span.Click(); + }); + cut.Contains("data-bb-mode=\"Minute\""); + + span = cut.Find(".second"); + await cut.InvokeAsync(() => + { + span.Click(); + }); + cut.Contains("data-bb-mode=\"Second\""); + } + + [Fact] + public async Task SetTime_Ok() + { + var cut = Context.RenderComponent(pb => + { + pb.Add(a => a.Value, TimeSpan.FromHours(11)); + }); + await cut.InvokeAsync(() => + { + cut.Instance.SetTime(12, 0, 0); + }); + Assert.Equal(TimeSpan.FromHours(0), cut.Instance.Value); + + cut.SetParametersAndRender(pb => + { + pb.Add(a => a.Value, TimeSpan.FromHours(11)); + }); + var button = cut.Find(".btn-am"); + await cut.InvokeAsync(() => + { + button.Click(); + }); + Assert.Equal(TimeSpan.FromHours(11), cut.Instance.Value); + + cut.SetParametersAndRender(pb => + { + pb.Add(a => a.Value, TimeSpan.FromHours(20)); + }); + button = cut.Find(".btn-pm"); + await cut.InvokeAsync(() => + { + button.Click(); + }); + Assert.Equal(TimeSpan.FromHours(20), cut.Instance.Value); + } + + [Fact] + public async Task SetTimePeriod_Ok() + { + var cut = Context.RenderComponent(pb => + { + pb.Add(a => a.Value, TimeSpan.FromHours(12.5)); + }); + cut.Contains("btn-pm active"); + + var button = cut.Find(".btn-am"); + await cut.InvokeAsync(() => + { + button.Click(); + }); + cut.Contains("btn-am active"); + + button = cut.Find(".btn-pm"); + await cut.InvokeAsync(() => + { + button.Click(); + }); + cut.Contains("btn-pm active"); + } + + [Fact] + public void ShowClockScale_Ok() + { + var cut = Context.RenderComponent(pb => + { + pb.Add(a => a.Value, TimeSpan.FromHours(12.5)); + pb.Add(a => a.ShowClockScale, true); + }); + cut.Contains("bb-clock-panel bb-clock-panel-scale"); + } + + [Fact] + public async Task DatePicker_Ok() + { + var cut = Context.RenderComponent>(pb => + { + pb.Add(a => a.Value, DateTime.Now); + pb.Add(a => a.ViewMode, DatePickerViewMode.DateTime); + }); + var span = cut.Find(".bb-time-text"); + await cut.InvokeAsync(() => + { + span.Click(); + }); + cut.Contains("data-bb-mode=\"Hour\""); + } +}