Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tooltip auto position #802

Merged
merged 21 commits into from
Sep 18, 2023
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
3e445b5
feat: get coordinates component method
jAllanOli Aug 17, 2023
1ea693d
feat: service created and function on component
jAllanOli Aug 18, 2023
d9d7e3f
refactor: removing multiple if statements
jAllanOli Aug 18, 2023
11449d5
refactor: split large method into multiple methods
jAllanOli Aug 18, 2023
5534040
refactor: tests and props names adjustments and unecessary value removed
jAllanOli Aug 21, 2023
7d82032
test: tooltip reposition test cases
jAllanOli Aug 21, 2023
03b0e53
Merge branch 'main' into tooltip-auto-position
allan-chagas-brisa Sep 4, 2023
1173b7b
Merge branch 'main' into tooltip-auto-position
iurynogueira Sep 11, 2023
328265b
refactor: code changes suggested and service test
allan-chagas-brisa Sep 11, 2023
89ce19c
Merge branch 'main' into tooltip-auto-position
allan-chagas-brisa Sep 11, 2023
ea73aff
Merge branch 'main' into tooltip-auto-position
iurynogueira Sep 13, 2023
078e1dc
refactor: removed unnecessary property
allan-chagas-brisa Sep 13, 2023
20de684
Merge branch 'main' into tooltip-auto-position
iurynogueira Sep 13, 2023
fa324f4
docs: storybook changes reverted
allan-chagas-brisa Sep 14, 2023
9bb34bf
refactor: review suggestions added
allan-chagas-brisa Sep 14, 2023
498acda
Merge branch 'main' into tooltip-auto-position
allan-chagas-brisa Sep 14, 2023
379dfcb
fix: type error on service test
allan-chagas-brisa Sep 14, 2023
8857991
Merge branch 'main' into tooltip-auto-position
danilo-moreira-brisa Sep 18, 2023
618cc10
fix: properties encapsulation
allan-chagas-brisa Sep 18, 2023
fcfeadd
fix: allow position input
allan-chagas-brisa Sep 18, 2023
7d62b19
Merge branch 'main' into tooltip-auto-position
iurynogueira Sep 18, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions projects/ion/src/lib/tooltip/tooltip.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
[class.ion-tooltip--visible]="ionTooltipVisible"
[style.top]="top + 'px'"
[style.left]="left + 'px'"
#tooltip
>
{{ ionTooltipTitle }}
<ng-container [ngTemplateOutlet]="ionTooltipTemplateRef"></ng-container>
Expand Down
13 changes: 1 addition & 12 deletions projects/ion/src/lib/tooltip/tooltip.component.spec.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
import { CommonModule } from '@angular/common';
import { render, screen } from '@testing-library/angular';
import { TooltipPosition, TooltipProps } from '../core/types';
import { TooltipProps } from '../core/types';
import { IonTooltipComponent } from './tooltip.component';

const tooltipTestId = 'ion-tooltip';

const positions = Object.values(TooltipPosition) as TooltipPosition[];

