From cd764fb753628b9b90ed88caf249761341054d0b Mon Sep 17 00:00:00 2001 From: simplejason Date: Mon, 23 May 2022 15:02:30 +0800 Subject: [PATCH] feat(module:cascader): suooprt setting status --- components/cascader/cascader.component.ts | 50 +++++++++++++++++++---- components/cascader/cascader.spec.ts | 38 ++++++++++++++++- components/cascader/demo/status.md | 14 +++++++ components/cascader/demo/status.ts | 22 ++++++++++ components/cascader/doc/index.en-US.md | 1 + components/cascader/doc/index.zh-CN.md | 1 + components/core/types/public-api.ts | 2 + components/core/types/status.ts | 11 +++++ components/core/types/type.ts | 6 +++ components/core/util/public-api.ts | 1 + components/core/util/status-util.ts | 20 +++++++++ 11 files changed, 158 insertions(+), 8 deletions(-) create mode 100644 components/cascader/demo/status.md create mode 100644 components/cascader/demo/status.ts create mode 100644 components/core/types/status.ts create mode 100644 components/core/types/type.ts create mode 100644 components/core/util/status-util.ts diff --git a/components/cascader/cascader.component.ts b/components/cascader/cascader.component.ts index d7921c2c16..9fa16d4bc9 100644 --- a/components/cascader/cascader.component.ts +++ b/components/cascader/cascader.component.ts @@ -17,12 +17,14 @@ import { HostListener, Input, NgZone, + OnChanges, OnDestroy, OnInit, Optional, Output, QueryList, Renderer2, + SimpleChanges, TemplateRef, ViewChild, ViewChildren, @@ -37,8 +39,15 @@ import { NzConfigKey, NzConfigService, WithConfig } from 'ng-zorro-antd/core/con import { NzNoAnimationDirective } from 'ng-zorro-antd/core/no-animation'; import { DEFAULT_CASCADER_POSITIONS } from 'ng-zorro-antd/core/overlay'; import { NzDestroyService } from 'ng-zorro-antd/core/services'; -import { BooleanInput, NgClassType, NgStyleInterface, NzSafeAny } from 'ng-zorro-antd/core/types'; -import { InputBoolean, toArray } from 'ng-zorro-antd/core/util'; +import { + BooleanInput, + NgClassInterface, + NgClassType, + NgStyleInterface, + NzSafeAny, + NzStatus +} from 'ng-zorro-antd/core/types'; +import { getStatusClassNames, InputBoolean, toArray } from 'ng-zorro-antd/core/util'; import { NzCascaderI18nInterface, NzI18nService } from 'ng-zorro-antd/i18n'; import { NzCascaderOptionComponent } from './cascader-li.component'; @@ -210,7 +219,9 @@ const defaultDisplayRender = (labels: string[]): string => labels.join(' / '); '[class.ant-select-rtl]': `dir ==='rtl'` } }) -export class NzCascaderComponent implements NzCascaderComponentAsSource, OnInit, OnDestroy, ControlValueAccessor { +export class NzCascaderComponent + implements NzCascaderComponentAsSource, OnInit, OnDestroy, OnChanges, ControlValueAccessor +{ readonly _nzModuleName: NzConfigKey = NZ_CONFIG_MODULE_NAME; static ngAcceptInputType_nzShowInput: BooleanInput; static ngAcceptInputType_nzShowArrow: BooleanInput; @@ -256,6 +267,8 @@ export class NzCascaderComponent implements NzCascaderComponentAsSource, OnInit, @Input() nzMenuStyle: NgStyleInterface | null = null; @Input() nzMouseEnterDelay: number = 150; // ms @Input() nzMouseLeaveDelay: number = 150; // ms + @Input() nzStatus?: NzStatus; + @Input() nzTriggerAction: NzCascaderTriggerType | NzCascaderTriggerType[] = ['click'] as NzCascaderTriggerType[]; @Input() nzChangeOn?: (option: NzCascaderOption, level: number) => boolean; @Input() nzLoadData?: (node: NzCascaderOption, index: number) => PromiseLike; @@ -277,6 +290,10 @@ export class NzCascaderComponent implements NzCascaderComponentAsSource, OnInit, @Output() readonly nzSelect = new EventEmitter<{ option: NzCascaderOption; index: number } | null>(); @Output() readonly nzClear = new EventEmitter(); + prefixCls: string = 'ant-select'; + statusCls: NgClassInterface = {}; + nzHasFeedback: boolean = false; + /** * If the dropdown should show the empty content. * `true` if there's no options. @@ -359,15 +376,15 @@ export class NzCascaderComponent implements NzCascaderComponentAsSource, OnInit, private cdr: ChangeDetectorRef, private i18nService: NzI18nService, private destroy$: NzDestroyService, - elementRef: ElementRef, - renderer: Renderer2, + private elementRef: ElementRef, + private renderer: Renderer2, @Optional() private directionality: Directionality, @Host() @Optional() public noAnimation?: NzNoAnimationDirective ) { this.el = elementRef.nativeElement; this.cascaderService.withComponent(this); - renderer.addClass(elementRef.nativeElement, 'ant-select'); - renderer.addClass(elementRef.nativeElement, 'ant-cascader'); + this.renderer.addClass(this.elementRef.nativeElement, 'ant-select'); + this.renderer.addClass(this.elementRef.nativeElement, 'ant-cascader'); } ngOnInit(): void { @@ -430,6 +447,13 @@ export class NzCascaderComponent implements NzCascaderComponentAsSource, OnInit, this.setupKeydownListener(); } + ngOnChanges(changes: SimpleChanges): void { + const { nzStatus } = changes; + if (nzStatus) { + this.setStatusStyles(); + } + } + ngOnDestroy(): void { this.clearDelayMenuTimer(); this.clearDelaySelectTimer(); @@ -761,6 +785,18 @@ export class NzCascaderComponent implements NzCascaderComponentAsSource, OnInit, } } + private setStatusStyles(): void { + // render status if nzStatus is set + this.statusCls = getStatusClassNames(this.prefixCls, this.nzStatus, this.nzHasFeedback); + Object.keys(this.statusCls).forEach(status => { + if (this.statusCls[status]) { + this.renderer.addClass(this.elementRef.nativeElement, status); + } else { + this.renderer.removeClass(this.elementRef.nativeElement, status); + } + }); + } + private setLocale(): void { this.locale = this.i18nService.getLocaleData('global'); this.cdr.markForCheck(); diff --git a/components/cascader/cascader.spec.ts b/components/cascader/cascader.spec.ts index 0c433372c3..07a64c9f68 100644 --- a/components/cascader/cascader.spec.ts +++ b/components/cascader/cascader.spec.ts @@ -68,7 +68,12 @@ describe('cascader', () => { NzCascaderModule, NzIconTestModule ], - declarations: [NzDemoCascaderDefaultComponent, NzDemoCascaderLoadDataComponent, NzDemoCascaderRtlComponent] + declarations: [ + NzDemoCascaderDefaultComponent, + NzDemoCascaderLoadDataComponent, + NzDemoCascaderRtlComponent, + NzDemoCascaderStatusComponent + ] }).compileComponents(); inject([OverlayContainer], (oc: OverlayContainer) => { @@ -1786,6 +1791,29 @@ describe('cascader', () => { expect(itemEl21.querySelector('.anticon')?.classList).toContain('anticon-left'); })); }); + + describe('Status', () => { + let fixture: ComponentFixture; + let cascader: DebugElement; + + beforeEach(() => { + fixture = TestBed.createComponent(NzDemoCascaderStatusComponent); + cascader = fixture.debugElement.query(By.directive(NzCascaderComponent)); + }); + + it('should className correct', () => { + fixture.detectChanges(); + expect(cascader.nativeElement.className).toContain('ant-select-status-error'); + + fixture.componentInstance.status = 'warning'; + fixture.detectChanges(); + expect(cascader.nativeElement.className).toContain('ant-select-status-warning'); + + fixture.componentInstance.status = ''; + fixture.detectChanges(); + expect(cascader.nativeElement.className).not.toContain('ant-select-status-warning'); + }); + }); }); const ID_NAME_LIST = [ @@ -2171,3 +2199,11 @@ export class NzDemoCascaderRtlComponent { @ViewChild(Dir) dir!: Dir; direction = 'rtl'; } + +@Component({ + template: ` ` +}) +export class NzDemoCascaderStatusComponent { + public nzOptions: any[] | null = options1; + public status = 'error'; +} diff --git a/components/cascader/demo/status.md b/components/cascader/demo/status.md new file mode 100644 index 0000000000..89ddb62786 --- /dev/null +++ b/components/cascader/demo/status.md @@ -0,0 +1,14 @@ +--- +order: 18 +title: + zh-CN: 自定义状态 + en-US: Status +--- + +## zh-CN + +使用 `nzStatus` 为 Cascader 添加状态,可选 `error` 或者 `warning`。 + +## en-US + +Add status to Cascader with `nzStatus`, which could be `error` or `warning`. diff --git a/components/cascader/demo/status.ts b/components/cascader/demo/status.ts new file mode 100644 index 0000000000..c5b1edc420 --- /dev/null +++ b/components/cascader/demo/status.ts @@ -0,0 +1,22 @@ +import { Component } from '@angular/core'; + +import { NzCascaderOption } from 'ng-zorro-antd/cascader'; + +@Component({ + selector: 'nz-demo-cascader-status', + template: ` + + + `, + styles: [ + ` + .ant-cascader { + width: 100%; + margin-bottom: 8px; + } + ` + ] +}) +export class NzDemoCascaderStatusComponent { + nzOptions: NzCascaderOption[] = []; +} diff --git a/components/cascader/doc/index.en-US.md b/components/cascader/doc/index.en-US.md index f518279b11..6ec7ba5cca 100755 --- a/components/cascader/doc/index.en-US.md +++ b/components/cascader/doc/index.en-US.md @@ -50,6 +50,7 @@ import { NzCascaderModule } from 'ng-zorro-antd/cascader'; | `[nzShowInput]` | Whether show input | `boolean` | `true` | | `[nzShowSearch]` | Whether support search. Cannot be used with `[nzLoadData]` at the same time | `boolean\|NzShowSearchOptions` | `false` | | `[nzSize]` | input size, one of `large` `default` `small` | `'large'\|'small'\|'default'` | `'default'` | ✅ | +| `[nzStatus]` | Set validation status | `'error' \| 'warning'` | - | | `[nzSuffixIcon]` | The custom suffix icon | `string\|TemplateRef` | - | | `[nzValueProperty]` | the value property name of options | `string` | `'value'` | | `(ngModelChange)` | Emit on values change | `EventEmitter` | - | diff --git a/components/cascader/doc/index.zh-CN.md b/components/cascader/doc/index.zh-CN.md index 9afcd22ebb..33452533c4 100755 --- a/components/cascader/doc/index.zh-CN.md +++ b/components/cascader/doc/index.zh-CN.md @@ -51,6 +51,7 @@ import { NzCascaderModule } from 'ng-zorro-antd/cascader'; | `[nzShowInput]` | 显示输入框 | `boolean` | `true` | | `[nzShowSearch]` | 是否支持搜索,默认情况下对 `label` 进行全匹配搜索,不能和 `[nzLoadData]` 同时使用 | `boolean\|NzShowSearchOptions` | `false` | | `[nzSize]` | 输入框大小,可选 `large` `default` `small` | `'large'\|'small'\|'default'` | `'default'` | ✅ | +| `[nzStatus]` | 设置校验状态 | `'error' \| 'warning'` | - | | `[nzSuffixIcon]` | 自定义的选择框后缀图标 | `string\|TemplateRef` | - | | `[nzValueProperty]` | 选项的实际值的属性名 | `string` | `'value'` | | `(ngModelChange)` | 值发生变化时触发 | `EventEmitter` | - | diff --git a/components/core/types/public-api.ts b/components/core/types/public-api.ts index 4890207b80..07aa5939bc 100644 --- a/components/core/types/public-api.ts +++ b/components/core/types/public-api.ts @@ -15,3 +15,5 @@ export * from './any'; export * from './control-value-accessor'; export * from './convert-input'; export * from './input-observable'; +export * from './type'; +export * from './status'; diff --git a/components/core/types/status.ts b/components/core/types/status.ts new file mode 100644 index 0000000000..565692278d --- /dev/null +++ b/components/core/types/status.ts @@ -0,0 +1,11 @@ +/** + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://github.com/NG-ZORRO/ng-zorro-antd/blob/master/LICENSE + */ + +import { tuple } from './type'; + +export type NzStatus = '' | 'error' | 'warning'; + +const ValidateStatuses = tuple('success', 'warning', 'error', 'validating', ''); +export type NzValidateStatus = typeof ValidateStatuses[number]; diff --git a/components/core/types/type.ts b/components/core/types/type.ts new file mode 100644 index 0000000000..a8f0401fad --- /dev/null +++ b/components/core/types/type.ts @@ -0,0 +1,6 @@ +/** + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://github.com/NG-ZORRO/ng-zorro-antd/blob/master/LICENSE + */ + +export const tuple = (...args: T): T => args; diff --git a/components/core/util/public-api.ts b/components/core/util/public-api.ts index 0834356155..2d6a10a56a 100644 --- a/components/core/util/public-api.ts +++ b/components/core/util/public-api.ts @@ -19,3 +19,4 @@ export * from './measure-scrollbar'; export * from './ensure-in-bounds'; export * from './tick'; export * from './observable'; +export * from './status-util'; diff --git a/components/core/util/status-util.ts b/components/core/util/status-util.ts new file mode 100644 index 0000000000..b73c9d7b1d --- /dev/null +++ b/components/core/util/status-util.ts @@ -0,0 +1,20 @@ +/** + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://github.com/NG-ZORRO/ng-zorro-antd/blob/master/LICENSE + */ + +import { NgClassInterface, NzValidateStatus } from 'ng-zorro-antd/core/types'; + +export function getStatusClassNames( + prefixCls: string, + status?: NzValidateStatus, + hasFeedback?: boolean +): NgClassInterface { + return { + [`${prefixCls}-status-success`]: status === 'success', + [`${prefixCls}-status-warning`]: status === 'warning', + [`${prefixCls}-status-error`]: status === 'error', + [`${prefixCls}-status-validating`]: status === 'validating', + [`${prefixCls}-has-feedback`]: hasFeedback + }; +}