Skip to content

Commit

Permalink
[Directive] Add Tooltip directive
Browse files Browse the repository at this point in the history
  • Loading branch information
louiiuol committed Oct 28, 2024
1 parent 1fbdf71 commit 000ac15
Show file tree
Hide file tree
Showing 8 changed files with 246 additions and 54 deletions.
7 changes: 7 additions & 0 deletions src/app/directives/ng-doc.category.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import type { NgDocCategory } from '@ng-doc/core';

const DirectivesCategory: NgDocCategory = {
title: 'Directives',
};

export default DirectivesCategory;
54 changes: 0 additions & 54 deletions src/app/directives/tooltip.directive.ts

This file was deleted.

25 changes: 25 additions & 0 deletions src/app/directives/tooltip/doc/demos/tooltip-demo.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { TooltipDirective } from '../../tooltip.directive';

/**
* @internal
*/
@Component({
selector: 'lib-tooltip-demo',
standalone: true,
template: `
<div class="ng-demo">
<section>
<h3
libTooltip="Hello, friend. <br/> Nice to meet you"
class="w-full text-2xl mb-3 bg-slate-400 text-center text-slate-800 py-3 px-5 rounded-lg"
>
Hover me and you'll see
</h3>
</section>
</div>
`,
imports: [TooltipDirective],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class TooltipDemoComponent {}
13 changes: 13 additions & 0 deletions src/app/directives/tooltip/doc/ng-doc.page.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import type { NgDocPage } from '@ng-doc/core';
import DirectivesCategory from '../../ng-doc.category';
import { TooltipDemoComponent } from './demos/tooltip-demo.component';

const Tooltip: NgDocPage = {
title: `Tooltip`,
route: 'tooltip',
mdFile: ['./tabs/index.md', './tabs/sources.md', './tabs/requirements.md'],
demos: { TooltipDemoComponent },
category: DirectivesCategory,
};

export default Tooltip;
12 changes: 12 additions & 0 deletions src/app/directives/tooltip/doc/tabs/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
title: Overview
keyword: TooltipOverview
---

> **note**
> {{ JSDoc.description("src/app/directives/tooltip/tooltip.directive.ts#TooltipDirective") }}
To learn more about the technical aspect of this directive, check the [API page](https://louiiuol.github.io/ngx-lib/api/classes/api/TooltipDirective).

## Demo 👀
{{ NgDocActions.demo("TooltipDemoComponent") }}
19 changes: 19 additions & 0 deletions src/app/directives/tooltip/doc/tabs/requirements.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
---
title: Requirements
route: requirements
keyword: TooltipRequirements
---

## Requirements

> In order to use this directive in your application, you must follow these steps:
### Tailwind CSS

> **note**
> 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.
### Thats it

> **success**
> This is a standalone directive that need no extras dependencies. You can import the source code and make it yours right away 🎉
10 changes: 10 additions & 0 deletions src/app/directives/tooltip/doc/tabs/sources.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
title: Sources
route: sources
keyword: TooltipSources
---

## Sources

```typescript group="comp" file="../../tooltip.directive.ts" name="tooltip.directive.ts"
```
160 changes: 160 additions & 0 deletions src/app/directives/tooltip/tooltip.directive.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
import type { OnDestroy } from '@angular/core';
import {
Directive,
effect,
ElementRef,
HostListener,
inject,
input,
SecurityContext,
} from '@angular/core';
import { DomSanitizer } from '@angular/platform-browser';

/**
* Directive to create a tooltip on hover.
*
* @example
* ````html
* <h3
* libTooltip="Hello, friend. <br/> Nice to meet you"
* [tooltipClass]="'bg-blue-500 text-white'"
* >
* Hover me and you'll see
* </h3>
* ````
*
* @author louiiuol
* @version 0.0.1
*/
@Directive({
selector: '[libTooltip]',
standalone: true,
})
export class TooltipDirective implements OnDestroy {
/**
* Required input for the tooltip content.
*/
libTooltip = input.required<string>();

/**
* Optional input for the Tailwind CSS classes of the tooltip.
*/
tooltipClass = input<string>();

private tooltipElement!: HTMLElement;
private readonly elementRef = inject(ElementRef);
private readonly sanitizer = inject(DomSanitizer);

constructor() {
// Create the tooltip element
this.createTooltipElement();

// Set up effects to react to changes in content and class inputs
this.setupContentEffect();
this.setupClassEffect();
}

private createTooltipElement() {
this.tooltipElement = document.createElement('div');
// Add default Tailwind CSS classes for styling
this.tooltipElement.classList.add(
'absolute',
'bg-gray-800',
'text-white',
'text-sm',
'py-1',
'px-2',
'rounded',
'opacity-0',
'transition-opacity',
'duration-200',
'pointer-events-none',
'z-50',
);
}

private setupContentEffect() {
effect(() => {
const content = this.libTooltip();

// Sanitize the HTML content
const sanitizedContent =
this.sanitizer.sanitize(SecurityContext.HTML, content) ?? '';

// Set the sanitized HTML content
this.tooltipElement.innerHTML = sanitizedContent;
});
}

private setupClassEffect() {
effect(() => {
const customClasses = this.tooltipClass();
// Remove all classes except the default ones
const defaultClasses = [
'absolute',
'opacity-0',
'transition-opacity',
'duration-200',
'pointer-events-none',
'z-50',
'bg-gray-800',
'text-white',
'text-sm',
'py-1',
'px-2',
'rounded',
];
this.tooltipElement.className = '';
this.tooltipElement.classList.add(...defaultClasses);

// Add custom Tailwind CSS classes if provided
if (customClasses) {
this.tooltipElement.classList.add(...customClasses.split(' '));
}
});
}

@HostListener('mouseenter')
onMouseEnter() {
document.body.appendChild(this.tooltipElement);
this.updateTooltipPosition();
this.tooltipElement.classList.remove('opacity-0');
this.tooltipElement.classList.add('opacity-100');
}

@HostListener('mouseleave')
onMouseLeave() {
this.tooltipElement.classList.remove('opacity-100');
this.tooltipElement.classList.add('opacity-0');
setTimeout(() => {
if (this.tooltipElement.parentNode) {
this.tooltipElement.parentNode.removeChild(this.tooltipElement);
}
}, 200); // Match the transition duration
}

@HostListener('mousemove')
onMouseMove() {
this.updateTooltipPosition();
}

private updateTooltipPosition() {
const hostPos = this.elementRef.nativeElement.getBoundingClientRect();
const tooltipPos = this.tooltipElement.getBoundingClientRect();

// Calculate the position of the tooltip
const top = hostPos.top - tooltipPos.height - 8 + window.scrollY;
const left =
hostPos.left + (hostPos.width - tooltipPos.width) / 2 + window.scrollX;

// Set the position
this.tooltipElement.style.top = `${top}px`;
this.tooltipElement.style.left = `${left}px`;
}

ngOnDestroy() {
if (this.tooltipElement.parentNode) {
this.tooltipElement.parentNode.removeChild(this.tooltipElement);
}
}
}

0 comments on commit 000ac15

Please sign in to comment.