From e65e46cb1e8c70d041b8f105d95dd5bf58edba5e Mon Sep 17 00:00:00 2001 From: Hsuan Lee Date: Tue, 13 Oct 2020 19:25:10 +0800 Subject: [PATCH] feat(module:typography): support custom icons and tooltips close #5888 --- components/core/config/config.ts | 6 +- components/typography/demo/interactive.ts | 25 ++- components/typography/doc/index.en-US.md | 4 + components/typography/doc/index.zh-CN.md | 4 + components/typography/text-copy.component.ts | 51 +++++- components/typography/text-edit.component.ts | 19 ++- components/typography/typography.component.ts | 16 +- components/typography/typography.module.ts | 3 +- components/typography/typography.spec.ts | 148 +++++++++++++++++- 9 files changed, 259 insertions(+), 17 deletions(-) diff --git a/components/core/config/config.ts b/components/core/config/config.ts index 86434b2cf07..928f6e47d78 100644 --- a/components/core/config/config.ts +++ b/components/core/config/config.ts @@ -7,7 +7,7 @@ import { InjectionToken, TemplateRef, Type } from '@angular/core'; import { SafeUrl } from '@angular/platform-browser'; import { NzBreakpointEnum } from 'ng-zorro-antd/core/services'; -import { NzSafeAny, NzShapeSCType, NzSizeDSType, NzSizeLDSType, NzSizeMDSType } from 'ng-zorro-antd/core/types'; +import { NzSafeAny, NzShapeSCType, NzSizeDSType, NzSizeLDSType, NzSizeMDSType, NzTSType } from 'ng-zorro-antd/core/types'; export interface NzConfig { affix?: AffixConfig; @@ -264,6 +264,10 @@ export interface TreeSelectConfig { export interface TypographyConfig { nzEllipsisRows?: number; + nzCopyTooltips?: [NzTSType, NzTSType] | null; + nzCopyIcons: [NzTSType, NzTSType]; + nzEditTooltip?: null | NzTSType; + nzEditIcon: NzTSType; } export type NzConfigKey = keyof NzConfig; diff --git a/components/typography/demo/interactive.ts b/components/typography/demo/interactive.ts index 22ed2ef7f0e..4c3b7747c37 100644 --- a/components/typography/demo/interactive.ts +++ b/components/typography/demo/interactive.ts @@ -4,11 +4,34 @@ import { Component } from '@angular/core'; selector: 'nz-demo-typography-interactive', template: `

+

+

Replace copy text.

