From 1a36f0f75824859d9657759bb88fc4f1cb4fa368 Mon Sep 17 00:00:00 2001 From: Haoxin Yang <1810849666@qq.com> Date: Fri, 14 Jun 2024 18:19:08 +0800 Subject: [PATCH 01/12] feat: year and month header will in locale order --- src/date-picker/calendar/header/component.ts | 32 +++++++++++++-- src/date-picker/calendar/header/template.html | 39 +++++++++++++------ 2 files changed, 55 insertions(+), 16 deletions(-) diff --git a/src/date-picker/calendar/header/component.ts b/src/date-picker/calendar/header/component.ts index 821d469ae..4e7f5488f 100644 --- a/src/date-picker/calendar/header/component.ts +++ b/src/date-picker/calendar/header/component.ts @@ -1,4 +1,4 @@ -import { NgIf, NgTemplateOutlet } from '@angular/common'; +import { AsyncPipe, NgIf, NgTemplateOutlet } from '@angular/common'; import { ChangeDetectionStrategy, Component, @@ -8,19 +8,22 @@ import { ViewEncapsulation, } from '@angular/core'; import dayjs, { ConfigType, Dayjs } from 'dayjs'; +import { map, startWith } from 'rxjs'; import { ButtonComponent } from '../../../button/button.component'; import { I18nPipe } from '../../../i18n/i18n.pipe'; import { IconComponent } from '../../../icon/icon.component'; -import { buildBem } from '../../../internal/utils'; +import { buildBem, publishRef } from '../../../internal/utils'; import { CalendarHeaderRange, DateNavRange, Side, } from '../../date-picker.type'; -import { MONTH, YEAR } from '../constant'; +import { DatePickerType, MONTH, YEAR } from '../constant'; import { calcRangeValue } from '../util'; +import { I18nService } from 'src/i18n'; + const bem = buildBem('aui-calendar-header'); @Component({ @@ -30,7 +33,14 @@ const bem = buildBem('aui-calendar-header'); encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, - imports: [NgIf, NgTemplateOutlet, ButtonComponent, IconComponent, I18nPipe], + imports: [ + NgIf, + NgTemplateOutlet, + ButtonComponent, + IconComponent, + I18nPipe, + AsyncPipe, + ], }) export class CalendarHeaderComponent { @Input() @@ -67,6 +77,20 @@ export class CalendarHeaderComponent { DateNavRange = DateNavRange; + monthBeforeYear$ = this.i18nService.localeChange$.pipe( + map(locale => { + const parts = new Intl.DateTimeFormat(locale).formatToParts(new Date()); + return ( + parts.findIndex(part => part.type === DatePickerType.Month) < + parts.findIndex(part => part.type === DatePickerType.Year) + ); + }), + startWith(false), + publishRef(), + ); + + constructor(private readonly i18nService: I18nService) {} + // maxAvail > current date :right btn hide // minAvail > current date :left btn hide shouldShowNav(type: DateNavRange, side: Side) { diff --git a/src/date-picker/calendar/header/template.html b/src/date-picker/calendar/header/template.html index 51cedfb9d..be72e9265 100644 --- a/src/date-picker/calendar/header/template.html +++ b/src/date-picker/calendar/header/template.html @@ -17,23 +17,21 @@ *ngIf="dateNavRange === DateNavRange.Month" [class]="bem.element('nav-content')" > - +
/
- + + + + + + + + From 6ac279868ac92257449f31f4f67ff95b79d7efed Mon Sep 17 00:00:00 2001 From: Haoxin Yang <1810849666@qq.com> Date: Mon, 17 Jun 2024 18:17:38 +0800 Subject: [PATCH 02/12] fix: picker header overflow and preview button font --- .storybook/global.scss | 2 + .storybook/reset-browser.scss | 92 +++++++++++++++++++ angular.json | 3 +- src/date-picker/calendar/header/style.scss | 6 ++ src/date-picker/calendar/header/template.html | 4 + .../date-picker/date-picker.template.html | 2 +- 6 files changed, 107 insertions(+), 2 deletions(-) create mode 100644 .storybook/global.scss create mode 100644 .storybook/reset-browser.scss diff --git a/.storybook/global.scss b/.storybook/global.scss new file mode 100644 index 000000000..b9becfb19 --- /dev/null +++ b/.storybook/global.scss @@ -0,0 +1,2 @@ +@import './reset-browser'; +@import '../src/theme/style.scss'; diff --git a/.storybook/reset-browser.scss b/.storybook/reset-browser.scss new file mode 100644 index 000000000..d03c43895 --- /dev/null +++ b/.storybook/reset-browser.scss @@ -0,0 +1,92 @@ +@import '../src/theme/var'; +@import '../src/theme/mixin'; + +* { + &, + &:before, + &:after { + margin: 0; + padding: 0; + border: 0; + font: inherit; + box-sizing: border-box; + vertical-align: baseline; + } + + &:focus { + outline: none; + } +} + +em { + font-style: italic; +} + +ol { + list-style: decimal; + margin-left: 16px; +} + +ul { + list-style: none; +} + +blockquote, +q { + quotes: none; + + &:before, + &:after { + content: ''; + content: none; + } +} + +table { + border-collapse: collapse; + border-spacing: 0; +} + +body { + -webkit-font-smoothing: auto; + -moz-osx-font-smoothing: auto; + display: flex; + align-items: center; + justify-content: center; + min-width: 100%; + min-height: 100%; + color: use-text-color(main); + background-color: use-rgb(main-bg); + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', + Arial, 'Microsoft YaHei', sans-serif; + font-size: 14px; + font-weight: 400; + line-height: 1.43; +} + +pre, +code { + font-family: Menlo, Monaco, 'Courier New', monospace; + white-space: pre-wrap; + font-size: 14px; + line-height: 20px; + padding: 12px; + background: use-rgb(n-9); + border-radius: 2px; + + &[ngCodeColorize] { + padding: 0 12px; + } +} + +input:-webkit-autofill, +input:-webkit-autofill:hover, +input:-webkit-autofill:focus, +input:-webkit-autofill:active { + -webkit-text-fill-color: #{use-text-color(main)} !important; + box-shadow: 0 0 0 3000px #{use-rgb(main-bg)} inset !important; +} + +[hidden] { + display: none !important; +} diff --git a/angular.json b/angular.json index 1a55ac474..cc8e44bcf 100644 --- a/angular.json +++ b/angular.json @@ -64,7 +64,8 @@ "browserTarget": "storybook:build", "compodoc": true, "compodocArgs": ["-e", "json", "-d", "."], - "port": 6006 + "port": 6006, + "styles": [".storybook/global.scss"] } }, "build-storybook": { diff --git a/src/date-picker/calendar/header/style.scss b/src/date-picker/calendar/header/style.scss index 0fa8a9ef1..dbafce8a8 100644 --- a/src/date-picker/calendar/header/style.scss +++ b/src/date-picker/calendar/header/style.scss @@ -1,6 +1,7 @@ @import '../../../theme/var'; $block: aui-calendar-header; +$prev-next-icon-button-width: 24px; .#{$block} { &__container { @@ -12,6 +13,7 @@ $block: aui-calendar-header; &__nav-content { flex: 1; + max-width: calc(100% - $prev-next-icon-button-width * 4); display: flex; flex-wrap: wrap; justify-content: center; @@ -43,6 +45,10 @@ $block: aui-calendar-header; margin-left: 0; } } + + .aui-button--text.header-range { + max-width: 100%; + } } } diff --git a/src/date-picker/calendar/header/template.html b/src/date-picker/calendar/header/template.html index be72e9265..2554d35c7 100644 --- a/src/date-picker/calendar/header/template.html +++ b/src/date-picker/calendar/header/template.html @@ -125,6 +125,8 @@ @@ -50,7 +42,7 @@ *ngIf="dateNavRange === DateNavRange.Decade" [class]="bem.element('nav-content')" > - {{ headerRange?.start.year() }} - {{ headerRange?.end?.year() }} + {{ $headerRange().start.year }} - {{ $headerRange().end.year }}
@@ -75,6 +67,8 @@ @@ -137,6 +137,6 @@ class="header-range" (click)="clickNav(DateNavRange.Month)" > - {{ headerRange?.start?.month() + 1 }}{{ 'month_suffix' | auiI18n }} + {{ $headerRange().start.month }} diff --git a/src/i18n/i18n.pipe.ts b/src/i18n/i18n.pipe.ts index f29649f12..af6ea098b 100644 --- a/src/i18n/i18n.pipe.ts +++ b/src/i18n/i18n.pipe.ts @@ -1,10 +1,4 @@ -import { - ChangeDetectorRef, - OnDestroy, - Pipe, - PipeTransform, -} from '@angular/core'; -import { Subject, takeUntil } from 'rxjs'; +import { Pipe, PipeTransform } from '@angular/core'; import { I18nService } from './i18n.service'; @@ -13,24 +7,10 @@ import { I18nService } from './i18n.service'; pure: false, standalone: true, }) -export class I18nPipe implements PipeTransform, OnDestroy { - private readonly destroy$$ = new Subject(); - - constructor( - private readonly i18n: I18nService, - private readonly cdr: ChangeDetectorRef, - ) { - this.i18n.localeChange$ - .pipe(takeUntil(this.destroy$$)) - .subscribe(() => this.cdr.markForCheck()); - } +export class I18nPipe implements PipeTransform { + constructor(private readonly i18n: I18nService) {} transform(value: any, data?: Record) { return this.i18n.translate(value, data); } - - ngOnDestroy(): void { - this.destroy$$.next(); - this.destroy$$.complete(); - } } diff --git a/src/i18n/i18n.service.ts b/src/i18n/i18n.service.ts index 31510ddae..c0da6a6e2 100644 --- a/src/i18n/i18n.service.ts +++ b/src/i18n/i18n.service.ts @@ -1,5 +1,4 @@ -import { Injectable, inject, isDevMode } from '@angular/core'; -import { BehaviorSubject, map, startWith } from 'rxjs'; +import { Injectable, computed, inject, isDevMode, signal } from '@angular/core'; import { DatePickerType } from '..'; @@ -9,34 +8,22 @@ import { I18NInterface, I18NInterfaceToken } from './i18n.type'; providedIn: 'root', }) export class I18nService { - private _i18n: I18NInterface = inject(I18NInterfaceToken); + readonly $$i18n = signal(inject(I18NInterfaceToken)); - private readonly i18nChange$$ = new BehaviorSubject( - this._i18n, - ); + $locale = computed(() => this.$$i18n().locale); - localeChange$ = this.i18nChange$$ - .asObservable() - .pipe(map(i18n => i18n.locale)); - - monthBeforeYear$ = this.localeChange$.pipe( - map(locale => { - const parts = new Intl.DateTimeFormat(locale).formatToParts(new Date()); - return ( - parts.findIndex(part => part.type === DatePickerType.Month) < - parts.findIndex(part => part.type === DatePickerType.Year) - ); - }), - startWith(false), - ); + $monthBeforeYear = computed(() => { + const parts = new Intl.DateTimeFormat(this.$locale()).formatToParts( + new Date(), + ); + return ( + parts.findIndex(part => part.type === DatePickerType.Month) < + parts.findIndex(part => part.type === DatePickerType.Year) + ); + }); setI18n(i18n: I18NInterface) { - this._i18n = i18n; - this.i18nChange$$.next(i18n); - } - - get i18n() { - return this._i18n; + this.$$i18n.set(i18n); } translate( @@ -44,7 +31,7 @@ export class I18nService { data?: Record, ignoreNonExist = false, ) { - let content = this._i18n.translation[key]; + let content = this.$$i18n().translation[key]; if (content == null) { if (isDevMode() && !ignoreNonExist) { console.warn(`No exist translate key for ${key}`); diff --git a/stories/rangepicker/basic.component.ts b/stories/rangepicker/basic.component.ts index 3eaaac9d5..749fa355c 100644 --- a/stories/rangepicker/basic.component.ts +++ b/stories/rangepicker/basic.component.ts @@ -1,4 +1,4 @@ -import { ChangeDetectionStrategy, Component } from '@angular/core'; +import { ChangeDetectionStrategy, Component, computed } from '@angular/core'; import { Dayjs } from 'dayjs'; import { I18nService, en, zh } from '@alauda/ui'; @@ -6,7 +6,7 @@ import { I18nService, en, zh } from '@alauda/ui'; @Component({ template: `
- Current Locale: {{ i18n.i18n.locale }} + Current Locale: {{ $locale() }}
@@ -25,9 +25,11 @@ import { I18nService, en, zh } from '@alauda/ui'; export class RangeBasicComponent { range: [Dayjs, Dayjs] = null; + $locale = computed(() => this.i18n.$$i18n().locale); + constructor(public i18n: I18nService) {} changeLocale() { - this.i18n.setI18n(this.i18n.i18n.locale === 'en' ? zh : en); + this.i18n.setI18n(this.$locale() === 'en' ? zh : en); } } From 139a9c47aeadfc6de38b9dd33e2c63a5af0561e9 Mon Sep 17 00:00:00 2001 From: Haoxin Yang <1810849666@qq.com> Date: Fri, 21 Jun 2024 00:57:31 +0800 Subject: [PATCH 10/12] chore: fix unit test case --- src/i18n/i18n.service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/i18n/i18n.service.ts b/src/i18n/i18n.service.ts index c0da6a6e2..a28e32682 100644 --- a/src/i18n/i18n.service.ts +++ b/src/i18n/i18n.service.ts @@ -1,6 +1,6 @@ import { Injectable, computed, inject, isDevMode, signal } from '@angular/core'; -import { DatePickerType } from '..'; +import { DatePickerType } from '../date-picker/calendar/constant'; import { I18NInterface, I18NInterfaceToken } from './i18n.type'; From 8e2acb424b5f5c5e80745ff7474e5f940b588390 Mon Sep 17 00:00:00 2001 From: Haoxin Yang <1810849666@qq.com> Date: Mon, 24 Jun 2024 13:59:51 +0800 Subject: [PATCH 11/12] feat: trigger show date use locale format default --- .../date-picker/date-picker.component.ts | 23 +----- .../range-picker/range-picker.component.ts | 2 +- src/date-picker/trigger/trigger.component.ts | 76 ++++++++++++++++++- src/date-picker/trigger/trigger.template.html | 6 +- 4 files changed, 77 insertions(+), 30 deletions(-) diff --git a/src/date-picker/date-picker/date-picker.component.ts b/src/date-picker/date-picker/date-picker.component.ts index 93969b348..76fe8f9de 100644 --- a/src/date-picker/date-picker/date-picker.component.ts +++ b/src/date-picker/date-picker/date-picker.component.ts @@ -3,7 +3,6 @@ import { Component, EventEmitter, Input, - OnInit, Output, TemplateRef, ViewEncapsulation, @@ -41,10 +40,7 @@ import { DatePickerTriggerComponent } from '../trigger/trigger.component'; FormsModule, ], }) -export class DatePickerComponent - extends CommonFormControl - implements OnInit -{ +export class DatePickerComponent extends CommonFormControl { @Input() clearable = true; @@ -96,13 +92,6 @@ export class DatePickerComponent value: Dayjs; DatePickerType = DatePickerType; - ngOnInit() { - if (!this.format) { - this.format = this.getDefaultFormat(this.type); - this.cdr.markForCheck(); - } - } - override valueIn(obj: ConfigType) { return obj ? dayjs(obj) : null; } @@ -113,16 +102,6 @@ export class DatePickerComponent this.cdr.markForCheck(); } - private getDefaultFormat(type = DatePickerType.Day) { - return type === DatePickerType.Year - ? 'YYYY' - : type === DatePickerType.Month - ? 'YYYY-MM' - : this.showTime - ? 'YYYY-MM-DD HH:mm:ss' - : 'YYYY-MM-DD'; - } - clearValue() { this.value = null; this.emitValue(null); diff --git a/src/date-picker/range-picker/range-picker.component.ts b/src/date-picker/range-picker/range-picker.component.ts index 734e6c2c9..116d6ccff 100644 --- a/src/date-picker/range-picker/range-picker.component.ts +++ b/src/date-picker/range-picker/range-picker.component.ts @@ -49,7 +49,7 @@ export class RangePickerComponent extends CommonFormControl< clearText: string; @Input() - format = 'YYYY-MM-DD'; + format: string; @Input() showFooter = true; diff --git a/src/date-picker/trigger/trigger.component.ts b/src/date-picker/trigger/trigger.component.ts index d481ec64d..fb338eab7 100644 --- a/src/date-picker/trigger/trigger.component.ts +++ b/src/date-picker/trigger/trigger.component.ts @@ -7,6 +7,9 @@ import { Output, ViewChild, ViewEncapsulation, + computed, + inject, + signal, } from '@angular/core'; import { Dayjs } from 'dayjs'; @@ -16,6 +19,8 @@ import { InputComponent } from '../../input/input.component'; import { ComponentSize } from '../../internal/types'; import { buildBem } from '../../internal/utils'; +import { I18nService } from 'src/i18n'; + const bem = buildBem('aui-date-picker-trigger'); @Component({ @@ -29,16 +34,50 @@ const bem = buildBem('aui-date-picker-trigger'); }) export class DatePickerTriggerComponent { @Input() - format = 'YYYY-MM-DD'; + get value() { + return this.$$value(); + } + + set value(val) { + const currentValue = this.$$value(); + if (Array.isArray(val)) { + if ( + !Array.isArray(currentValue) || + val.some((v, i) => !v.isSame(currentValue[i])) + ) { + this.$$value.set(val); + } + } else { + if (Array.isArray(currentValue) || !val?.isSame(currentValue)) { + this.$$value.set(val); + } + } + } @Input() - size: ComponentSize; + get format() { + return this.$$format(); + } + + set format(val) { + if (this.$$format() !== val) { + this.$$format.set(val); + } + } @Input() - isRange: boolean; + get isRange() { + return this.$$isRange(); + } + + set isRange(val) { + if (this.$$isRange() !== val) { + this.$$isRange.set(val); + } + } @Input() - value: Dayjs | Dayjs[]; + size: ComponentSize; @Input() clearable = true; @@ -84,6 +123,35 @@ export class DatePickerTriggerComponent { return !this.disabled && this.clearable && this.hasValue && this.hovered; } + private readonly i18nService = inject(I18nService); + + private readonly $$value = signal(null); + + private readonly $$format = signal(null); + + private readonly $$isRange = signal(null); + + $formatValue = computed(() => { + const format = this.$$format(); + const value = this.$$value(); + const isRange = this.$$isRange(); + const locale = this.i18nService.$locale(); + + if (!value) { + return isRange ? [null, null] : null; + } + + if (this.format) { + return Array.isArray(value) + ? value.map(v => v.format(format)) + : value.format(format); + } + + return Array.isArray(value) + ? value.map(v => v.toDate().toLocaleDateString(locale)) + : value.toDate().toLocaleDateString(locale); + }); + constructor() { this.focusInput = this.focusInput.bind(this); } diff --git a/src/date-picker/trigger/trigger.template.html b/src/date-picker/trigger/trigger.template.html index 0c6e8e973..2d76cb856 100644 --- a/src/date-picker/trigger/trigger.template.html +++ b/src/date-picker/trigger/trigger.template.html @@ -14,7 +14,7 @@ style="flex: 1" aui-input #focusRef - [value]="$any(value || [])[0]?.format(format)" + [value]="$any($formatValue() || [])[0]" [readonly]="true" [size]="size" (focus)="leftFocus = true" @@ -29,7 +29,7 @@ Date: Mon, 24 Jun 2024 16:17:41 +0800 Subject: [PATCH 12/12] feat: mod style var and storybook preview --- src/date-picker/calendar/header/style.scss | 2 +- stories/rangepicker/basic.component.ts | 2 +- stories/rangepicker/basic.stories.ts | 3 ++- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/date-picker/calendar/header/style.scss b/src/date-picker/calendar/header/style.scss index 55ddea30f..ce2f3b2f7 100644 --- a/src/date-picker/calendar/header/style.scss +++ b/src/date-picker/calendar/header/style.scss @@ -3,7 +3,7 @@ $block: aui-calendar-header; $aui-text-button-mini-width: use-var(inline-height-xs); -$aui-text-button-mini-spacing: 2px; +$aui-text-button-mini-spacing: use-var(spacing-s); .#{$block} { &__container { diff --git a/stories/rangepicker/basic.component.ts b/stories/rangepicker/basic.component.ts index 749fa355c..4e9965f4c 100644 --- a/stories/rangepicker/basic.component.ts +++ b/stories/rangepicker/basic.component.ts @@ -15,7 +15,7 @@ import { I18nService, en, zh } from '@alauda/ui';
Form value: {{ range | json }} diff --git a/stories/rangepicker/basic.stories.ts b/stories/rangepicker/basic.stories.ts index 980d4a0e7..f657e962d 100644 --- a/stories/rangepicker/basic.stories.ts +++ b/stories/rangepicker/basic.stories.ts @@ -4,7 +4,7 @@ import { Meta, StoryObj, moduleMetadata } from '@storybook/angular'; import { RangeBasicComponent } from './basic.component'; -import { ButtonModule, DatePickerModule } from '@alauda/ui'; +import { ButtonModule, DatePickerModule, I18nModule } from '@alauda/ui'; const meta: Meta = { title: 'Example/RangePicker', @@ -18,6 +18,7 @@ const meta: Meta = { ReactiveFormsModule, ButtonModule, BrowserAnimationsModule, + I18nModule, ], }), ],