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:
110
libs/components/src/tooltip/tooltip.directive.ts
Normal file
110
libs/components/src/tooltip/tooltip.directive.ts
Normal 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 },
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user