- ` +

+ + + you clicked!! + +

+ `, + styles: [ + ` + p[nz-typography] { + margin-bottom: 1em; + } + ` + ] }) export class NzDemoTypographyInteractiveComponent { editStr = 'This is an editable text.'; + customEditIconStr = 'Custom edit icon and tooltip text.'; + hideEditTooltipStr = 'Hide edit tooltip.'; copyStr = 'This is a copyable text.'; } diff --git a/components/typography/doc/index.en-US.md b/components/typography/doc/index.en-US.md index 9b470bd7951..73813e76b28 100644 --- a/components/typography/doc/index.en-US.md +++ b/components/typography/doc/index.en-US.md @@ -26,6 +26,10 @@ import { NzTypographyModule } from 'ng-zorro-antd/typography'; | `[nzContent]` | Component content | `string` | - || | `[nzCopyable]` | Can copy, require use `[nzContent]` | `boolean` | `false` || | `[nzEditable]` | Editable, require use `[nzContent]` | `boolean` | `false` || +| `[nzCopyIcons]` | Custom copy icons | `[string \| TemplateRef, string \| TemplateRef]` | `['copy', 'check']` | ✅ | +| `[nzCopyTooltips]` | Custom tooltips text, hide when it is `null` | `null \| [string \| TemplateRef, string \| TemplateRef]` | - | ✅ | +| `[nzEditIcon]` | Custom edit icon | `string \| TemplateRef` | `'edit'` | ✅ | +| `[nzEditTooltip]` | Custom tooltip text, hide when it is `null` | `null \| string \| TemplateRef` | - | ✅ | | `[nzEllipsis]` | Display ellipsis when overflow, require use `[nzContent]` when dynamic content | `boolean` | `false` || | `[nzSuffix]` | The text suffix when used `nzEllipsis` | `string` | - || | `[nzCopyText]` | Customize the copy text | `string` | - || diff --git a/components/typography/doc/index.zh-CN.md b/components/typography/doc/index.zh-CN.md index e758e924041..74c9176de14 100644 --- a/components/typography/doc/index.zh-CN.md +++ b/components/typography/doc/index.zh-CN.md @@ -26,6 +26,10 @@ import { NzTypographyModule } from 'ng-zorro-antd/typography'; | `[nzContent]` | 组件内容 | `string` | - | | `[nzCopyable]` | 是否可拷贝,需要配合 `[nzContent]` 使用 | `boolean` | `false` | | `[nzEditable]` | 是否可编辑,需要配合 `[nzContent]` 使用 | `boolean` | `false` | +| `[nzCopyIcons]` | 自定义拷贝图标 | `[string \| TemplateRef, string \| TemplateRef]` | `['copy', 'check']` | ✅ | +| `[nzCopyTooltips]` | 自定义提示文案,为 `null` 时隐藏文案 | `null \| [string \| TemplateRef, string \| TemplateRef]` | - | ✅ | +| `[nzEditIcon]` | 自定义编辑图标 | `string \| TemplateRef` | `'edit'` | ✅ | +| `[nzEditTooltip]` | 自定义提示文案,为 `null` 时隐藏文案 | `null \| string \| TemplateRef` | - | ✅ | | `[nzEllipsis]` | 自动溢出省略,动态内容时需要配合 `[nzContent]` 使用 | `boolean` | `false` | | `[nzExpandable]` | 自动溢出省略时是否可展开 | `boolean` | `false` || | `[nzSuffix]` | 自动溢出省略时的文本后缀 | `string` | - || diff --git a/components/typography/text-copy.component.ts b/components/typography/text-copy.component.ts index 6744f61f7a6..784cb537c0e 100644 --- a/components/typography/text-copy.component.ts +++ b/components/typography/text-copy.component.ts @@ -11,11 +11,14 @@ import { ElementRef, EventEmitter, Input, + OnChanges, OnDestroy, OnInit, Output, + SimpleChanges, ViewEncapsulation } from '@angular/core'; +import { NzTSType } from 'ng-zorro-antd/core/types'; import { NzI18nService, NzTextI18nInterface } from 'ng-zorro-antd/i18n'; import { Subject } from 'rxjs'; @@ -28,26 +31,35 @@ import { takeUntil } from 'rxjs/operators'; `, changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, preserveWhitespaces: false }) -export class NzTextCopyComponent implements OnInit, OnDestroy { +export class NzTextCopyComponent implements OnInit, OnDestroy, OnChanges { copied = false; copyId: number = -1; locale!: NzTextI18nInterface; nativeElement = this.host.nativeElement; + copyTooltip: NzTSType | null = null; + copedTooltip: NzTSType | null = null; + copyIcon: NzTSType = 'copy'; + copedIcon: NzTSType = 'check'; private destroy$ = new Subject(); @Input() text!: string; + @Input() tooltips?: [NzTSType, NzTSType] | null; + @Input() icons: [NzTSType, NzTSType] = ['copy', 'check']; + @Output() readonly textCopy = new EventEmitter(); constructor(private host: ElementRef, private cdr: ChangeDetectorRef, private clipboard: Clipboard, private i18n: NzI18nService) {} @@ -55,10 +67,21 @@ export class NzTextCopyComponent implements OnInit, OnDestroy { ngOnInit(): void { this.i18n.localeChange.pipe(takeUntil(this.destroy$)).subscribe(() => { this.locale = this.i18n.getLocaleData('Text'); + this.updateTooltips(); this.cdr.markForCheck(); }); } + ngOnChanges(changes: SimpleChanges): void { + const { tooltips, icons } = changes; + if (tooltips) { + this.updateTooltips(); + } + if (icons) { + this.updateIcons(); + } + } + ngOnDestroy(): void { clearTimeout(this.copyId); this.destroy$.next(); @@ -84,4 +107,26 @@ export class NzTextCopyComponent implements OnInit, OnDestroy { this.cdr.detectChanges(); }, 3000); } + + private updateTooltips(): void { + if (this.tooltips === null) { + this.copedTooltip = null; + this.copyTooltip = null; + } else if (Array.isArray(this.tooltips)) { + const [copyTooltip, copedTooltip] = this.tooltips; + this.copyTooltip = copyTooltip || this.locale?.copy; + this.copedTooltip = copedTooltip || this.locale?.copied; + } else { + this.copyTooltip = this.locale?.copy; + this.copedTooltip = this.locale?.copied; + } + this.cdr.markForCheck(); + } + + private updateIcons(): void { + const [copyIcon, copedIcon] = this.icons; + this.copyIcon = copyIcon; + this.copedIcon = copedIcon; + this.cdr.markForCheck(); + } } diff --git a/components/typography/text-edit.component.ts b/components/typography/text-edit.component.ts index 72f04bc0f90..d8abe315bc9 100644 --- a/components/typography/text-edit.component.ts +++ b/components/typography/text-edit.component.ts @@ -16,6 +16,7 @@ import { ViewChild, ViewEncapsulation } from '@angular/core'; +import { NzTSType } from 'ng-zorro-antd/core/types'; import { NzI18nService, NzTextI18nInterface } from 'ng-zorro-antd/i18n'; import { NzAutosizeDirective } from 'ng-zorro-antd/input'; @@ -27,8 +28,17 @@ import { takeUntil } from 'rxjs/operators'; selector: 'nz-text-edit', exportAs: 'nzTextEdit', template: ` - + > @@ -56,6 +65,8 @@ export class NzTextEditComponent implements OnInit, OnDestroy { private destroy$ = new Subject(); @Input() text?: string; + @Input() icon: NzTSType = 'edit'; + @Input() tooltip?: null | NzTSType; @Output() readonly startEditing = new EventEmitter(); @Output() readonly endEditing = new EventEmitter(); @ViewChild('textarea', { static: false }) textarea!: ElementRef; diff --git a/components/typography/typography.component.ts b/components/typography/typography.component.ts index 6fc74fe3f17..22b0b4eeece 100644 --- a/components/typography/typography.component.ts +++ b/components/typography/typography.component.ts @@ -29,7 +29,7 @@ import { import { NzConfigKey, NzConfigService, WithConfig } from 'ng-zorro-antd/core/config'; import { cancelRequestAnimationFrame, reqAnimFrame } from 'ng-zorro-antd/core/polyfill'; import { NzResizeService } from 'ng-zorro-antd/core/services'; -import { BooleanInput, NumberInput, NzSafeAny } from 'ng-zorro-antd/core/types'; +import { BooleanInput, NumberInput, NzSafeAny, NzTSType } from 'ng-zorro-antd/core/types'; import { InputBoolean, InputNumber, isStyleSupport, measure } from 'ng-zorro-antd/core/util'; import { Subject, Subscription } from 'rxjs'; @@ -78,11 +78,19 @@ const EXPAND_ELEMENT_CLASSNAME = 'ant-typography-expand'; - + `, changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, @@ -116,6 +124,10 @@ export class NzTypographyComponent implements OnInit, AfterViewInit, OnDestroy, @Input() @InputBoolean() nzDisabled = false; @Input() @InputBoolean() nzExpandable = false; @Input() @InputBoolean() nzEllipsis = false; + @Input() @WithConfig() nzCopyTooltips?: [NzTSType, NzTSType] | null = undefined; + @Input() @WithConfig() nzCopyIcons: [NzTSType, NzTSType] = ['copy', 'check']; + @Input() @WithConfig() nzEditTooltip?: null | NzTSType = undefined; + @Input() @WithConfig() nzEditIcon: NzTSType = 'edit'; @Input() nzContent?: string; @Input() @WithConfig() @InputNumber() nzEllipsisRows: number = 1; @Input() nzType: 'secondary' | 'warning' | 'danger' | 'success' | undefined; diff --git a/components/typography/typography.module.ts b/components/typography/typography.module.ts index b71d6a0b595..59fadd46f50 100644 --- a/components/typography/typography.module.ts +++ b/components/typography/typography.module.ts @@ -6,6 +6,7 @@ import { ClipboardModule } from '@angular/cdk/clipboard'; import { PlatformModule } from '@angular/cdk/platform'; import { CommonModule } from '@angular/common'; import { NgModule } from '@angular/core'; +import { NzOutletModule } from 'ng-zorro-antd/core/outlet'; import { NzTransButtonModule } from 'ng-zorro-antd/core/trans-button'; import { NzI18nModule } from 'ng-zorro-antd/i18n'; @@ -18,7 +19,7 @@ import { NzTextEditComponent } from './text-edit.component'; import { NzTypographyComponent } from './typography.component'; @NgModule({ - imports: [CommonModule, NzIconModule, NzToolTipModule, NzInputModule, NzI18nModule, NzTransButtonModule, ClipboardModule], + imports: [CommonModule, NzIconModule, NzToolTipModule, NzInputModule, NzI18nModule, NzTransButtonModule, ClipboardModule, NzOutletModule], exports: [NzTypographyComponent, NzTextCopyComponent, NzTextEditComponent, PlatformModule], declarations: [NzTypographyComponent, NzTextCopyComponent, NzTextEditComponent] }) diff --git a/components/typography/typography.spec.ts b/components/typography/typography.spec.ts index 20bfbf3923c..96991ae7dde 100644 --- a/components/typography/typography.spec.ts +++ b/components/typography/typography.spec.ts @@ -1,8 +1,11 @@ import { ENTER } from '@angular/cdk/keycodes'; +import { OverlayContainer } from '@angular/cdk/overlay'; import { CommonModule } from '@angular/common'; import { Component, ViewChild } from '@angular/core'; -import { ComponentFixture, fakeAsync, flush, TestBed, tick } from '@angular/core/testing'; -import { createKeyboardEvent, dispatchFakeEvent, typeInElement } from 'ng-zorro-antd/core/testing'; +import { ComponentFixture, fakeAsync, flush, inject, TestBed, tick } from '@angular/core/testing'; +import { NoopAnimationsModule } from '@angular/platform-browser/animations'; +import { createKeyboardEvent, dispatchFakeEvent, dispatchMouseEvent, typeInElement } from 'ng-zorro-antd/core/testing'; +import { NzSafeAny } from 'ng-zorro-antd/core/types'; import { NzIconTestModule } from 'ng-zorro-antd/icon/testing'; import { NzTypographyComponent } from './typography.component'; @@ -13,10 +16,12 @@ declare const viewport: any; describe('typography', () => { let componentElement: HTMLElement; + let overlayContainer: OverlayContainer; + let overlayContainerElement: HTMLElement; beforeEach(() => { TestBed.configureTestingModule({ - imports: [CommonModule, NzTypographyModule, NzIconTestModule], + imports: [CommonModule, NzTypographyModule, NzIconTestModule, NoopAnimationsModule], declarations: [ NzTestTypographyComponent, NzTestTypographyCopyComponent, @@ -26,6 +31,41 @@ describe('typography', () => { }).compileComponents(); }); + beforeEach(inject([OverlayContainer], (oc: OverlayContainer) => { + overlayContainer = oc; + overlayContainerElement = oc.getContainerElement(); + })); + + afterEach(() => { + overlayContainer.ngOnDestroy(); + }); + + function testCopyButton( + fixture: ComponentFixture, + copyButton: HTMLButtonElement, + onHover: () => void, + onClick: () => void + ): void { + fixture.detectChanges(); + + dispatchMouseEvent(copyButton, 'mouseenter'); + fixture.detectChanges(); + tick(1000); + fixture.detectChanges(); + + onHover(); + + copyButton.click(); + fixture.detectChanges(); + + onClick(); + + dispatchMouseEvent(copyButton, 'mouseleave'); + fixture.detectChanges(); + tick(3000); + fixture.detectChanges(); + } + describe('base', () => { let fixture: ComponentFixture; @@ -68,7 +108,7 @@ describe('typography', () => { it('should copyable', () => { spyOn(testComponent, 'onCopy'); const copyButtons = componentElement.querySelectorAll('.ant-typography-copy'); - expect(copyButtons.length).toBe(4); + expect(copyButtons.length).toBe(5); copyButtons.forEach((btn, i) => { btn.click(); fixture.detectChanges(); @@ -76,6 +116,61 @@ describe('typography', () => { }); }); + it('should be set tooltips', fakeAsync(() => { + const copyButton = componentElement.querySelector('.custom-tooltips .ant-typography-copy')!; + testCopyButton( + fixture, + copyButton, + () => { + expect(overlayContainerElement.textContent).toContain(testComponent.tooltips![0]); + }, + () => { + expect(overlayContainerElement.textContent).toContain(testComponent.tooltips![1]); + } + ); + })); + + it('should be hied all tooltips', fakeAsync(() => { + const copyButton = componentElement.querySelector('.custom-tooltips .ant-typography-copy')!; + testComponent.tooltips = null; + fixture.detectChanges(); + + testCopyButton( + fixture, + copyButton, + () => { + expect(overlayContainerElement.textContent).toBeFalsy(); + }, + () => { + expect(overlayContainerElement.textContent).toBeFalsy(); + } + ); + })); + + it('should be set icons', fakeAsync(() => { + const copyButton = componentElement.querySelector('.custom-icons .ant-typography-copy')!; + const icon = copyButton.querySelector('.anticon')!; + + // init + expect(icon.className).toContain('meh'); + + testCopyButton( + fixture, + copyButton, + () => { + // hover + expect(icon.className).toContain('meh'); + }, + () => { + // clicked + expect(icon.className).toContain('smile'); + } + ); + + // done + expect(icon.className).toContain('meh'); + })); + it('should only trigger once within 3000ms', fakeAsync(() => { spyOn(testComponent, 'onCopy'); const copyButton = componentElement.querySelector('.ant-typography-copy'); @@ -124,6 +219,35 @@ describe('typography', () => { expect(testComponent.str).toBe('This is an editable text.'); }); + it('should be set icon', fakeAsync(() => { + const icon = componentElement.querySelector('.anticon')!; + expect(icon.className).toContain('edit'); + + testComponent.icon = 'smile'; + fixture.detectChanges(); + + expect(icon.className).toContain('smile'); + })); + + it('should be set tooltip', fakeAsync(() => { + testComponent.tooltip = 'click to copy.'; + const editButton = componentElement.querySelector('.ant-typography-edit')!; + + fixture.detectChanges(); + + dispatchMouseEvent(editButton, 'mouseenter'); + fixture.detectChanges(); + tick(1000); + fixture.detectChanges(); + + expect(overlayContainerElement.textContent).toContain(testComponent.tooltip); + + dispatchMouseEvent(editButton, 'mouseleave'); + fixture.detectChanges(); + tick(3000); + fixture.detectChanges(); + })); + it('should edit work', () => { const editButton = componentElement.querySelector('.ant-typography-edit'); editButton!.click(); @@ -363,9 +487,20 @@ export class NzTestTypographyComponent {}

Test +

` }) export class NzTestTypographyCopyComponent { + tooltips: [string | null, string | null] | null = ['click here', 'coped']; + icons: [string, string] | null = ['meh', 'smile']; onCopy(_text: string): void { // noop } @@ -373,12 +508,15 @@ export class NzTestTypographyCopyComponent { @Component({ template: ` -

+

` }) export class NzTestTypographyEditComponent { @ViewChild(NzTypographyComponent, { static: false }) nzTypographyComponent!: NzTypographyComponent; str = 'This is an editable text.'; + icon = 'edit'; + tooltip?: string | null; + onChange = (text: string): void => { this.str = text; };