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
No related branches found
No related tags found
No related merge requests found
...@@ -19,6 +19,7 @@ ...@@ -19,6 +19,7 @@
<a <a
href="#" 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" 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-1 basis-5/6">
<div class="flex flex-wrap items-center gap-x-3"> <div class="flex flex-wrap items-center gap-x-3">
......
...@@ -24,7 +24,7 @@ import { ...@@ -24,7 +24,7 @@ import {
StampIconComponent, StampIconComponent,
TextInputComponent, TextInputComponent,
TextareaComponent, TextareaComponent,
TooltipModule, TooltipDirective,
} from '@ods/system'; } from '@ods/system';
import { EMPTY_STRING } from '@alfa-client/tech-shared'; import { EMPTY_STRING } from '@alfa-client/tech-shared';
...@@ -66,7 +66,7 @@ import { CustomStepperComponent } from './components/cdk-demo/custom-stepper.com ...@@ -66,7 +66,7 @@ import { CustomStepperComponent } from './components/cdk-demo/custom-stepper.com
TextInputComponent, TextInputComponent,
TextareaComponent, TextareaComponent,
ErrorMessageComponent, ErrorMessageComponent,
TooltipModule, TooltipDirective,
], ],
selector: 'app-root', selector: 'app-root',
templateUrl: './app.component.html', templateUrl: './app.component.html',
......
...@@ -47,4 +47,4 @@ export * from './lib/list/list.component'; ...@@ -47,4 +47,4 @@ export * from './lib/list/list.component';
export * from './lib/navbar/nav-item/nav-item.component'; export * from './lib/navbar/nav-item/nav-item.component';
export * from './lib/navbar/navbar/navbar.component'; export * from './lib/navbar/navbar/navbar.component';
export * from './lib/testbtn/testbtn.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'; ...@@ -2,17 +2,19 @@ import { Component } from '@angular/core';
@Component({ @Component({
selector: 'ods-tooltip', 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" 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.left]="left + 'px'"
[style.top]="top + 'px'" [style.top]="top + 'px'"
[attr.id]="id"
> >
{{ tooltip }} {{ text }}
</div>`, </p>`,
styles: [':host {@apply contents}'], styles: [':host {@apply contents}'],
}) })
export class TooltipComponent { export class TooltipComponent {
tooltip: string = ''; text: string = '';
left: number = 0; left: number = 0;
top: number = 0; top: number = 0;
id: string;
} }
import { ElementRef, ViewContainerRef } from '@angular/core';
import { TestBed } from '@angular/core/testing';
import { TooltipDirective } from './tooltip.directive'; import { TooltipDirective } from './tooltip.directive';
export class MockElementRef extends ElementRef {}
describe('TooltipDirective', () => { describe('TooltipDirective', () => {
beforeEach((): void => {
TestBed.configureTestingModule({
providers: [ViewContainerRef, { provide: ElementRef, useClass: MockElementRef }],
});
});
it('should create an instance', () => { it('should create an instance', () => {
const directive = new TooltipDirective(); TestBed.runInInjectionContext(() => {
const directive: TooltipDirective = new TooltipDirective();
expect(directive).toBeTruthy(); 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'; import { TooltipComponent } from './tooltip.component';
@Directive({ @Directive({
selector: '[tooltip]', selector: '[tooltip]',
standalone: true,
}) })
export class TooltipDirective implements OnDestroy { export class TooltipDirective implements OnDestroy {
@Input() tooltip: string = ''; @Input() tooltip: string = '';
private componentRef: ComponentRef<TooltipComponent> = null; private componentRef: ComponentRef<TooltipComponent> = null;
private tooltipId: string;
constructor( public readonly viewContainerRef: ViewContainerRef = inject(ViewContainerRef);
private elementRef: ElementRef<HTMLElement>, public readonly elementRef: ElementRef<HTMLElement> = inject(ElementRef);
private injector: Injector,
private viewContainerRef: ViewContainerRef,
) {}
ngOnDestroy(): void { ngOnDestroy(): void {
this.destroy(); this.destroy();
} }
@HostBinding('attr.aria-describedby') get describedBy() {
return this.tooltipId;
}
@HostListener('mouseenter') @HostListener('mouseenter')
onMouseEnter(): void { @HostListener('focusin')
createTooltip(): void {
if (this.componentRef === null) { if (this.componentRef === null) {
this.componentRef = this.viewContainerRef.createComponent(TooltipComponent, { const attachedToFocused: boolean = this.elementRef.nativeElement.contains(document.activeElement);
injector: this.injector, this.tooltipId = uniqueId('tooltip');
}); this.componentRef = this.viewContainerRef.createComponent(TooltipComponent);
this.viewContainerRef.insert(this.componentRef.hostView); this.viewContainerRef.insert(this.componentRef.hostView);
this.setTooltipProperties(); this.setTooltipProperties(attachedToFocused);
} }
} }
@HostListener('mouseleave') @HostListener('mouseleave')
@HostListener('window:scroll') @HostListener('window:scroll')
@HostListener('focusout')
destroyTooltip(): void { destroyTooltip(): void {
this.destroy(); this.destroy();
} }
private setTooltipProperties() { @HostListener('keydown', ['$event']) onKeydown(e: KeyboardEvent) {
if (this.isEscapeKey(e)) {
this.destroy();
}
}
setTooltipProperties(attachedToFocused = false): void {
if (this.componentRef !== null) { if (this.componentRef !== null) {
const { left, right, bottom } = this.elementRef.nativeElement.getBoundingClientRect(); const { left, right, bottom } = this.elementRef.nativeElement.getBoundingClientRect();
this.componentRef.instance.left = (right + left) / 2; this.componentRef.instance.left = (right + left) / 2;
this.componentRef.instance.top = bottom; this.componentRef.instance.top = attachedToFocused ? bottom + 4 : bottom;
this.componentRef.instance.tooltip = this.tooltip; this.componentRef.instance.text = this.tooltip;
this.componentRef.instance.id = this.tooltipId;
} }
} }
private destroy(): void { destroy(): void {
if (this.componentRef !== null) { if (this.componentRef !== null) {
this.componentRef.destroy(); this.componentRef.destroy();
this.componentRef = null; 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