Skip to content
Snippets Groups Projects
Commit 76e285eb authored by OZGCloud's avatar OZGCloud
Browse files

Add more a11y features

* Add skeleton spec for directive
* Make directive standalone
parent ef41c1bf
Branches
Tags
No related merge requests found
......@@ -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">
......
......@@ -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',
......
......@@ -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';
......@@ -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;
}
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();
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();
});
});
});
});
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';
}
}
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 {}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment