Enhance OTS Signage Theme with Icon Dashboard and UI Improvements
- Updated theme.js to refine dropdown initialization, excluding user menu handling. - Modified authed.twig to allow CSS variable theming for background and text colors, and adjusted content wrapper classes based on navigation state. - Adjusted override-styles.twig to improve layout responsiveness and ensure proper styling for horizontal navigation mode. - Enhanced theme-scripts.twig to improve user menu functionality, including repositioning and click handling. - Introduced a new dashboard-icon-page.twig for a stylized icon dashboard, featuring card-based buttons for quick access to various functionalities, along with custom styles for better user experience.
This commit is contained in:
@@ -379,53 +379,107 @@
|
||||
// Only handle the user-menu dropdown.
|
||||
// Let Bootstrap handle topbar nav dropdowns (Schedule, Design, etc.) natively
|
||||
// so that links like Dayparting navigate normally.
|
||||
const userDropdown = document.querySelector('#navbarUserMenu') && document.querySelector('#navbarUserMenu').closest('.dropdown');
|
||||
var userToggle = document.querySelector('#navbarUserMenu');
|
||||
if (!userToggle) return;
|
||||
|
||||
var userDropdown = userToggle.closest('.dropdown');
|
||||
if (!userDropdown) return;
|
||||
|
||||
const userMenu = userDropdown.querySelector('.dropdown-menu');
|
||||
var userMenu = userDropdown.querySelector('.dropdown-menu');
|
||||
if (!userMenu) return;
|
||||
|
||||
userDropdown.addEventListener('click', function(e) {
|
||||
if (e.target.closest('.user-btn') || e.target.closest('[aria-label="User menu"]') || e.target.closest('#navbarUserMenu')) {
|
||||
e.preventDefault();
|
||||
const nowActive = !userDropdown.classList.contains('active');
|
||||
userDropdown.classList.toggle('active');
|
||||
// ── Neutralize Bootstrap ──────────────────────────────────────────
|
||||
// Remove data-toggle so Bootstrap's delegated handler never fires.
|
||||
userToggle.removeAttribute('data-toggle');
|
||||
try {
|
||||
var jq = window.jQuery || window.$;
|
||||
if (jq) {
|
||||
jq(userToggle).off('.bs.dropdown');
|
||||
jq(userDropdown).off('.bs.dropdown');
|
||||
}
|
||||
} catch (e) {}
|
||||
|
||||
// Float / unfloat the user menu
|
||||
try {
|
||||
if (nowActive) {
|
||||
floatMenu(userMenu, userDropdown);
|
||||
} else {
|
||||
unfloatMenu(userMenu);
|
||||
}
|
||||
} catch (err) { /* ignore */ }
|
||||
// ── Move menu to <body> ONCE and leave it there ───────────────────
|
||||
// This escapes any overflow:hidden ancestors permanently.
|
||||
// We toggle visibility via the .ots-user-menu-open class (no DOM moves).
|
||||
document.body.appendChild(userMenu);
|
||||
// Mark it so the MutationObserver in observeAndFloatMenus() skips it
|
||||
userMenu.setAttribute('data-ots-floating', 'permanent');
|
||||
userMenu.setAttribute('data-ots-floating-obs', '1');
|
||||
// Start hidden
|
||||
userMenu.classList.add('ots-user-menu-body');
|
||||
userMenu.classList.remove('show', 'ots-floating-menu');
|
||||
|
||||
// Compute placement to avoid going off-screen
|
||||
const trigger = userDropdown.querySelector('#navbarUserMenu');
|
||||
if (trigger) {
|
||||
userMenu.classList.remove('dropdown-menu-left', 'dropdown-menu-right');
|
||||
const trigRect = trigger.getBoundingClientRect();
|
||||
const menuWidth = userMenu.offsetWidth || 220;
|
||||
const spaceRight = window.innerWidth - trigRect.right;
|
||||
const spaceLeft = trigRect.left;
|
||||
if (spaceRight < menuWidth && spaceLeft > menuWidth) {
|
||||
userMenu.classList.add('dropdown-menu-left');
|
||||
} else {
|
||||
userMenu.classList.add('dropdown-menu-right');
|
||||
}
|
||||
}
|
||||
function positionMenu() {
|
||||
var rect = userToggle.getBoundingClientRect();
|
||||
var menuWidth = userMenu.offsetWidth || 220;
|
||||
var spaceRight = window.innerWidth - rect.right;
|
||||
|
||||
// Vertically: below the avatar
|
||||
userMenu.style.top = Math.round(rect.bottom + 6) + 'px';
|
||||
|
||||
// Horizontally: align right edge to avatar right edge,
|
||||
// but fall back to left-aligned if not enough space
|
||||
if (spaceRight >= menuWidth) {
|
||||
userMenu.style.left = Math.round(rect.left) + 'px';
|
||||
userMenu.style.right = 'auto';
|
||||
} else {
|
||||
userMenu.style.left = 'auto';
|
||||
userMenu.style.right = Math.round(window.innerWidth - rect.right) + 'px';
|
||||
}
|
||||
}
|
||||
|
||||
function openUserMenu() {
|
||||
userDropdown.classList.remove('show');
|
||||
userMenu.classList.remove('show');
|
||||
positionMenu();
|
||||
userMenu.classList.add('ots-user-menu-open');
|
||||
userDropdown.classList.add('active');
|
||||
}
|
||||
|
||||
function closeUserMenu() {
|
||||
userMenu.classList.remove('ots-user-menu-open');
|
||||
userDropdown.classList.remove('active', 'show');
|
||||
userMenu.classList.remove('show');
|
||||
}
|
||||
|
||||
// ── Click handler on the toggle element itself ─────────────────────
|
||||
userToggle.addEventListener('click', function(e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
if (userMenu.classList.contains('ots-user-menu-open')) {
|
||||
closeUserMenu();
|
||||
} else {
|
||||
openUserMenu();
|
||||
}
|
||||
});
|
||||
|
||||
// Close user menu when clicking outside
|
||||
// ── Close when clicking outside ───────────────────────────────────
|
||||
document.addEventListener('click', function(e) {
|
||||
if (!userDropdown.contains(e.target) && !userMenu.contains(e.target)) {
|
||||
const hadActive = userDropdown.classList.contains('active');
|
||||
userDropdown.classList.remove('active');
|
||||
if (hadActive) {
|
||||
try { unfloatMenu(userMenu); } catch (err) {}
|
||||
}
|
||||
if (!userMenu.classList.contains('ots-user-menu-open')) return;
|
||||
if (userToggle.contains(e.target)) return;
|
||||
if (userMenu.contains(e.target)) return;
|
||||
closeUserMenu();
|
||||
});
|
||||
|
||||
// ── Close when a modal opens ─────────────────────────────────────
|
||||
// Menu items like Preferences / Edit Profile trigger Bootstrap modals
|
||||
// via .XiboFormButton — close the dropdown as soon as any modal shows.
|
||||
document.addEventListener('show.bs.modal', function() { closeUserMenu(); }, true);
|
||||
try {
|
||||
var jq = window.jQuery || window.$;
|
||||
if (jq) {
|
||||
jq(document).on('show.bs.modal', function() { closeUserMenu(); });
|
||||
}
|
||||
} catch (e) {}
|
||||
|
||||
// ── Reposition on scroll/resize ───────────────────────────────────
|
||||
window.addEventListener('scroll', function() {
|
||||
if (userMenu.classList.contains('ots-user-menu-open')) positionMenu();
|
||||
}, true);
|
||||
window.addEventListener('resize', function() {
|
||||
if (userMenu.classList.contains('ots-user-menu-open')) positionMenu();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -434,7 +488,10 @@
|
||||
* Adds `.ots-floating-menu` and positions absolutely based on the trigger rect.
|
||||
*/
|
||||
function floatMenu(menuEl, triggerEl) {
|
||||
if (!menuEl || !triggerEl || menuEl.getAttribute('data-ots-floating') === '1') return;
|
||||
if (!menuEl || !triggerEl) return;
|
||||
// Skip if already floating or permanently managed by initDropdowns
|
||||
var floatAttr = menuEl.getAttribute('data-ots-floating');
|
||||
if (floatAttr === '1' || floatAttr === 'permanent') return;
|
||||
try {
|
||||
// Remember original parent and next sibling so we can restore later
|
||||
menuEl._otsOriginalParent = menuEl.parentNode || null;
|
||||
@@ -511,16 +568,25 @@
|
||||
}
|
||||
|
||||
function unfloatMenu(menuEl) {
|
||||
if (!menuEl || menuEl.getAttribute('data-ots-floating') !== '1') return;
|
||||
if (!menuEl) return;
|
||||
var floatAttr = menuEl.getAttribute('data-ots-floating');
|
||||
// Skip permanently managed menus and menus that aren't floating
|
||||
if (floatAttr === 'permanent' || floatAttr !== '1') return;
|
||||
try {
|
||||
menuEl.removeAttribute('data-ots-floating');
|
||||
menuEl.classList.remove('ots-floating-menu');
|
||||
// Clear ALL inline styles that floatMenu() may have set (including
|
||||
// transform which was previously missed, causing stale styles).
|
||||
menuEl.style.position = '';
|
||||
menuEl.style.top = '';
|
||||
menuEl.style.left = '';
|
||||
menuEl.style.zIndex = '';
|
||||
menuEl.style.minWidth = '';
|
||||
menuEl.style.pointerEvents = '';
|
||||
menuEl.style.transform = '';
|
||||
menuEl.style.visibility = '';
|
||||
menuEl.style.display = '';
|
||||
menuEl.style.opacity = '';
|
||||
// Remove reposition listeners
|
||||
if (menuEl._otsReposition) {
|
||||
window.removeEventListener('scroll', menuEl._otsReposition, true);
|
||||
|
||||
Reference in New Issue
Block a user