diff --git a/jey_theme/public/css/shared.css b/jey_theme/public/css/shared.css index c33c331..775a75f 100644 --- a/jey_theme/public/css/shared.css +++ b/jey_theme/public/css/shared.css @@ -253,7 +253,8 @@ input.list-header-checkbox:checked::after { Sidebar — Full Width button ========================================================================== */ -.jey-fullwidth-link { +.jey-fullwidth-link, +.jey-theme-switch-link { text-decoration: none; font-size: var(--text-sm); display: flex; @@ -262,18 +263,28 @@ input.list-header-checkbox:checked::after { margin-bottom: 8px; } -.jey-fullwidth-link svg { +.jey-fullwidth-link svg, +.jey-theme-switch-link svg { margin: 0; flex: 0 0 auto; } -.jey-fullwidth-link span { +.jey-fullwidth-link span, +.jey-theme-switch-link span { margin-left: 10px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } +/* Hide dropdown items now surfaced as sidebar buttons */ +.dropdown-menu-item[onclick*="toggle_full_width"], +.dropdown-menu-item[onclick*="ThemeSwitcher"], +.dropdown-menu-item[data-name="website"], +.dropdown-menu-item.jey-hidden-item { + display: none !important; +} + /* ========================================================================== Desktop Icons — Shared base (all themes) @@ -467,7 +478,14 @@ input.list-header-checkbox:checked::after { transition: transform 0.3s cubic-bezier(0.22, 1, 0.36, 1), box-shadow 0.35s ease; position: relative; - overflow: hidden; +} + +/* Allow dropdown menus (card actions) to escape the widget box. + Without this, .widget-control .dropdown-menu is clipped/hidden behind + the widget because Popper renders it as a sibling inside the box. */ +.ce-block .links-widget-box .widget-control .dropdown-menu, +.ce-block .number-widget-box .widget-control .dropdown-menu { + z-index: 1050; } /* Stagger delays */ @@ -503,7 +521,7 @@ input.list-header-checkbox:checked::after { } to { opacity: 1; - transform: translateY(0) scale(1); + transform: none; } } @@ -535,6 +553,18 @@ input.list-header-checkbox:checked::after { 0 2px 6px rgba(0, 0, 0, 0.04); } +/* Lift widget above its siblings while hovered or while its card-actions + dropdown is open, so the dropdown menu (which lives inside the widget's + own stacking context due to transform) is not covered by neighbours. */ +.ce-block .links-widget-box:hover, +.ce-block .number-widget-box:hover, +.ce-block .links-widget-box:focus-within, +.ce-block .number-widget-box:focus-within, +.ce-block .links-widget-box:has(.dropdown-menu.show), +.ce-block .number-widget-box:has(.dropdown-menu.show) { + z-index: 100; +} + /* Link items: colored left accent bar slides in */ .links-widget-box .link-item { @@ -604,6 +634,90 @@ input.list-header-checkbox:checked::after { background-position: center; } +/* ========================================================================== + Notification Bell — Top-right position + ========================================================================== */ + +/* Hide original notification button in sidebar */ +.sidebar-notification { + display: none !important; +} + +/* Fixed wrapper */ +.jey-topright-wrapper { + position: fixed; + top: 16px; + right: 24px; + z-index: 1039; +} + +/* Bell button */ +.jey-topright-bell { + position: relative; + width: 36px; + height: 36px; + border-radius: 50%; + background: var(--card-bg, #fff); + border: 1px solid var(--border-color, #e2e2e2); + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + box-shadow: 0 1px 4px rgba(0,0,0,0.08); + transition: box-shadow 0.2s, background 0.2s; + color: var(--text-muted, #74808b); + padding: 0; +} + +.jey-topright-bell:hover { + background: var(--subtle-fg, #f5f5f5); + box-shadow: 0 2px 8px rgba(0,0,0,0.12); + color: var(--text-color, #333); +} + +/* Unread count badge */ +.jey-notif-badge { + position: absolute; + top: -4px; + right: -4px; + background: var(--red-500, #e53e3e); + color: #fff; + font-size: 10px; + font-weight: 600; + min-width: 16px; + height: 16px; + line-height: 16px; + border-radius: 8px; + text-align: center; + padding: 0 4px; + pointer-events: none; +} + +/* Notification dropdown panel */ +.jey-topright-wrapper .dropdown-notifications { + display: block; + position: absolute; + top: 44px; + right: 0; + width: 360px; + border-radius: 12px; + box-shadow: 0 8px 24px rgba(0,0,0,0.12), 0 2px 6px rgba(0,0,0,0.04); + background: var(--card-bg, #fff); + border: 1px solid var(--border-color, #e2e2e2); + max-height: 70vh; + overflow-y: auto; +} + +.jey-topright-wrapper .dropdown-notifications.hidden { + display: none !important; +} + +@media (max-width: 400px) { + .jey-topright-wrapper .dropdown-notifications { + width: calc(100vw - 32px); + } +} + /* ========================================================================== Hide "Getting Started" onboarding panels ========================================================================== */ diff --git a/jey_theme/public/js/jey_theme.js b/jey_theme/public/js/jey_theme.js index f0ef256..b92057d 100644 --- a/jey_theme/public/js/jey_theme.js +++ b/jey_theme/public/js/jey_theme.js @@ -677,6 +677,238 @@ $(document).on("page-change", addFullWidthButton); })(); +/* ========================================================================== + Toggle Theme button in sidebar (above Collapse) + ========================================================================== */ + +(function () { + "use strict"; + + function addThemeSwitchButton() { + var collapse = document.querySelector(".collapse-sidebar-link"); + if (!collapse || document.querySelector(".jey-theme-switch-link")) return; + + var link = document.createElement("a"); + link.className = "jey-theme-switch-link"; + link.innerHTML = + frappe.utils.icon("palette", "sm", "", "", "text-ink-gray-7 current-color", true) + + "" + __("Switch Theme") + ""; + + link.addEventListener("click", function () { + new frappe.ui.ThemeSwitcher().show(); + }); + + collapse.parentNode.insertBefore(link, collapse); + } + + $(document).ready(addThemeSwitchButton); + $(document).on("page-change", addThemeSwitchButton); +})(); + +/* ========================================================================== + Hide "Website" item from sidebar user dropdown + ========================================================================== */ + +(function () { + "use strict"; + + function tagWebsiteItem() { + var label = __("Website"); + document.querySelectorAll(".dropdown-menu-item:not(.jey-hidden-item)").forEach(function (el) { + var title = el.querySelector(".menu-item-title"); + if (title && title.textContent.trim() === label) { + el.classList.add("jey-hidden-item"); + } + }); + } + + $(document).ready(tagWebsiteItem); + var observer = new MutationObserver(tagWebsiteItem); + observer.observe(document.body, { childList: true, subtree: true }); +})(); + +/* ========================================================================== + Reload button next to Notifications in sidebar + ========================================================================== */ + +(function () { + "use strict"; + + function addReloadButton() { + var notif = document.querySelector(".sidebar-notification"); + if (!notif || document.querySelector(".jey-reload-btn")) return; + + var btn = notif.cloneNode(true); + btn.classList.remove("sidebar-notification"); + btn.classList.remove("hidden"); + btn.classList.add("jey-reload-btn"); + btn.removeAttribute("id"); + btn.setAttribute("title", __("Reload")); + + var iconWrap = btn.querySelector(".sidebar-item-icon"); + if (iconWrap) { + iconWrap.setAttribute("item-icon", "rotate-ccw"); + iconWrap.innerHTML = frappe.utils.icon( + "rotate-ccw", "sm", "", "", "text-ink-gray-7 current-color", true + ); + } + var label = btn.querySelector(".sidebar-item-label"); + if (label) label.textContent = __("Reload"); + + btn.addEventListener("click", function (e) { + e.preventDefault(); + e.stopPropagation(); + frappe.ui.toolbar.clear_cache(); + }); + + notif.parentNode.insertBefore(btn, notif.nextSibling); + } + + $(document).ready(function () { + addReloadButton(); + var tries = 0; + var poll = setInterval(function () { + addReloadButton(); + if (++tries > 30 || document.querySelector(".jey-reload-btn")) { + clearInterval(poll); + } + }, 300); + }); + $(document).on("page-change", addReloadButton); +})(); + +/* ========================================================================== + Notification Bell — Move to top-right corner + ========================================================================== */ + +(function () { + "use strict"; + + function moveNotifications() { + var dropdown = document.querySelector(".standard-items-sections .dropdown-notifications, .body-sidebar .dropdown-notifications:not(.jey-moved)"); + var wrapper = document.querySelector(".jey-topright-wrapper"); + + // A new dropdown appeared after page rebuild — move it into existing wrapper + if (dropdown && wrapper) { + dropdown.classList.add("jey-moved", "hidden"); + dropdown.parentNode.removeChild(dropdown); + var old = wrapper.querySelector(".dropdown-notifications"); + if (old) old.remove(); + wrapper.appendChild(dropdown); + return; + } + + if (wrapper) return; // already set up + if (!dropdown) return; // nothing to move yet + + // --- First-time setup --- + wrapper = document.createElement("div"); + wrapper.className = "jey-topright-wrapper"; + + var bell = document.createElement("button"); + bell.className = "jey-topright-bell btn-reset"; + bell.setAttribute("title", __("Notifications")); + bell.innerHTML = + '' + + '' + + '' + + ''; + wrapper.appendChild(bell); + + dropdown.classList.add("jey-moved", "hidden"); + dropdown.parentNode.removeChild(dropdown); + wrapper.appendChild(dropdown); + + document.body.appendChild(wrapper); + + function updateBadge() { + var dd = wrapper.querySelector(".dropdown-notifications"); + if (!dd) return; + var unread = dd.querySelectorAll(".notification-item.unread").length; + var badge = bell.querySelector(".jey-notif-badge"); + if (unread > 0) { + if (!badge) { + badge = document.createElement("span"); + badge.className = "jey-notif-badge"; + bell.appendChild(badge); + } + badge.textContent = unread > 9 ? "9+" : unread; + } else if (badge) { + badge.remove(); + } + } + + bell.addEventListener("click", function (e) { + e.preventDefault(); + e.stopPropagation(); + var dd = wrapper.querySelector(".dropdown-notifications"); + if (dd) dd.classList.toggle("hidden"); + updateBadge(); + }); + + document.addEventListener("click", function (e) { + if (!wrapper.contains(e.target)) { + var dd = wrapper.querySelector(".dropdown-notifications"); + if (dd) dd.classList.add("hidden"); + } + }); + + wrapper.addEventListener("click", function (e) { + if (e.target.closest(".close-notification-dialogue")) { + var dd = wrapper.querySelector(".dropdown-notifications"); + if (dd) dd.classList.add("hidden"); + } + }); + + updateBadge(); + var obs = new MutationObserver(updateBadge); + obs.observe(dropdown, { childList: true, subtree: true, attributes: true, attributeFilter: ["class"] }); + } + + $(document).ready(function () { + moveNotifications(); + var tries = 0; + var poll = setInterval(function () { + moveNotifications(); + if (++tries > 30) clearInterval(poll); + }, 300); + }); + $(document).on("page-change", moveNotifications); +})(); + +/* ========================================================================== + Fix: nested menus left orphaned when parent menu is toggled closed + (sidebar-header app-switcher → Workspaces submenu stays on screen) + ========================================================================== */ + +(function () { + "use strict"; + + function patchMenuHide() { + if (!window.frappe || !frappe.ui || !frappe.ui.menu) return false; + var proto = frappe.ui.menu.prototype; + if (proto.__jeyHidePatched) return true; + var origHide = proto.hide; + proto.hide = function () { + if (this.nested_menus && this.nested_menus.length) { + this.nested_menus.forEach(function (m) { m && m.hide && m.hide(); }); + } + this.current_menu = null; + return origHide.apply(this, arguments); + }; + proto.__jeyHidePatched = true; + return true; + } + + if (!patchMenuHide()) { + var tries = 0; + var poll = setInterval(function () { + if (patchMenuHide() || ++tries > 50) clearInterval(poll); + }, 200); + } +})(); + /* ========================================================================== Override Frappe Theme Switcher — Jey themes ========================================================================== */