From 468e80b9e75cc5650e2fcf8d580d9eecb5081d5b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8D=A1=E8=89=B2?= Date: Sat, 26 Aug 2017 18:45:33 +0800 Subject: [PATCH] feat(module:affix&anchor&back-top&avatar): add components to library (#88) * feat(module:back-top): add `nz-back-top` component * feat(module:affix): add `nz-affix` component * feat(module:anchor): add `nz-anchor` component * feat(module:avatar): add `nz-avatar` component --- .../affix/nz-affix.component.spec.ts | 59 +++++++ src/components/affix/nz-affix.component.ts | 121 ++++++++++++++ src/components/affix/nz-affix.module.ts | 15 ++ src/components/affix/style/index.less | 6 + src/components/affix/style/patch.less | 3 + .../anchor/nz-anchor-link.component.ts | 52 ++++++ src/components/anchor/nz-anchor.component.ts | 157 ++++++++++++++++++ src/components/anchor/nz-anchor.module.ts | 16 ++ src/components/anchor/style/index.less | 78 +++++++++ src/components/anchor/style/patch.less | 3 + src/components/avatar/nz-avatar.component.ts | 116 +++++++++++++ src/components/avatar/nz-avatar.module.ts | 12 ++ src/components/avatar/style/index.less | 47 ++++++ src/components/avatar/style/patch.less | 0 .../back-top/nz-back-top.component.ts | 110 ++++++++++++ src/components/back-top/nz-back-top.module.ts | 15 ++ src/components/back-top/style/index.less | 33 ++++ src/components/back-top/style/patch.less | 0 .../core/scroll/nz-scroll.service.spec.ts | 60 +++++++ .../core/scroll/nz-scroll.service.ts | 130 +++++++++++++++ src/components/ng-zorro-antd.module.ts | 14 +- .../nz-demo-affix-basic.component.ts | 13 ++ .../nz-demo-affix-container.component.ts | 29 ++++ .../nz-demo-affix-fixed.component.ts | 17 ++ .../nz-demo-affix/nz-demo-affix.component.ts | 18 ++ src/showcase/nz-demo-affix/nz-demo-affix.html | 78 +++++++++ .../nz-demo-affix/nz-demo-affix.module.ts | 21 +++ .../nz-demo-affix.routing.module.ts | 12 ++ .../nz-demo-anchor-basic.component.ts | 17 ++ .../nz-demo-anchor-fixed.component.ts | 27 +++ .../nz-demo-anchor.component.ts | 17 ++ .../nz-demo-anchor/nz-demo-anchor.html | 100 +++++++++++ .../nz-demo-anchor/nz-demo-anchor.module.ts | 21 +++ .../nz-demo-anchor.routing.module.ts | 12 ++ .../nz-demo-avatar-autosize.component.ts | 32 ++++ .../nz-demo-avatar-badge.component.ts | 14 ++ .../nz-demo-avatar-basic.component.ts | 24 +++ .../nz-demo-avatar-type.component.ts | 20 +++ .../nz-demo-avatar.component.ts | 12 ++ .../nz-demo-avatar/nz-demo-avatar.html | 97 +++++++++++ .../nz-demo-avatar/nz-demo-avatar.module.ts | 20 +++ .../nz-demo-avatar.routing.module.ts | 12 ++ .../nz-demo-back-top-basic.component.ts | 24 +++ .../nz-demo-back-top-custom.component.ts | 45 +++++ .../nz-demo-back-top-target.component.ts | 41 +++++ .../nz-demo-back-top.component.ts | 18 ++ .../nz-demo-back-top/nz-demo-back-top.html | 90 ++++++++++ .../nz-demo-back-top.module.ts | 22 +++ .../nz-demo-back-top.routing.module.ts | 12 ++ src/showcase/router.ts | 45 +++++ 50 files changed, 1956 insertions(+), 1 deletion(-) create mode 100644 src/components/affix/nz-affix.component.spec.ts create mode 100644 src/components/affix/nz-affix.component.ts create mode 100644 src/components/affix/nz-affix.module.ts create mode 100644 src/components/affix/style/index.less create mode 100644 src/components/affix/style/patch.less create mode 100644 src/components/anchor/nz-anchor-link.component.ts create mode 100644 src/components/anchor/nz-anchor.component.ts create mode 100644 src/components/anchor/nz-anchor.module.ts create mode 100644 src/components/anchor/style/index.less create mode 100644 src/components/anchor/style/patch.less create mode 100644 src/components/avatar/nz-avatar.component.ts create mode 100644 src/components/avatar/nz-avatar.module.ts create mode 100644 src/components/avatar/style/index.less create mode 100644 src/components/avatar/style/patch.less create mode 100644 src/components/back-top/nz-back-top.component.ts create mode 100644 src/components/back-top/nz-back-top.module.ts create mode 100644 src/components/back-top/style/index.less create mode 100644 src/components/back-top/style/patch.less create mode 100644 src/components/core/scroll/nz-scroll.service.spec.ts create mode 100644 src/components/core/scroll/nz-scroll.service.ts create mode 100644 src/showcase/nz-demo-affix/nz-demo-affix-basic.component.ts create mode 100644 src/showcase/nz-demo-affix/nz-demo-affix-container.component.ts create mode 100644 src/showcase/nz-demo-affix/nz-demo-affix-fixed.component.ts create mode 100644 src/showcase/nz-demo-affix/nz-demo-affix.component.ts create mode 100644 src/showcase/nz-demo-affix/nz-demo-affix.html create mode 100644 src/showcase/nz-demo-affix/nz-demo-affix.module.ts create mode 100644 src/showcase/nz-demo-affix/nz-demo-affix.routing.module.ts create mode 100644 src/showcase/nz-demo-anchor/nz-demo-anchor-basic.component.ts create mode 100644 src/showcase/nz-demo-anchor/nz-demo-anchor-fixed.component.ts create mode 100644 src/showcase/nz-demo-anchor/nz-demo-anchor.component.ts create mode 100644 src/showcase/nz-demo-anchor/nz-demo-anchor.html create mode 100644 src/showcase/nz-demo-anchor/nz-demo-anchor.module.ts create mode 100644 src/showcase/nz-demo-anchor/nz-demo-anchor.routing.module.ts create mode 100644 src/showcase/nz-demo-avatar/nz-demo-avatar-autosize.component.ts create mode 100644 src/showcase/nz-demo-avatar/nz-demo-avatar-badge.component.ts create mode 100644 src/showcase/nz-demo-avatar/nz-demo-avatar-basic.component.ts create mode 100644 src/showcase/nz-demo-avatar/nz-demo-avatar-type.component.ts create mode 100644 src/showcase/nz-demo-avatar/nz-demo-avatar.component.ts create mode 100644 src/showcase/nz-demo-avatar/nz-demo-avatar.html create mode 100644 src/showcase/nz-demo-avatar/nz-demo-avatar.module.ts create mode 100644 src/showcase/nz-demo-avatar/nz-demo-avatar.routing.module.ts create mode 100644 src/showcase/nz-demo-back-top/nz-demo-back-top-basic.component.ts create mode 100644 src/showcase/nz-demo-back-top/nz-demo-back-top-custom.component.ts create mode 100644 src/showcase/nz-demo-back-top/nz-demo-back-top-target.component.ts create mode 100644 src/showcase/nz-demo-back-top/nz-demo-back-top.component.ts create mode 100644 src/showcase/nz-demo-back-top/nz-demo-back-top.html create mode 100644 src/showcase/nz-demo-back-top/nz-demo-back-top.module.ts create mode 100644 src/showcase/nz-demo-back-top/nz-demo-back-top.routing.module.ts diff --git a/src/components/affix/nz-affix.component.spec.ts b/src/components/affix/nz-affix.component.spec.ts new file mode 100644 index 0000000000..de2ca37e0c --- /dev/null +++ b/src/components/affix/nz-affix.component.spec.ts @@ -0,0 +1,59 @@ +/* tslint:disable:no-unused-variable */ +import { async, ComponentFixture, TestBed, ComponentFixtureAutoDetect, fakeAsync, tick } from '@angular/core/testing'; +import { Component, DebugElement } from '@angular/core'; +import { By } from '@angular/platform-browser'; + +import { NzAffixModule } from './nz-affix.module'; +import { NzScrollService } from "../core/scroll/nz-scroll.service"; +import { NzAffixComponent } from "./nz-affix.component"; + +describe('Component:nz-affix', () => { + + let scrollSrv: MockNzScrollService; + let fixture: ComponentFixture; + let context: TestAffixComponent; + let el: HTMLDivElement; + let comp: NzAffixComponent; + + beforeEach(fakeAsync(() => { + TestBed.configureTestingModule({ + imports: [NzAffixModule], + declarations: [TestAffixComponent], + providers: [ + { provide: NzScrollService, useClass: MockNzScrollService } + ] + }).compileComponents(); + + fixture = TestBed.createComponent(TestAffixComponent); + context = fixture.componentInstance; + spyOn(context, 'onChange'); + fixture.detectChanges(); + el = fixture.nativeElement; + comp = fixture.debugElement.query(By.css('nz-affix')).componentInstance as NzAffixComponent; + tick(); + })); + + it('should correctly initialize and attach to DOM', () => { + expect(el.querySelectorAll('.ant-affix').length).toBe(1); + }); + +}); + +@Component({ template: `` }) +class TestAffixComponent { + onChange(status: boolean) { + return status; + } +} + +class MockNzScrollService { + + getOffset(el: Element): { top: number, left: number } { + return { top: 0, left: 0 }; + } + + getScroll(el?: Element | Window, top: boolean = true): number { + return 0; + } + +} diff --git a/src/components/affix/nz-affix.component.ts b/src/components/affix/nz-affix.component.ts new file mode 100644 index 0000000000..14b9b78069 --- /dev/null +++ b/src/components/affix/nz-affix.component.ts @@ -0,0 +1,121 @@ +import { + Component, + ViewEncapsulation, + OnInit, + Input, + EventEmitter, + Output, + Renderer2, + OnDestroy, + ViewChild, + ElementRef, + HostBinding +} from '@angular/core'; +import { Observable } from 'rxjs/Observable'; +import { Subscription } from 'rxjs/Subscription'; + +import { NzScrollService } from "../core/scroll/nz-scroll.service"; + +@Component({ + selector: 'nz-affix', + encapsulation: ViewEncapsulation.None, + template: `
`, + styleUrls: [ + './style/index.less', + './style/patch.less' + ] +}) +export class NzAffixComponent implements OnInit, OnDestroy { + + private scroll$: Subscription = null; + private scrollWinInTarget$: Subscription = null; + private target: Element = null; + @ViewChild('wrap') private wrap: ElementRef; + // 缓存固定状态 + private fixed: boolean = false; + // 原始位置 + private orgOffset: { top: number, left: number }; + + @Input() + set nzTarget(el: Element) { + this.target = el; + this.registerScrollEvent(); + } + + @Input() nzOffsetTop: number = 0; + + @Input() nzOffsetBottom: number = 0; + + @Output() nzChange: EventEmitter = new EventEmitter(); + + constructor(private scrollSrv: NzScrollService, private _el: ElementRef, private _renderer: Renderer2) { } + + ngOnInit(): void { + if (!this.scroll$) this.registerScrollEvent(); + } + + private getTarget(): Element | Window { + return this.target || window; + } + + private reCalculate() { + let elOffset = this.scrollSrv.getOffset(this._el.nativeElement); + this.orgOffset = { + top: elOffset.top + this.scrollSrv.getScroll(this.getTarget()), + left: elOffset.left + this.scrollSrv.getScroll(this.getTarget(), false) + }; + + return this; + } + + private process() { + if (!this.orgOffset) this.reCalculate(); + const containerScrollTop = this.scrollSrv.getScroll(this.getTarget()); + let fixTop = this.getTarget() === window ? 0 : this.scrollSrv.getOffset(this.getTarget() as Element).top; + let hasFixed = this.orgOffset.top - fixTop - containerScrollTop - this.nzOffsetTop <= 0; + if (this.fixed === hasFixed) return; + + const wrapEl = this.wrap.nativeElement; + wrapEl.classList[hasFixed ? 'add' : 'remove']('ant-affix'); + if (hasFixed) { + wrapEl.style.cssText = `top:${this.nzOffsetTop + fixTop}px;left:${this.orgOffset.left}px`; + } else { + wrapEl.style.cssText = ``; + } + + this.fixed = hasFixed; + this.nzChange.emit(hasFixed); + } + + private removeListen() { + if (this.scroll$) this.scroll$.unsubscribe(); + if (this.scrollWinInTarget$) this.scrollWinInTarget$.unsubscribe(); + } + + private registerScrollEvent() { + this.removeListen(); + this.reCalculate().process(); + this.scroll$ = Observable.fromEvent(this.getTarget(), 'scroll') + .throttleTime(50) + .distinctUntilChanged() + .subscribe(e => { + this.process(); + }); + + if (this.getTarget() !== window) { + // 当 window 滚动位发生变动时,需要重新计算滚动容器 + this.scrollWinInTarget$ = Observable.fromEvent(window, 'scroll') + .throttleTime(50) + .distinctUntilChanged() + .subscribe(e => { + this.orgOffset = null; + this.fixed = false; + }); + } + } + + ngOnDestroy(): void { + this.removeListen(); + } + +} diff --git a/src/components/affix/nz-affix.module.ts b/src/components/affix/nz-affix.module.ts new file mode 100644 index 0000000000..644c6dde7f --- /dev/null +++ b/src/components/affix/nz-affix.module.ts @@ -0,0 +1,15 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; + +import { NzAffixComponent } from './nz-affix.component'; +import { SCROLL_SERVICE_PROVIDER } from "../core/scroll/nz-scroll.service"; + +@NgModule({ + declarations: [ NzAffixComponent ], + exports : [ NzAffixComponent ], + imports : [ CommonModule ], + providers : [ SCROLL_SERVICE_PROVIDER ] +}) +export class NzAffixModule { +} + diff --git a/src/components/affix/style/index.less b/src/components/affix/style/index.less new file mode 100644 index 0000000000..1043507df9 --- /dev/null +++ b/src/components/affix/style/index.less @@ -0,0 +1,6 @@ +@import "../../style/themes/default"; + +.@{ant-prefix}-affix { + position: fixed; + z-index: @zindex-affix; +} diff --git a/src/components/affix/style/patch.less b/src/components/affix/style/patch.less new file mode 100644 index 0000000000..5bbe06a78b --- /dev/null +++ b/src/components/affix/style/patch.less @@ -0,0 +1,3 @@ +nz-affix { + display: block; +} diff --git a/src/components/anchor/nz-anchor-link.component.ts b/src/components/anchor/nz-anchor-link.component.ts new file mode 100644 index 0000000000..aeb4f387a5 --- /dev/null +++ b/src/components/anchor/nz-anchor-link.component.ts @@ -0,0 +1,52 @@ +import { + Component, + ViewEncapsulation, + Input, + TemplateRef, + ContentChild, + HostBinding, + HostListener, + ElementRef +} from '@angular/core'; + +import { NzAnchorComponent } from './nz-anchor.component'; + +@Component({ + selector: 'nz-link', + encapsulation: ViewEncapsulation.None, + template: ` + + {{nzTitle}} + + + + ` +}) +export class NzAnchorLinkComponent { + + @Input() nzHref: string; + + @Input() nzTitle: string; + + @ContentChild('nzTemplate') nzTemplate: TemplateRef; + + @HostBinding('class.ant-anchor-link') _nzAnchorLink = true; + + @HostBinding('class.ant-anchor-link-active') active: boolean = false; + + @HostListener('click') + _onClick() { + this._anchorComp.scrollTo(this); + } + + constructor(public el: ElementRef, private _anchorComp: NzAnchorComponent) { + this._anchorComp.add(this); + } + + goToClick(e: Event) { + e.preventDefault(); + e.stopPropagation(); + this._anchorComp.scrollTo(this); + return false; + } +} diff --git a/src/components/anchor/nz-anchor.component.ts b/src/components/anchor/nz-anchor.component.ts new file mode 100644 index 0000000000..95036818ca --- /dev/null +++ b/src/components/anchor/nz-anchor.component.ts @@ -0,0 +1,157 @@ +import { + Component, + ViewEncapsulation, + OnInit, + Input, + EventEmitter, + Output, + Renderer2, + OnDestroy, + ViewChild, + ElementRef, + Inject +} from '@angular/core'; +import { DOCUMENT } from '@angular/platform-browser'; +import { Observable } from 'rxjs/Observable'; +import { Subscription } from 'rxjs/Subscription'; + +import { NzScrollService } from "../core/scroll/nz-scroll.service"; +import { NzAnchorLinkComponent } from "./nz-anchor-link.component"; + +type Section = { + comp: NzAnchorLinkComponent, + top: number +}; + +@Component({ + selector: 'nz-anchor', + encapsulation: ViewEncapsulation.None, + template: ` +
+
+
+
+
+ +
+
+ `, + styleUrls: [ + './style/index.less', + './style/patch.less' + ] +}) +export class NzAnchorComponent { + + private links: NzAnchorLinkComponent[] = []; + private scroll$: Subscription = null; + private target: Element = null; + private animating: boolean = false; + + @ViewChild('container') + private container: ElementRef; + + @ViewChild('ball') + private ball: ElementRef; + + _top: number = 0; + _visible: boolean = false; + + @Input() + set nzTarget(el: Element) { + this.target = el; + this.registerScrollEvent(); + } + + @Input() nzOffsetTop: number = 0; + + @Input() nzBounds: number = 5; + + @Output() nzScroll: EventEmitter = new EventEmitter(); + + constructor(private scrollSrv: NzScrollService, private _renderer: Renderer2, @Inject(DOCUMENT) private doc: Document) { } + + ngOnInit(): void { + if (!this.scroll$) this.registerScrollEvent(); + } + + private getTarget(): Element | Window { + return this.target || window; + } + + private handleScroll() { + if (this.animating) return; + + let sections: Section[] = []; + this.links.forEach(comp => { + comp.active = false; + const target = this.doc.querySelector(comp.nzHref); + const top = this.scrollSrv.getOffset(target).top; + if (target && top < this.nzOffsetTop + this.nzBounds) { + sections.push({ + top, + comp + }) + } + }); + + this._visible = !!sections.length; + if (!this._visible) + return; + + const maxSection = sections.reduce((prev, curr) => curr.top > prev.top ? curr : prev); + maxSection.comp.active = true; + + let linkNode = (maxSection.comp.el.nativeElement as HTMLDivElement).querySelector('.ant-anchor-link-title') as HTMLElement; + this.ball.nativeElement.style.top = `${linkNode.offsetTop + linkNode.clientHeight / 2 - 4.5}px`; + console.log(linkNode, linkNode.offsetTop + linkNode.clientHeight / 2 - 4.5); + + this.nzScroll.emit(maxSection.comp); + } + + private removeListen() { + if (this.scroll$) this.scroll$.unsubscribe(); + } + + private registerScrollEvent() { + this.removeListen(); + // 由于页面刷新时滚动条位置的记忆 + // 倒置在dom未渲染完成,导致计算不正确(500ms用于延后执行解决) + setTimeout(() => { + this.handleScroll(); + }, 500); + this.scroll$ = Observable.fromEvent(this.getTarget(), 'scroll') + .throttleTime(50) + .distinctUntilChanged() + .subscribe(e => { + this.handleScroll(); + }); + } + + add(linkComp: NzAnchorLinkComponent) { + this.links.push(linkComp); + } + + /** 设置滚动条至 `linkComp` 所处位置 */ + scrollTo(linkComp: NzAnchorLinkComponent) { + const el = this.doc.querySelector(linkComp.nzHref); + if (!el) return; + + this.animating = true; + const containerScrollTop = this.scrollSrv.getScroll(this.getTarget()); + const elOffsetTop = this.scrollSrv.getOffset(el).top; + const targetScrollTop = containerScrollTop + elOffsetTop - this.nzOffsetTop; + this.scrollSrv.scrollTo(this.getTarget(), targetScrollTop, null, () => { + this.animating = false; + this.handleScroll(); + }); + + if (!location.href.includes('#')) + history.pushState(null, '', linkComp.nzHref); + } + + ngOnDestroy(): void { + this.removeListen(); + } + +} diff --git a/src/components/anchor/nz-anchor.module.ts b/src/components/anchor/nz-anchor.module.ts new file mode 100644 index 0000000000..122fbcfdaa --- /dev/null +++ b/src/components/anchor/nz-anchor.module.ts @@ -0,0 +1,16 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; + +import { NzAnchorComponent } from './nz-anchor.component'; +import { NzAnchorLinkComponent } from "./nz-anchor-link.component"; +import { SCROLL_SERVICE_PROVIDER } from "../core/scroll/nz-scroll.service"; + +@NgModule({ + declarations: [ NzAnchorComponent, NzAnchorLinkComponent ], + exports : [ NzAnchorComponent, NzAnchorLinkComponent ], + imports : [ CommonModule ], + providers : [ SCROLL_SERVICE_PROVIDER ] +}) +export class NzAnchorModule { +} + diff --git a/src/components/anchor/style/index.less b/src/components/anchor/style/index.less new file mode 100644 index 0000000000..acf5ea3487 --- /dev/null +++ b/src/components/anchor/style/index.less @@ -0,0 +1,78 @@ +@import "../../style/themes/default"; + +.@{ant-prefix} { + &-anchor { + position: relative; + &-wrapper { + background-color: @component-background; + } + + &-ink { + position: absolute; + height: 100%; + left: 0; + top: 0; + &:before { + content: ' '; + position: relative; + width: 2px; + height: 100%; + display: block; + background-color: @border-color-split; + margin: 0 auto; + } + &-ball { + display: none; + position: absolute; + width: 9px; + height: 9px; + border-radius: 9px; + border: 3px solid @primary-color; + background-color: @component-background; + left: 50%; + transition: top .3s ease-in-out; + transform: translateX(-50%); + &.visible { + display: inline-block; + } + } + } + + &.fixed &-ink &-ink-ball { + display: none; + } + } + + &-anchor-link { + padding: 8px 0 8px 18px; + line-height: 1; + + & & { + padding-top: 6px; + padding-bottom: 6px; + } + + &-title { + display: block; + position: relative; + transition: all .3s; + color: @text-color; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + margin-bottom: 8px; + } + + &-title:only-child { + margin-bottom: 0; + } + + &-active > &-title { + color: @primary-color; + } + + & > & { + font-size: @font-size-base; + } + } +} diff --git a/src/components/anchor/style/patch.less b/src/components/anchor/style/patch.less new file mode 100644 index 0000000000..89bd5d6e8e --- /dev/null +++ b/src/components/anchor/style/patch.less @@ -0,0 +1,3 @@ +nz-link { + display: block; +} diff --git a/src/components/avatar/nz-avatar.component.ts b/src/components/avatar/nz-avatar.component.ts new file mode 100644 index 0000000000..f0d3583a07 --- /dev/null +++ b/src/components/avatar/nz-avatar.component.ts @@ -0,0 +1,116 @@ +import { Component, ViewEncapsulation, Renderer2, ElementRef, Input, ViewChild, OnChanges, SimpleChanges } from '@angular/core'; + +export type NzAvatarShape = 'square' | 'circle'; +export type NzAvatarSize = 'small' | 'large' | 'default'; + +@Component({ + selector: 'nz-avatar', + encapsulation: ViewEncapsulation.None, + template: ` + + + {{nzText}} + `, + styleUrls: [ + './style/index.less', + './style/patch.less' + ] +}) +export class NzAvatarComponent implements OnChanges { + + private _el: HTMLElement; + private _prefixCls = 'ant-avatar'; + private _classList: string[] = []; + private _sizeMap = { large: 'lg', small: 'sm' }; + + _hasText: boolean = false; + @ViewChild('textEl') _textEl: ElementRef; + _textStyles: {}; + + _isSrcExist: boolean = true; + + _hasIcon: boolean = false; + + @Input() nzShape: NzAvatarShape = 'circle'; + + @Input() nzSize: NzAvatarSize = 'default'; + + @Input() nzText: string; + + @Input() nzSrc: string; + + @Input() nzIcon: string; + + _setClassMap() { + this._classList.forEach(_className => { + this._renderer.removeClass(this._el, _className); + }) + this._classList = [ + this._sizeMap[this.nzSize] && `${this._prefixCls}-${this._sizeMap[this.nzSize]}`, + this.nzShape && `${this._prefixCls}-${this.nzShape}`, + this.nzIcon && `${this._prefixCls}-icon`, + this.nzSrc && `${this._prefixCls}-image` + ].filter((item) => { + return !!item; + }); + this._classList.forEach(_className => { + this._renderer.addClass(this._el, _className); + }); + return this; + } + + _imgError() { + this._isSrcExist = false; + // TODO(i): need force remove [nzSrc] if broken image? + this._hasIcon = false; + this._hasText = false; + if (this.nzIcon) { + this._hasIcon = true; + } else if (this.nzText) { + this._hasText = true; + } + this._setClassMap()._notifyCalc(); + } + + private _calcStringSize() { + if (!this._hasText) return; + + const el = this._textEl && this._textEl.nativeElement; + if (!el) return; + + const childrenWidth = el.offsetWidth; + const avatarWidth = this._el.getBoundingClientRect().width; + const scale = avatarWidth - 8 < childrenWidth ? (avatarWidth - 8) / childrenWidth : 1; + if (scale === 1) { + this._textStyles = {}; + } else { + this._textStyles = { + transform: `scale(${scale})`, + position: 'absolute', + display: 'inline-block', + left: `calc(50% - ${Math.round(childrenWidth / 2)}px)` + }; + } + } + + private _notifyCalc() { + // If use ngAfterViewChecked, always demands more computations, so...... + setTimeout(() => { + this._calcStringSize(); + }); + return this; + } + + constructor(private _elementRef: ElementRef, private _renderer: Renderer2) { + this._el = _elementRef.nativeElement; + this._renderer.addClass(this._el, this._prefixCls); + } + + ngOnChanges(changes: SimpleChanges): void { + this._hasText = !this.nzSrc && !!this.nzText; + this._hasIcon = !this.nzSrc && !!this.nzIcon; + + this._setClassMap()._notifyCalc(); + } + +} diff --git a/src/components/avatar/nz-avatar.module.ts b/src/components/avatar/nz-avatar.module.ts new file mode 100644 index 0000000000..51d95759a1 --- /dev/null +++ b/src/components/avatar/nz-avatar.module.ts @@ -0,0 +1,12 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { NzAvatarComponent } from './nz-avatar.component'; + +@NgModule({ + declarations: [ NzAvatarComponent ], + exports : [ NzAvatarComponent ], + imports : [ CommonModule ] +}) + +export class NzAvatarModule { +} diff --git a/src/components/avatar/style/index.less b/src/components/avatar/style/index.less new file mode 100644 index 0000000000..37545da9a5 --- /dev/null +++ b/src/components/avatar/style/index.less @@ -0,0 +1,47 @@ +@import "../../style/themes/default"; + +@avatar-prefix-cls: ~"@{ant-prefix}-avatar"; + +.@{avatar-prefix-cls} { + display: inline-block; + text-align: center; + background: @avatar-bg; + color: @avatar-color; + white-space: nowrap; + position: relative; + overflow: hidden; + + .avatar-size(@avatar-size-base, @avatar-font-size-base); + + &-lg { + .avatar-size(@avatar-size-lg, @avatar-font-size-lg); + } + + &-sm { + .avatar-size(@avatar-size-sm, @avatar-font-size-sm); + } + + &-square { + border-radius: @avatar-border-radius; + } + + & > img { + width: 100%; + height: 100%; + } +} + +.avatar-size(@size, @font-size) { + width: @size; + height: @size; + line-height: @size; + border-radius: @size / 2; + + & > * { + line-height: @size; + } + + &.@{avatar-prefix-cls}-icon { + font-size: @font-size; + } +} diff --git a/src/components/avatar/style/patch.less b/src/components/avatar/style/patch.less new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/components/back-top/nz-back-top.component.ts b/src/components/back-top/nz-back-top.component.ts new file mode 100644 index 0000000000..c1db4a9838 --- /dev/null +++ b/src/components/back-top/nz-back-top.component.ts @@ -0,0 +1,110 @@ +import { + Component, + ViewEncapsulation, + OnInit, + Input, + TemplateRef, + EventEmitter, + Output, + Renderer2, + OnDestroy, + ContentChild +} from '@angular/core'; + +import { + trigger, + style, + transition, + animate +} from '@angular/animations'; +import { Observable } from 'rxjs/Observable'; +import { Subscription } from 'rxjs/Subscription'; + +import { NzScrollService } from "../core/scroll/nz-scroll.service"; + +@Component({ + selector: 'nz-back-top', + encapsulation: ViewEncapsulation.None, + animations: [ + trigger('enterLeave', [ + transition(':enter', [ + style({ opacity: 0 }), + animate(300, style({ opacity: 1 })) + ]), + transition(':leave', [ + style({ opacity: 1 }), + animate(300, style({ opacity: 0 })) + ]) + ]) + ], + template: ` +
+ +
+
+ +
+ `, + styleUrls: [ + './style/index.less', + './style/patch.less' + ] +}) +export class NzBackTopComponent implements OnInit, OnDestroy { + + private scroll$: Subscription = null; + private target: HTMLElement = null; + + _display: boolean = false; + + @ContentChild('nzTemplate') nzTemplate: TemplateRef; + + @Input() nzVisibilityHeight: number = 400; + + @Input() + set nzTarget(el: HTMLElement) { + this.target = el; + this.registerScrollEvent(); + } + + @Output() nzClick: EventEmitter = new EventEmitter(); + + constructor(private scrollSrv: NzScrollService, private _renderer: Renderer2) { } + + ngOnInit(): void { + if (!this.scroll$) this.registerScrollEvent(); + } + + clickBackTop() { + this.scrollSrv.scrollTo(this.getTarget(), 0); + this.nzClick.emit(true); + } + + private getTarget() { + return this.target || window; + } + + private handleScroll() { + this._display = this.scrollSrv.getScroll(this.getTarget()) > this.nzVisibilityHeight; + } + + private removeListen() { + if (this.scroll$) this.scroll$.unsubscribe(); + } + + private registerScrollEvent() { + this.removeListen(); + this.handleScroll(); + this.scroll$ = Observable.fromEvent(this.getTarget(), 'scroll') + .throttleTime(50) + .distinctUntilChanged() + .subscribe(e => { + this.handleScroll(); + }); + } + + ngOnDestroy(): void { + this.removeListen(); + } + +} diff --git a/src/components/back-top/nz-back-top.module.ts b/src/components/back-top/nz-back-top.module.ts new file mode 100644 index 0000000000..6f6b2b638f --- /dev/null +++ b/src/components/back-top/nz-back-top.module.ts @@ -0,0 +1,15 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; + +import { NzBackTopComponent } from './nz-back-top.component'; +import { SCROLL_SERVICE_PROVIDER } from "../core/scroll/nz-scroll.service"; + +@NgModule({ + declarations: [ NzBackTopComponent ], + exports : [ NzBackTopComponent ], + imports : [ CommonModule ], + providers : [ SCROLL_SERVICE_PROVIDER ] +}) +export class NzBackTopModule { +} + diff --git a/src/components/back-top/style/index.less b/src/components/back-top/style/index.less new file mode 100644 index 0000000000..d4f51bb1d0 --- /dev/null +++ b/src/components/back-top/style/index.less @@ -0,0 +1,33 @@ +@import "../../style/themes/default"; + +@backtop-prefix-cls: ~"@{ant-prefix}-back-top"; + +.@{backtop-prefix-cls} { + z-index: @zindex-back-top; + position: fixed; + right: 100px; + bottom: 50px; + height: 40px; + width: 40px; + cursor: pointer; + + &-content { + height: 40px; + width: 40px; + border-radius: 20px; + background-color: @back-top-bg; + color: @back-top-color; + text-align: center; + transition: all .3s @ease-in-out; + + &:hover { + background-color: @back-top-hover-bg; + transition: all .3s @ease-in-out; + } + } + + &-icon { + font-size: 20px; + margin-top: 10px; + } +} diff --git a/src/components/back-top/style/patch.less b/src/components/back-top/style/patch.less new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/components/core/scroll/nz-scroll.service.spec.ts b/src/components/core/scroll/nz-scroll.service.spec.ts new file mode 100644 index 0000000000..e45809ca81 --- /dev/null +++ b/src/components/core/scroll/nz-scroll.service.spec.ts @@ -0,0 +1,60 @@ +import { ReflectiveInjector } from '@angular/core'; +import { PlatformLocation } from '@angular/common'; +import { DOCUMENT } from '@angular/platform-browser'; + +import { NzScrollService } from './nz-scroll.service'; + +describe('NzScrollService', () => { + + const TOP: number = 10; + let injector: ReflectiveInjector; + let document: MockDocument; + let location: MockPlatformLocation; + let scrollService: NzScrollService; + + class MockDocument { + body = new MockElement(); + documentElement = new MockDocumentElement(); + } + + class MockDocumentElement { + scrollTop = jasmine.createSpy('scrollTop'); + } + + class MockElement { + scrollTop = jasmine.createSpy('scrollTop'); + } + + class MockPlatformLocation { + hash: string; + } + + beforeEach(() => { + spyOn(window, 'scrollBy'); + }); + + beforeEach(() => { + injector = ReflectiveInjector.resolveAndCreate([ + NzScrollService, + { provide: DOCUMENT, useClass: MockDocument }, + { provide: PlatformLocation, useClass: MockPlatformLocation } + ]); + location = injector.get(PlatformLocation); + document = injector.get(DOCUMENT); + scrollService = injector.get(NzScrollService); + }); + + describe('#setScrollTop', () => { + it(`should scroll to window ${TOP} x`, () => { + scrollService.setScrollTop(window, TOP); + expect(document.body.scrollTop).toBe(TOP); + }); + + it(`should scroll to dom element ${TOP} x`, () => { + let el = new MockElement(); + scrollService.setScrollTop(el, TOP); + expect(el.scrollTop).toBe(TOP); + }); + }); + +}); diff --git a/src/components/core/scroll/nz-scroll.service.ts b/src/components/core/scroll/nz-scroll.service.ts new file mode 100644 index 0000000000..7b17951538 --- /dev/null +++ b/src/components/core/scroll/nz-scroll.service.ts @@ -0,0 +1,130 @@ +import { Injectable, Inject, Provider, SkipSelf, Optional } from '@angular/core'; +import { DOCUMENT } from '@angular/platform-browser'; + +const availablePrefixs = ['moz', 'ms', 'webkit']; + +function requestAnimationFramePolyfill() { + let lastTime = 0; + return function (callback) { + const currTime = new Date().getTime(); + const timeToCall = Math.max(0, 16 - (currTime - lastTime)); + const id = window.setTimeout(function () { callback(currTime + timeToCall); }, timeToCall); + lastTime = currTime + timeToCall; + return id; + }; +} + +function getRequestAnimationFrame() { + if (typeof window === 'undefined') { + return () => { }; + } + if (window.requestAnimationFrame) { + // https://github.com/vuejs/vue/issues/4465 + return window.requestAnimationFrame.bind(window); + } + + const prefix = availablePrefixs.filter(key => `${key}RequestAnimationFrame` in window)[0]; + + return prefix + ? window[`${prefix}RequestAnimationFrame`] + : requestAnimationFramePolyfill(); +} + +const reqAnimFrame = getRequestAnimationFrame(); + +function easeInOutCubic(t: number, b: number, c: number, d: number) { + const cc = c - b; + t /= d / 2; + if (t < 1) { + return cc / 2 * t * t * t + b; + } else { + return cc / 2 * ((t -= 2) * t * t + 2) + b; + } +} + +@Injectable() +export class NzScrollService { + + constructor(@Inject(DOCUMENT) private doc: Document) { } + + /** 设置 `el` 滚动条位置 */ + setScrollTop(el: Element | Window, topValue: number = 0) { + if (el === window) { + this.doc.body.scrollTop = topValue; + this.doc.documentElement.scrollTop = topValue; + } else { + (el as Element).scrollTop = topValue; + } + } + + /** 获取 `el` 相对于视窗距离 */ + getOffset(el: Element): { top: number, left: number } { + let ret = { + top: 0, + left: 0 + }; + if (!el || !el.getClientRects().length) return ret; + + const rect = el.getBoundingClientRect(); + if (rect.width || rect.height) { + const doc = el.ownerDocument.documentElement; + ret.top = rect.top - doc.clientTop; + ret.left = rect.left - doc.clientLeft; + } else { + ret.top = rect.top; + ret.left = rect.left; + } + + return ret; + } + + /** 获取 `el` 滚动条位置 */ + getScroll(el?: Element | Window, top: boolean = true): number { + if (!el) el = window; + const prop = top ? 'pageYOffset' : 'pageXOffset'; + const method = top ? 'scrollTop' : 'scrollLeft'; + const isWindow = el === window; + let ret = isWindow ? el[prop] : el[method]; + if (isWindow && typeof ret !== 'number') + ret = this.doc.documentElement[method]; + + return ret; + } + + /** + * 使用动画形式将 `el` 滚动至某位置 + * + * @param {(Element | Window)} containerEl 容器,默认 `window` + * @param {number} [targetTopValue=0] 滚动至目标 `top` 值,默认:0,相当于顶部 + * @param {Function} [easing] 动作算法,默认:`easeInOutCubic` + * @param {Function} [callback] 动画结束后回调 + */ + scrollTo(containerEl: Element | Window, targetTopValue: number = 0, easing?: Function, callback?: Function) { + if (!containerEl) containerEl = window; + const scrollTop = this.getScroll(containerEl); + const startTime = Date.now(); + const frameFunc = () => { + const timestamp = Date.now(); + const time = timestamp - startTime; + this.setScrollTop(containerEl, (easing || easeInOutCubic)(time, scrollTop, targetTopValue, 450)); + if (time < 450) { + reqAnimFrame(frameFunc); + } else { + if (callback) callback(); + } + }; + reqAnimFrame(frameFunc); + } + + +} + +export function SCROLL_SERVICE_PROVIDER_FACTORY(doc, scrollService) { + return scrollService || new NzScrollService(doc); +} + +export const SCROLL_SERVICE_PROVIDER: Provider = { + provide : NzScrollService, + useFactory: SCROLL_SERVICE_PROVIDER_FACTORY, + deps : [ DOCUMENT, [ new Optional(), new SkipSelf(), NzScrollService ] ] +}; diff --git a/src/components/ng-zorro-antd.module.ts b/src/components/ng-zorro-antd.module.ts index 15d166a8d9..fdeba4de5e 100644 --- a/src/components/ng-zorro-antd.module.ts +++ b/src/components/ng-zorro-antd.module.ts @@ -46,6 +46,10 @@ import { NzCardModule } from './card/nz-card.module'; import { NzCollapseModule } from './collapse/nz-collapse.module'; import { NzTimelineModule } from './timeline/nz-timeline.module'; import { NzToolTipModule } from './tooltip/nz-tooltip.module'; +import { NzBackTopModule } from './back-top/nz-back-top.module'; +import { NzAffixModule } from './affix/nz-affix.module'; +import { NzAnchorModule } from './anchor/nz-anchor.module'; +import { NzAvatarModule } from './avatar/nz-avatar.module'; // Services import { NzNotificationService } from './notification/nz-notification.service'; @@ -101,6 +105,10 @@ export { NzCardModule } from './card/nz-card.module'; export { NzCollapseModule } from './collapse/nz-collapse.module'; export { NzTimelineModule } from './timeline/nz-timeline.module'; export { NzToolTipModule } from './tooltip/nz-tooltip.module'; +export { NzBackTopModule } from './back-top/nz-back-top.module'; +export { NzAffixModule } from './affix/nz-affix.module'; +export { NzAnchorModule } from './anchor/nz-anchor.module'; +export { NzAvatarModule } from './avatar/nz-avatar.module'; // Services export { NzNotificationService } from './notification/nz-notification.service'; @@ -158,7 +166,11 @@ export { NZ_ROOT_CONFIG, NzRootConfig } from './root/nz-root-config'; NzCardModule, NzCollapseModule, NzTimelineModule, - NzToolTipModule + NzToolTipModule, + NzBackTopModule, + NzAffixModule, + NzAnchorModule, + NzAvatarModule ] }) export class NgZorroAntdModule { diff --git a/src/showcase/nz-demo-affix/nz-demo-affix-basic.component.ts b/src/showcase/nz-demo-affix/nz-demo-affix-basic.component.ts new file mode 100644 index 0000000000..2a0b3a5240 --- /dev/null +++ b/src/showcase/nz-demo-affix/nz-demo-affix-basic.component.ts @@ -0,0 +1,13 @@ +import { Component, OnInit } from '@angular/core'; + +@Component({ + selector: 'nz-demo-affix-basic', + template: ` + + + + ` +}) +export class NzDemoAffixBasicComponent { } diff --git a/src/showcase/nz-demo-affix/nz-demo-affix-container.component.ts b/src/showcase/nz-demo-affix/nz-demo-affix-container.component.ts new file mode 100644 index 0000000000..e4b3f67232 --- /dev/null +++ b/src/showcase/nz-demo-affix/nz-demo-affix-container.component.ts @@ -0,0 +1,29 @@ +import { Component, OnInit } from '@angular/core'; + +@Component({ + selector: 'nz-demo-affix-container', + template: ` +
+
+ + + +
+
+ `, + styles: [` + :host ::ng-deep .scrollable-container { + height: 100px; + overflow-y: scroll; + } + + :host ::ng-deep .background { + padding-top: 60px; + height: 300px; + background-image: url(//zos.alipayobjects.com/rmsportal/RmjwQiJorKyobvI.jpg); + } + `] +}) +export class NzDemoAffixContainerComponent { } diff --git a/src/showcase/nz-demo-affix/nz-demo-affix-fixed.component.ts b/src/showcase/nz-demo-affix/nz-demo-affix-fixed.component.ts new file mode 100644 index 0000000000..a424955594 --- /dev/null +++ b/src/showcase/nz-demo-affix/nz-demo-affix-fixed.component.ts @@ -0,0 +1,17 @@ +import { Component, OnInit } from '@angular/core'; + +@Component({ + selector: 'nz-demo-affix-fixed', + template: ` + + + + ` +}) +export class NzDemoAffixFixedComponent { + onChange(status: boolean) { + console.log(status); + } +} diff --git a/src/showcase/nz-demo-affix/nz-demo-affix.component.ts b/src/showcase/nz-demo-affix/nz-demo-affix.component.ts new file mode 100644 index 0000000000..8ab17e72fd --- /dev/null +++ b/src/showcase/nz-demo-affix/nz-demo-affix.component.ts @@ -0,0 +1,18 @@ +import { Component, OnInit } from '@angular/core'; + +@Component({ + selector : 'nz-demo-affix', + templateUrl: './nz-demo-affix.html' +}) + +export class NzDemoAffixComponent implements OnInit { + NzDemoAffixBasicCode = require('!!raw-loader!./nz-demo-affix-basic.component') + NzDemoAffixFixedCode = require('!!raw-loader!./nz-demo-affix-fixed.component') + NzDemoAffixContainerCode = require('!!raw-loader!./nz-demo-affix-container.component') + + constructor() { + } + + ngOnInit() { + } +} diff --git a/src/showcase/nz-demo-affix/nz-demo-affix.html b/src/showcase/nz-demo-affix/nz-demo-affix.html new file mode 100644 index 0000000000..49f8537052 --- /dev/null +++ b/src/showcase/nz-demo-affix/nz-demo-affix.html @@ -0,0 +1,78 @@ +
+

Affix 固钉

+

将页面元素钉在可视范围。

+

何时使用 + # +

+

当内容区域比较长,需要滚动页面时,这部分内容对应的操作或者导航需要在滚动范围内始终展现。常用于侧边菜单和按钮组合。

+

页面可视范围过小时,慎用此功能以免遮挡页面内容。

+
+

代码演示

+
+
+
+ + +
+

最简单的用法。

+
+
+
+
+ + +
+

可以获得是否固定的状态。

+
+
+
+
+
+
+ + +
+

target 设置 nz-affix 需要监听其滚动事件的元素,默认为 window

+
+
+
+
+
+

API + # +

+

nz-affix + # +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
参数说明类型默认值
nzOffsetTop距离窗口顶部达到指定偏移量后触发number
nzTarget设置需要监听其滚动事件的元素,值为一个返回对应 DOM 元素的函数Elementwindow
nzChange固定状态改变时触发的回调函数function
+
+
diff --git a/src/showcase/nz-demo-affix/nz-demo-affix.module.ts b/src/showcase/nz-demo-affix/nz-demo-affix.module.ts new file mode 100644 index 0000000000..a385e1ff16 --- /dev/null +++ b/src/showcase/nz-demo-affix/nz-demo-affix.module.ts @@ -0,0 +1,21 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; + +import { NzDemoAffixBasicComponent } from './nz-demo-affix-basic.component'; +import { NzDemoAffixFixedComponent } from './nz-demo-affix-fixed.component'; +import { NzDemoAffixContainerComponent } from './nz-demo-affix-container.component'; +import { NzDemoAffixComponent } from './nz-demo-affix.component'; + +import { NzCodeBoxModule } from '../share/nz-codebox/nz-codebox.module'; +import { NgZorroAntdModule } from '../../../index.showcase'; + +import { NzDemoAffixRoutingModule } from './nz-demo-affix.routing.module'; + +@NgModule({ + declarations: [ NzDemoAffixComponent, NzDemoAffixBasicComponent, NzDemoAffixFixedComponent, NzDemoAffixContainerComponent ], + imports : [ NzDemoAffixRoutingModule, CommonModule, NzCodeBoxModule, NgZorroAntdModule ] +}) + +export class NzDemoAffixModule { + +} diff --git a/src/showcase/nz-demo-affix/nz-demo-affix.routing.module.ts b/src/showcase/nz-demo-affix/nz-demo-affix.routing.module.ts new file mode 100644 index 0000000000..698a1f8769 --- /dev/null +++ b/src/showcase/nz-demo-affix/nz-demo-affix.routing.module.ts @@ -0,0 +1,12 @@ +import { NgModule } from '@angular/core'; +import { RouterModule } from '@angular/router'; +import { NzDemoAffixComponent } from './nz-demo-affix.component'; + +@NgModule({ + imports: [ RouterModule.forChild([ + { path: '', component: NzDemoAffixComponent } + ]) ], + exports: [ RouterModule ] +}) +export class NzDemoAffixRoutingModule { +} diff --git a/src/showcase/nz-demo-anchor/nz-demo-anchor-basic.component.ts b/src/showcase/nz-demo-anchor/nz-demo-anchor-basic.component.ts new file mode 100644 index 0000000000..638ee9da55 --- /dev/null +++ b/src/showcase/nz-demo-anchor/nz-demo-anchor-basic.component.ts @@ -0,0 +1,17 @@ +import { Component, OnInit } from '@angular/core'; + +@Component({ + selector: 'nz-demo-anchor-basic', + template: ` + + + + + + + + + ` +}) + +export class NzDemoAnchorBasicComponent { } diff --git a/src/showcase/nz-demo-anchor/nz-demo-anchor-fixed.component.ts b/src/showcase/nz-demo-anchor/nz-demo-anchor-fixed.component.ts new file mode 100644 index 0000000000..62a0c38e14 --- /dev/null +++ b/src/showcase/nz-demo-anchor/nz-demo-anchor-fixed.component.ts @@ -0,0 +1,27 @@ +import { Component, OnInit } from '@angular/core'; + +@Component({ + selector: 'nz-demo-anchor-fixed', + template: ` + + + + + + + + + + + `, + styles: [ + ` + :host ::ng-deep nz-anchor { + display: block; + width: 250px; + } + ` + ] +}) + +export class NzDemoAnchorFixedComponent { } diff --git a/src/showcase/nz-demo-anchor/nz-demo-anchor.component.ts b/src/showcase/nz-demo-anchor/nz-demo-anchor.component.ts new file mode 100644 index 0000000000..b06ac96da6 --- /dev/null +++ b/src/showcase/nz-demo-anchor/nz-demo-anchor.component.ts @@ -0,0 +1,17 @@ +import { Component, OnInit } from '@angular/core'; + +@Component({ + selector : 'nz-demo-anchor', + templateUrl: './nz-demo-anchor.html' +}) + +export class NzDemoAnchorComponent implements OnInit { + NzDemoAnchorFixedCode = require('!!raw-loader!./nz-demo-anchor-fixed.component') + NzDemoAnchorBasicCode = require('!!raw-loader!./nz-demo-anchor-basic.component') + + constructor() { + } + + ngOnInit() { + } +} diff --git a/src/showcase/nz-demo-anchor/nz-demo-anchor.html b/src/showcase/nz-demo-anchor/nz-demo-anchor.html new file mode 100644 index 0000000000..c6ff23a255 --- /dev/null +++ b/src/showcase/nz-demo-anchor/nz-demo-anchor.html @@ -0,0 +1,100 @@ +
+

Anchor 锚点

+

用于跳转到页面指定位置。

+

何时使用 + # +

+

需要展现当前页面上可供跳转的锚点链接,以及快速在锚点之间跳转。

+
+

代码演示

+
+
+
+ + +
+

需要配合 nz-affix 使用。

+
+
+
+
+ + +
+

最简单的用法。

+
+
+
+
+
+

API + # +

+

nz-anchor + # +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
参数说明类型默认值
nzOffsetTop距离窗口顶部达到指定偏移量后触发number
nzBounds锚点区域边界number5(px)
nzScroll滚动至某锚点时触发function
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
参数说明类型默认值
nzHref锚点链接string
nzTitle文字内容string
nzTemplate文字内容,会覆盖掉 nzTitle 的内容ng-template
+
+
diff --git a/src/showcase/nz-demo-anchor/nz-demo-anchor.module.ts b/src/showcase/nz-demo-anchor/nz-demo-anchor.module.ts new file mode 100644 index 0000000000..790481a1ec --- /dev/null +++ b/src/showcase/nz-demo-anchor/nz-demo-anchor.module.ts @@ -0,0 +1,21 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; + +import { NzDemoAnchorFixedComponent } from './nz-demo-anchor-fixed.component'; +import { NzDemoAnchorBasicComponent } from './nz-demo-anchor-basic.component'; +import { NzDemoAnchorComponent } from './nz-demo-anchor.component'; + + +import { NzCodeBoxModule } from '../share/nz-codebox/nz-codebox.module'; +import { NgZorroAntdModule } from '../../../index.showcase'; + +import { NzDemoAnchorRoutingModule } from './nz-demo-anchor.routing.module'; + +@NgModule({ + declarations: [ NzDemoAnchorComponent, NzDemoAnchorBasicComponent, NzDemoAnchorFixedComponent ], + imports : [ NzDemoAnchorRoutingModule, CommonModule, NzCodeBoxModule, NgZorroAntdModule ] +}) + +export class NzDemoAnchorModule { + +} diff --git a/src/showcase/nz-demo-anchor/nz-demo-anchor.routing.module.ts b/src/showcase/nz-demo-anchor/nz-demo-anchor.routing.module.ts new file mode 100644 index 0000000000..f7c4e478de --- /dev/null +++ b/src/showcase/nz-demo-anchor/nz-demo-anchor.routing.module.ts @@ -0,0 +1,12 @@ +import { NgModule } from '@angular/core'; +import { RouterModule } from '@angular/router'; +import { NzDemoAnchorComponent } from './nz-demo-anchor.component'; + +@NgModule({ + imports: [ RouterModule.forChild([ + { path: '', component: NzDemoAnchorComponent } + ]) ], + exports: [ RouterModule ] +}) +export class NzDemoAnchorRoutingModule { +} diff --git a/src/showcase/nz-demo-avatar/nz-demo-avatar-autosize.component.ts b/src/showcase/nz-demo-avatar/nz-demo-avatar-autosize.component.ts new file mode 100644 index 0000000000..584bc56eb5 --- /dev/null +++ b/src/showcase/nz-demo-avatar/nz-demo-avatar-autosize.component.ts @@ -0,0 +1,32 @@ +import { Component } from '@angular/core'; + +const UserList = ['U', 'Lucy', 'Tom', 'Edward']; +const ColorList = ['#f56a00', '#7265e6', '#ffbf00', '#00a2ae']; + +@Component({ + selector: 'nz-demo-avatar-autosize', + template: ` + + + `, + styles: [` + :host ::ng-deep .ant-avatar { + margin-top: 16px; + margin-right: 16px; + } + `] +}) +export class NzDemoAvatarAutoSizeComponent { + text: string = UserList[3]; + color: string = ColorList[3]; + + change() { + let idx = UserList.indexOf(this.text); + ++idx; + if (idx == UserList.length) idx = 0; + this.text = UserList[idx]; + this.color = ColorList[idx]; + } +} diff --git a/src/showcase/nz-demo-avatar/nz-demo-avatar-badge.component.ts b/src/showcase/nz-demo-avatar/nz-demo-avatar-badge.component.ts new file mode 100644 index 0000000000..6fa7c31616 --- /dev/null +++ b/src/showcase/nz-demo-avatar/nz-demo-avatar-badge.component.ts @@ -0,0 +1,14 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'nz-demo-avatar-badge', + template: ` + + + + + + + ` +}) +export class NzDemoAvatarBadgeComponent { } diff --git a/src/showcase/nz-demo-avatar/nz-demo-avatar-basic.component.ts b/src/showcase/nz-demo-avatar/nz-demo-avatar-basic.component.ts new file mode 100644 index 0000000000..862c24c9e9 --- /dev/null +++ b/src/showcase/nz-demo-avatar/nz-demo-avatar-basic.component.ts @@ -0,0 +1,24 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'nz-demo-avatar-basic', + template: ` +
+ + + +
+
+ + + +
+ `, + styles: [` + :host ::ng-deep .ant-avatar { + margin-top: 16px; + margin-right: 16px; + } + `] +}) +export class NzDemoAvatarBasicComponent { } diff --git a/src/showcase/nz-demo-avatar/nz-demo-avatar-type.component.ts b/src/showcase/nz-demo-avatar/nz-demo-avatar-type.component.ts new file mode 100644 index 0000000000..7ba3823670 --- /dev/null +++ b/src/showcase/nz-demo-avatar/nz-demo-avatar-type.component.ts @@ -0,0 +1,20 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'nz-demo-avatar-type', + template: ` + + + + + + + `, + styles: [` + :host ::ng-deep .ant-avatar { + margin-top: 16px; + margin-right: 16px; + } + `] +}) +export class NzDemoAvatarTypeComponent { } diff --git a/src/showcase/nz-demo-avatar/nz-demo-avatar.component.ts b/src/showcase/nz-demo-avatar/nz-demo-avatar.component.ts new file mode 100644 index 0000000000..dbf8fa664a --- /dev/null +++ b/src/showcase/nz-demo-avatar/nz-demo-avatar.component.ts @@ -0,0 +1,12 @@ +import { Component } from '@angular/core'; + +@Component({ + selector : 'nz-demo-avatar', + templateUrl: './nz-demo-avatar.html' +}) +export class NzDemoAvatarComponent { + NzDemoAvatarBasicCode = require('!!raw-loader!./nz-demo-avatar-basic.component') + NzDemoAvatarTypeCode = require('!!raw-loader!./nz-demo-avatar-type.component') + NzDemoAvatarAutoSizeCode = require('!!raw-loader!./nz-demo-avatar-autosize.component') + NzDemoAvatarBadgeCode = require('!!raw-loader!./nz-demo-avatar-badge.component') +} diff --git a/src/showcase/nz-demo-avatar/nz-demo-avatar.html b/src/showcase/nz-demo-avatar/nz-demo-avatar.html new file mode 100644 index 0000000000..402e0818be --- /dev/null +++ b/src/showcase/nz-demo-avatar/nz-demo-avatar.html @@ -0,0 +1,97 @@ +
+

