Skip to content

Commit

Permalink
feat(theme): add new Tooltip component (#703)
Browse files Browse the repository at this point in the history
Closes #663
  • Loading branch information
tibing-old-email authored and nnixaa committed Sep 11, 2018
1 parent 29e4fef commit 0e05034
Show file tree
Hide file tree
Showing 21 changed files with 589 additions and 17 deletions.
26 changes: 26 additions & 0 deletions docs/assets/images/components/tooltip.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
9 changes: 9 additions & 0 deletions docs/structure.ts
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,15 @@ export const structure = [
'NbToastrConfig',
],
},
{
type: 'tabs',
name: 'Tooltip',
icon: 'tooltip.svg',
source: [
'NbTooltipDirective',
'NbTooltipComponent',
],
},
{
type: 'group',
name: 'Extra',
Expand Down
Original file line number Diff line number Diff line change
@@ -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');
}
}
71 changes: 71 additions & 0 deletions src/framework/theme/components/tooltip/tooltip.component.scss
Original file line number Diff line number Diff line change
@@ -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);
}
}
76 changes: 76 additions & 0 deletions src/framework/theme/components/tooltip/tooltip.component.ts
Original file line number Diff line number Diff line change
@@ -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: `
<span class="arrow"></span>
<div class="content">
<i *ngIf="context?.icon" class="icon {{ context?.icon }}"></i>
<span *ngIf="content">{{ content }}</span>
</div>
`,
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 } = {};
}
161 changes: 161 additions & 0 deletions src/framework/theme/components/tooltip/tooltip.directive.ts
Original file line number Diff line number Diff line change
@@ -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<any>;
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());
}
}
Loading

0 comments on commit 0e05034

Please sign in to comment.