diff --git a/alfa-client/apps/demo/src/app/app.component.html b/alfa-client/apps/demo/src/app/app.component.html index 50e901d4d212f36551485f27b8d6e73614f2c684..f11b4884f22682b0ae9288880fa5d3b353e55e16 100644 --- a/alfa-client/apps/demo/src/app/app.component.html +++ b/alfa-client/apps/demo/src/app/app.component.html @@ -19,6 +19,7 @@ <a href="#" class="flex flex-col items-start justify-between gap-2 rounded-t-md border-primary-600/50 px-6 py-4 hover:bg-background-150 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-focus lg:flex-row lg:gap-6" + [tooltip]="'This is tooltip attached to link element'" > <div class="flex-1 basis-5/6"> <div class="flex flex-wrap items-center gap-x-3"> diff --git a/alfa-client/apps/demo/src/app/app.component.ts b/alfa-client/apps/demo/src/app/app.component.ts index 7230b59063e72f5b872c31e0ba999fac7a85d1b7..eef4a756761f2ad91604ffd019aa645a2c97b9f6 100644 --- a/alfa-client/apps/demo/src/app/app.component.ts +++ b/alfa-client/apps/demo/src/app/app.component.ts @@ -24,7 +24,7 @@ import { StampIconComponent, TextInputComponent, TextareaComponent, - TooltipModule, + TooltipDirective, } from '@ods/system'; import { EMPTY_STRING } from '@alfa-client/tech-shared'; @@ -66,7 +66,7 @@ import { CustomStepperComponent } from './components/cdk-demo/custom-stepper.com TextInputComponent, TextareaComponent, ErrorMessageComponent, - TooltipModule, + TooltipDirective, ], selector: 'app-root', templateUrl: './app.component.html', diff --git a/alfa-client/libs/design-system/src/index.ts b/alfa-client/libs/design-system/src/index.ts index 20ddf3a5dcaf8048ff9299d6e590700cd91a70e4..055062c48ce72b9d037dea89020b24bd0b706fae 100644 --- a/alfa-client/libs/design-system/src/index.ts +++ b/alfa-client/libs/design-system/src/index.ts @@ -47,4 +47,4 @@ export * from './lib/list/list.component'; export * from './lib/navbar/nav-item/nav-item.component'; export * from './lib/navbar/navbar/navbar.component'; export * from './lib/testbtn/testbtn.component'; -export * from './lib/tooltip/tooltip.module'; +export * from './lib/tooltip/tooltip.directive'; diff --git a/alfa-client/libs/design-system/src/lib/tooltip/tooltip.component.ts b/alfa-client/libs/design-system/src/lib/tooltip/tooltip.component.ts index 12861659aa49895ff3bf9389f4bd8bff0d424a85..dd996ceb554faca8a93db44f758dbe057163e438 100644 --- a/alfa-client/libs/design-system/src/lib/tooltip/tooltip.component.ts +++ b/alfa-client/libs/design-system/src/lib/tooltip/tooltip.component.ts @@ -2,17 +2,19 @@ import { Component } from '@angular/core'; @Component({ selector: 'ods-tooltip', - template: `<div + template: `<p class="fixed z-50 mt-2 -translate-x-1/2 animate-fadeIn rounded bg-ozggray-900 px-3 py-2 text-sm text-whitetext before:absolute before:-top-2 before:left-[calc(50%-0.5rem)] before:size-0 before:border-b-8 before:border-l-8 before:border-r-8 before:border-b-ozggray-900 before:border-l-transparent before:border-r-transparent before:content-[''] dark:bg-white dark:before:border-b-white" [style.left]="left + 'px'" [style.top]="top + 'px'" + [attr.id]="id" > - {{ tooltip }} - </div>`, + {{ text }} + </p>`, styles: [':host {@apply contents}'], }) export class TooltipComponent { - tooltip: string = ''; + text: string = ''; left: number = 0; top: number = 0; + id: string; } diff --git a/alfa-client/libs/design-system/src/lib/tooltip/tooltip.directive.spec.ts b/alfa-client/libs/design-system/src/lib/tooltip/tooltip.directive.spec.ts index 3bfca07d5646ca14be535d30e4e3d47419201300..7047e82146d33a4c82584f4c85f531fcf2a85fbd 100644 --- a/alfa-client/libs/design-system/src/lib/tooltip/tooltip.directive.spec.ts +++ b/alfa-client/libs/design-system/src/lib/tooltip/tooltip.directive.spec.ts @@ -1,8 +1,34 @@ +import { ElementRef, ViewContainerRef } from '@angular/core'; +import { TestBed } from '@angular/core/testing'; import { TooltipDirective } from './tooltip.directive'; +export class MockElementRef extends ElementRef {} + describe('TooltipDirective', () => { + beforeEach((): void => { + TestBed.configureTestingModule({ + providers: [ViewContainerRef, { provide: ElementRef, useClass: MockElementRef }], + }); + }); + it('should create an instance', () => { - const directive = new TooltipDirective(); - expect(directive).toBeTruthy(); + TestBed.runInInjectionContext(() => { + const directive: TooltipDirective = new TooltipDirective(); + + expect(directive).toBeTruthy(); + }); + }); + + describe('ngOnDestroy', () => { + it('should destroy tooltip', () => { + TestBed.runInInjectionContext(() => { + const directive = new TooltipDirective(); + directive.destroy = jest.fn(); + + directive.ngOnDestroy(); + + expect(directive.destroy).toHaveBeenCalled(); + }); + }); }); }); diff --git a/alfa-client/libs/design-system/src/lib/tooltip/tooltip.directive.ts b/alfa-client/libs/design-system/src/lib/tooltip/tooltip.directive.ts index 39b15c3692b83e10fb8b0a511ae462d118a62d3b..69a4cfe48bc99cf92061ab0eb9d8f361da980029 100644 --- a/alfa-client/libs/design-system/src/lib/tooltip/tooltip.directive.ts +++ b/alfa-client/libs/design-system/src/lib/tooltip/tooltip.directive.ts @@ -1,54 +1,81 @@ -import { ComponentRef, Directive, ElementRef, HostListener, Injector, Input, OnDestroy, ViewContainerRef } from '@angular/core'; +import { + ComponentRef, + Directive, + ElementRef, + HostBinding, + HostListener, + inject, + Input, + OnDestroy, + ViewContainerRef, +} from '@angular/core'; +import { uniqueId } from 'lodash-es'; import { TooltipComponent } from './tooltip.component'; @Directive({ selector: '[tooltip]', + standalone: true, }) export class TooltipDirective implements OnDestroy { @Input() tooltip: string = ''; private componentRef: ComponentRef<TooltipComponent> = null; - - constructor( - private elementRef: ElementRef<HTMLElement>, - private injector: Injector, - private viewContainerRef: ViewContainerRef, - ) {} + private tooltipId: string; + public readonly viewContainerRef: ViewContainerRef = inject(ViewContainerRef); + public readonly elementRef: ElementRef<HTMLElement> = inject(ElementRef); ngOnDestroy(): void { this.destroy(); } + @HostBinding('attr.aria-describedby') get describedBy() { + return this.tooltipId; + } + @HostListener('mouseenter') - onMouseEnter(): void { + @HostListener('focusin') + createTooltip(): void { if (this.componentRef === null) { - this.componentRef = this.viewContainerRef.createComponent(TooltipComponent, { - injector: this.injector, - }); + const attachedToFocused: boolean = this.elementRef.nativeElement.contains(document.activeElement); + this.tooltipId = uniqueId('tooltip'); + this.componentRef = this.viewContainerRef.createComponent(TooltipComponent); this.viewContainerRef.insert(this.componentRef.hostView); - this.setTooltipProperties(); + this.setTooltipProperties(attachedToFocused); } } @HostListener('mouseleave') @HostListener('window:scroll') + @HostListener('focusout') destroyTooltip(): void { this.destroy(); } - private setTooltipProperties() { + @HostListener('keydown', ['$event']) onKeydown(e: KeyboardEvent) { + if (this.isEscapeKey(e)) { + this.destroy(); + } + } + + setTooltipProperties(attachedToFocused = false): void { if (this.componentRef !== null) { const { left, right, bottom } = this.elementRef.nativeElement.getBoundingClientRect(); this.componentRef.instance.left = (right + left) / 2; - this.componentRef.instance.top = bottom; - this.componentRef.instance.tooltip = this.tooltip; + this.componentRef.instance.top = attachedToFocused ? bottom + 4 : bottom; + this.componentRef.instance.text = this.tooltip; + this.componentRef.instance.id = this.tooltipId; } } - private destroy(): void { + destroy(): void { if (this.componentRef !== null) { this.componentRef.destroy(); this.componentRef = null; + this.tooltipId = null; } } + + isEscapeKey(e: KeyboardEvent): boolean { + return e.key === 'Escape'; + } } diff --git a/alfa-client/libs/design-system/src/lib/tooltip/tooltip.module.ts b/alfa-client/libs/design-system/src/lib/tooltip/tooltip.module.ts deleted file mode 100644 index 979b0f7398226a0afed45512d84520081dfd319c..0000000000000000000000000000000000000000 --- a/alfa-client/libs/design-system/src/lib/tooltip/tooltip.module.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { CommonModule } from '@angular/common'; -import { NgModule } from '@angular/core'; -import { TooltipComponent } from './tooltip.component'; -import { TooltipDirective } from './tooltip.directive'; - -@NgModule({ - declarations: [TooltipComponent, TooltipDirective], - imports: [CommonModule], - exports: [TooltipDirective], -}) -export class TooltipModule {}