From 3169c636c571056a857028d1957811dfa5f6b11a Mon Sep 17 00:00:00 2001 From: Novy San87 Date: Mon, 3 Aug 2026 18:08:57 +0700 Subject: [PATCH] Tooltip kustom premium via Alpine directive x-tooltip - Directive global di app.js: satu tooltip singleton di body, posisi dari getBoundingClientRect (fixed, tidak terpotong overflow-hidden), animasi fade + slide halus, delay 120ms anti-kedip, fallback ke bawah jika di tepi atas viewport, clamp ke viewport - Tooltip gelap glass: bg-slate-900/95 + ring + shadow + backdrop-blur - Badge kalender pelanggaran ('X pelanggaran') & presensi ('X siswa, Y alpha') beralih dari :title native ke x-tooltip - Sembunyi otomatis saat scroll/resize/klik --- resources/js/app.js | 83 +++++++++++++++++++++ resources/views/attendances/index.blade.php | 2 +- resources/views/dashboard/index.blade.php | 2 +- 3 files changed, 85 insertions(+), 2 deletions(-) diff --git a/resources/js/app.js b/resources/js/app.js index a8093be..e4d3716 100644 --- a/resources/js/app.js +++ b/resources/js/app.js @@ -4,4 +4,87 @@ import Alpine from 'alpinejs'; window.Alpine = Alpine; +/** + * x-tooltip — tooltip kustom premium (Alpine directive) + * + * Pemakaian: ... + * Satu elemen tooltip global di , posisi dihitung dari bounding rect + * (fixed), animasi fade + slide halus. Tidak terpotong elemen overflow-hidden. + */ +document.addEventListener('alpine:init', () => { + let sharedTip = null; + let showTimer = null; + let activeEl = null; + + const getTip = () => { + if (sharedTip) return sharedTip; + sharedTip = document.createElement('div'); + sharedTip.className = + 'pointer-events-none fixed z-[70] px-2.5 py-1.5 rounded-lg bg-slate-900/95 text-white text-[11px] font-semibold shadow-xl ring-1 ring-white/10 backdrop-blur-sm whitespace-nowrap transition-all duration-150 opacity-0 translate-y-1'; + sharedTip.style.display = 'none'; + document.body.appendChild(sharedTip); + return sharedTip; + }; + + const hide = () => { + window.clearTimeout(showTimer); + if (!sharedTip) return; + sharedTip.classList.add('opacity-0', 'translate-y-1'); + window.setTimeout(() => { + if (sharedTip) sharedTip.style.display = 'none'; + }, 150); + activeEl = null; + }; + + const show = (el, text) => { + if (!text) return; + const tip = getTip(); + tip.textContent = text; + tip.style.display = 'block'; + tip.classList.add('opacity-0', 'translate-y-1'); + + const r = el.getBoundingClientRect(); + const tw = tip.offsetWidth; + const th = tip.offsetHeight; + let x = Math.round(r.left + r.width / 2 - tw / 2); + let y = r.top - th - 8; + if (y < 8) y = r.bottom + 8; // fallback: tampil di bawah elemen + x = Math.max(8, Math.min(x, window.innerWidth - tw - 8)); + tip.style.left = x + 'px'; + tip.style.top = y + 'px'; + + requestAnimationFrame(() => { + tip.classList.remove('opacity-0', 'translate-y-1'); + }); + }; + + Alpine.directive('tooltip', (el, { expression }) => { + const getText = () => { + try { + return Alpine.evaluate(el, expression); + } catch { + return expression; + } + }; + + el.addEventListener('mouseenter', () => { + window.clearTimeout(showTimer); + const text = getText(); + activeEl = el; + // delay kecil agar tidak berkedip saat hover lintas elemen + showTimer = window.setTimeout(() => { + if (activeEl === el) show(el, text); + }, 120); + }); + + el.addEventListener('mouseleave', hide); + el.addEventListener('click', hide); + }); + + // Sembunyikan saat halaman digulir/di-klik (posisi berubah / sel kalender re-render) + window.addEventListener('scroll', hide, true); + window.addEventListener('resize', hide); + document.addEventListener('click', hide); +}); + Alpine.start(); diff --git a/resources/views/attendances/index.blade.php b/resources/views/attendances/index.blade.php index 6fa2e75..eafe1c4 100644 --- a/resources/views/attendances/index.blade.php +++ b/resources/views/attendances/index.blade.php @@ -132,7 +132,7 @@ diff --git a/resources/views/dashboard/index.blade.php b/resources/views/dashboard/index.blade.php index 55df271..4be8835 100644 --- a/resources/views/dashboard/index.blade.php +++ b/resources/views/dashboard/index.blade.php @@ -196,7 +196,7 @@