Skip to content

Commit 000ac15

Browse files
author
louiiuol
committed
[Directive] Add Tooltip directive
1 parent 1fbdf71 commit 000ac15

File tree

8 files changed

+246
-54
lines changed

8 files changed

+246
-54
lines changed
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import type { NgDocCategory } from '@ng-doc/core';
2+
3+
const DirectivesCategory: NgDocCategory = {
4+
title: 'Directives',
5+
};
6+
7+
export default DirectivesCategory;

src/app/directives/tooltip.directive.ts

Lines changed: 0 additions & 54 deletions
This file was deleted.
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { ChangeDetectionStrategy, Component } from '@angular/core';
2+
import { TooltipDirective } from '../../tooltip.directive';
3+
4+
/**
5+
* @internal
6+
*/
7+
@Component({
8+
selector: 'lib-tooltip-demo',
9+
standalone: true,
10+
template: `
11+
<div class="ng-demo">
12+
<section>
13+
<h3
14+
libTooltip="Hello, friend. <br/> Nice to meet you"
15+
class="w-full text-2xl mb-3 bg-slate-400 text-center text-slate-800 py-3 px-5 rounded-lg"
16+
>
17+
Hover me and you'll see
18+
</h3>
19+
</section>
20+
</div>
21+
`,
22+
imports: [TooltipDirective],
23+
changeDetection: ChangeDetectionStrategy.OnPush,
24+
})
25+
export class TooltipDemoComponent {}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import type { NgDocPage } from '@ng-doc/core';
2+
import DirectivesCategory from '../../ng-doc.category';
3+
import { TooltipDemoComponent } from './demos/tooltip-demo.component';
4+
5+
const Tooltip: NgDocPage = {
6+
title: `Tooltip`,
7+
route: 'tooltip',
8+
mdFile: ['./tabs/index.md', './tabs/sources.md', './tabs/requirements.md'],
9+
demos: { TooltipDemoComponent },
10+
category: DirectivesCategory,
11+
};
12+
13+
export default Tooltip;
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
---
2+
title: Overview
3+
keyword: TooltipOverview
4+
---
5+
6+
> **note**
7+
> {{ JSDoc.description("src/app/directives/tooltip/tooltip.directive.ts#TooltipDirective") }}
8+
9+
To learn more about the technical aspect of this directive, check the [API page](https://louiiuol.github.io/ngx-lib/api/classes/api/TooltipDirective).
10+
11+
## Demo 👀
12+
{{ NgDocActions.demo("TooltipDemoComponent") }}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
---
2+
title: Requirements
3+
route: requirements
4+
keyword: TooltipRequirements
5+
---
6+
7+
## Requirements
8+
9+
> In order to use this directive in your application, you must follow these steps:
10+
11+
### Tailwind CSS
12+
13+
> **note**
14+
> All of the directives in this library use tailwind to render the UI. So make sure you have it configured on you project or adapt the code accordingly.
15+
16+
### Thats it
17+
18+
> **success**
19+
> This is a standalone directive that need no extras dependencies. You can import the source code and make it yours right away 🎉
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
---
2+
title: Sources
3+
route: sources
4+
keyword: TooltipSources
5+
---
6+
7+
## Sources
8+
9+
```typescript group="comp" file="../../tooltip.directive.ts" name="tooltip.directive.ts"
10+
```
Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
import type { OnDestroy } from '@angular/core';
2+
import {
3+
Directive,
4+
effect,
5+
ElementRef,
6+
HostListener,
7+
inject,
8+
input,
9+
SecurityContext,
10+
} from '@angular/core';
11+
import { DomSanitizer } from '@angular/platform-browser';
12+
13+
/**
14+
* Directive to create a tooltip on hover.
15+
*
16+
* @example
17+
* ````html
18+
* <h3
19+
* libTooltip="Hello, friend. <br/> Nice to meet you"
20+
* [tooltipClass]="'bg-blue-500 text-white'"
21+
* >
22+
* Hover me and you'll see
23+
* </h3>
24+
* ````
25+
*
26+
* @author louiiuol
27+
* @version 0.0.1
28+
*/
29+
@Directive({
30+
selector: '[libTooltip]',
31+
standalone: true,
32+
})
33+
export class TooltipDirective implements OnDestroy {
34+
/**
35+
* Required input for the tooltip content.
36+
*/
37+
libTooltip = input.required<string>();
38+
39+
/**
40+
* Optional input for the Tailwind CSS classes of the tooltip.
41+
*/
42+
tooltipClass = input<string>();
43+
44+
private tooltipElement!: HTMLElement;
45+
private readonly elementRef = inject(ElementRef);
46+
private readonly sanitizer = inject(DomSanitizer);
47+
48+
constructor() {
49+
// Create the tooltip element
50+
this.createTooltipElement();
51+
52+
// Set up effects to react to changes in content and class inputs
53+
this.setupContentEffect();
54+
this.setupClassEffect();
55+
}
56+
57+
private createTooltipElement() {
58+
this.tooltipElement = document.createElement('div');
59+
// Add default Tailwind CSS classes for styling
60+
this.tooltipElement.classList.add(
61+
'absolute',
62+
'bg-gray-800',
63+
'text-white',
64+
'text-sm',
65+
'py-1',
66+
'px-2',
67+
'rounded',
68+
'opacity-0',
69+
'transition-opacity',
70+
'duration-200',
71+
'pointer-events-none',
72+
'z-50',
73+
);
74+
}
75+
76+
private setupContentEffect() {
77+
effect(() => {
78+
const content = this.libTooltip();
79+
80+
// Sanitize the HTML content
81+
const sanitizedContent =
82+
this.sanitizer.sanitize(SecurityContext.HTML, content) ?? '';
83+
84+
// Set the sanitized HTML content
85+
this.tooltipElement.innerHTML = sanitizedContent;
86+
});
87+
}
88+
89+
private setupClassEffect() {
90+
effect(() => {
91+
const customClasses = this.tooltipClass();
92+
// Remove all classes except the default ones
93+
const defaultClasses = [
94+
'absolute',
95+
'opacity-0',
96+
'transition-opacity',
97+
'duration-200',
98+
'pointer-events-none',
99+
'z-50',
100+
'bg-gray-800',
101+
'text-white',
102+
'text-sm',
103+
'py-1',
104+
'px-2',
105+
'rounded',
106+
];
107+
this.tooltipElement.className = '';
108+
this.tooltipElement.classList.add(...defaultClasses);
109+
110+
// Add custom Tailwind CSS classes if provided
111+
if (customClasses) {
112+
this.tooltipElement.classList.add(...customClasses.split(' '));
113+
}
114+
});
115+
}
116+
117+
@HostListener('mouseenter')
118+
onMouseEnter() {
119+
document.body.appendChild(this.tooltipElement);
120+
this.updateTooltipPosition();
121+
this.tooltipElement.classList.remove('opacity-0');
122+
this.tooltipElement.classList.add('opacity-100');
123+
}
124+
125+
@HostListener('mouseleave')
126+
onMouseLeave() {
127+
this.tooltipElement.classList.remove('opacity-100');
128+
this.tooltipElement.classList.add('opacity-0');
129+
setTimeout(() => {
130+
if (this.tooltipElement.parentNode) {
131+
this.tooltipElement.parentNode.removeChild(this.tooltipElement);
132+
}
133+
}, 200); // Match the transition duration
134+
}
135+
136+
@HostListener('mousemove')
137+
onMouseMove() {
138+
this.updateTooltipPosition();
139+
}
140+
141+
private updateTooltipPosition() {
142+
const hostPos = this.elementRef.nativeElement.getBoundingClientRect();
143+
const tooltipPos = this.tooltipElement.getBoundingClientRect();
144+
145+
// Calculate the position of the tooltip
146+
const top = hostPos.top - tooltipPos.height - 8 + window.scrollY;
147+
const left =
148+
hostPos.left + (hostPos.width - tooltipPos.width) / 2 + window.scrollX;
149+
150+
// Set the position
151+
this.tooltipElement.style.top = `${top}px`;
152+
this.tooltipElement.style.left = `${left}px`;
153+
}
154+
155+
ngOnDestroy() {
156+
if (this.tooltipElement.parentNode) {
157+
this.tooltipElement.parentNode.removeChild(this.tooltipElement);
158+
}
159+
}
160+
}

0 commit comments

Comments
 (0)