From 0ed2bd7ffd4315b5f1c6c20c8a9ec2cf14f22fa3 Mon Sep 17 00:00:00 2001 From: Flaminel Date: Mon, 23 Mar 2026 11:55:37 +0200 Subject: [PATCH] fixed search stats tooltips --- .../search-stats/search-stats.component.scss | 7 ++ .../src/app/ui/tooltip/tooltip.component.html | 12 ++- .../src/app/ui/tooltip/tooltip.component.scss | 40 ++++++--- .../src/app/ui/tooltip/tooltip.component.ts | 86 ++++++++++++++++++- 4 files changed, 129 insertions(+), 16 deletions(-) diff --git a/code/frontend/src/app/features/search-stats/search-stats.component.scss b/code/frontend/src/app/features/search-stats/search-stats.component.scss index 25e0474e..e4fc633f 100644 --- a/code/frontend/src/app/features/search-stats/search-stats.component.scss +++ b/code/frontend/src/app/features/search-stats/search-stats.component.scss @@ -94,6 +94,13 @@ } .instance-card { + position: relative; + z-index: 0; + + &:has(.tooltip-bubble--visible) { + z-index: 1; + } + &__header { display: flex; align-items: center; diff --git a/code/frontend/src/app/ui/tooltip/tooltip.component.html b/code/frontend/src/app/ui/tooltip/tooltip.component.html index c93b35cf..878fc979 100644 --- a/code/frontend/src/app/ui/tooltip/tooltip.component.html +++ b/code/frontend/src/app/ui/tooltip/tooltip.component.html @@ -1,4 +1,12 @@ - + - {{ text() }} + {{ text() }} diff --git a/code/frontend/src/app/ui/tooltip/tooltip.component.scss b/code/frontend/src/app/ui/tooltip/tooltip.component.scss index 0e237512..4205e82a 100644 --- a/code/frontend/src/app/ui/tooltip/tooltip.component.scss +++ b/code/frontend/src/app/ui/tooltip/tooltip.component.scss @@ -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%); } } diff --git a/code/frontend/src/app/ui/tooltip/tooltip.component.ts b/code/frontend/src/app/ui/tooltip/tooltip.component.ts index b6d4f913..2e50730e 100644 --- a/code/frontend/src/app/ui/tooltip/tooltip.component.ts +++ b/code/frontend/src/app/ui/tooltip/tooltip.component.ts @@ -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(); - position = input<'top' | 'bottom'>('top'); + position = input('top'); + + resolvedPosition = signal('top'); + visible = signal(false); + + private wrapper = viewChild>('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 = { + 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; + } }