ux(tooltip): fix(tooltip): move tooltip into top-bar, that fix issue with RTE languages
This commit is contained in:
@@ -1,29 +1,19 @@
|
|||||||
/* ── Item tooltip — "technical sheet" ─────────────────────────────────────── */
|
/* ── Item tooltip — "technical sheet" ─────────────────────────────────────── */
|
||||||
/* */
|
/* */
|
||||||
/* Overlays the search bar area in the top-bar while hovering a file item. */
|
/* Absolutely overlays `.search-slot` in the top-bar. The search bar stays in */
|
||||||
/* Spans from the sidebar right-edge to just before .user-controls. */
|
/* place underneath; only the tooltip's opacity is animated, so there is no */
|
||||||
|
/* second element to keep in sync and no display:none flicker between rapid */
|
||||||
|
/* hover transitions. RTL just works (inset:0). */
|
||||||
/* */
|
/* */
|
||||||
/* 3-column CSS grid: [icon] [label] [value] */
|
/* 3-column CSS grid: [icon] [label] [value] */
|
||||||
/* All values start at the same x position regardless of label width. */
|
/* All values start at the same x position regardless of label width. */
|
||||||
|
|
||||||
.path-tooltip {
|
.path-tooltip {
|
||||||
position: fixed;
|
/* Cover the search slot, sit above it (later in DOM order). */
|
||||||
|
position: absolute;
|
||||||
/* Fill most of the top-bar (topbar height 70px, 8px margin top/bottom) */
|
inset: 0;
|
||||||
top: 8px;
|
/* Opaque background so the search bar underneath is fully masked. */
|
||||||
|
background-color: var(--color-bg-surface);
|
||||||
/* Desktop: starts at sidebar right edge + top-bar padding */
|
|
||||||
left: calc(var(--sidebar-width) + 30px);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Right edge stops just before .user-controls:
|
|
||||||
* 30px (top-bar right padding) + ~90px (notif bell + gap + avatar btn) + 4px gap
|
|
||||||
* Using left + right instead of width so the browser computes it dynamically.
|
|
||||||
*/
|
|
||||||
right: 124px;
|
|
||||||
|
|
||||||
/* High enough to sit above the top-bar content */
|
|
||||||
z-index: 200;
|
|
||||||
|
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 1em max-content 1fr;
|
grid-template-columns: 1em max-content 1fr;
|
||||||
@@ -31,20 +21,24 @@
|
|||||||
row-gap: 6px;
|
row-gap: 6px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
/* Fill the top-bar height minus an 8px margin on each side */
|
|
||||||
height: 54px;
|
|
||||||
padding: 0 16px;
|
|
||||||
border-radius: 10px;
|
|
||||||
border: 2px solid var(--color-border-medium);
|
|
||||||
|
|
||||||
background-color: var(--color-bg-input);
|
|
||||||
color: var(--color-text-muted);
|
color: var(--color-text-muted);
|
||||||
font-size: 0.75rem;
|
font-size: 0.75rem;
|
||||||
|
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
|
|
||||||
/* avoid blinking when pointer moves between items */
|
/* Show instantly. The hide direction (.hidden) overrides this with a
|
||||||
transition: display 0.2s allow-discrete;
|
1s delay so the tooltip lingers after the mouse leaves an item;
|
||||||
|
rapid item-to-item moves cancel the pending fade before it fires. */
|
||||||
|
opacity: 1;
|
||||||
|
transition: opacity 0.2s ease 0s;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Hidden state — override the global `.hidden { display:none !important }`
|
||||||
|
so we can fade with opacity instead of snapping to display:none. */
|
||||||
|
.path-tooltip.hidden {
|
||||||
|
display: grid !important;
|
||||||
|
opacity: 0;
|
||||||
|
transition-delay: 1s;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Icon column */
|
/* Icon column */
|
||||||
@@ -85,23 +79,6 @@
|
|||||||
font-style: italic;
|
font-style: italic;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ── Narrow screens (sidebar hidden) ────────────────────────────────────── */
|
|
||||||
/* On mobile the sidebar slides off-screen; align after the #sidebar-toggle */
|
|
||||||
/* button (≈44px) that takes its place in the top-bar. */
|
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
|
||||||
.path-tooltip {
|
|
||||||
/* 16px topbar padding + ~44px sidebar-toggle + 8px gap */
|
|
||||||
left: 68px;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Right edge stops before search-toggle-btn + user-controls:
|
|
||||||
* ~40px (search-toggle) + 12px (gap) + ~90px (user-controls) + 16px (padding)
|
|
||||||
*/
|
|
||||||
right: 158px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ── Vignette inside tooltip ─────────────────────────────────────────────── */
|
/* ── Vignette inside tooltip ─────────────────────────────────────────────── */
|
||||||
/* The vignette name hard-codes 14px; scale it down to match the tooltip. */
|
/* The vignette name hard-codes 14px; scale it down to match the tooltip. */
|
||||||
|
|
||||||
|
|||||||
@@ -86,11 +86,14 @@
|
|||||||
color: var(--color-text-heading);
|
color: var(--color-text-heading);
|
||||||
}
|
}
|
||||||
|
|
||||||
.search-container {
|
/* Slot wrapping .search-container + #path-tooltip — it owns the flex slot
|
||||||
|
so the tooltip can absolutely overlay the search bar via inset:0 instead
|
||||||
|
of viewport-fixed positioning. */
|
||||||
|
.search-slot {
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
max-width: 600px;
|
max-width: 600px;
|
||||||
position: relative;
|
|
||||||
margin-right: 20px;
|
margin-right: 20px;
|
||||||
|
position: relative;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
@@ -100,6 +103,13 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.search-container {
|
||||||
|
flex-grow: 1;
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
.search-container input {
|
.search-container input {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 12px 50px 12px 44px;
|
padding: 12px 50px 12px 44px;
|
||||||
@@ -204,8 +214,8 @@
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Hide full search bar on mobile by default */
|
/* Hide full search slot on mobile by default */
|
||||||
.search-container {
|
.search-slot {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -224,7 +234,7 @@
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.top-bar--search-active .search-container {
|
.top-bar--search-active .search-slot {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
max-width: none;
|
max-width: none;
|
||||||
|
|||||||
+11
-6
@@ -136,12 +136,17 @@
|
|||||||
<button class="search-back-btn" id="search-back-btn" aria-label="Close search">
|
<button class="search-back-btn" id="search-back-btn" aria-label="Close search">
|
||||||
<i class="fas fa-arrow-left"></i>
|
<i class="fas fa-arrow-left"></i>
|
||||||
</button>
|
</button>
|
||||||
<div class="search-container">
|
<!-- Search slot — positioning context for the item tooltip, which
|
||||||
<i class="fas fa-search search-icon"></i>
|
absolutely overlays the search bar when an item is hovered. -->
|
||||||
<input type="text" id="search-input" data-i18n-placeholder="actions.search" placeholder="Search files, folders...">
|
<div class="search-slot">
|
||||||
<button id="search-button" class="search-button" data-i18n-title="actions.search_btn" title="Search">
|
<div class="search-container">
|
||||||
<i class="fas fa-search"></i>
|
<i class="fas fa-search search-icon"></i>
|
||||||
</button>
|
<input type="text" id="search-input" data-i18n-placeholder="actions.search" placeholder="Search files, folders...">
|
||||||
|
<button id="search-button" class="search-button" data-i18n-title="actions.search_btn" title="Search">
|
||||||
|
<i class="fas fa-search"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div id="path-tooltip" class="path-tooltip hidden" aria-hidden="true"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="user-controls">
|
<div class="user-controls">
|
||||||
|
|||||||
@@ -27,21 +27,19 @@ import { i18n } from '../core/i18n.js';
|
|||||||
import { systemUsers } from '../model/systemUsers.js';
|
import { systemUsers } from '../model/systemUsers.js';
|
||||||
|
|
||||||
// ── Tooltip DOM ───────────────────────────────────────────────────────────────
|
// ── Tooltip DOM ───────────────────────────────────────────────────────────────
|
||||||
|
//
|
||||||
|
// The tooltip element is a static node inside `.search-slot` (see index.html)
|
||||||
|
// and is absolutely positioned over `.search-container`. Show/hide is a
|
||||||
|
// simple `.hidden` toggle on the tooltip itself — the search bar underneath
|
||||||
|
// is never touched, so there's no two-element swap to keep in sync.
|
||||||
|
|
||||||
/** @returns {HTMLElement} */
|
/** @returns {HTMLElement | null} */
|
||||||
function _getOrCreateTooltip() {
|
function _getTooltip() {
|
||||||
let el = document.getElementById('path-tooltip');
|
return document.getElementById('path-tooltip');
|
||||||
if (!el) {
|
|
||||||
el = document.createElement('div');
|
|
||||||
el.id = 'path-tooltip';
|
|
||||||
el.className = 'path-tooltip hidden';
|
|
||||||
document.body.appendChild(el);
|
|
||||||
}
|
|
||||||
return el;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function _hide() {
|
function _hide() {
|
||||||
const el = document.getElementById('path-tooltip');
|
const el = _getTooltip();
|
||||||
if (el) el.classList.add('hidden');
|
if (el) el.classList.add('hidden');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -98,7 +96,8 @@ function _onEnter(e) {
|
|||||||
// Nothing to show — don't display an all-? tooltip.
|
// Nothing to show — don't display an all-? tooltip.
|
||||||
if (!ownerId && !path) return;
|
if (!ownerId && !path) return;
|
||||||
|
|
||||||
const tooltip = _getOrCreateTooltip();
|
const tooltip = _getTooltip();
|
||||||
|
if (!tooltip) return;
|
||||||
|
|
||||||
// Clear previous content.
|
// Clear previous content.
|
||||||
while (tooltip.firstChild) tooltip.removeChild(tooltip.firstChild);
|
while (tooltip.firstChild) tooltip.removeChild(tooltip.firstChild);
|
||||||
@@ -140,14 +139,19 @@ const _registry = new WeakMap();
|
|||||||
|
|
||||||
// ── Public API ────────────────────────────────────────────────────────────────
|
// ── Public API ────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
/** Containers known to expose `data-path`/`data-owner-id` for tooltip use.
|
||||||
|
* Add new opt-in row classes here when other views want the tooltip — each
|
||||||
|
* one must stamp the dataset attributes itself. */
|
||||||
|
const _TOOLTIP_SELECTOR = '.file-item, .ms-resource-row';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Attach tooltip listeners to every `.file-item` inside `container`.
|
* Attach tooltip listeners to every tooltip-capable row inside `container`.
|
||||||
* Items with neither `data-owner-id` nor `data-path` will not trigger the
|
* Rows with neither `data-owner-id` nor `data-path` will not trigger the
|
||||||
* tooltip. Safe to call repeatedly — already-wired elements are skipped.
|
* tooltip. Safe to call repeatedly — already-wired elements are skipped.
|
||||||
* @param {HTMLElement} container
|
* @param {HTMLElement} container
|
||||||
*/
|
*/
|
||||||
function init(container) {
|
function init(container) {
|
||||||
for (const item of container.querySelectorAll('.file-item')) {
|
for (const item of container.querySelectorAll(_TOOLTIP_SELECTOR)) {
|
||||||
const el = /** @type {HTMLElement} */ (item);
|
const el = /** @type {HTMLElement} */ (item);
|
||||||
if (_registry.has(el)) continue; // already wired
|
if (_registry.has(el)) continue; // already wired
|
||||||
|
|
||||||
@@ -161,12 +165,12 @@ function init(container) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove tooltip listeners from all `.file-item` elements inside `container`
|
* Remove tooltip listeners from all tooltip-capable rows inside `container`
|
||||||
* and hide any visible tooltip.
|
* and hide any visible tooltip.
|
||||||
* @param {HTMLElement} container
|
* @param {HTMLElement} container
|
||||||
*/
|
*/
|
||||||
function destroy(container) {
|
function destroy(container) {
|
||||||
for (const item of container.querySelectorAll('.file-item')) {
|
for (const item of container.querySelectorAll(_TOOLTIP_SELECTOR)) {
|
||||||
const el = /** @type {HTMLElement} */ (item);
|
const el = /** @type {HTMLElement} */ (item);
|
||||||
const h = _registry.get(el);
|
const h = _registry.get(el);
|
||||||
if (h) {
|
if (h) {
|
||||||
|
|||||||
Reference in New Issue
Block a user