mirror of
https://github.com/Cleanuparr/Cleanuparr.git
synced 2026-03-24 17:13:41 -04:00
fixed search stats tooltips
This commit is contained in:
@@ -94,6 +94,13 @@
|
||||
}
|
||||
|
||||
.instance-card {
|
||||
position: relative;
|
||||
z-index: 0;
|
||||
|
||||
&:has(.tooltip-bubble--visible) {
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
&__header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
@@ -1,4 +1,12 @@
|
||||
<span class="tooltip-wrapper">
|
||||
<span class="tooltip-wrapper" #wrapper>
|
||||
<ng-content />
|
||||
<span class="tooltip-bubble" [class]="'tooltip-bubble--' + position()">{{ text() }}</span>
|
||||
<span
|
||||
#bubble
|
||||
class="tooltip-bubble"
|
||||
[class.tooltip-bubble--top]="resolvedPosition() === 'top'"
|
||||
[class.tooltip-bubble--bottom]="resolvedPosition() === 'bottom'"
|
||||
[class.tooltip-bubble--left]="resolvedPosition() === 'left'"
|
||||
[class.tooltip-bubble--right]="resolvedPosition() === 'right'"
|
||||
[class.tooltip-bubble--visible]="visible()"
|
||||
>{{ text() }}</span>
|
||||
</span>
|
||||
|
||||
@@ -3,39 +3,55 @@
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
cursor: help;
|
||||
|
||||
&:hover .tooltip-bubble {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
transform: translateX(-50%) translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.tooltip-bubble {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
transform: translateX(-50%) translateY(4px);
|
||||
padding: var(--space-2) var(--space-3);
|
||||
background: var(--surface-overlay, rgba(30, 20, 50, 0.95));
|
||||
border: 1px solid var(--glass-border);
|
||||
border-radius: var(--radius-md);
|
||||
font-size: var(--font-size-xs);
|
||||
color: var(--text-primary);
|
||||
white-space: nowrap;
|
||||
white-space: normal;
|
||||
width: max-content;
|
||||
max-width: 220px;
|
||||
text-align: center;
|
||||
z-index: var(--z-tooltip);
|
||||
pointer-events: none;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
transition: opacity var(--duration-fast) ease, visibility var(--duration-fast) ease, transform var(--duration-fast) ease;
|
||||
transition: opacity var(--duration-fast) ease, visibility var(--duration-fast) ease;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
|
||||
|
||||
&--visible {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
// Vertical positions (top/bottom): centered horizontally
|
||||
&--top {
|
||||
bottom: calc(100% + 6px);
|
||||
top: auto;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
|
||||
&--bottom {
|
||||
top: calc(100% + 6px);
|
||||
bottom: auto;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
|
||||
// Horizontal positions (left/right): centered vertically
|
||||
&--left {
|
||||
right: calc(100% + 6px);
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
|
||||
&--right {
|
||||
left: calc(100% + 6px);
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import { Component, ChangeDetectionStrategy, input } from '@angular/core';
|
||||
import { Component, ChangeDetectionStrategy, input, signal, ElementRef, viewChild } from '@angular/core';
|
||||
|
||||
type TooltipPosition = 'top' | 'bottom' | 'left' | 'right';
|
||||
|
||||
@Component({
|
||||
selector: 'app-tooltip',
|
||||
@@ -6,8 +8,88 @@ import { Component, ChangeDetectionStrategy, input } from '@angular/core';
|
||||
templateUrl: './tooltip.component.html',
|
||||
styleUrl: './tooltip.component.scss',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
host: {
|
||||
'(mouseenter)': 'onMouseEnter()',
|
||||
'(mouseleave)': 'onMouseLeave()',
|
||||
},
|
||||
})
|
||||
export class TooltipComponent {
|
||||
text = input.required<string>();
|
||||
position = input<'top' | 'bottom'>('top');
|
||||
position = input<TooltipPosition>('top');
|
||||
|
||||
resolvedPosition = signal<TooltipPosition>('top');
|
||||
visible = signal(false);
|
||||
|
||||
private wrapper = viewChild<ElementRef<HTMLElement>>('wrapper');
|
||||
|
||||
onMouseEnter(): void {
|
||||
this.resolvedPosition.set(this.calculatePosition());
|
||||
this.visible.set(true);
|
||||
}
|
||||
|
||||
onMouseLeave(): void {
|
||||
this.visible.set(false);
|
||||
}
|
||||
|
||||
private calculatePosition(): TooltipPosition {
|
||||
const preferred = this.position();
|
||||
const wrapperEl = this.wrapper()?.nativeElement;
|
||||
if (!wrapperEl) return preferred;
|
||||
|
||||
const rect = wrapperEl.getBoundingClientRect();
|
||||
const margin = 12;
|
||||
const estimatedTooltipHeight = 50;
|
||||
const estimatedTooltipWidth = 230;
|
||||
|
||||
// Find the actual content boundary (accounts for sidebar)
|
||||
const contentArea = wrapperEl.closest('.shell__content') as HTMLElement | null;
|
||||
const contentLeft = contentArea ? contentArea.getBoundingClientRect().left : 0;
|
||||
|
||||
const spaceTop = rect.top;
|
||||
const spaceBottom = window.innerHeight - rect.bottom;
|
||||
const spaceLeft = rect.left - contentLeft;
|
||||
const spaceRight = window.innerWidth - rect.right;
|
||||
|
||||
const hasSpace: Record<TooltipPosition, boolean> = {
|
||||
top: spaceTop > estimatedTooltipHeight + margin,
|
||||
bottom: spaceBottom > estimatedTooltipHeight + margin,
|
||||
left: spaceLeft > estimatedTooltipWidth + margin,
|
||||
right: spaceRight > estimatedTooltipWidth + margin,
|
||||
};
|
||||
|
||||
if (hasSpace[preferred] && (preferred === 'left' || preferred === 'right')) {
|
||||
return preferred;
|
||||
}
|
||||
|
||||
if (hasSpace[preferred] && (preferred === 'top' || preferred === 'bottom')) {
|
||||
const halfTooltip = estimatedTooltipWidth / 2;
|
||||
const centerX = rect.left + rect.width / 2;
|
||||
|
||||
// Check if centered tooltip would overflow left (behind sidebar)
|
||||
if (centerX - halfTooltip < contentLeft + margin) {
|
||||
if (hasSpace['right']) return 'right';
|
||||
if (hasSpace['bottom']) return 'bottom';
|
||||
}
|
||||
|
||||
// Check if centered tooltip would overflow right
|
||||
if (centerX + halfTooltip > window.innerWidth - margin) {
|
||||
if (hasSpace['left']) return 'left';
|
||||
if (hasSpace['bottom']) return 'bottom';
|
||||
}
|
||||
|
||||
return preferred;
|
||||
}
|
||||
|
||||
// Fallback order
|
||||
const fallbacks: TooltipPosition[] =
|
||||
preferred === 'top' || preferred === 'bottom'
|
||||
? ['top', 'bottom', 'right', 'left']
|
||||
: ['right', 'left', 'top', 'bottom'];
|
||||
|
||||
for (const pos of fallbacks) {
|
||||
if (hasSpace[pos]) return pos;
|
||||
}
|
||||
|
||||
return preferred;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user