diff --git a/docs/assets/images/components/tooltip.svg b/docs/assets/images/components/tooltip.svg new file mode 100644 index 0000000000..37ab23f489 --- /dev/null +++ b/docs/assets/images/components/tooltip.svg @@ -0,0 +1,26 @@ + + + + icon/component/tooltip + Created with Sketch. + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/structure.ts b/docs/structure.ts index ae7e48d59c..72546eef36 100644 --- a/docs/structure.ts +++ b/docs/structure.ts @@ -322,6 +322,15 @@ export const structure = [ 'NbToastrConfig', ], }, + { + type: 'tabs', + name: 'Tooltip', + icon: 'tooltip.svg', + source: [ + 'NbTooltipDirective', + 'NbTooltipComponent', + ], + }, { type: 'group', name: 'Extra', diff --git a/src/framework/theme/components/tooltip/_tooltip.component.theme.scss b/src/framework/theme/components/tooltip/_tooltip.component.theme.scss new file mode 100644 index 0000000000..ff17e62383 --- /dev/null +++ b/src/framework/theme/components/tooltip/_tooltip.component.theme.scss @@ -0,0 +1,44 @@ +/** + * @license + * Copyright Akveo. All Rights Reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + */ + +@mixin nb-tooltip-status($status) { + $color: nb-theme(tooltip-#{$status}-bg); + + &.#{$status}-tooltip { + background: $color; + .arrow { + border-bottom-color: $color; + } + + .content { + color: nb-theme(tooltip-status-fg); + } + } +} + +@mixin nb-tooltip-theme { + nb-tooltip { + $arrow-size: 5px; + $arrow-content-size: 3px; + + background: nb-theme(tooltip-bg); + + .content { + font-size: nb-theme(tooltip-font-size); + color: nb-theme(tooltip-fg); + } + + .arrow { + border-bottom: $arrow-size solid nb-theme(tooltip-bg); + } + + @include nb-tooltip-status('primary'); + @include nb-tooltip-status('danger'); + @include nb-tooltip-status('success'); + @include nb-tooltip-status('warning'); + @include nb-tooltip-status('info'); + } +} diff --git a/src/framework/theme/components/tooltip/tooltip.component.scss b/src/framework/theme/components/tooltip/tooltip.component.scss new file mode 100644 index 0000000000..a5e0129bd8 --- /dev/null +++ b/src/framework/theme/components/tooltip/tooltip.component.scss @@ -0,0 +1,71 @@ +/** + * @license + * Copyright Akveo. All Rights Reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + */ + +:host { + $arrow-size: 5px; + + z-index: 10000; + border-radius: 5px; + + .content { + padding: 0.5rem 1.25rem; + display: flex; + } + + &.right .content { + flex-direction: row-reverse; + } + + .arrow { + position: absolute; + + width: 0; + height: 0; + } + + .icon { + font-size: 1.25rem; + } + + span { + line-height: 1.25rem; + } + + .icon + span { + margin-left: 0.5rem; + } + &.right .icon + span { + margin-right: 0.5rem; + } + + .arrow { + border-left: $arrow-size solid transparent; + border-right: $arrow-size solid transparent; + } + + &.bottom .arrow { + top: -#{$arrow-size}; + left: calc(50% - #{$arrow-size}); + } + + &.left .arrow { + right: round(-$arrow-size - $arrow-size / 2.5); + top: calc(50% - #{$arrow-size / 2.5}); + transform: rotate(90deg); + } + + &.top .arrow { + bottom: -#{$arrow-size}; + left: calc(50% - #{$arrow-size}); + transform: rotate(180deg); + } + + &.right .arrow { + left: round(-$arrow-size - $arrow-size / 2.5); + top: calc(50% - #{$arrow-size / 2.5}); + transform: rotate(270deg); + } +} diff --git a/src/framework/theme/components/tooltip/tooltip.component.ts b/src/framework/theme/components/tooltip/tooltip.component.ts new file mode 100644 index 0000000000..570c18aa2b --- /dev/null +++ b/src/framework/theme/components/tooltip/tooltip.component.ts @@ -0,0 +1,76 @@ +/* + * @license + * Copyright Akveo. All Rights Reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + */ + +import { Component, HostBinding, Input } from '@angular/core'; + +import { NbPosition } from '../cdk'; +import { animate, state, style, transition, trigger } from '@angular/animations'; + + +/** + * Tooltip container. + * Renders provided tooltip inside. + * + * @styles + * + * tooltip-bg + * tooltip-primary-bg + * tooltip-info-bg + * tooltip-success-bg + * tooltip-warning-bg + * tooltip-danger-bg + * tooltip-fg + * tooltip-shadow + * tooltip-font-size + * + */ +@Component({ + selector: 'nb-tooltip', + styleUrls: ['./tooltip.component.scss'], + template: ` + +
+ + {{ content }} +
+ `, + animations: [ + trigger('showTooltip', [ + state('in', style({ opacity: 1 })), + transition('void => *', [ + style({ opacity: 0 }), + animate(100), + ]), + transition('* => void', [ + animate(100, style({ opacity: 0 })), + ]), + ]), + ], +}) +export class NbTooltipComponent { + + @Input() + content: string; + + /** + * Popover position relatively host element. + * */ + @Input() + position: NbPosition = NbPosition.TOP; + + @HostBinding('class') + get binding() { + return `${this.position} ${this.context.status}-tooltip`; + } + + @HostBinding('@showTooltip') + get show() { + return true; + } + + @Input() + context: { icon?: string, status?: string } = {}; +} diff --git a/src/framework/theme/components/tooltip/tooltip.directive.ts b/src/framework/theme/components/tooltip/tooltip.directive.ts new file mode 100644 index 0000000000..d04c3ccbcd --- /dev/null +++ b/src/framework/theme/components/tooltip/tooltip.directive.ts @@ -0,0 +1,161 @@ +/* + * @license + * Copyright Akveo. All Rights Reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + */ + +import { AfterViewInit, ComponentRef, Directive, ElementRef, Inject, Input, OnDestroy } from '@angular/core'; +import { takeWhile } from 'rxjs/operators'; + +import { + createContainer, + NbAdjustableConnectedPositionStrategy, + NbAdjustment, + NbOverlayRef, + NbOverlayService, + NbPosition, + NbPositionBuilderService, + NbTrigger, + NbTriggerStrategy, + NbTriggerStrategyBuilder, + patch, +} from '../cdk'; +import { NB_DOCUMENT } from '../../theme.options'; +import { NbTooltipComponent } from './tooltip.component'; + +/** + * + * Tooltip directive for small text/icon hints. + * + * @stacked-example(Showcase, tooltip/tooltip-showcase.component) + * + * Tooltip can accept a hint text and/or an icon: + * @stacked-example(With Icon, tooltip/tooltip-with-icon.component) + * + * Same way as Popover, tooltip can accept placement position with `nbTooltipPlacement` proprety: + * @stacked-example(Placements, tooltip/tooltip-placements.component) + * + * It is also possible to specify tooltip color using `nbTooltipStatus` property: + * @stacked-example(Colored Tooltips, tooltip/tooltip-colors.component) + * + */ +@Directive({ selector: '[nbTooltip]' }) +export class NbTooltipDirective implements AfterViewInit, OnDestroy { + + context: Object = {}; + + /** + * Popover content which will be rendered in NbTooltipComponent. + * Available content: template ref, component and any primitive. + * + */ + @Input('nbTooltip') + content: string; + /** + * Position will be calculated relatively host element based on the position. + * Can be top, right, bottom, left, start or end. + */ + @Input('nbTooltipPlacement') + position: NbPosition = NbPosition.TOP; + /** + * Container position will be changes automatically based on this strategy if container can't fit view port. + * Set this property to any falsy value if you want to disable automatically adjustment. + * Available values: clockwise, counterclockwise. + */ + @Input('nbTooltipAdjustment') + adjustment: NbAdjustment = NbAdjustment.CLOCKWISE; + + /** + * + * @param {string} icon + */ + @Input('nbTooltipIcon') + set icon(icon: string) { + this.context = Object.assign(this.context, { icon }); + } + + /** + * + * @param {string} status + */ + @Input('nbTooltipStatus') + set status(status: string) { + this.context = Object.assign(this.context, { status }); + } + + protected ref: NbOverlayRef; + protected container: ComponentRef; + protected positionStrategy: NbAdjustableConnectedPositionStrategy; + protected triggerStrategy: NbTriggerStrategy; + protected alive: boolean = true; + + constructor(@Inject(NB_DOCUMENT) protected document, + private hostRef: ElementRef, + private positionBuilder: NbPositionBuilderService, + private overlay: NbOverlayService) { + } + + ngAfterViewInit() { + this.positionStrategy = this.createPositionStrategy(); + this.ref = this.overlay.create({ + positionStrategy: this.positionStrategy, + scrollStrategy: this.overlay.scrollStrategies.reposition(), + }); + this.triggerStrategy = this.createTriggerStrategy(); + + this.subscribeOnTriggers(); + this.subscribeOnPositionChange(); + } + + ngOnDestroy() { + this.alive = false; + } + + show() { + this.container = createContainer(this.ref, NbTooltipComponent, { + position: this.position, + content: this.content, + context: this.context, + }); + } + + hide() { + this.ref.detach(); + } + + toggle() { + if (this.ref && this.ref.hasAttached()) { + this.hide(); + } else { + this.show(); + } + } + + protected createPositionStrategy(): NbAdjustableConnectedPositionStrategy { + return this.positionBuilder + .connectedTo(this.hostRef) + .position(this.position) + .adjustment(this.adjustment) + .offset(8); + } + + protected createTriggerStrategy(): NbTriggerStrategy { + return new NbTriggerStrategyBuilder() + .document(this.document) + .trigger(NbTrigger.HINT) + .host(this.hostRef.nativeElement) + .container(() => this.container) + .build(); + } + + protected subscribeOnPositionChange() { + this.positionStrategy.positionChange + .pipe(takeWhile(() => this.alive)) + .subscribe((position: NbPosition) => patch(this.container, { position })); + } + + protected subscribeOnTriggers() { + this.triggerStrategy.show$.pipe(takeWhile(() => this.alive)).subscribe(() => this.show()); + this.triggerStrategy.hide$.pipe(takeWhile(() => this.alive)).subscribe(() => this.hide()); + } +} diff --git a/src/framework/theme/components/tooltip/tooltip.module.ts b/src/framework/theme/components/tooltip/tooltip.module.ts new file mode 100644 index 0000000000..dba81f2bf1 --- /dev/null +++ b/src/framework/theme/components/tooltip/tooltip.module.ts @@ -0,0 +1,22 @@ +/* + * @license + * Copyright Akveo. All Rights Reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + */ + +import { NgModule } from '@angular/core'; + +import { NbTooltipComponent } from './tooltip.component'; +import { NbSharedModule } from '../shared/shared.module'; +import { NbTooltipDirective } from './tooltip.directive'; +import { NbOverlayModule } from '../cdk'; + + +@NgModule({ + imports: [NbSharedModule, NbOverlayModule], + declarations: [NbTooltipComponent, NbTooltipDirective], + exports: [NbTooltipDirective], + entryComponents: [NbTooltipComponent], +}) +export class NbTooltipModule { +} diff --git a/src/framework/theme/index.ts b/src/framework/theme/index.ts index 938c6b4a1d..20cb57be7f 100644 --- a/src/framework/theme/index.ts +++ b/src/framework/theme/index.ts @@ -74,3 +74,5 @@ export * from './components/cdk/overlay'; export * from './components/dialog'; export * from './components/toastr/toastr.module'; export * from './components/toastr/toastr.service'; +export * from './components/tooltip/tooltip.module'; +export * from './components/tooltip/tooltip.directive'; diff --git a/src/framework/theme/styles/global/_components.scss b/src/framework/theme/styles/global/_components.scss index 8a166b2356..cce144f2f6 100644 --- a/src/framework/theme/styles/global/_components.scss +++ b/src/framework/theme/styles/global/_components.scss @@ -32,6 +32,7 @@ @import '../../components/popover/popover.component.theme'; @import '../../components/context-menu/context-menu.component.theme'; @import '../../components/toastr/toast.component.theme'; +@import '../../components/tooltip/tooltip.component.theme'; @mixin nb-theme-components() { @@ -63,4 +64,5 @@ @include nb-popover-theme(); @include nb-context-menu-theme(); @include nb-toaster-theme(); + @include nb-tooltip-theme(); } diff --git a/src/framework/theme/styles/themes/_cosmic.scss b/src/framework/theme/styles/themes/_cosmic.scss index c79985340f..a080e2c1b3 100644 --- a/src/framework/theme/styles/themes/_cosmic.scss +++ b/src/framework/theme/styles/themes/_cosmic.scss @@ -163,6 +163,9 @@ $theme: ( toastr-padding: 1.25rem, toastr-border: 0, toastr-default-background: #bcc3cc, + + tooltip-fg: color-bg, + tooltip-status-fg: color-white, ); // register the theme diff --git a/src/framework/theme/styles/themes/_default.scss b/src/framework/theme/styles/themes/_default.scss index b4c81843ad..b1430b1e2f 100644 --- a/src/framework/theme/styles/themes/_default.scss +++ b/src/framework/theme/styles/themes/_default.scss @@ -646,6 +646,17 @@ $theme: ( calendar-year-cell-large-height: calendar-month-cell-height, overlay-backdrop-bg: rgba(0, 0, 0, 0.288), + + tooltip-bg: color-fg-text, + tooltip-primary-bg: color-primary, + tooltip-info-bg: color-info, + tooltip-success-bg: color-success, + tooltip-warning-bg: color-warning, + tooltip-danger-bg: color-danger, + tooltip-fg: color-bg-active, + tooltip-status-fg: color-bg-active, + tooltip-shadow: shadow, + tooltip-font-size: font-size, ); // register the theme diff --git a/src/playground/playground-routing.module.ts b/src/playground/playground-routing.module.ts index 700f853591..a6c9edf9cc 100644 --- a/src/playground/playground-routing.module.ts +++ b/src/playground/playground-routing.module.ts @@ -175,6 +175,10 @@ import { NbToastrDurationComponent } from './toastr/toastr-duration.component'; import { NbToastrDestroyByClickComponent } from './toastr/toastr-destroy-by-click.component'; import { NbToastrPreventDuplicatesComponent } from './toastr/toastr-prevent-duplicates.component'; import { NbToastrIconComponent } from './toastr/toastr-icon.component'; +import { NbTooltipShowcaseComponent } from './tooltip/tooltip-showcase.component'; +import { NbTooltipWithIconComponent } from './tooltip/tooltip-with-icon.component'; +import { NbTooltipPlacementsComponent } from './tooltip/tooltip-placements.component'; +import { NbTooltipColorsComponent } from './tooltip/tooltip-colors.component'; export const routes: Routes = [ @@ -211,6 +215,27 @@ export const routes: Routes = [ }, ], }, + { + path: 'tooltip', + children: [ + { + path: 'tooltip-showcase.component', + component: NbTooltipShowcaseComponent, + }, + { + path: 'tooltip-with-icon.component', + component: NbTooltipWithIconComponent, + }, + { + path: 'tooltip-placements.component', + component: NbTooltipPlacementsComponent, + }, + { + path: 'tooltip-colors.component', + component: NbTooltipColorsComponent, + }, + ], + }, { path: 'button', children: [ diff --git a/src/playground/playground.module.ts b/src/playground/playground.module.ts index e5bd7d60f5..1c3c615f85 100644 --- a/src/playground/playground.module.ts +++ b/src/playground/playground.module.ts @@ -9,35 +9,35 @@ import { CommonModule } from '@angular/common'; import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { - NbAccordionModule, - NbActionsModule, - NbAlertModule, - NbBadgeModule, - NbButtonModule, + NbThemeModule, NbCalendarKitModule, NbCalendarModule, NbCalendarRangeModule, NbCardModule, - NbChatModule, NbCheckboxModule, - NbContextMenuModule, - NbInputModule, NbLayoutModule, - NbListModule, NbMenuModule, NbPopoverModule, - NbProgressBarModule, - NbRouteTabsetModule, - NbSearchModule, NbSidebarModule, - NbSpinnerModule, - NbStepperModule, + NbActionsModule, + NbSearchModule, NbTabsetModule, - NbThemeModule, - NbToastrModule, NbUserModule, + NbBadgeModule, + NbContextMenuModule, + NbRouteTabsetModule, + NbProgressBarModule, + NbAlertModule, + NbChatModule, + NbSpinnerModule, + NbStepperModule, + NbAccordionModule, + NbListModule, + NbButtonModule, + NbInputModule, NbOverlayModule, - NbDialogModule, + NbToastrModule, + NbTooltipModule, NbDialogModule, } from '@nebular/theme'; import { NbPlaygroundRoutingModule } from './playground-routing.module'; @@ -220,6 +220,10 @@ import { NbDialogScrollComponent, NbScrollDialogComponent } from './dialog/dialo import { NbAutoFocusDialogComponent, NbDialogAutoFocusComponent } from './dialog/dialog-auto-focus.component'; import { NbDialogNamePromptComponent, NbDialogResultComponent } from './dialog/dialog-result.component'; import { NbDialogTemplateComponent } from './dialog/dialog-template.component'; +import { NbTooltipShowcaseComponent } from './tooltip/tooltip-showcase.component'; +import { NbTooltipWithIconComponent } from './tooltip/tooltip-with-icon.component'; +import { NbTooltipPlacementsComponent } from './tooltip/tooltip-placements.component'; +import { NbTooltipColorsComponent } from './tooltip/tooltip-colors.component'; export const NB_MODULES = [ NbCardModule, @@ -256,6 +260,7 @@ export const NB_MODULES = [ NbOverlayModule.forRoot(), NbToastrModule.forRoot(), NbDialogModule.forRoot(), + NbTooltipModule, ]; export const NB_EXAMPLE_COMPONENTS = [ @@ -431,6 +436,10 @@ export const NB_EXAMPLE_COMPONENTS = [ NbDialogResultComponent, NbDialogNamePromptComponent, NbDialogTemplateComponent, + NbTooltipShowcaseComponent, + NbTooltipWithIconComponent, + NbTooltipPlacementsComponent, + NbTooltipColorsComponent, ]; @NgModule({ diff --git a/src/playground/tooltip/tooltip-colors.component.html b/src/playground/tooltip/tooltip-colors.component.html new file mode 100644 index 0000000000..df9ae9f34e --- /dev/null +++ b/src/playground/tooltip/tooltip-colors.component.html @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/playground/tooltip/tooltip-colors.component.ts b/src/playground/tooltip/tooltip-colors.component.ts new file mode 100644 index 0000000000..c91f0d0a4b --- /dev/null +++ b/src/playground/tooltip/tooltip-colors.component.ts @@ -0,0 +1,25 @@ +/** + * @license + * Copyright Akveo. All Rights Reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + */ + +import { Component } from '@angular/core'; + + +@Component({ + selector: 'nb-tooltip-colors', + templateUrl: './tooltip-colors.component.html', + styles: [` + ::ng-deep nb-layout-column { + justify-content: center; + align-items: center; + display: flex; + } + button { + margin: 0.5rem; + } + `], +}) +export class NbTooltipColorsComponent { +} diff --git a/src/playground/tooltip/tooltip-placements.component.html b/src/playground/tooltip/tooltip-placements.component.html new file mode 100644 index 0000000000..ce75c74f24 --- /dev/null +++ b/src/playground/tooltip/tooltip-placements.component.html @@ -0,0 +1,4 @@ + + + + diff --git a/src/playground/tooltip/tooltip-placements.component.ts b/src/playground/tooltip/tooltip-placements.component.ts new file mode 100644 index 0000000000..b629fcd67c --- /dev/null +++ b/src/playground/tooltip/tooltip-placements.component.ts @@ -0,0 +1,26 @@ +/** + * @license + * Copyright Akveo. All Rights Reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + */ + +import { Component } from '@angular/core'; + + +@Component({ + selector: 'nb-tooltip-placements', + templateUrl: './tooltip-placements.component.html', + styles: [` + :host { + justify-content: center; + align-items: center; + display: flex; + height: 10rem; + } + button { + margin: 0.5rem; + } + `], +}) +export class NbTooltipPlacementsComponent { +} diff --git a/src/playground/tooltip/tooltip-showcase.component.html b/src/playground/tooltip/tooltip-showcase.component.html new file mode 100644 index 0000000000..c99cfb9414 --- /dev/null +++ b/src/playground/tooltip/tooltip-showcase.component.html @@ -0,0 +1 @@ + diff --git a/src/playground/tooltip/tooltip-showcase.component.ts b/src/playground/tooltip/tooltip-showcase.component.ts new file mode 100644 index 0000000000..0af6651ce7 --- /dev/null +++ b/src/playground/tooltip/tooltip-showcase.component.ts @@ -0,0 +1,22 @@ +/** + * @license + * Copyright Akveo. All Rights Reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + */ + +import { Component } from '@angular/core'; + + +@Component({ + selector: 'nb-tooltip-showcase', + templateUrl: './tooltip-showcase.component.html', + styles: [` + ::ng-deep nb-layout-column { + justify-content: center; + align-items: center; + display: flex; + } + `], +}) +export class NbTooltipShowcaseComponent { +} diff --git a/src/playground/tooltip/tooltip-with-icon.component.html b/src/playground/tooltip/tooltip-with-icon.component.html new file mode 100644 index 0000000000..2ecdc5964e --- /dev/null +++ b/src/playground/tooltip/tooltip-with-icon.component.html @@ -0,0 +1,2 @@ + + diff --git a/src/playground/tooltip/tooltip-with-icon.component.ts b/src/playground/tooltip/tooltip-with-icon.component.ts new file mode 100644 index 0000000000..612088d32d --- /dev/null +++ b/src/playground/tooltip/tooltip-with-icon.component.ts @@ -0,0 +1,25 @@ +/** + * @license + * Copyright Akveo. All Rights Reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + */ + +import { Component } from '@angular/core'; + + +@Component({ + selector: 'nb-tooltip-with-icon', + templateUrl: './tooltip-with-icon.component.html', + styles: [` + ::ng-deep nb-layout-column { + justify-content: center; + align-items: center; + display: flex; + } + button { + margin: 0.5rem; + } + `], +}) +export class NbTooltipWithIconComponent { +}