mirror of
https://github.com/bitwarden/clients.git
synced 2025-12-10 00:08:42 -06:00
[CL-910] Use tooltip in title directive (#17084)
* use tooltip in a11y directive * remove commented code * add deprecation warning to appA11yTitle directive * use label for tooltip in carousel nav * wait for timeout before assertion * remove unnecessary title directive use * fix private variable lint errors * increase tooltip show delay * fix spec delay and export as constant * use delay constant --------- Co-authored-by: Vicki League <vleague@bitwarden.com>
This commit is contained in:
parent
4a2858132d
commit
963a9156fb
@ -2,7 +2,7 @@
|
|||||||
*ngIf="state === SetupExtensionState.Loading"
|
*ngIf="state === SetupExtensionState.Loading"
|
||||||
class="bwi bwi-spinner bwi-spin bwi-3x tw-text-muted"
|
class="bwi bwi-spinner bwi-spin bwi-3x tw-text-muted"
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
[appA11yTitle]="'loading' | i18n"
|
[title]="'loading' | i18n"
|
||||||
></i>
|
></i>
|
||||||
|
|
||||||
<section *ngIf="state === SetupExtensionState.NeedsExtension" class="tw-text-center tw-mt-4">
|
<section *ngIf="state === SetupExtensionState.NeedsExtension" class="tw-text-center tw-mt-4">
|
||||||
|
|||||||
@ -1,23 +1,21 @@
|
|||||||
import { Directive, effect, ElementRef, input } from "@angular/core";
|
import { Directive } from "@angular/core";
|
||||||
|
|
||||||
import { setA11yTitleAndAriaLabel } from "./set-a11y-title-and-aria-label";
|
import { TooltipDirective } from "../tooltip/tooltip.directive";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated This function is deprecated in favor of `bitTooltip`.
|
||||||
|
* Please use `bitTooltip` instead.
|
||||||
|
*
|
||||||
|
* Directive that provides accessible tooltips by internally using TooltipDirective.
|
||||||
|
* This maintains the appA11yTitle API while leveraging the enhanced tooltip functionality.
|
||||||
|
*/
|
||||||
@Directive({
|
@Directive({
|
||||||
selector: "[appA11yTitle]",
|
selector: "[appA11yTitle]",
|
||||||
|
hostDirectives: [
|
||||||
|
{
|
||||||
|
directive: TooltipDirective,
|
||||||
|
inputs: ["bitTooltip: appA11yTitle", "tooltipPosition"],
|
||||||
|
},
|
||||||
|
],
|
||||||
})
|
})
|
||||||
export class A11yTitleDirective {
|
export class A11yTitleDirective {}
|
||||||
readonly title = input.required<string>({ alias: "appA11yTitle" });
|
|
||||||
|
|
||||||
constructor(private el: ElementRef) {
|
|
||||||
const originalTitle = this.el.nativeElement.getAttribute("title");
|
|
||||||
const originalAriaLabel = this.el.nativeElement.getAttribute("aria-label");
|
|
||||||
|
|
||||||
effect(() => {
|
|
||||||
setA11yTitleAndAriaLabel({
|
|
||||||
element: this.el.nativeElement,
|
|
||||||
title: originalTitle ?? this.title(),
|
|
||||||
label: originalAriaLabel ?? this.title(),
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@ -120,7 +120,7 @@ export class BitIconButtonComponent implements ButtonLikeAbstraction, FocusableE
|
|||||||
* label input will be used to set the `aria-label` attributes on the button.
|
* label input will be used to set the `aria-label` attributes on the button.
|
||||||
* This is for accessibility purposes, as it provides a text alternative for the icon button.
|
* This is for accessibility purposes, as it provides a text alternative for the icon button.
|
||||||
*
|
*
|
||||||
* NOTE: It will also be used to set the `title` attribute on the button if no `title` is provided.
|
* NOTE: It will also be used to set the content of the tooltip on the button if no `title` is provided.
|
||||||
*/
|
*/
|
||||||
readonly label = input<string>();
|
readonly label = input<string>();
|
||||||
|
|
||||||
|
|||||||
@ -81,10 +81,5 @@ with less padding around the icon, such as in the navigation component.
|
|||||||
|
|
||||||
Follow guidelines outlined in the [Button docs](?path=/docs/component-library-button--doc)
|
Follow guidelines outlined in the [Button docs](?path=/docs/component-library-button--doc)
|
||||||
|
|
||||||
Always use the `appA11yTitle` directive set to a string that describes the action of the
|
label input will be used to set the `aria-label` attributes on the button. This is for accessibility
|
||||||
icon-button. This will auto assign the same string to the `title` and `aria-label` attributes.
|
purposes, as it provides a text alternative for the icon button.
|
||||||
|
|
||||||
`aria-label` allows assistive technology to announce the action the button takes to the users.
|
|
||||||
|
|
||||||
`title` attribute provides a user with the browser tool tip if they do not understand what the icon
|
|
||||||
is indicating.
|
|
||||||
|
|||||||
@ -16,6 +16,7 @@ import {
|
|||||||
import { TooltipPositionIdentifier, tooltipPositions } from "./tooltip-positions";
|
import { TooltipPositionIdentifier, tooltipPositions } from "./tooltip-positions";
|
||||||
import { TooltipComponent, TOOLTIP_DATA } from "./tooltip.component";
|
import { TooltipComponent, TOOLTIP_DATA } from "./tooltip.component";
|
||||||
|
|
||||||
|
export const TOOLTIP_DELAY_MS = 800;
|
||||||
/**
|
/**
|
||||||
* Directive to add a tooltip to any element. The tooltip content is provided via the `bitTooltip` input.
|
* Directive to add a tooltip to any element. The tooltip content is provided via the `bitTooltip` input.
|
||||||
* The position of the tooltip can be set via the `tooltipPosition` input. Default position is "above-center".
|
* The position of the tooltip can be set via the `tooltipPosition` input. Default position is "above-center".
|
||||||
@ -85,7 +86,7 @@ export class TooltipDirective implements OnInit {
|
|||||||
this.isVisible.set(false);
|
this.isVisible.set(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
private showTooltip = () => {
|
protected showTooltip = () => {
|
||||||
if (!this.overlayRef) {
|
if (!this.overlayRef) {
|
||||||
this.overlayRef = this.overlay.create({
|
this.overlayRef = this.overlay.create({
|
||||||
...this.defaultPopoverConfig,
|
...this.defaultPopoverConfig,
|
||||||
@ -94,14 +95,17 @@ export class TooltipDirective implements OnInit {
|
|||||||
|
|
||||||
this.overlayRef.attach(this.tooltipPortal);
|
this.overlayRef.attach(this.tooltipPortal);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
this.isVisible.set(true);
|
this.isVisible.set(true);
|
||||||
|
}, TOOLTIP_DELAY_MS);
|
||||||
};
|
};
|
||||||
|
|
||||||
private hideTooltip = () => {
|
protected hideTooltip = () => {
|
||||||
this.destroyTooltip();
|
this.destroyTooltip();
|
||||||
};
|
};
|
||||||
|
|
||||||
private readonly resolvedDescribedByIds = computed(() => {
|
protected readonly resolvedDescribedByIds = computed(() => {
|
||||||
if (this.addTooltipToDescribedby()) {
|
if (this.addTooltipToDescribedby()) {
|
||||||
if (this.currentDescribedByIds) {
|
if (this.currentDescribedByIds) {
|
||||||
return `${this.currentDescribedByIds || ""} ${this.tooltipId}`;
|
return `${this.currentDescribedByIds || ""} ${this.tooltipId}`;
|
||||||
|
|||||||
@ -6,11 +6,11 @@ import {
|
|||||||
} from "@angular/cdk/overlay";
|
} from "@angular/cdk/overlay";
|
||||||
import { ComponentPortal } from "@angular/cdk/portal";
|
import { ComponentPortal } from "@angular/cdk/portal";
|
||||||
import { Component } from "@angular/core";
|
import { Component } from "@angular/core";
|
||||||
import { ComponentFixture, TestBed } from "@angular/core/testing";
|
import { ComponentFixture, TestBed, fakeAsync, tick } from "@angular/core/testing";
|
||||||
import { By } from "@angular/platform-browser";
|
import { By } from "@angular/platform-browser";
|
||||||
import { Observable, Subject } from "rxjs";
|
import { Observable, Subject } from "rxjs";
|
||||||
|
|
||||||
import { TooltipDirective } from "./tooltip.directive";
|
import { TooltipDirective, TOOLTIP_DELAY_MS } from "./tooltip.directive";
|
||||||
|
|
||||||
// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush
|
// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush
|
||||||
// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection
|
// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection
|
||||||
@ -90,23 +90,25 @@ describe("TooltipDirective (visibility only)", () => {
|
|||||||
return hostDE.injector.get(TooltipDirective);
|
return hostDE.injector.get(TooltipDirective);
|
||||||
}
|
}
|
||||||
|
|
||||||
it("sets isVisible to true on mouseenter", () => {
|
it("sets isVisible to true on mouseenter", fakeAsync(() => {
|
||||||
const button: HTMLButtonElement = fixture.debugElement.query(By.css("button")).nativeElement;
|
const button: HTMLButtonElement = fixture.debugElement.query(By.css("button")).nativeElement;
|
||||||
const directive = getDirective();
|
const directive = getDirective();
|
||||||
|
|
||||||
const isVisible = (directive as unknown as { isVisible: () => boolean }).isVisible;
|
const isVisible = (directive as unknown as { isVisible: () => boolean }).isVisible;
|
||||||
|
|
||||||
button.dispatchEvent(new Event("mouseenter"));
|
button.dispatchEvent(new Event("mouseenter"));
|
||||||
|
tick(TOOLTIP_DELAY_MS);
|
||||||
expect(isVisible()).toBe(true);
|
expect(isVisible()).toBe(true);
|
||||||
});
|
}));
|
||||||
|
|
||||||
it("sets isVisible to true on focus", () => {
|
it("sets isVisible to true on focus", fakeAsync(() => {
|
||||||
const button: HTMLButtonElement = fixture.debugElement.query(By.css("button")).nativeElement;
|
const button: HTMLButtonElement = fixture.debugElement.query(By.css("button")).nativeElement;
|
||||||
const directive = getDirective();
|
const directive = getDirective();
|
||||||
|
|
||||||
const isVisible = (directive as unknown as { isVisible: () => boolean }).isVisible;
|
const isVisible = (directive as unknown as { isVisible: () => boolean }).isVisible;
|
||||||
|
|
||||||
button.dispatchEvent(new Event("focus"));
|
button.dispatchEvent(new Event("focus"));
|
||||||
|
tick(TOOLTIP_DELAY_MS);
|
||||||
expect(isVisible()).toBe(true);
|
expect(isVisible()).toBe(true);
|
||||||
});
|
}));
|
||||||
});
|
});
|
||||||
|
|||||||
@ -12,10 +12,9 @@
|
|||||||
bitIconButton="bwi-angle-left"
|
bitIconButton="bwi-angle-left"
|
||||||
class="tw-size-6 tw-p-0 tw-flex tw-items-center tw-justify-center"
|
class="tw-size-6 tw-p-0 tw-flex tw-items-center tw-justify-center"
|
||||||
size="small"
|
size="small"
|
||||||
[attr.label]="'back' | i18n"
|
|
||||||
(click)="prevSlide()"
|
(click)="prevSlide()"
|
||||||
[disabled]="selectedIndex <= 0"
|
[disabled]="selectedIndex <= 0"
|
||||||
appA11yTitle="{{ 'back' | i18n }}"
|
label="{{ 'back' | i18n }}"
|
||||||
></button>
|
></button>
|
||||||
<div
|
<div
|
||||||
class="tw-w-full tw-flex tw-gap-2 tw-justify-center tw-mt-auto"
|
class="tw-w-full tw-flex tw-gap-2 tw-justify-center tw-mt-auto"
|
||||||
@ -34,11 +33,10 @@
|
|||||||
type="button"
|
type="button"
|
||||||
bitIconButton="bwi-angle-right"
|
bitIconButton="bwi-angle-right"
|
||||||
class="tw-size-6 tw-p-0 tw-flex tw-items-center tw-justify-center"
|
class="tw-size-6 tw-p-0 tw-flex tw-items-center tw-justify-center"
|
||||||
[attr.label]="'next' | i18n"
|
|
||||||
size="small"
|
size="small"
|
||||||
(click)="nextSlide()"
|
(click)="nextSlide()"
|
||||||
[disabled]="selectedIndex >= slides.length - 1"
|
[disabled]="selectedIndex >= slides.length - 1"
|
||||||
appA11yTitle="{{ 'next' | i18n }}"
|
label="{{ 'next' | i18n }}"
|
||||||
></button>
|
></button>
|
||||||
</div>
|
</div>
|
||||||
<div class="tw-absolute tw-invisible" #tempSlideContainer *ngIf="minHeight === null">
|
<div class="tw-absolute tw-invisible" #tempSlideContainer *ngIf="minHeight === null">
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user