diff --git a/AGENTS.md b/AGENTS.md index 02f804ab932..979e340a029 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -34,6 +34,7 @@ - 所有用户可见文本必须使用 `IStringLocalizer` - 禁止硬编码中文/英文 UI 文本 +- 本地化文件使用 json 文件存储在 localization 文件夹与相应项目 Locales 目录下 --- diff --git a/localization/de.json b/localization/de.json index b35e87d2a9f..438f68dee74 100644 --- a/localization/de.json +++ b/localization/de.json @@ -56,10 +56,7 @@ "Months": "Jan,Feb,Mär,Apr,Mai,Jun,Jul,Aug,Sep,Okt,Nov,Dez", "MonthLists": "Jan,Feb,Mär,Apr,Mai,Jun,Jul,Aug,Sep,Okt,Nov,Dez", "WeekLists": "So,Mo,Di,Mi,Do,Fr,Sa", - "GenericTypeErrorMessage": "DateTimePicker unterstützt nur DateTime, DateTime?, DateTimeOffset, DateTimeOffset?", - "Today": "Heute", - "Yesterday": "Gestern", - "Week": "Vor einer Woche" + "GenericTypeErrorMessage": "DateTimePicker unterstützt nur DateTime, DateTime?, DateTimeOffset, DateTimeOffset?" }, "BootstrapBlazor.Components.DateTimeRange": { "SeparateText": "Bis", diff --git a/localization/es.json b/localization/es.json index d00078ac86f..3869ca72dfa 100644 --- a/localization/es.json +++ b/localization/es.json @@ -56,10 +56,7 @@ "Months": "Ene,Feb,Mar,Abr,May,Jun,Jul,Ago,Sep,Oct,Nov,Dic", "MonthLists": "Ene,Feb,Mar,Abr,May,Jun,Jul,Ago,Sep,Oct,Nov,Dic", "WeekLists": "Dom,Lun,Mar,Mié,Jue,Vie,Sáb", - "GenericTypeErrorMessage": "DateTimePicker solo soporta DateTime, DateTime?, DateTimeOffset, DateTimeOffset?", - "Today": "Hoy", - "Yesterday": "Ayer", - "Week": "Hace una semana" + "GenericTypeErrorMessage": "DateTimePicker solo soporta DateTime, DateTime?, DateTimeOffset, DateTimeOffset?" }, "BootstrapBlazor.Components.DateTimeRange": { "SeparateText": "A", diff --git a/localization/it.json b/localization/it.json index 2b4d6669470..3aca49c20a4 100644 --- a/localization/it.json +++ b/localization/it.json @@ -56,10 +56,7 @@ "Months": "Gennaio,Febbraio,Marzo,Aprile,Maggio,Giugno,Luglio,Agosto,Settembre,Ottobre,Novembre,Dicembre", "MonthLists": "Gen,Feb,Mar,Apr,Mag,Giu,Lug,Ago,Set,Ott,Nov,Dic", "WeekLists": "Dom,Lun,Mar,Mer,Gio,Ven,Sab", - "GenericTypeErrorMessage": "DateTimePicker supporta solo DateTime o Nullable", - "Today": "Oggi", - "Yesterday": "Ieri", - "Week": "Una settimana fa" + "GenericTypeErrorMessage": "DateTimePicker supporta solo DateTime o Nullable" }, "BootstrapBlazor.Components.DateTimeRange": { "SeparateText": "A", diff --git a/localization/km-KH.json b/localization/km-KH.json index be18274346b..bc7b952a604 100644 --- a/localization/km-KH.json +++ b/localization/km-KH.json @@ -56,10 +56,7 @@ "Months": "មករា,កុម្ភៈ,មីនា,មេសា,ឧសភា,មិថុនា,កក្កដា,សីហា,កញ្ញា,តុលា,វិច្ឆិកា,ធ្នូ", "MonthLists": "មករា,កុម្ភៈ,មីនា,មេសា,ឧសភា,មិថុនា,កក្កដា,សីហា,កញ្ញា,តុលា,វិច្ឆិកា,ធ្នូ", "WeekLists": "អាទិត្យ,ចន្ទ,អង្គារ,ពុធ,ព្រហស្បតិ៍,សុក្រ,សៅរ៍", - "GenericTypeErrorMessage": "DateTimePicker ដំណើរការតែ DateTime, DateTime?, DateTimeOffset និង DateTimeOffset? ប៉ុណ្ណោះ", - "Today": "ថ្ងៃនេះ", - "Yesterday": "ម្សិលមិញ", - "Week": "មួយ​សប្តាហ៍​មុន" + "GenericTypeErrorMessage": "DateTimePicker ដំណើរការតែ DateTime, DateTime?, DateTimeOffset និង DateTimeOffset? ប៉ុណ្ណោះ" }, "BootstrapBlazor.Components.DateTimeRange": { "SeparateText": "ដល់", diff --git a/localization/pt-BR.json b/localization/pt-BR.json index 5b26fd0668c..11e28df7eaa 100644 --- a/localization/pt-BR.json +++ b/localization/pt-BR.json @@ -56,10 +56,7 @@ "Months": "Jan,Fev,Mar,Abr,Mai,Jun,Jul,Ago,Set,Out,Nov,Dez", "MonthLists": "Jan,Fev,Mar,Abr,Mai,Jun,Jul,Ago,Set,Out,Nov,Dez", "WeekLists": "Dom,Seg,Ter,Qua,Qui,Sex,Sáb", - "GenericTypeErrorMessage": "DateTimePicker só suporta DateTime DateTime? DateTimeOffset DateTimeOffset?", - "Today": "Hoje", - "Yesterday": "Ontem", - "Week": "Há uma semana" + "GenericTypeErrorMessage": "DateTimePicker só suporta DateTime DateTime? DateTimeOffset DateTimeOffset?" }, "BootstrapBlazor.Components.DateTimeRange": { "SeparateText": "Até", diff --git a/localization/pt.json b/localization/pt.json index b295e8c559a..1683290ab7c 100644 --- a/localization/pt.json +++ b/localization/pt.json @@ -56,10 +56,7 @@ "Months": "Jan,Fev,Mar,Abr,Mai,Jun,Jul,Ago,Set,Out,Nov,Dez", "MonthLists": "Jan,Fev,Mar,Abr,Mai,Jun,Jul,Ago,Set,Out,Nov,Dez", "WeekLists": "Dom,Seg,Ter,Qua,Qui,Sex,Sáb", - "GenericTypeErrorMessage": "DateTimePicker suporta apenas DateTime, DateTime?, DateTimeOffset, DateTimeOffset?", - "Today": "Hoje", - "Yesterday": "Ontem", - "Week": "Uma semana atrás" + "GenericTypeErrorMessage": "DateTimePicker suporta apenas DateTime, DateTime?, DateTimeOffset, DateTimeOffset?" }, "BootstrapBlazor.Components.DateTimeRange": { "SeparateText": "Até", diff --git a/localization/ru-RU.json b/localization/ru-RU.json index 5a06500b52b..da82c4bf1da 100644 --- a/localization/ru-RU.json +++ b/localization/ru-RU.json @@ -56,10 +56,7 @@ "Months": "Янв,Фев,Мар,Апр,Май,Июн,Июл,Авг,Сен,Окт,Ноя,Дек", "MonthLists": "Янв,Фев,Мар,Апр,Май,Июн,Июл,Авг,Сен,Окт,Ноя,Дек", "WeekLists": "Вс,Пн,Вт,Ср,Чт,Пт,Сб", - "GenericTypeErrorMessage": "DateTimePicker поддерживает только DateTime, DateTime?, DateTimeOffset, DateTimeOffset?", - "Today": "Сегодня", - "Yesterday": "Вчера", - "Week": "Неделю назад" + "GenericTypeErrorMessage": "DateTimePicker поддерживает только DateTime, DateTime?, DateTimeOffset, DateTimeOffset?" }, "BootstrapBlazor.Components.DateTimeRange": { "SeparateText": "До", diff --git a/localization/th-TH.json b/localization/th-TH.json index 6cb85f59a5f..333b645978c 100644 --- a/localization/th-TH.json +++ b/localization/th-TH.json @@ -56,10 +56,7 @@ "Months": "ม.ค.,ก.พ.,มี.ค.,เม.ย.,พ.ค.,มิ.ย.,ก.ค.,ส.ค.,ก.ย.,ต.ค.,พ.ย.,ธ.ค.", "MonthLists": "ม.ค.,ก.พ.,มี.ค.,เม.ย.,พ.ค.,มิ.ย.,ก.ค.,ส.ค.,ก.ย.,ต.ค.,พ.ย.,ธ.ค.", "WeekLists": "อา,จ,อ,พ,พฤ,ศ,ส", - "GenericTypeErrorMessage": "DateTimePicker รองรับเฉพาะ DateTime, DateTime?, DateTimeOffset, DateTimeOffset?", - "Today": "วันนี้", - "Yesterday": "เมื่อวาน", - "Week": "หนึ่งสัปดาห์ก่อน" + "GenericTypeErrorMessage": "DateTimePicker รองรับเฉพาะ DateTime, DateTime?, DateTimeOffset, DateTimeOffset?" }, "BootstrapBlazor.Components.DateTimeRange": { "SeparateText": "ถึง", diff --git a/localization/uk-UA.json b/localization/uk-UA.json index 02f9abb8ab9..c81595cd30e 100644 --- a/localization/uk-UA.json +++ b/localization/uk-UA.json @@ -56,10 +56,7 @@ "Months": "Січ,Лют,Бер,Квіт,Трав,Черв,Лип,Серп,Вер,Жовт,Лист,Груд", "MonthLists": "Січ,Лют,Бер,Квіт,Трав,Черв,Лип,Серп,Вер,Жовт,Лист,Груд", "WeekLists": "Нд,Пн,Вт,Ср,Чт,Пт,Сб", - "GenericTypeErrorMessage": "DateTimePicker підтримує лише DateTime, DateTime?, DateTimeOffset, DateTimeOffset?", - "Today": "Сьогодні", - "Yesterday": "Вчора", - "Week": "Тиждень тому" + "GenericTypeErrorMessage": "DateTimePicker підтримує лише DateTime, DateTime?, DateTimeOffset, DateTimeOffset?" }, "BootstrapBlazor.Components.DateTimeRange": { "SeparateText": "До", diff --git a/localization/zh-TW.json b/localization/zh-TW.json index 7622f7ce3ff..e15293cd359 100644 --- a/localization/zh-TW.json +++ b/localization/zh-TW.json @@ -56,10 +56,7 @@ "Months": "1,2,3,4,5,6,7,8,9,10,11,12", "MonthLists": "一月,二月,三月,四月,五月,六月,七月,八月,九月,十月,十一月,十二月", "WeekLists": "日,一,二,三,四,五,六", - "GenericTypeErrorMessage": "DateTimePicker 元件僅支援綁定泛型為 DateTime DateTime? DateTimeOffset DateTimeOffset?", - "Today": "今天", - "Yesterday": "昨天", - "Week": "一週前" + "GenericTypeErrorMessage": "DateTimePicker 元件僅支援綁定泛型為 DateTime DateTime? DateTimeOffset DateTimeOffset?" }, "BootstrapBlazor.Components.DateTimeRange": { "SeparateText": "至", diff --git a/src/BootstrapBlazor.Server/Components/Components/DatePickerDialogForm.razor b/src/BootstrapBlazor.Server/Components/Components/DatePickerDialogForm.razor new file mode 100644 index 00000000000..65acde45324 --- /dev/null +++ b/src/BootstrapBlazor.Server/Components/Components/DatePickerDialogForm.razor @@ -0,0 +1,94 @@ +@inject IStringLocalizer Localizer + + +
+
+ +
+
+ + + + + + + + + + +
+
+ +
+
+ +
+
+
+
+ @if (_submitted) + { +
@Localizer["NestedSubmitted"]
+ } +
+ @DateValue?.ToString("yyyy-MM-dd") + @DateTimeValue?.ToString("yyyy-MM-dd HH:mm:ss") + @RangeValue.Start.ToString("yyyy-MM-dd") — @RangeValue.End.ToString("yyyy-MM-dd") +
+
+ +@code { + /// + /// 获得/设置 表单日期。 + /// Gets or sets the form date. + /// + [Required] + public DateTime? DateValue { get; set; } + + /// + /// 获得/设置 自定义侧边栏日期。 + /// Gets or sets the custom sidebar date. + /// + public DateTime? SidebarDateValue { get; set; } + + /// + /// 获得/设置 表单日期时间。 + /// Gets or sets the form date and time. + /// + public DateTime? DateTimeValue { get; set; } = DateTime.Now; + + /// + /// 获得/设置 表单日期范围。 + /// Gets or sets the form date range. + /// + public DateTimeRangeValue RangeValue { get; set; } = new(); + + private bool _submitted; + + private Task OnValidSubmitAsync(EditContext context) + { + _submitted = true; + return Task.CompletedTask; + } +} diff --git a/src/BootstrapBlazor.Server/Components/Samples/DateTimePickers.razor b/src/BootstrapBlazor.Server/Components/Samples/DateTimePickers.razor index d7cc008a755..d016dac0919 100644 --- a/src/BootstrapBlazor.Server/Components/Samples/DateTimePickers.razor +++ b/src/BootstrapBlazor.Server/Components/Samples/DateTimePickers.razor @@ -1,9 +1,14 @@ @page "/datetime-picker" +@page "/datetime-picker/dialog"

@Localizer["Title"]

@Localizer["Description"]

+ + + } - else +
+ @YearString + @MonthString + @if (IsDateTimeMode && PickTimeMode == PickTimeMode.Clock) + { + @CurrentTime.ToString(TimeFormat) + } +
+ @if (ShowRightButtons) { - - - + + } - } -
-
-
-
- @if (ShowLeftButtons) - { - - - } -
- @YearString - @MonthString - @if (IsDateTimeMode && PickTimeMode == PickTimeMode.Clock) +
+ + + + @foreach (var w in WeekLists) { - @CurrentTime.ToString(TimeFormat) + } - - @if (ShowRightButtons) + + @for (var week = StartDate; week < EndDate; week = GetSafeDayDateTime(week, 7)) { - - - } - -
-
@w
- - - @foreach (var w in WeekLists) - { - - } - - @for (var week = StartDate; week < EndDate; week = GetSafeDayDateTime(week, 7)) + + @for (var index = 0; index < 7; index++) { - - @for (var index = 0; index < 7; index++) + var day = GetSafeDayDateTime(week, index); + var text = GetDayText(day.Day); + var isOverflow = IsDayOverflow(week, index); + + } - - } - -
@w
+ @if (isOverflow) + { + - + } + else if (IsDisabled(day)) { - var day = GetSafeDayDateTime(week, index); - var text = GetDayText(day.Day); - var isOverflow = IsDayOverflow(week, index); - - @if (isOverflow) + + @if (ShowLunar) { - - + @text + @GetLunarText(day) } - else if (IsDisabled(day)) + else if (DayDisabledTemplate != null) { - - @if (ShowLunar) - { - @text - @GetLunarText(day) - } - else if (DayDisabledTemplate != null) - { - @DayDisabledTemplate(day) - } - else - { - @text - } - + @DayDisabledTemplate(day) } else { - - + @text } -
- - - @for (var row = 0; row < 5; row++) - { - - @for (var index = 0; index < 4; index++) + else { - var year = index + row * 4; - var isOverflow = IsYearOverflow(CurrentDate, year - (CurrentDate.Year % 20)); - + + } - + } - -
- @if (isOverflow) - { - - - } - else - { - - } -
- - - @for (var row = 0; row < 3; row++) + + } + +
+ + + @for (var row = 0; row < 5; row++) + { + + @for (var index = 0; index < 4; index++) { - - @for (var index = 0; index < 4; index++) + var year = index + row * 4; + var isOverflow = IsYearOverflow(CurrentDate, year - (CurrentDate.Year % 20)); + + } - + } - -
+ @if (isOverflow) + { + - + } + else { - var month = index + row * 4 + 1; - - -
-
-
- @if (ViewMode == DatePickerViewMode.DateTime) - { - - @ChildContent - - - - - - } + + } + + + + + @for (var row = 0; row < 3; row++) + { + + @for (var index = 0; index < 4; index++) + { + var month = index + row * 4 + 1; + + } + + } + +
+ +
+ @if (ViewMode == DatePickerViewMode.DateTime && PickTimeMode == PickTimeMode.Clock) + { + + @ChildContent + + + + + + }
@if (IsDateTimeMode && PickTimeMode == PickTimeMode.Dropdown) { diff --git a/src/BootstrapBlazor/Components/DateTimePicker/DatePickerBody.razor.cs b/src/BootstrapBlazor/Components/DateTimePicker/DatePickerBody.razor.cs index b632147b65b..a74e54c6e8a 100644 --- a/src/BootstrapBlazor/Components/DateTimePicker/DatePickerBody.razor.cs +++ b/src/BootstrapBlazor/Components/DateTimePicker/DatePickerBody.razor.cs @@ -54,7 +54,6 @@ private DateTime StartDate private bool _showClockPicker; private string? ClassString => CssBuilder.Default("picker-panel") - .AddClass("is-sidebar", ShowSidebar) .AddClass("is-lunar", ShowLunar) .AddClassFromAttributes(AdditionalAttributes) .Build(); @@ -84,7 +83,7 @@ private bool IsRange(DateTime day) => Ranger != null && day >= Ranger.SelectedValue.Start.Date && day <= Ranger.SelectedValue.End.Date; - private string? WrapperClassString => CssBuilder.Default("picker-panel-body-main-wrapper") + private string? WrapperClassString => CssBuilder.Default("picker-panel-body") .AddClass("is-open", _showClockPicker) .Build(); @@ -203,6 +202,8 @@ private bool IsRange(DateTime day) => Ranger != null /// Gets or sets Whether to Show Sidebar. Default is false /// [Parameter] + [Obsolete("已弃用,请删除;Deprecated, please delete")] + [ExcludeFromCodeCoverage] public bool ShowSidebar { get; set; } /// @@ -210,6 +211,8 @@ private bool IsRange(DateTime day) => Ranger != null /// Gets or sets Sidebar Template. Default is null /// [Parameter] + [Obsolete("已弃用,请删除;Deprecated, please delete")] + [ExcludeFromCodeCoverage] public RenderFragment>? SidebarTemplate { get; set; } /// @@ -495,15 +498,6 @@ public bool AllowNull [NotNull] private List? WeekLists { get; set; } - [NotNull] - private string? Today { get; set; } - - [NotNull] - private string? Yesterday { get; set; } - - [NotNull] - private string? Week { get; set; } - private TimePickerOption TimePickerOption { get; } = new(); private Dictionary> AllowSwitchModes { get; } = new Dictionary> @@ -574,10 +568,6 @@ protected override void OnParametersSet() Months = [.. Localizer[nameof(Months)].Value.Split(',')]; WeekLists = GetWeekList(); - Today ??= Localizer[nameof(Today)]; - Yesterday ??= Localizer[nameof(Yesterday)]; - Week ??= Localizer[nameof(Week)]; - PreviousYearIcon ??= IconTheme.GetIconByKey(ComponentIcons.DatePickBodyPreviousYearIcon); PreviousMonthIcon ??= IconTheme.GetIconByKey(ComponentIcons.DatePickBodyPreviousMonthIcon); NextMonthIcon ??= IconTheme.GetIconByKey(ComponentIcons.DatePickBodyNextMonthIcon); @@ -977,14 +967,6 @@ private async Task ClickClearButton() } } - private async Task OnClickSidebarButton(DateTime d) - { - CurrentDate = d.Date; - CurrentTime = d.TimeOfDay; - - await ClickConfirmButton(); - } - private void ResetTimePickerPanel() { // 关闭 TimerPicker diff --git a/src/BootstrapBlazor/Components/DateTimePicker/DatePickerBody.razor.scss b/src/BootstrapBlazor/Components/DateTimePicker/DatePickerBody.razor.scss index 2b9d677cf59..ac0d1d68361 100644 --- a/src/BootstrapBlazor/Components/DateTimePicker/DatePickerBody.razor.scss +++ b/src/BootstrapBlazor/Components/DateTimePicker/DatePickerBody.razor.scss @@ -3,7 +3,6 @@ .picker-panel { --bb-picker-panel-body-width: 320px; --bb-picker-hover-color: #409eff; - --bb-picker-panel-side-width: 0; --bb-picker-panel-today-color: #fff; --bb-picker-panel-today-bg: #409eff; color: var(--bs-body-color); @@ -12,301 +11,283 @@ background-color: var(--bs-body-bg); border-radius: var(--bs-border-radius); line-height: 30px; - display: inline-block; + display: block; + width: var(--bb-picker-panel-body-width); + overflow: hidden; - &.is-sidebar { - --bb-picker-panel-side-width: 110px; - } + .picker-panel-body { + display: flex; + transition: transform .3s ease-in-out; - @include pick-side; + &.is-open { + transform: translateX(calc(0px - var(--bb-picker-panel-body-width))); + } + } - .picker-panel-body { + .picker-panel-header { + margin: 12px; display: flex; + align-items: center; - .picker-panel-sidebar { - width: var(--bb-picker-panel-side-width); + .pick-panel-arrow-left, + .pick-panel-arrow-right { + margin: 0 6px; } - .picker-panel-body-main { - width: var(--bb-picker-panel-body-width); - overflow: hidden; - display: flex; + .picker-panel-icon-btn { + color: var(--bs-body-color); + border: 0; + background: transparent; + cursor: pointer; + outline: none; + padding: 0 6px; - .picker-panel-body-main-wrapper { - display: flex; - transition: transform .3s ease-in-out; + &:hover { + color: var(--bb-picker-hover-color); + } + } - &.is-open { - transform: translateX(calc(0px - var(--bb-picker-panel-body-width))); - } + .picker-panel-header-bar { + .picker-panel-header-label { + font-size: 14px; + } + } + + .picker-panel-header-label { + font-size: 16px; + font-weight: 500; + padding: 0 4px; + text-align: center; + cursor: pointer; + user-select: none; + white-space: nowrap; + + &:hover { + color: var(--bb-picker-hover-color); } + } + } - .picker-panel-header { - margin: 12px; - display: flex; - align-items: center; + .picker-panel-content { + width: calc(var(--bb-picker-panel-body-width) - 30px); + position: relative; + margin: 15px; - .pick-panel-arrow-left, - .pick-panel-arrow-right { - margin: 0 6px; - } + table { + table-layout: fixed; + width: 100%; + user-select: none; + + .date-table-row { + font-size: 12px; + } - .picker-panel-icon-btn { - color: var(--bs-body-color); - border: 0; - background: transparent; + td { + text-align: center; + + .cell { + display: block; + margin: 0 auto; cursor: pointer; - outline: none; - padding: 0 6px; - &:hover { - color: var(--bb-picker-hover-color); + &:has(.bb-picker-body-lunar-text):hover { + color: var(--bb-picker-panel-today-color); + + &:before { + content: ""; + position: absolute; + width: 33px; + height: 33px; + border-radius: var(--bs-border-radius); + background-color: var(--bb-picker-panel-today-bg); + top: 3px; + left: -3.5px; + opacity: .8; + } + + .bb-picker-body-lunar-text { + color: var(--bb-picker-panel-today-color); + } } - } - .picker-panel-header-bar { - .picker-panel-header-label { - font-size: 14px; + span.is-holiday, span.is-workday { + background-color: #c0c4cc; + color: var(--bb-picker-panel-today-color); + height: 14px; + width: 15px; + font-size: 9px; + border-radius: 3px; + top: -1px; + right: -9px; + line-height: 14px; + opacity: .8; } } - .picker-panel-header-label { - font-size: 16px; - font-weight: 500; - padding: 0 4px; - text-align: center; - cursor: pointer; - user-select: none; - white-space: nowrap; - - &:hover { + &.today { + .cell { color: var(--bb-picker-hover-color); + font-weight: 700; } } - } - - .picker-panel-content { - width: 290px; - position: relative; - margin: 15px; - - table { - table-layout: fixed; - width: 100%; - user-select: none; - - .date-table-row { - font-size: 12px; - } - - td { - text-align: center; - - .cell { - display: block; - margin: 0 auto; - cursor: pointer; - - &:has(.bb-picker-body-lunar-text):hover { - color: var(--bb-picker-panel-today-color); - - &:before { - content: ""; - position: absolute; - width: 33px; - height: 33px; - border-radius: var(--bs-border-radius); - background-color: var(--bb-picker-panel-today-bg); - top: 3px; - left: -3.5px; - opacity: .8; - } - - .bb-picker-body-lunar-text { - color: var(--bb-picker-panel-today-color); - } - } - span.is-holiday, span.is-workday { - background-color: #c0c4cc; - color: var(--bb-picker-panel-today-color); - height: 14px; - width: 15px; - font-size: 9px; - border-radius: 3px; - top: -1px; - right: -9px; - line-height: 14px; - opacity: .8; - } - } + &.current:not(.disabled), + &.start:not(.disabled), + &.end:not(.disabled) { + .cell { + color: var(--bb-picker-panel-today-color); - &.today { - .cell { - color: var(--bb-picker-hover-color); - font-weight: 700; - } + &:not(:has(.bb-picker-body-lunar-text)) { + background-color: var(--bb-picker-panel-today-bg); } - &.current:not(.disabled), - &.start:not(.disabled), - &.end:not(.disabled) { - .cell { - color: var(--bb-picker-panel-today-color); - - &:not(:has(.bb-picker-body-lunar-text)) { - background-color: var(--bb-picker-panel-today-bg); - } - - &:has(.bb-picker-body-lunar-text) { - &:before { - content: ""; - position: absolute; - width: 33px; - height: 33px; - border-radius: var(--bs-border-radius); - background-color: #409eff; - top: 3px; - left: -3.5px; - } - } + &:has(.bb-picker-body-lunar-text) { + &:before { + content: ""; + position: absolute; + width: 33px; + height: 33px; + border-radius: var(--bs-border-radius); + background-color: #409eff; + top: 3px; + left: -3.5px; } } + } + } - &:not(.next-month):not(.prev-month):not(.disabled) { - &:not(.current):not(.start):not(.end):not(.range) { - .cell { - &.is-solar-term:not(:hover) { - .bb-picker-body-lunar-text { - color: var(--bs-success); - } - } - - &.is-festival:not(:hover) { - .bb-picker-body-lunar-text { - color: var(--bs-info); - } - } + &:not(.next-month):not(.prev-month):not(.disabled) { + &:not(.current):not(.start):not(.end):not(.range) { + .cell { + &.is-solar-term:not(:hover) { + .bb-picker-body-lunar-text { + color: var(--bs-success); } } - .cell { - span.is-holiday { - background-color: var(--bs-danger); - } - - span.is-workday { - background-color: var(--bs-black); + &.is-festival:not(:hover) { + .bb-picker-body-lunar-text { + color: var(--bs-info); } } } + } - &.range { - position: relative; - - &.start:before { - border-top-left-radius: 40%; - border-bottom-left-radius: 40%; - left: 6px; - } - - &.end:before { - border-top-right-radius: 40%; - border-bottom-right-radius: 40%; - right: 6px; - } - - &:before { - content: ""; - position: absolute; - background-color: #f2f6fc; - top: 4px; - bottom: 4px; - left: 0; - right: 0; - } + .cell { + span.is-holiday { + background-color: var(--bs-danger); } - &.disabled { - .cell { - cursor: not-allowed; - color: #c0c4cc; - } + span.is-workday { + background-color: var(--bs-black); } } } - .year-table, .month-table { - margin-block-start: 29px; + &.range { + position: relative; - tr { - border-top: 1px solid var(--bs-border-color); + &.start:before { + border-top-left-radius: 40%; + border-bottom-left-radius: 40%; + left: 6px; } - } - .year-table td { - padding: 13px 0; + &.end:before { + border-top-right-radius: 40%; + border-bottom-right-radius: 40%; + right: 6px; + } - .cell { - width: 48px; - height: 24px; - line-height: 24px; - border-radius: 16px; + &:before { + content: ""; + position: absolute; + background-color: #f2f6fc; + top: 4px; + bottom: 4px; + left: 0; + right: 0; } } - .month-table td { - padding: 27px 0; - + &.disabled { .cell { - width: 60px; - border-radius: 18px; + cursor: not-allowed; + color: #c0c4cc; } } + } + } - .date-table { - th { - padding: 5px; - font-weight: 400; - border-bottom: 1px solid var(--bs-border-color); - text-align: center; - } + .year-table, .month-table { + margin-block-start: 29px; - td { - padding: 7px 0; + tr { + border-top: 1px solid var(--bs-border-color); + } + } - &.next-month, &.prev-month { - color: #c0c4cc; - } + .year-table td { + padding: 13px 0; - &:not(.prev-month):not(.next-month):hover { - color: #409eff; - } + .cell { + width: 48px; + height: 24px; + line-height: 24px; + border-radius: 16px; + } + } - .cell { - width: 24px; - height: 24px; - margin: 0 auto; - line-height: 24px; - border-radius: 50%; - cursor: pointer; - position: relative; - display: flex; - flex-direction: column; - - .bb-picker-body-lunar-text { - top: 22px; - white-space: nowrap; - line-height: 9px; - height: 9px; - font-size: 9px; - } + .month-table td { + padding: 27px 0; - span { - position: absolute; - width: 100%; - } - } + .cell { + width: 60px; + border-radius: 18px; + } + } + + .date-table { + th { + padding: 5px; + font-weight: 400; + border-bottom: 1px solid var(--bs-border-color); + text-align: center; + } + + td { + padding: 7px 0; + + &.next-month, &.prev-month { + color: #c0c4cc; + } + + &:not(.prev-month):not(.next-month):hover { + color: #409eff; + } + + .cell { + width: 24px; + height: 24px; + margin: 0 auto; + line-height: 24px; + border-radius: 50%; + cursor: pointer; + position: relative; + display: flex; + flex-direction: column; + + .bb-picker-body-lunar-text { + top: 22px; + white-space: nowrap; + line-height: 9px; + height: 9px; + font-size: 9px; + } + + span { + position: absolute; + width: 100%; } } } @@ -315,12 +296,7 @@ .clock-panel-body { width: var(--bb-picker-panel-body-width); - } -} - -.popover-body { - .picker-panel { - box-shadow: none; + padding: 0 28px; } } diff --git a/src/BootstrapBlazor/Components/DateTimePicker/DateTimePicker.razor b/src/BootstrapBlazor/Components/DateTimePicker/DateTimePicker.razor index f6883d0b98c..1c5effa26bf 100644 --- a/src/BootstrapBlazor/Components/DateTimePicker/DateTimePicker.razor +++ b/src/BootstrapBlazor/Components/DateTimePicker/DateTimePicker.razor @@ -7,12 +7,14 @@ { } -
- @if(IsButton) +
+ @if (IsButton) { + data-bs-custom-class="@CustomClassString"> + @PickerButtonText + } else { @@ -28,14 +30,43 @@ } } - - @ChildContent - + + +
+
@DialogHeaderText
+ +
+
+
+ @if (ShowSidebar) + { +
+ @if (SidebarTemplate != null) + { + @SidebarTemplate(OnClickSidebarTemplate) + } + else + { + @foreach (var item in SidebarItems) + { + + } + } +
+ } + + +
+
+
diff --git a/src/BootstrapBlazor/Components/DateTimePicker/DateTimePicker.razor.cs b/src/BootstrapBlazor/Components/DateTimePicker/DateTimePicker.razor.cs index 76201cc1e1c..77ccacf838e 100644 --- a/src/BootstrapBlazor/Components/DateTimePicker/DateTimePicker.razor.cs +++ b/src/BootstrapBlazor/Components/DateTimePicker/DateTimePicker.razor.cs @@ -193,12 +193,20 @@ public string? Format [Parameter] public bool ShowSidebar { get; set; } + /// + /// 获得/设置 侧边栏快捷选项集合 + /// Gets or sets Sidebar Items + /// 11.1.0 + /// + [Parameter] + [NotNull] + public List? SidebarItems { get; set; } + /// /// 获得/设置 侧边栏模板 默认 null /// Gets or sets Sidebar Template. Default is null /// [Parameter] - [NotNull] public RenderFragment>? SidebarTemplate { get; set; } /// @@ -383,8 +391,7 @@ protected override void OnInitialized() } /// - /// OnParametersSet 方法 - /// OnParametersSet method + /// /// protected override void OnParametersSet() { @@ -397,6 +404,7 @@ protected override void OnParametersSet() DateFormat ??= Localizer[nameof(DateFormat)]; TimeFormat ??= Localizer[nameof(TimeFormat)]; PickerButtonText ??= Localizer[nameof(PickerButtonText)]; + SidebarItems ??= []; Icon ??= IconTheme.GetIconByKey(ComponentIcons.DateTimePickerIcon); ClearIcon ??= IconTheme.GetIconByKey(ComponentIcons.InputClearIcon); @@ -442,6 +450,7 @@ protected override void OnParametersSet() } private List _disabledDaysList = []; + private readonly HashSet _disabledSidebarDays = []; /// /// @@ -450,6 +459,19 @@ protected override async Task OnParametersSetAsync() { await base.OnParametersSetAsync(); + _disabledSidebarDays.Clear(); + if (ShowSidebar && SidebarTemplate == null && OnGetDisabledDaysCallback != null) + { + foreach (var day in SidebarItems.Select(item => item.Value.Date).Distinct()) + { + var disabledDays = await OnGetDisabledDaysCallback(day, day); + if (disabledDays.Any(value => value.Date == day)) + { + _disabledSidebarDays.Add(day); + } + } + } + if (OnGetDisabledDaysCallback != null && DisplayDisabledDayAsEmpty) { DateTime d = Value switch @@ -548,7 +570,7 @@ private async Task OnConfirm() if (AutoClose) { - await InvokeVoidAsync("hide", Id); + await ClosePickerPanel(); } } @@ -560,10 +582,12 @@ private async Task OnClear() if (AutoClose) { - await InvokeVoidAsync("hide", Id); + await ClosePickerPanel(); } } + private Task ClosePickerPanel() => InvokeVoidAsync("hide", Id); + private TValue? GetValue() { TValue? ret = default; @@ -651,4 +675,34 @@ public Task TriggerHideCallback() StateHasChanged(); return Task.CompletedTask; } + + private string? DialogHeaderText => DisplayText ?? PlaceholderString; + + private bool IsSidebarValueDisabled(DateTime value) => IsDisabled + || value < MinValue || value > MaxValue || _disabledSidebarDays.Contains(value.Date); + + private bool CanTriggerOnclickSidebarItem(DateTime value) => !IsSidebarValueDisabled(value); + + private async Task OnClickSidebarTemplate(DateTime value) + { + if (CanTriggerOnclickSidebarItem(value)) + { + if (OnGetDisabledDaysCallback != null) + { + var disabledDays = await OnGetDisabledDaysCallback(value.Date, value.Date); + if (disabledDays.Any(day => day.Date == value.Date)) + { + return; + } + } + + await OnClickSidebarItem(value); + } + } + + private async Task OnClickSidebarItem(DateTime value) + { + SelectedValue = value; + await OnConfirm(); + } } diff --git a/src/BootstrapBlazor/Components/DateTimePicker/DateTimePicker.razor.js b/src/BootstrapBlazor/Components/DateTimePicker/DateTimePicker.razor.js index 30afd7ecdb9..fbb991b005f 100644 --- a/src/BootstrapBlazor/Components/DateTimePicker/DateTimePicker.razor.js +++ b/src/BootstrapBlazor/Components/DateTimePicker/DateTimePicker.razor.js @@ -1,6 +1,7 @@ -import Data from "../../modules/data.js" +import Data from "../../modules/data.js" import EventHandler from "../../modules/event-handler.js" import Popover from "../../modules/base-popover.js" +import Dialog from "../../modules/dialog.js" export function init(id, invoke, options) { const el = document.getElementById(id) @@ -8,41 +9,96 @@ export function init(id, invoke, options) { return } - const popover = createPopover(el, invoke, options); - const input = handlerInput(el, popover); - Data.set(id, { el, input, invoke, options, popover }); + const mediaQuery = window.matchMedia('(min-width: 768px)'); + const panel = el.querySelector('.datetime-picker-panel'); + const dialogBody = el.querySelector('.datetime-dialog-body'); + const picker = createPicker(el, invoke, options, mediaQuery, panel, dialogBody); + const onModeChanged = () => syncPickerMode(id); + const data = { el, invoke, options, picker, mediaQuery, panel, dialogBody, onModeChanged, isPopoverHiding: false }; + data.input = handlerInput(data); + Data.set(id, data); + EventHandler.on(mediaQuery, 'change', onModeChanged); +} + +const syncPickerMode = id => { + const data = Data.get(id); + if (data) { + const { picker, mediaQuery, dialogBody, isPopoverHiding } = data; + const isShown = picker.mode === 'dialog' ? picker.isShown() : picker.popover?._isShown(); + const mode = dialogBody && !mediaQuery.matches ? 'dialog' : 'popover'; + if (!isShown && !isPopoverHiding && picker.mode !== mode) { + reset(id); + } + } +} + +const createPicker = (el, invoke, options, mediaQuery, panel, dialogBody) => { + if (dialogBody && !mediaQuery.matches) { + dialogBody.append(panel); + return createDialog(el, invoke, options); + } + + el.append(panel); + return createPopover(el, invoke, options); } const createPopover = (el, invoke, options) => { const popover = Popover.init(el, { - dropdownSelector: el.getAttribute('data-bb-dropdown'), + dropdownSelector: '.datetime-picker-panel', isDisabled: () => { return el.classList.contains('disabled'); }, hideCallback: () => { + const data = Data.get(el.id); + if (data?.picker === popover) { + data.isPopoverHiding = true; + popover.onHidden ??= () => { + const current = Data.get(el.id); + if (current?.picker === popover) { + current.isPopoverHiding = false; + syncPickerMode(el.id); + } + }; + EventHandler.one(popover.toggleElement, 'hidden.bs.popover', popover.onHidden); + } if (invoke) { invoke.invokeMethodAsync(options.triggerHideCallback); } } }); + popover.mode = 'popover'; return popover; } -const handlerInput = (el, popover) => { - const input = el.querySelector('.datetime-picker-input'); +const createDialog = (el, invoke, options) => { + const isDisabled = () => el.classList.contains('disabled') || el.querySelector('.form-control')?.disabled; + return Dialog.init(el, { + isDisabled, + hideCallback: () => { + if (invoke) { + invoke.invokeMethodAsync(options.triggerHideCallback); + } + // Keep the dialog hide callback ahead of mode synchronization after rebuilding. + syncPickerMode(el.id); + } + }); +} + +const handlerInput = picker => { + const input = picker.el.querySelector('.datetime-picker-input'); if (input) { EventHandler.on(input, 'keydown', e => { - if (e.key === 'Tab' && popover.isShown()) { - popover.hide(); + if (e.key === 'Tab' && picker.picker.mode === 'popover' && picker.picker.isShown()) { + picker.picker.hide(); } }); EventHandler.on(input, 'keyup', e => { if (e.key === 'Escape') { - popover.hide(); + picker.picker.hide(); input.blur(); } - else if (e.key === 'Tab') { - popover.show(); + else if (e.key === 'Tab' && picker.picker.mode === 'popover') { + picker.picker.show(); } }); } @@ -59,19 +115,37 @@ const disposeInput = input => { export function reset(id) { const picker = Data.get(id); if (picker) { - const { el, input, popover, invoke, options } = picker; - disposeInput(input); - Popover.dispose(popover); + const { el, input, invoke, options, mediaQuery, panel, dialogBody } = picker; + disposePicker(picker.picker); - picker.popover = createPopover(el, invoke, options); - picker.input = handlerInput(picker.el, picker.popover); + picker.picker = createPicker(el, invoke, options, mediaQuery, panel, dialogBody); + picker.isPopoverHiding = false; + if (input !== el.querySelector('.datetime-picker-input')) { + disposeInput(input); + picker.input = handlerInput(picker); + } + } +} + +const disposePicker = picker => { + if (picker.mode === 'dialog') { + Dialog.dispose(picker); + } + else { + if (picker.popover?._isShown()) { + picker.hide(); + } + if (picker.onHidden) { + EventHandler.off(picker.toggleElement, 'hidden.bs.popover', picker.onHidden); + } + Popover.dispose(picker); } } export function hide(id) { const data = Data.get(id) if (data) { - data.popover.hide() + data.picker.hide() } } @@ -80,8 +154,12 @@ export function dispose(id) { Data.remove(id) if (data) { - const { input, popover } = data; + const { input, picker, mediaQuery, onModeChanged, panel, dialogBody } = data; + EventHandler.off(mediaQuery, 'change', onModeChanged); disposeInput(input); - Popover.dispose(popover) + disposePicker(picker); + if (dialogBody) { + dialogBody.append(panel); + } } } diff --git a/src/BootstrapBlazor/Components/DateTimePicker/DateTimePicker.razor.scss b/src/BootstrapBlazor/Components/DateTimePicker/DateTimePicker.razor.scss index 92d09b6a2bd..c72209febd2 100644 --- a/src/BootstrapBlazor/Components/DateTimePicker/DateTimePicker.razor.scss +++ b/src/BootstrapBlazor/Components/DateTimePicker/DateTimePicker.razor.scss @@ -32,9 +32,11 @@ } } } +} - .picker-panel { - display: none +.select { + .datetime-picker-panel { + display: none; } } @@ -48,6 +50,68 @@ color: var(--bb-dt-picker-bar-color); } -.popover .picker-panel { - box-shadow: none; +.datetime-dialog { + overflow: auto; + padding: 0; + border: var(--bs-border-width) solid var(--bs-border-color-translucent); + border-radius: var(--bs-border-radius); + outline: 0; + box-shadow: 0 2px 12px 0 rgba(0,0,0,.1); + + .datetime-picker-panel { + display: flex; + flex-direction: column; + } + + .picker-panel-sidebar { + border-right: 0; + border-bottom: var(--bs-border-width) solid var(--bs-border-color); + width: 100%; + overflow: auto; + max-height: 69px; + display: grid; + grid-template-columns: repeat(2, 1fr); + } + + .datetime-dialog-header { + display: flex; + align-items: center; + padding: .5rem 1rem; + border-bottom: var(--bs-border-width) solid var(--bs-border-color); + border-top-left-radius: var(--bs-border-radius); + border-top-right-radius: var(--bs-border-radius); + + .datetime-dialog-header-title { + flex: 1 1 auto; + margin-bottom: 0; + line-height: 1.5; + } + } +} + +.datetime-picker-panel { + display: flex; + + .picker-panel { + border: 0; + box-shadow: none; + } +} + +.picker-panel-sidebar { + width: 110px; + border-right: var(--bs-border-width) solid var(--bs-border-color); + padding: 6px 0; + overflow: auto; + + .sidebar-item { + line-height: 28px; + padding: 0 12px; + cursor: pointer; + transition: color .3s linear; + + &:hover { + @include color_409eff + } + } } diff --git a/src/BootstrapBlazor/Components/DateTimePicker/DateTimePickerSidebarItem.cs b/src/BootstrapBlazor/Components/DateTimePicker/DateTimePickerSidebarItem.cs new file mode 100644 index 00000000000..3cfdc556ba1 --- /dev/null +++ b/src/BootstrapBlazor/Components/DateTimePicker/DateTimePickerSidebarItem.cs @@ -0,0 +1,29 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the Apache 2.0 License +// See the LICENSE file in the project root for more information. +// Maintainer: Argo Zhang(argo@live.ca) Website: https://www.blazor.zone + +namespace BootstrapBlazor.Components; + +/// +/// DateTimePicker 组件侧边栏快捷项目类 +/// DateTimePicker Component Sidebar Item Class +/// 11.1.0 +/// +public class DateTimePickerSidebarItem +{ + /// + /// 获得/设置 快捷项目文本 + /// Gets or sets Item Text + /// 11.1.0 + /// + [NotNull] + public string? Text { get; set; } + + /// + /// 获得/设置 快捷项目值 + /// Gets or sets Item Value + /// 11.1.0 + /// + public DateTime Value { get; set; } +} diff --git a/src/BootstrapBlazor/Components/DateTimeRange/DateTimeRange.razor b/src/BootstrapBlazor/Components/DateTimeRange/DateTimeRange.razor index e418f132eb3..a05d96e666f 100644 --- a/src/BootstrapBlazor/Components/DateTimeRange/DateTimeRange.razor +++ b/src/BootstrapBlazor/Components/DateTimeRange/DateTimeRange.razor @@ -1,4 +1,4 @@ -@namespace BootstrapBlazor.Components +@namespace BootstrapBlazor.Components @inherits PopoverDropdownBase @attribute [BootstrapModuleAutoLoader("DateTimePicker/DateTimePicker.razor.js", JSObjectReference = true)] @@ -6,7 +6,7 @@ { } -
+