mirror of
https://github.com/bitwarden/browser
synced 2025-12-18 17:23:37 +00:00
[CL-879] use tooltip on icon button (#16576)
* Add tooltip to icon button to display label * remove legacy cdr variable * create overlay on focus or hover * attach describdedby ids * fix type errors * remove aria-describedby when not necessary * fix failing tests * implement Claude feedback * fixing broken specs * remove host attr binding * Simplify directive aria logic * Move id to statis number * do not render empty tooltip * pass id to tooltip component * remove pointer-events none to allow tooltip on normal buttons * exclude some tooltip stories * change describedby input name * add story with tooltip on regular button * enhanced tooltip docs * set model directly * change model to input
This commit is contained in:
@@ -8,8 +8,9 @@ import {
|
||||
ElementRef,
|
||||
Injector,
|
||||
input,
|
||||
effect,
|
||||
signal,
|
||||
model,
|
||||
computed,
|
||||
} from "@angular/core";
|
||||
|
||||
import { TooltipPositionIdentifier, tooltipPositions } from "./tooltip-positions";
|
||||
@@ -26,30 +27,39 @@ import { TooltipComponent, TOOLTIP_DATA } from "./tooltip.component";
|
||||
"(mouseleave)": "hideTooltip()",
|
||||
"(focus)": "showTooltip()",
|
||||
"(blur)": "hideTooltip()",
|
||||
"[attr.aria-describedby]": "resolvedDescribedByIds()",
|
||||
},
|
||||
})
|
||||
export class TooltipDirective implements OnInit {
|
||||
private static nextId = 0;
|
||||
/**
|
||||
* The value of this input is forwarded to the tooltip.component to render
|
||||
*/
|
||||
readonly bitTooltip = input.required<string>();
|
||||
readonly tooltipContent = model("", { alias: "bitTooltip" });
|
||||
/**
|
||||
* The value of this input is forwarded to the tooltip.component to set its position explicitly.
|
||||
* @default "above-center"
|
||||
*/
|
||||
readonly tooltipPosition = input<TooltipPositionIdentifier>("above-center");
|
||||
|
||||
/**
|
||||
* Input so the consumer can choose to add the tooltip id to the aria-describedby attribute of the host element.
|
||||
*/
|
||||
readonly addTooltipToDescribedby = input<boolean>(false);
|
||||
|
||||
private readonly isVisible = signal(false);
|
||||
private overlayRef: OverlayRef | undefined;
|
||||
private elementRef = inject(ElementRef);
|
||||
private elementRef = inject<ElementRef<HTMLElement>>(ElementRef);
|
||||
private overlay = inject(Overlay);
|
||||
private viewContainerRef = inject(ViewContainerRef);
|
||||
private injector = inject(Injector);
|
||||
private positionStrategy = this.overlay
|
||||
.position()
|
||||
.flexibleConnectedTo(this.elementRef)
|
||||
.withFlexibleDimensions(false)
|
||||
.withPush(true);
|
||||
private tooltipId = `bit-tooltip-${TooltipDirective.nextId++}`;
|
||||
private currentDescribedByIds =
|
||||
this.elementRef.nativeElement.getAttribute("aria-describedby") || null;
|
||||
|
||||
private tooltipPortal = new ComponentPortal(
|
||||
TooltipComponent,
|
||||
@@ -59,23 +69,50 @@ export class TooltipDirective implements OnInit {
|
||||
{
|
||||
provide: TOOLTIP_DATA,
|
||||
useValue: {
|
||||
content: this.bitTooltip,
|
||||
content: this.tooltipContent,
|
||||
isVisible: this.isVisible,
|
||||
tooltipPosition: this.tooltipPosition,
|
||||
id: signal(this.tooltipId),
|
||||
},
|
||||
},
|
||||
],
|
||||
}),
|
||||
);
|
||||
|
||||
private destroyTooltip = () => {
|
||||
this.overlayRef?.dispose();
|
||||
this.overlayRef = undefined;
|
||||
this.isVisible.set(false);
|
||||
};
|
||||
|
||||
private showTooltip = () => {
|
||||
if (!this.overlayRef) {
|
||||
this.overlayRef = this.overlay.create({
|
||||
...this.defaultPopoverConfig,
|
||||
positionStrategy: this.positionStrategy,
|
||||
});
|
||||
|
||||
this.overlayRef.attach(this.tooltipPortal);
|
||||
}
|
||||
this.isVisible.set(true);
|
||||
};
|
||||
|
||||
private hideTooltip = () => {
|
||||
this.isVisible.set(false);
|
||||
this.destroyTooltip();
|
||||
};
|
||||
|
||||
private readonly resolvedDescribedByIds = computed(() => {
|
||||
if (this.addTooltipToDescribedby()) {
|
||||
if (this.currentDescribedByIds) {
|
||||
return `${this.currentDescribedByIds || ""} ${this.tooltipId}`;
|
||||
} else {
|
||||
return this.tooltipId;
|
||||
}
|
||||
} else {
|
||||
return this.currentDescribedByIds;
|
||||
}
|
||||
});
|
||||
|
||||
private computePositions(tooltipPosition: TooltipPositionIdentifier) {
|
||||
const chosenPosition = tooltipPositions.find((position) => position.id === tooltipPosition);
|
||||
|
||||
@@ -91,20 +128,5 @@ export class TooltipDirective implements OnInit {
|
||||
|
||||
ngOnInit() {
|
||||
this.positionStrategy.withPositions(this.computePositions(this.tooltipPosition()));
|
||||
|
||||
this.overlayRef = this.overlay.create({
|
||||
...this.defaultPopoverConfig,
|
||||
positionStrategy: this.positionStrategy,
|
||||
});
|
||||
|
||||
this.overlayRef.attach(this.tooltipPortal);
|
||||
|
||||
effect(
|
||||
() => {
|
||||
this.positionStrategy.withPositions(this.computePositions(this.tooltipPosition()));
|
||||
this.overlayRef?.updatePosition();
|
||||
},
|
||||
{ injector: this.injector },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user