diff --git a/alfa-client/libs/admin/user/src/lib/user-form/user-form-roles/user-form-roles.component.html b/alfa-client/libs/admin/user/src/lib/user-form/user-form-roles/user-form-roles.component.html index 026e846163f1602e26c751e551e0cfa5f016663c..98a2303f351d72dfe65d7710ec73d7566cfdacd7 100644 --- a/alfa-client/libs/admin/user/src/lib/user-form/user-form-roles/user-form-roles.component.html +++ b/alfa-client/libs/admin/user/src/lib/user-form/user-form-roles/user-form-roles.component.html @@ -3,13 +3,33 @@ <div [formGroupName]="UserFormService.CLIENT_ROLES" class="mb-8 flex gap-56"> <div [formGroupName]="UserFormService.ADMINISTRATION_GROUP" class="flex flex-col gap-2"> <h3 class="text-md block font-medium text-text">Administration</h3> - <ods-checkbox-editor [formControlName]="UserFormService.ADMIN" label="Admin" inputId="admin" /> + <div class="flex items-center gap-2"> + <ods-checkbox-editor [formControlName]="UserFormService.ADMIN" label="Admin" inputId="admin" /> + <ods-info-icon + tooltip='Wird nur in Kombination mit "User" verwendet. Diese Rolle kann Funktionen in Keycloak und der Administration konfigurieren, z.B. Benutzer anlegen, Gruppen erstellen bzw. Organisationseinheiten hinzufügen und Rollen zuweisen.' + /> + </div> </div> <div [formGroupName]="UserFormService.ALFA_GROUP" class="flex flex-col gap-2"> <h3 class="text-md block font-medium text-text">Alfa</h3> - <ods-checkbox-editor [formControlName]="UserFormService.LOESCHEN" label="Löschen" inputId="delete" /> - <ods-checkbox-editor [formControlName]="UserFormService.USER" label="User" inputId="user" /> - <ods-checkbox-editor [formControlName]="UserFormService.POSTSTELLE" label="Poststelle" inputId="post_office" /> + <div class="flex items-center gap-2"> + <ods-checkbox-editor [formControlName]="UserFormService.LOESCHEN" label="Löschen" inputId="delete" /> + <ods-info-icon + tooltip='Diese Rolle hat dieselben Rechte wie die Rolle "User". Zusätzlich kann "Löschen" ausgewählte Vorgänge aus Alfa löschen. Diese Rolle sollten zwei Benutzer haben, da das Löschen einem Vieraugen-Prinzip folgt.' + /> + </div> + <div class="flex items-center gap-2"> + <ods-checkbox-editor [formControlName]="UserFormService.USER" label="User" inputId="user" /> + <ods-info-icon + tooltip='Diese Rolle kann alle Vorgänge sehen und bearbeiten, wenn diese seiner Organisationseinheit zugewiesen sind. Ist kompatibel mit "Löschen" und "Admin".' + /> + </div> + <div class="flex items-center gap-2"> + <ods-checkbox-editor [formControlName]="UserFormService.POSTSTELLE" label="Poststelle" inputId="post_office" /> + <ods-info-icon + tooltip='Diese Rolle kann ausschließlich alle neu eingegangenen Vorgänge sehen und anderen Benutzern zuweisen. Sie sollte nur einem Benutzer zugewiesen sein. Dieser sollte keine weiteren Rollen besitzen. (Sie ist aber kompatibel mit der "Admin")' + /> + </div> </div> </div> </div> diff --git a/alfa-client/libs/admin/user/src/lib/user-form/user-form-roles/user-form-roles.component.spec.ts b/alfa-client/libs/admin/user/src/lib/user-form/user-form-roles/user-form-roles.component.spec.ts index 386a028e927b68a8277cd8076ee11b357344cb48..b27337b2786891b5738a6d09bae607cecd492cdb 100644 --- a/alfa-client/libs/admin/user/src/lib/user-form/user-form-roles/user-form-roles.component.spec.ts +++ b/alfa-client/libs/admin/user/src/lib/user-form/user-form-roles/user-form-roles.component.spec.ts @@ -1,5 +1,7 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ReactiveFormsModule } from '@angular/forms'; +import { InfoIconComponent, TooltipDirective } from '@ods/system'; +import { MockComponent, MockDirective } from 'ng-mocks'; import { createUserFormGroup } from '../../../../test/form'; import { UserFormRolesComponent } from './user-form-roles.component'; @@ -9,7 +11,7 @@ describe('UserFormRolesComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ - imports: [UserFormRolesComponent, ReactiveFormsModule], + imports: [UserFormRolesComponent, ReactiveFormsModule, MockComponent(InfoIconComponent), MockDirective(TooltipDirective)], }).compileComponents(); fixture = TestBed.createComponent(UserFormRolesComponent); diff --git a/alfa-client/libs/admin/user/src/lib/user-form/user-form-roles/user-form-roles.component.ts b/alfa-client/libs/admin/user/src/lib/user-form/user-form-roles/user-form-roles.component.ts index d3c25f54bb31a6d18a39ec113a8f166bc14271ef..c7d46e948f2ddc5b4c50fd5880ddf7ff2221030c 100644 --- a/alfa-client/libs/admin/user/src/lib/user-form/user-form-roles/user-form-roles.component.ts +++ b/alfa-client/libs/admin/user/src/lib/user-form/user-form-roles/user-form-roles.component.ts @@ -1,12 +1,13 @@ import { Component, Input } from '@angular/core'; import { ReactiveFormsModule, UntypedFormGroup } from '@angular/forms'; import { CheckboxEditorComponent } from '@ods/component'; +import { InfoIconComponent, TooltipDirective } from '@ods/system'; import { UserFormService } from '../user.formservice'; @Component({ selector: 'admin-user-form-roles', standalone: true, - imports: [CheckboxEditorComponent, ReactiveFormsModule], + imports: [CheckboxEditorComponent, ReactiveFormsModule, TooltipDirective, InfoIconComponent], templateUrl: './user-form-roles.component.html', }) export class UserFormRolesComponent { diff --git a/alfa-client/libs/design-system/src/index.ts b/alfa-client/libs/design-system/src/index.ts index e7ae662925633f6c656ce0c64ba919debf94b774..eda668409767d19830222cb490f11f1608efff9b 100644 --- a/alfa-client/libs/design-system/src/index.ts +++ b/alfa-client/libs/design-system/src/index.ts @@ -63,6 +63,7 @@ export * from './lib/icons/file-icon/file-icon.component'; export * from './lib/icons/forward-vorgang-icon/forward-vorgang-icon.component'; export * from './lib/icons/help-icon/help-icon.component'; export * from './lib/icons/iconVariants'; +export * from './lib/icons/info-icon/info-icon.component'; export * from './lib/icons/logout-icon/logout-icon.component'; export * from './lib/icons/mailbox-icon/mailbox-icon.component'; export * from './lib/icons/more-icon/more-icon.component'; diff --git a/alfa-client/libs/design-system/src/lib/icons/info-icon/info-icon.component.spec.ts b/alfa-client/libs/design-system/src/lib/icons/info-icon/info-icon.component.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..7b0b08534c6c010f7c676d6d1ffcc8c4bbcea98d --- /dev/null +++ b/alfa-client/libs/design-system/src/lib/icons/info-icon/info-icon.component.spec.ts @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2025 Das Land Schleswig-Holstein vertreten durch den + * Ministerpräsidenten des Landes Schleswig-Holstein + * Staatskanzlei + * Abteilung Digitalisierung und zentrales IT-Management der Landesregierung + * + * Lizenziert unter der EUPL, Version 1.2 oder - sobald + * diese von der Europäischen Kommission genehmigt wurden - + * Folgeversionen der EUPL ("Lizenz"); + * Sie dürfen dieses Werk ausschließlich gemäß + * dieser Lizenz nutzen. + * Eine Kopie der Lizenz finden Sie hier: + * + * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 + * + * Sofern nicht durch anwendbare Rechtsvorschriften + * gefordert oder in schriftlicher Form vereinbart, wird + * die unter der Lizenz verbreitete Software "so wie sie + * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN - + * ausdrücklich oder stillschweigend - verbreitet. + * Die sprachspezifischen Genehmigungen und Beschränkungen + * unter der Lizenz sind dem Lizenztext zu entnehmen. + */ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { InfoIconComponent } from './info-icon.component'; + +describe('InfoIconComponent', () => { + let component: InfoIconComponent; + let fixture: ComponentFixture<InfoIconComponent>; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [InfoIconComponent], + }).compileComponents(); + + fixture = TestBed.createComponent(InfoIconComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/alfa-client/libs/design-system/src/lib/icons/info-icon/info-icon.component.ts b/alfa-client/libs/design-system/src/lib/icons/info-icon/info-icon.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..5fd6e58c3345ecef1e704daf8e423e623d427e62 --- /dev/null +++ b/alfa-client/libs/design-system/src/lib/icons/info-icon/info-icon.component.ts @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2025 Das Land Schleswig-Holstein vertreten durch den + * Ministerpräsidenten des Landes Schleswig-Holstein + * Staatskanzlei + * Abteilung Digitalisierung und zentrales IT-Management der Landesregierung + * + * Lizenziert unter der EUPL, Version 1.2 oder - sobald + * diese von der Europäischen Kommission genehmigt wurden - + * Folgeversionen der EUPL ("Lizenz"); + * Sie dürfen dieses Werk ausschließlich gemäß + * dieser Lizenz nutzen. + * Eine Kopie der Lizenz finden Sie hier: + * + * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 + * + * Sofern nicht durch anwendbare Rechtsvorschriften + * gefordert oder in schriftlicher Form vereinbart, wird + * die unter der Lizenz verbreitete Software "so wie sie + * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN - + * ausdrücklich oder stillschweigend - verbreitet. + * Die sprachspezifischen Genehmigungen und Beschränkungen + * unter der Lizenz sind dem Lizenztext zu entnehmen. + */ +import { NgClass } from '@angular/common'; +import { Component, Input } from '@angular/core'; +import { twMerge } from 'tailwind-merge'; +import { IconVariants, iconVariants } from '../iconVariants'; + +@Component({ + selector: 'ods-info-icon', + standalone: true, + imports: [NgClass], + template: `<svg + [ngClass]="twMerge(iconVariants({ size }), 'fill-primary', class)" + aria-hidden="true" + viewBox="0 0 24 24" + xmlns="http://www.w3.org/2000/svg" + > + <path + d="M11.25 16.75H12.75V11H11.25V16.75ZM12 9.2885C12.2288 9.2885 12.4207 9.21108 12.5755 9.05625C12.7303 8.90142 12.8077 8.70958 12.8077 8.48075C12.8077 8.25192 12.7303 8.06008 12.5755 7.90525C12.4207 7.75058 12.2288 7.67325 12 7.67325C11.7712 7.67325 11.5793 7.75058 11.4245 7.90525C11.2697 8.06008 11.1923 8.25192 11.1923 8.48075C11.1923 8.70958 11.2697 8.90142 11.4245 9.05625C11.5793 9.21108 11.7712 9.2885 12 9.2885ZM12.0017 21.5C10.6877 21.5 9.45267 21.2507 8.2965 20.752C7.14033 20.2533 6.13467 19.5766 5.2795 18.7218C4.42433 17.8669 3.74725 16.8617 3.24825 15.706C2.74942 14.5503 2.5 13.3156 2.5 12.0017C2.5 10.6877 2.74933 9.45267 3.248 8.2965C3.74667 7.14033 4.42342 6.13467 5.27825 5.2795C6.13308 4.42433 7.13833 3.74725 8.294 3.24825C9.44967 2.74942 10.6844 2.5 11.9983 2.5C13.3123 2.5 14.5473 2.74933 15.7035 3.248C16.8597 3.74667 17.8653 4.42342 18.7205 5.27825C19.5757 6.13308 20.2528 7.13833 20.7518 8.294C21.2506 9.44967 21.5 10.6844 21.5 11.9983C21.5 13.3123 21.2507 14.5473 20.752 15.7035C20.2533 16.8597 19.5766 17.8653 18.7218 18.7205C17.8669 19.5757 16.8617 20.2528 15.706 20.7518C14.5503 21.2506 13.3156 21.5 12.0017 21.5ZM12 20C14.2333 20 16.125 19.225 17.675 17.675C19.225 16.125 20 14.2333 20 12C20 9.76667 19.225 7.875 17.675 6.325C16.125 4.775 14.2333 4 12 4C9.76667 4 7.875 4.775 6.325 6.325C4.775 7.875 4 9.76667 4 12C4 14.2333 4.775 16.125 6.325 17.675C7.875 19.225 9.76667 20 12 20Z" + /> + </svg>`, +}) +export class InfoIconComponent { + @Input() size: IconVariants['size'] = 'medium'; + @Input() class: string = undefined; + + protected readonly iconVariants = iconVariants; + protected readonly twMerge = twMerge; +} diff --git a/alfa-client/libs/design-system/src/lib/icons/info-icon/info-icon.stories.ts b/alfa-client/libs/design-system/src/lib/icons/info-icon/info-icon.stories.ts new file mode 100644 index 0000000000000000000000000000000000000000..70a6e7940585b4a5696ea9e53a9043e1c276b6a2 --- /dev/null +++ b/alfa-client/libs/design-system/src/lib/icons/info-icon/info-icon.stories.ts @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2025 Das Land Schleswig-Holstein vertreten durch den + * Ministerpräsidenten des Landes Schleswig-Holstein + * Staatskanzlei + * Abteilung Digitalisierung und zentrales IT-Management der Landesregierung + * + * Lizenziert unter der EUPL, Version 1.2 oder - sobald + * diese von der Europäischen Kommission genehmigt wurden - + * Folgeversionen der EUPL ("Lizenz"); + * Sie dürfen dieses Werk ausschließlich gemäß + * dieser Lizenz nutzen. + * Eine Kopie der Lizenz finden Sie hier: + * + * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 + * + * Sofern nicht durch anwendbare Rechtsvorschriften + * gefordert oder in schriftlicher Form vereinbart, wird + * die unter der Lizenz verbreitete Software "so wie sie + * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN - + * ausdrücklich oder stillschweigend - verbreitet. + * Die sprachspezifischen Genehmigungen und Beschränkungen + * unter der Lizenz sind dem Lizenztext zu entnehmen. + */ +import type { Meta, StoryObj } from '@storybook/angular'; + +import { InfoIconComponent } from './info-icon.component'; + +const meta: Meta<InfoIconComponent> = { + title: 'Icons/Info icon', + component: InfoIconComponent, + excludeStories: /.*Data$/, + tags: ['autodocs'], +}; + +export default meta; +type Story = StoryObj<InfoIconComponent>; + +export const Default: Story = { + args: { size: 'large' }, + argTypes: { + size: { + control: 'select', + options: ['small', 'medium', 'large', 'extra-large', 'full'], + description: 'Size of icon. Property "full" means 100%', + table: { + defaultValue: { summary: 'medium' }, + }, + }, + }, +}; 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 a5e130fefe15bca8001c94c50b93e17a35b01003..671af957965555f680862dad37081cc00c537c9d 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 @@ -29,12 +29,15 @@ import { TooltipPosition } from './tooltip.directive'; selector: 'ods-tooltip', imports: [NgClass], template: `<span - class="tooltip fixed z-[100] max-w-md animate-fadeIn cursor-default break-words rounded bg-ozggray-900 px-3 py-2 text-sm font-normal text-whitetext before:absolute before:border-l-[0.5rem] before:border-r-[0.5rem] before:border-l-transparent before:border-r-transparent dark:bg-white md:max-w-[calc(90vw)]" + class="tooltip fixed z-[100] animate-fadeIn cursor-default break-words rounded bg-ozggray-900 px-3 py-2 text-sm font-normal text-whitetext before:absolute before:border-l-[0.5rem] before:border-r-[0.5rem] before:border-l-transparent before:border-r-transparent dark:bg-white" [ngClass]="class" [class.visible]="show" [class.invisible]="!show" + [class.text-wrap]="width" + [class.text-nowrap]="!width" [style.left.px]="left" [style.top.px]="top" + [style.width.rem]="width" [style.--before-left.px]="leftOffset" [attr.id]="id" role="tooltip" @@ -49,6 +52,7 @@ export class TooltipComponent { text: string = ''; left: number = 0; top: number = 0; + width: number = null; show: boolean = false; id: string; class: 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 e41c410f0efb001d9c7896eaec93fb388fb9d23a..76c569e4245841930a5387dc5c8a2e34e6328c1b 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 @@ -23,10 +23,10 @@ */ import { InteractivityChecker } from '@angular/cdk/a11y'; import { ComponentRef, ElementRef, Renderer2, ViewContainerRef } from '@angular/core'; -import { TestBed } from '@angular/core/testing'; +import { fakeAsync, TestBed, tick } from '@angular/core/testing'; import { faker } from '@faker-js/faker/.'; import { TooltipComponent } from './tooltip.component'; -import { TooltipDirective, TooltipPosition } from './tooltip.directive'; +import { MAX_WIDTH, REM, TooltipDirective, TooltipPosition } from './tooltip.directive'; class MockElementRef extends ElementRef { nativeElement = { @@ -46,7 +46,17 @@ describe('TooltipDirective', () => { location: null, hostView: null, injector: null, - instance: { id: '', left: 0, top: 0, text: '', show: false, position: TooltipPosition.BELOW, class: '', leftOffset: 0 }, + instance: { + id: '', + left: 0, + top: 0, + text: '', + show: false, + position: TooltipPosition.BELOW, + class: '', + leftOffset: 0, + width: null, + }, }; const parentRect: DOMRect = { bottom: 0, top: 0, height: 0, left: 0, right: 0, toJSON: jest.fn(), width: 0, x: 0, y: 0 }; @@ -152,6 +162,7 @@ describe('TooltipDirective', () => { beforeEach(() => { directive.componentRef = mockComponentRef; directive.setTooltipProperties = jest.fn(); + directive.setWidth = jest.fn(); directive.elementRef.nativeElement.contains = jest.fn().mockReturnValue(true); }); @@ -175,11 +186,19 @@ describe('TooltipDirective', () => { expect(directive.attachedToFocused).toBeTruthy(); }); - it('should set tooltip properties', () => { + it('should set tooltip width', () => { directive.showTooltip(); - expect(directive.setTooltipProperties).toHaveBeenCalled(); + expect(directive.setWidth).toHaveBeenCalled(); }); + + it('should set tooltip properties after tick', fakeAsync(() => { + directive.showTooltip(); + + tick(); + + expect(directive.setTooltipProperties).toHaveBeenCalled(); + })); }); describe('hideTooltip', () => { @@ -253,6 +272,34 @@ describe('TooltipDirective', () => { }); }); + describe('setWidth', () => { + const maxTooltipWidth: number = MAX_WIDTH * REM; + + it('should set width to max width', () => { + setWidthOnComponentRef(maxTooltipWidth + 100); + + directive.setWidth(); + + expect(directive.componentRef.instance.width).toBe(MAX_WIDTH); + }); + + it('should set width to null', () => { + setWidthOnComponentRef(maxTooltipWidth - 100); + + directive.setWidth(); + + expect(directive.componentRef.instance.width).toBe(null); + }); + + function setWidthOnComponentRef(width: number): void { + directive.componentRef = Object.assign(mockComponentRef, { + location: { + nativeElement: { children: [{ getBoundingClientRect: jest.fn().mockReturnValue({ width }) }] }, + }, + }); + } + }); + describe('setAutoPosition', () => { it('should set tooltip position above if it cannot be displayed below', () => { directive.tooltipPosition = TooltipPosition.BELOW; @@ -282,48 +329,40 @@ describe('TooltipDirective', () => { }); }); - describe('setLeftOffset', () => { + describe('calculateLeftOffset', () => { beforeEach(() => { directive.leftOffset = 0; }); - it('should set 0 offset, if parent wider than tooltip', () => { - const rectForStandard: DOMRect = { ...parentRect, left: 50, right: 500, width: 100 }; - - directive.setLeftOffset(36, rectForStandard, 1000); - - expect(directive.leftOffset).toBe(0); - }); - it('should set positive left offset', () => { const rectForLeft: DOMRect = { ...parentRect, left: 10 }; - directive.setLeftOffset(50, rectForLeft, 1000); + const result: number = directive.calculateLeftOffset(50, rectForLeft, 1000); - expect(directive.leftOffset).toBe(15); + expect(result).toBe(15); }); it('should set negative left offset', () => { const rectForRight: DOMRect = { ...parentRect, left: 50, right: 990 }; - directive.setLeftOffset(50, rectForRight, 1000); + const result: number = directive.calculateLeftOffset(50, rectForRight, 1000); - expect(directive.leftOffset).toBe(-15); + expect(result).toBe(-15); }); it('should set 0 offset', () => { const rectForStandard: DOMRect = { ...parentRect, left: 50, right: 500 }; - directive.setLeftOffset(36, rectForStandard, 1000); + const result: number = directive.calculateLeftOffset(36, rectForStandard, 1000); - expect(directive.leftOffset).toBe(0); + expect(result).toBe(0); }); }); describe('setTooltipOffsetAndPosition', () => { beforeEach(() => { directive.setAutoPosition = jest.fn(); - directive.setLeftOffset = jest.fn(); + directive.calculateLeftOffset = jest.fn(); directive.componentRef = Object.assign(mockComponentRef, { location: { nativeElement: { children: [{ getBoundingClientRect: jest.fn().mockReturnValue({ height: 100, width: 200 }) }] }, @@ -340,10 +379,10 @@ describe('TooltipDirective', () => { expect(directive.setAutoPosition).toHaveBeenCalledWith(100, {}, 404); }); - it('should set tooltip left offset', () => { + it('should calculate^ tooltip left offset', () => { directive.setTooltipOffsetAndPosition(); - expect(directive.setLeftOffset).toHaveBeenCalledWith(200, {}, 503); + expect(directive.calculateLeftOffset).toHaveBeenCalledWith(200, {}, 503); }); }); 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 ad238fc30e5c06357950f574eae44f518582a75a..d8f42d4ab8dd4e8bdab6e49031a00fbbbddbb480 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 @@ -23,7 +23,17 @@ */ import { isEscapeKey, isNotNull } from '@alfa-client/tech-shared'; import { InteractivityChecker } from '@angular/cdk/a11y'; -import { ComponentRef, Directive, ElementRef, HostListener, inject, Input, OnDestroy, Renderer2, ViewContainerRef, } from '@angular/core'; +import { + ComponentRef, + Directive, + ElementRef, + HostListener, + inject, + Input, + OnDestroy, + Renderer2, + ViewContainerRef, +} from '@angular/core'; import { isEmpty, isNull, uniqueId } from 'lodash-es'; import { TooltipComponent } from './tooltip.component'; @@ -32,6 +42,9 @@ export enum TooltipPosition { BELOW = 'below', } +export const REM = 16; +export const MAX_WIDTH = 24; // Max tooltip width in rem + const OUTLINE_INDENT = 4; // Outline offset (2) + outline width (2) type TooltipAriaType = 'aria-describedby' | 'aria-labelledby'; @@ -80,7 +93,10 @@ export class TooltipDirective implements OnDestroy { const nativeElement: HTMLElement = this.elementRef.nativeElement; this.attachedToFocused = nativeElement.contains(document.activeElement); - this.setTooltipProperties(); + this.setWidth(); + setTimeout(() => { + this.setTooltipProperties(); + }); } @HostListener('mouseleave') @@ -126,6 +142,15 @@ export class TooltipDirective implements OnDestroy { this.componentRef.instance.show = true; } + setWidth(): void { + const currentWidth: number = this.componentRef.location.nativeElement.children[0].getBoundingClientRect().width; + if (currentWidth >= MAX_WIDTH * REM) { + this.componentRef.instance.width = MAX_WIDTH; + } else { + this.componentRef.instance.width = null; + } + } + setAutoPosition(tooltipHeight: number, parentRect: DOMRect, windowHeight: number): void { const { top, bottom } = parentRect; @@ -142,32 +167,25 @@ export class TooltipDirective implements OnDestroy { this.position = this.tooltipPosition; } - setLeftOffset(tooltipWidth: number, parentRect: DOMRect, windowWidth: number): void { - const { left, right, width } = parentRect; + calculateLeftOffset(tooltipWidth: number, parentRect: DOMRect, windowWidth: number): number { + const { left, right } = parentRect; const halfTooltipWidth: number = tooltipWidth / 2; - if (tooltipWidth < width) { - this.leftOffset = 0; - return; - } - if (halfTooltipWidth > left) { - this.leftOffset = halfTooltipWidth - left; - return; + return halfTooltipWidth - left; } if (halfTooltipWidth > windowWidth - right) { - this.leftOffset = windowWidth - right - halfTooltipWidth; - return; + return windowWidth - right - halfTooltipWidth; } - this.leftOffset = 0; + return 0; } setTooltipOffsetAndPosition(): void { const { width, height } = this.componentRef.location.nativeElement.children[0].getBoundingClientRect(); const parentRect: DOMRect = this.elementRef.nativeElement.getBoundingClientRect(); - this.setLeftOffset(width, parentRect, window.innerWidth); + this.leftOffset = this.calculateLeftOffset(width, parentRect, window.innerWidth); this.setAutoPosition(height, parentRect, window.innerHeight); }