const defaultProps: TooltipProps = {
ionTooltipTitle: 'Title',
};
Expand Down Expand Up @@ -38,15 +36,6 @@ describe('IonTooltipComponent', () => {
await sut({ ionTooltipColorScheme: 'light' });
expect(screen.getByTestId(tooltipTestId)).toHaveClass('ion-tooltip-light');
});
it.each(positions)(
'should render tooltip on position %s',
async (position) => {
await sut({ ionTooltipPosition: position });
expect(screen.getByTestId(tooltipTestId)).toHaveClass(
`ion-tooltip-position--${position}`
);
}
);
it('should not have visible class when visibility is false', async () => {
await sut();
expect(screen.getByTestId(tooltipTestId)).not.toHaveClass(
Expand Down
39 changes: 37 additions & 2 deletions projects/ion/src/lib/tooltip/tooltip.component.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,52 @@
import { Component, TemplateRef } from '@angular/core';
import {
AfterViewChecked,
ChangeDetectorRef,
Component,
ElementRef,
TemplateRef,
ViewChild,
} from '@angular/core';
import { TooltipColorScheme, TooltipPosition } from '../core/types';
import { TooltipService } from './tooltip.service';

const PADDING = 16;
export const TOOLTIP_MAX_WIDTH = 208 + PADDING;
allan-chagas-brisa marked this conversation as resolved.
Show resolved Hide resolved

@Component({
selector: 'ion-tooltip',
templateUrl: './tooltip.component.html',
styleUrls: ['./tooltip.component.scss'],
})
export class IonTooltipComponent {
export class IonTooltipComponent implements AfterViewChecked {
@ViewChild('tooltip', { static: true }) tooltip: ElementRef;

ionTooltipTitle: string;
ionTooltipTemplateRef: TemplateRef<void>;
ionTooltipColorScheme: TooltipColorScheme = 'dark';
ionTooltipPosition: TooltipPosition = TooltipPosition.DEFAULT;
ionTooltipVisible = false;
left = 0;
top = 0;

constructor(
private cdr: ChangeDetectorRef,
private tooltipService: TooltipService
allan-chagas-brisa marked this conversation as resolved.
Show resolved Hide resolved
) {}

ngAfterViewChecked(): void {
this.repositionTooltip();
this.cdr.detectChanges();
}

private repositionTooltip(): void {
this.sendCoordinates();
this.tooltipService.screenSize = document.body;
allan-chagas-brisa marked this conversation as resolved.
Show resolved Hide resolved
this.ionTooltipPosition = this.tooltipService.getNewPosition();
this.tooltipService.reposition.next();
allan-chagas-brisa marked this conversation as resolved.
Show resolved Hide resolved
}

private sendCoordinates(): void {
this.tooltipService.tootipCoordinates =
this.tooltip.nativeElement.getBoundingClientRect();
}
}
19 changes: 8 additions & 11 deletions projects/ion/src/lib/tooltip/tooltip.directive.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,17 +100,6 @@ describe('Directive: Tooltip', () => {
}
);

it.each(Object.values(TooltipPosition))(
'should render tooltip on %s position',
async (ionTooltipPosition) => {
await sut({ ionTooltipPosition });
fireEvent.mouseEnter(screen.getByTestId('hostTooltip'));
expect(screen.getByTestId('ion-tooltip')).toHaveClass(
`ion-tooltip-position--${ionTooltipPosition}`
);
}
);

it('should show tooltip after delay time setted', async () => {
jest.useFakeTimers();
const timeDelay = 300;
Expand All @@ -133,6 +122,14 @@ describe('Directive: Tooltip', () => {
);
});

it('should reposition the tooltip when exceed the screen size', async () => {
await sut();
fireEvent.mouseEnter(screen.getByTestId('hostTooltip'));
expect(screen.getByTestId('ion-tooltip')).toHaveClass(
`ion-tooltip-position--bottomRight`
);
});

describe('trigger: click', () => {
afterEach(async () => {
fireEvent.click(screen.getByTestId('hostTooltip'));
Expand Down
35 changes: 25 additions & 10 deletions projects/ion/src/lib/tooltip/tooltip.directive.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { TooltipPosition } from './../core/types/tooltip';
import {
ApplicationRef,
ComponentFactoryResolver,
Expand All @@ -9,21 +10,20 @@ import {
Injector,
Input,
OnDestroy,
OnInit,
TemplateRef,
} from '@angular/core';
import {
TooltipColorScheme,
TooltipPosition,
TooltipTrigger,
} from '../core/types';
import { TooltipColorScheme, TooltipTrigger } from '../core/types';
import { SafeAny } from '../utils/safe-any';
import { IonTooltipComponent } from './tooltip.component';
import { getPositions } from './utilsTooltip';
import { TooltipService } from './tooltip.service';
import { Subscription } from 'rxjs';

@Directive({
selector: '[ionTooltip]',
})
export class IonTooltipDirective implements OnDestroy {
export class IonTooltipDirective implements OnDestroy, OnInit {
@Input() ionTooltipTitle = '';
@Input() ionTooltipTemplateRef: TemplateRef<void>;
@Input() ionTooltipColorScheme: TooltipColorScheme = 'dark';
Expand All @@ -32,16 +32,26 @@ export class IonTooltipDirective implements OnDestroy {
@Input() ionTooltipTrigger: TooltipTrigger = TooltipTrigger.DEFAULT;
@Input() ionTooltipShowDelay = 0;

public subscription$: Subscription;
private componentRef: ComponentRef<IonTooltipComponent> = null;
private delayTimeout: number;

constructor(
private componentFactoryResolver: ComponentFactoryResolver,
private appRef: ApplicationRef,
private injector: Injector,
private elementRef: ElementRef
private elementRef: ElementRef,
private tooltipService: TooltipService
) {}

ngOnInit(): void {
this.subscription$ = this.tooltipService.reposition.subscribe(() => {
if (!this.isComponentRefNull()) {
this.setComponentPosition();
}
});
}

isComponentRefNull(): boolean {
return this.componentRef === null;
}
Expand Down Expand Up @@ -77,7 +87,6 @@ export class IonTooltipDirective implements OnDestroy {
this.showTooltip.bind(this),
this.ionTooltipShowDelay
);

this.setComponentPosition();
}
}
Expand All @@ -87,13 +96,18 @@ export class IonTooltipDirective implements OnDestroy {
this.elementRef.nativeElement.getBoundingClientRect();

const hostPositions = { left, right, top, bottom };

this.tooltipService.hostPosition = hostPositions;
allan-chagas-brisa marked this conversation as resolved.
Show resolved Hide resolved

const positions = getPositions(
hostPositions,
this.ionTooltipArrowPointAtCenter
);

this.componentRef.instance.left = positions[this.ionTooltipPosition].left;
this.componentRef.instance.top = positions[this.ionTooltipPosition].top;
this.componentRef.instance.left =
positions[this.componentRef.instance.ionTooltipPosition].left;
this.componentRef.instance.top =
positions[this.componentRef.instance.ionTooltipPosition].top;
}

attachComponentToView(): void {
Expand Down Expand Up @@ -145,5 +159,6 @@ export class IonTooltipDirective implements OnDestroy {

ngOnDestroy(): void {
this.destroyComponent();
this.subscription$.unsubscribe();
}
}
2 changes: 2 additions & 0 deletions projects/ion/src/lib/tooltip/tooltip.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@ import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { IonTooltipComponent } from './tooltip.component';
import { IonTooltipDirective } from './tooltip.directive';
import { TooltipService } from './tooltip.service';

@NgModule({
declarations: [IonTooltipComponent, IonTooltipDirective],
imports: [CommonModule],
exports: [IonTooltipDirective],
entryComponents: [IonTooltipComponent],
providers: [TooltipService],
})
export class IonTooltipModule {}
76 changes: 76 additions & 0 deletions projects/ion/src/lib/tooltip/tooltip.service.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import { TestBed, getTestBed } from '@angular/core/testing';
import { TooltipService } from './tooltip.service';

const screenWidth = 1440;
const screenHeight = 900;
const tooltipPositions = {
centerRight: {
tooltipLeft: 1200,
tooltipHeight: 88,
hostPosition: { top: 450, bottom: 538, left: 1200, right: 1380 },
screenWidth,
screenHeight,
},
bottomCenter: {
tooltipLeft: 700,
tooltipHeight: 88,
hostPosition: { top: 450, bottom: 820, left: 700, right: 880 },
screenWidth,
screenHeight,
},
centerLeft: {
tooltipLeft: -20,
tooltipHeight: 88,
hostPosition: { top: 750, bottom: 650, left: 0, right: 80 },
screenWidth,
screenHeight,
},
topRight: {
tooltipLeft: 1380,
tooltipHeight: 88,
hostPosition: { top: 0, bottom: 100, left: 1440, right: 1540 },
screenWidth,
screenHeight,
},
bottomLeft: {
tooltipLeft: -20,
tooltipHeight: 88,
hostPosition: { top: 880, bottom: 900, left: 0, right: 80 },
screenWidth,
screenHeight,
},
bottomRight: {
tooltipLeft: 1300,
tooltipHeight: 88,
hostPosition: { top: 880, bottom: 900, left: 1380, right: 1440 },
screenWidth,
screenHeight,
},
};

let injector: TestBed;
let service: TooltipService;

const sut = async (): Promise<void> => {
TestBed.configureTestingModule({
providers: [TooltipService],
});

injector = getTestBed();
service = injector.get(TooltipService);
};

describe('TooltipService', () => {
beforeEach(async () => {
await sut();
});

it.each(Object.entries(tooltipPositions))(
'should return %s as the new position if at the edges',
async (positionKey, positionValue) => {
const positionChecks = service.getTooltipPositions(positionValue);
const newPosition = service.checkPositions(positionChecks);
expect(newPosition).toBe(positionKey);
}
);
});
Loading