Avatar 头像

+

用来代表用户或事物,支持图片、图标或字符展示。

+

何时使用 + # +

+

需要展现当前页面上可供跳转的锚点链接,以及快速在锚点之间跳转。

+
+

代码演示

+
+
+
+ + +
+

头像有三种尺寸,两种形状可选。

+
+
+
+
+ + +
+

支持三种类型:图片、Icon 以及字符,其中 Icon 和字符型可以自定义图标颜色及背景色。

+
+
+
+
+
+
+ + +
+

对于字符型的头像,当字符串较长时,字体大小可以根据头像宽度自动调整。

+
+
+
+
+ + +
+

通常用于消息提示。

+
+
+
+
+
+

API + # +

+

nz-avatar + # +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
参数说明类型默认值
nzShape指定头像的形状circle squarecircle
nzSize设置头像的大小default large smalldefault
nzText文本类头像string-
nzSrc图片类头像的资源地址;倘若图片加载失败,自动显示 nzIcon > nzTextstring-
nzIcon设置头像的图标类型,参考 icon 组件string-
+
+
diff --git a/src/showcase/nz-demo-avatar/nz-demo-avatar.module.ts b/src/showcase/nz-demo-avatar/nz-demo-avatar.module.ts new file mode 100644 index 0000000000..beda4faf6c --- /dev/null +++ b/src/showcase/nz-demo-avatar/nz-demo-avatar.module.ts @@ -0,0 +1,20 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; + +import { NzDemoAvatarBasicComponent } from './nz-demo-avatar-basic.component'; +import { NzDemoAvatarTypeComponent } from './nz-demo-avatar-type.component'; +import { NzDemoAvatarAutoSizeComponent } from './nz-demo-avatar-autosize.component'; +import { NzDemoAvatarBadgeComponent } from './nz-demo-avatar-badge.component'; +import { NzDemoAvatarComponent } from './nz-demo-avatar.component'; + +import { NzCodeBoxModule } from '../share/nz-codebox/nz-codebox.module'; +import { NgZorroAntdModule } from '../../../index.showcase'; + +import { NzDemoAvatarRoutingModule } from './nz-demo-avatar.routing.module'; + +@NgModule({ + declarations: [ NzDemoAvatarComponent, NzDemoAvatarBasicComponent, NzDemoAvatarTypeComponent, NzDemoAvatarAutoSizeComponent, NzDemoAvatarBadgeComponent ], + imports : [ NzDemoAvatarRoutingModule, CommonModule, NzCodeBoxModule, NgZorroAntdModule ] +}) +export class NzDemoAvatarModule { +} diff --git a/src/showcase/nz-demo-avatar/nz-demo-avatar.routing.module.ts b/src/showcase/nz-demo-avatar/nz-demo-avatar.routing.module.ts new file mode 100644 index 0000000000..3982b3535d --- /dev/null +++ b/src/showcase/nz-demo-avatar/nz-demo-avatar.routing.module.ts @@ -0,0 +1,12 @@ +import { NgModule } from '@angular/core'; +import { RouterModule } from '@angular/router'; +import { NzDemoAvatarComponent } from './nz-demo-avatar.component'; + +@NgModule({ + imports: [RouterModule.forChild([ + { path: '', component: NzDemoAvatarComponent } + ])], + exports: [RouterModule] +}) +export class NzDemoAvatarRoutingModule { +} diff --git a/src/showcase/nz-demo-back-top/nz-demo-back-top-basic.component.ts b/src/showcase/nz-demo-back-top/nz-demo-back-top-basic.component.ts new file mode 100644 index 0000000000..d5e2e691a7 --- /dev/null +++ b/src/showcase/nz-demo-back-top/nz-demo-back-top-basic.component.ts @@ -0,0 +1,24 @@ +import { Component, OnInit } from '@angular/core'; + +@Component({ + selector: 'nz-demo-back-top-basic', + template: ` + + Scroll down to see the bottom-right + gray + button. + `, + styles: [` + :host ::ng-deep strong { + color: rgba(64, 64, 64, 0.6); + } + `] +}) + +export class NzDemoBackTopBasicComponent implements OnInit { + constructor() { + } + + ngOnInit() { + } +} diff --git a/src/showcase/nz-demo-back-top/nz-demo-back-top-custom.component.ts b/src/showcase/nz-demo-back-top/nz-demo-back-top-custom.component.ts new file mode 100644 index 0000000000..7304202445 --- /dev/null +++ b/src/showcase/nz-demo-back-top/nz-demo-back-top-custom.component.ts @@ -0,0 +1,45 @@ +import { Component, OnInit } from '@angular/core'; + +@Component({ + selector: 'nz-demo-back-top-custom', + template: ` + + +
UP
+
+
+ Scroll down to see the bottom-right + blue + button. + `, + styles: [` + :host ::ng-deep .ant-back-top { + bottom: 100px; + } + :host ::ng-deep .ant-back-top-inner { + height: 40px; + width: 40px; + line-height: 40px; + border-radius: 4px; + background-color: #1088e9; + color: #fff; + text-align: center; + font-size: 20px; + } + :host ::ng-deep strong { + color: #1088e9; + } + `] +}) + +export class NzDemoBackTopCustomComponent implements OnInit { + constructor() { + } + + ngOnInit() { + } + + notify() { + console.log('notify'); + } +} diff --git a/src/showcase/nz-demo-back-top/nz-demo-back-top-target.component.ts b/src/showcase/nz-demo-back-top/nz-demo-back-top-target.component.ts new file mode 100644 index 0000000000..aca25a68ea --- /dev/null +++ b/src/showcase/nz-demo-back-top/nz-demo-back-top-target.component.ts @@ -0,0 +1,41 @@ +import { Component, OnInit } from '@angular/core'; + +@Component({ + selector: 'nz-demo-back-top-target', + template: ` + Scroll down to see the bottom-right + gray + button. +
+
+ +
+ `, + styles: [` + :host ::ng-deep .long-div { + height: 300px; + overflow-y: scroll; + background-image: url(//zos.alipayobjects.com/rmsportal/RmjwQiJorKyobvI.jpg); + } + + :host ::ng-deep .long-div-inner { + height: 1500px; + } + + :host ::ng-deep .long-div .ant-back-top { + right: 150px; + } + + :host ::ng-deep strong { + color: rgba(64, 64, 64, 0.6); + } + `] +}) + +export class NzDemoBackTopTargetComponent implements OnInit { + constructor() { + } + + ngOnInit() { + } +} diff --git a/src/showcase/nz-demo-back-top/nz-demo-back-top.component.ts b/src/showcase/nz-demo-back-top/nz-demo-back-top.component.ts new file mode 100644 index 0000000000..ccb98b6628 --- /dev/null +++ b/src/showcase/nz-demo-back-top/nz-demo-back-top.component.ts @@ -0,0 +1,18 @@ +import { Component, OnInit } from '@angular/core'; + +@Component({ + selector : 'nz-demo-back-top', + templateUrl: './nz-demo-back-top.html' +}) + +export class NzDemoBackTopComponent implements OnInit { + NzDemoBackTopBasicCode = require('!!raw-loader!./nz-demo-back-top-basic.component') + NzDemoBackTopCustomCode = require('!!raw-loader!./nz-demo-back-top-custom.component') + NzDemoBackTopTargetCode = require('!!raw-loader!./nz-demo-back-top-target.component') + + constructor() { + } + + ngOnInit() { + } +} diff --git a/src/showcase/nz-demo-back-top/nz-demo-back-top.html b/src/showcase/nz-demo-back-top/nz-demo-back-top.html new file mode 100644 index 0000000000..4d4d703886 --- /dev/null +++ b/src/showcase/nz-demo-back-top/nz-demo-back-top.html @@ -0,0 +1,90 @@ +
+

