1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-18 17:23:37 +00:00

[CL-227] Tooltip component (#16442)

* add tooltip component

* fix typescript errors

* fix more typescript errors

* remove css comments

* fix tooltip blocking mouse events

* move default position logic to shared util

* fix tooltip stories options

* add tooltip spec

* add offset arg to default positions

* add shadow to tooltip

* increase offset

* adding max width

* fix disabled button cursor

* add stronger position type

* fixing types

* change get positions function to type return correctly

* more fixing types

* default options object

* add mock to tooltip stories

* add figma link to story

* update positions file name. remove getter

* remove standalone. add comment about component use

* add jsdoc comment to directive inputs

* fix typo

* remove instances of setInput

* fix storybook injection error

* remove unneeded functions

* remove unneeded variables

* remove comment

* move popover positions back with component

* fix popover i18n mock

* creat etooltip positions file

* update test to account for change to setInput calls

* remove panel class as it's not necessary

* improve tooltip docs page

* use classes for styling. Simpliy position changes

* simplify tests. No longer need to track position changes

* move comment to correct place

* fix typos

* remove unnecessary standalone declaration
This commit is contained in:
Bryan Cunningham
2025-10-01 14:01:53 -04:00
committed by GitHub
parent de3759fa85
commit 08a022fa52
12 changed files with 644 additions and 1 deletions

View File

@@ -0,0 +1,110 @@
import { Overlay, OverlayConfig, OverlayRef } from "@angular/cdk/overlay";
import { ComponentPortal } from "@angular/cdk/portal";
import {
Directive,
ViewContainerRef,
inject,
OnInit,
ElementRef,
Injector,
input,
effect,
signal,
} from "@angular/core";
import { TooltipPositionIdentifier, tooltipPositions } from "./tooltip-positions";
import { TooltipComponent, TOOLTIP_DATA } from "./tooltip.component";
/**
* 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".
*/
@Directive({
selector: "[bitTooltip]",
host: {
"(mouseenter)": "showTooltip()",
"(mouseleave)": "hideTooltip()",
"(focus)": "showTooltip()",
"(blur)": "hideTooltip()",
},
})
export class TooltipDirective implements OnInit {
/**
* The value of this input is forwarded to the tooltip.component to render
*/
readonly bitTooltip = input.required<string>();
/**
* 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");
private isVisible = signal(false);
private overlayRef: OverlayRef | undefined;
private elementRef = inject(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 tooltipPortal = new ComponentPortal(
TooltipComponent,
this.viewContainerRef,
Injector.create({
providers: [
{
provide: TOOLTIP_DATA,
useValue: {
content: this.bitTooltip,
isVisible: this.isVisible,
tooltipPosition: this.tooltipPosition,
},
},
],
}),
);
private showTooltip = () => {
this.isVisible.set(true);
};
private hideTooltip = () => {
this.isVisible.set(false);
};
private computePositions(tooltipPosition: TooltipPositionIdentifier) {
const chosenPosition = tooltipPositions.find((position) => position.id === tooltipPosition);
return chosenPosition ? [chosenPosition, ...tooltipPositions] : tooltipPositions;
}
get defaultPopoverConfig(): OverlayConfig {
return {
hasBackdrop: false,
scrollStrategy: this.overlay.scrollStrategies.reposition(),
};
}
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 },
);
}
}