diff --git a/bicorder-app/src/components/Tooltip.svelte b/bicorder-app/src/components/Tooltip.svelte index cfc7dc0..6b8f1e6 100644 --- a/bicorder-app/src/components/Tooltip.svelte +++ b/bicorder-app/src/components/Tooltip.svelte @@ -4,12 +4,16 @@ let showTooltip = false; let touchTimer: number; let isLongPress = false; + let tooltipElement: HTMLSpanElement; + let wrapperElement: HTMLSpanElement; + let tooltipPosition = { align: 'center', vertical: 'top' }; function handleTouchStart() { isLongPress = false; touchTimer = window.setTimeout(() => { showTooltip = true; isLongPress = true; + updateTooltipPosition(); }, 500); // Show after 500ms press } @@ -29,6 +33,7 @@ showTooltip = !showTooltip; // Auto-hide after 3 seconds if (showTooltip) { + updateTooltipPosition(); setTimeout(() => { showTooltip = false; }, 3000); @@ -39,14 +44,43 @@ function handleMouseEnter() { showTooltip = true; + // Use requestAnimationFrame to ensure DOM is updated before positioning + requestAnimationFrame(() => updateTooltipPosition()); } function handleMouseLeave() { showTooltip = false; } + + function updateTooltipPosition() { + if (!tooltipElement || !wrapperElement) return; + + const tooltipRect = tooltipElement.getBoundingClientRect(); + const wrapperRect = wrapperElement.getBoundingClientRect(); + const viewportWidth = window.innerWidth; + const viewportHeight = window.innerHeight; + const padding = 10; // Minimum padding from screen edge + + // Check horizontal overflow + let align = 'center'; + if (tooltipRect.left < padding) { + align = 'left'; + } else if (tooltipRect.right > viewportWidth - padding) { + align = 'right'; + } + + // Check vertical overflow (if tooltip would go above viewport) + let vertical = 'top'; + if (tooltipRect.top < padding) { + vertical = 'bottom'; + } + + tooltipPosition = { align, vertical }; + } {#if showTooltip} - + {text} {/if} @@ -79,8 +122,6 @@ .tooltip-text { position: absolute; bottom: 125%; - left: 50%; - transform: translateX(-50%); background-color: var(--bg-color); color: var(--fg-color); border: 2px solid var(--border-color); @@ -94,13 +135,37 @@ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3); } + /* Horizontal alignment variants */ + .tooltip-text.align-center { + left: 50%; + transform: translateX(-50%); + } + + .tooltip-text.align-left { + left: 0; + transform: none; + } + + .tooltip-text.align-right { + right: 0; + left: auto; + transform: none; + } + + /* Vertical position variant (when tooltip would overflow top) */ + .tooltip-text.position-bottom { + bottom: auto; + top: 125%; + } + @media (prefers-color-scheme: dark) { .tooltip-text { box-shadow: 0 2px 8px rgba(255, 255, 255, 0.2); } } - .tooltip-text::after { + /* Arrow positioning for center-aligned tooltip */ + .tooltip-text.align-center::after { content: ''; position: absolute; top: 100%; @@ -111,9 +176,49 @@ border-color: var(--border-color) transparent transparent transparent; } + .tooltip-text.align-center.position-bottom::after { + top: auto; + bottom: 100%; + border-color: transparent transparent var(--border-color) transparent; + } + + /* Arrow positioning for left-aligned tooltip */ + .tooltip-text.align-left::after { + content: ''; + position: absolute; + top: 100%; + left: 1rem; + border-width: 5px; + border-style: solid; + border-color: var(--border-color) transparent transparent transparent; + } + + .tooltip-text.align-left.position-bottom::after { + top: auto; + bottom: 100%; + border-color: transparent transparent var(--border-color) transparent; + } + + /* Arrow positioning for right-aligned tooltip */ + .tooltip-text.align-right::after { + content: ''; + position: absolute; + top: 100%; + right: 1rem; + border-width: 5px; + border-style: solid; + border-color: var(--border-color) transparent transparent transparent; + } + + .tooltip-text.align-right.position-bottom::after { + top: auto; + bottom: 100%; + border-color: transparent transparent var(--border-color) transparent; + } + @media (max-width: 768px) { .tooltip-text { - max-width: 250px; + max-width: calc(100vw - 20px); font-size: 0.8rem; } }