BackTop 回到顶部

+

返回页面顶部的操作按钮。

+

何时使用 + # +

+
    +
  • 当页面内容区域比较长时;

  • +
  • 当用户需要频繁返回顶部查看相关内容时。

  • +
+
+

代码演示

+
+
+
+ + +
+

最简单的用法。

+
+
+
+
+ + +
+

可以自定义回到顶部按钮的样式,限制宽高:40px * 40px

+
+
+
+
+
+
+ + +
+

设置 [nzTarget] 参数,允许对某个容器返回顶部。

+
+
+
+
+
+

API + # +

+

nz-back-top + # +

+
+

有默认样式,距离底部50px,可覆盖。

+

自定义样式宽高不大于 40px * 40px。

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
参数说明类型默认值
nzTemplate自定义内容,见示例ng-template
nzVisibilityHeight滚动高度达到此参数值才出现 nz-back-topnumber400
nzClick点击按钮的回调函数function-
nzTarget设置需要监听其滚动事件的元素,值为一个返回对应 DOM 元素的函数Elementwindow
+
+
diff --git a/src/showcase/nz-demo-back-top/nz-demo-back-top.module.ts b/src/showcase/nz-demo-back-top/nz-demo-back-top.module.ts new file mode 100644 index 0000000000..7ab9f8577f --- /dev/null +++ b/src/showcase/nz-demo-back-top/nz-demo-back-top.module.ts @@ -0,0 +1,22 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; + +import { NzDemoBackTopBasicComponent } from './nz-demo-back-top-basic.component'; +import { NzDemoBackTopCustomComponent } from './nz-demo-back-top-custom.component'; +import { NzDemoBackTopTargetComponent } from './nz-demo-back-top-target.component'; +import { NzDemoBackTopComponent } from './nz-demo-back-top.component'; + + +import { NzCodeBoxModule } from '../share/nz-codebox/nz-codebox.module'; +import { NgZorroAntdModule } from '../../../index.showcase'; + +import { NzDemoBackTopRoutingModule } from './nz-demo-back-top.routing.module'; + +@NgModule({ + declarations: [ NzDemoBackTopComponent, NzDemoBackTopBasicComponent, NzDemoBackTopCustomComponent, NzDemoBackTopTargetComponent ], + imports : [ NzDemoBackTopRoutingModule, CommonModule, NzCodeBoxModule, NgZorroAntdModule ] +}) + +export class NzDemoBackTopModule { + +} diff --git a/src/showcase/nz-demo-back-top/nz-demo-back-top.routing.module.ts b/src/showcase/nz-demo-back-top/nz-demo-back-top.routing.module.ts new file mode 100644 index 0000000000..d590e387fb --- /dev/null +++ b/src/showcase/nz-demo-back-top/nz-demo-back-top.routing.module.ts @@ -0,0 +1,12 @@ +import { NgModule } from '@angular/core'; +import { RouterModule } from '@angular/router'; +import { NzDemoBackTopComponent } from './nz-demo-back-top.component'; + +@NgModule({ + imports: [ RouterModule.forChild([ + { path: '', component: NzDemoBackTopComponent } + ]) ], + exports: [ RouterModule ] +}) +export class NzDemoBackTopRoutingModule { +} diff --git a/src/showcase/router.ts b/src/showcase/router.ts index 00e4a98457..4c787826fa 100644 --- a/src/showcase/router.ts +++ b/src/showcase/router.ts @@ -54,6 +54,12 @@ export const ROUTER_LIST = { { 'name' : 'Navigation', 'children': [ + { + 'label' : 'Affix', + 'path' : 'components/affix', + 'loadChildren': './nz-demo-affix/nz-demo-affix.module#NzDemoAffixModule', + 'zh' : '固钉' + }, { 'label' : 'Breadcrumb', 'path' : 'components/breadcrumb', @@ -166,6 +172,12 @@ export const ROUTER_LIST = { { 'name' : 'Data Display', 'children': [ + { + 'label' : 'Avatar', + 'path' : 'components/avatar', + 'loadChildren': './nz-demo-avatar/nz-demo-avatar.module#NzDemoAvatarModule', + 'zh' : '头像' + }, { 'label' : 'Badge', 'path' : 'components/badge', @@ -280,6 +292,23 @@ export const ROUTER_LIST = { 'zh' : '加载中' } ] + }, + { + 'name' : 'Other', + 'children': [ + { + 'label' : 'Anchor', + 'path' : 'other/anchor', + 'loadChildren': './nz-demo-anchor/nz-demo-anchor.module#NzDemoAnchorModule', + 'zh' : '锚点' + }, + { + 'label' : 'BackTop', + 'path' : 'other/back-top', + 'loadChildren': './nz-demo-back-top/nz-demo-back-top.module#NzDemoBackTopModule', + 'zh' : '回到顶部' + } + ] } ] }; @@ -454,6 +483,22 @@ export const DEMO_ROUTES = [ { 'path' : 'components/spin', 'loadChildren': './nz-demo-spin/nz-demo-spin.module#NzDemoSpinModule' + }, + { + 'path' : 'components/affix', + 'loadChildren': './nz-demo-affix/nz-demo-affix.module#NzDemoAffixModule' + }, + { + 'path' : 'other/back-top', + 'loadChildren': './nz-demo-back-top/nz-demo-back-top.module#NzDemoBackTopModule' + }, + { + 'path' : 'other/anchor', + 'loadChildren': './nz-demo-anchor/nz-demo-anchor.module#NzDemoAnchorModule' + }, + { + 'path' : 'components/avatar', + 'loadChildren': './nz-demo-avatar/nz-demo-avatar.module#NzDemoAvatarModule' } ];