feat: Enhance sidebar functionality with dynamic width adjustments and improved toggle interactions

This commit is contained in:
Matt Batchelder
2026-02-04 16:22:51 -05:00
parent f392e5d016
commit d8f8c0f916
4 changed files with 308 additions and 92 deletions

View File

@@ -27,8 +27,8 @@
{% set scheduleCount = currentUser.featureEnabledCount(["schedule.view", "daypart.view"]) %}
{% if scheduleCount > 0 %}
<li class="sidebar-group">
<a class="sidebar-group-toggle" href="#" aria-expanded="true">
<li class="sidebar-group is-open">
<a class="sidebar-group-toggle" href="#" aria-expanded="true" data-group="scheduling">
<span class="ots-nav-icon fa fa-calendar" aria-hidden="true"></span>
<span class="ots-nav-text">{% trans "Scheduling" %}</span>
<span class="sidebar-group-caret fa fa-chevron-down" aria-hidden="true"></span>
@@ -57,8 +57,8 @@
{% set countViewable = currentUser.featureEnabledCount(["library.view", "playlist.view", "dataset.view", "menuBoard.view"]) %}
{% if countViewable > 0 %}
<li class="sidebar-group">
<a class="sidebar-group-toggle" href="#" aria-expanded="true">
<li class="sidebar-group is-open">
<a class="sidebar-group-toggle" href="#" aria-expanded="true" data-group="media">
<span class="ots-nav-icon fa fa-picture-o" aria-hidden="true"></span>
<span class="ots-nav-text">{% trans "Media" %}</span>
<span class="sidebar-group-caret fa fa-chevron-down" aria-hidden="true"></span>
@@ -105,8 +105,8 @@
{% set countViewable = currentUser.featureEnabledCount(["campaign.view", "layout.view", "template.view", "resolution.view"]) %}
{% if countViewable > 0 %}
<li class="sidebar-group">
<a class="sidebar-group-toggle" href="#" aria-expanded="true">
<li class="sidebar-group is-open">
<a class="sidebar-group-toggle" href="#" aria-expanded="true" data-group="design">
<span class="ots-nav-icon fa fa-paint-brush" aria-hidden="true"></span>
<span class="ots-nav-text">{% trans "Design" %}</span>
<span class="sidebar-group-caret fa fa-chevron-down" aria-hidden="true"></span>
@@ -153,8 +153,8 @@
{% set countViewable = currentUser.featureEnabledCount(["displays.view", "displaygroup.view", "displayprofile.view", "playersoftware.view", "command.view", "display.syncView"]) %}
{% if countViewable > 0 %}
<li class="sidebar-group">
<a class="sidebar-group-toggle" href="#" aria-expanded="true">
<li class="sidebar-group is-open">
<a class="sidebar-group-toggle" href="#" aria-expanded="true" data-group="displays">
<span class="ots-nav-icon fa fa-desktop" aria-hidden="true"></span>
<span class="ots-nav-text">{% trans "Displays" %}</span>
<span class="sidebar-group-caret fa fa-chevron-down" aria-hidden="true"></span>
@@ -225,8 +225,8 @@
{% set countViewable = currentUser.featureEnabledCount(["usergroup.view", "module.view", "transition.view", "task.view", "tag.view", "font.view"]) %}
{% if countViewable > 0 or userMenuViewable %}
<li class="sidebar-group">
<a class="sidebar-group-toggle" href="#" aria-expanded="true">
<li class="sidebar-group is-open">
<a class="sidebar-group-toggle" href="#" aria-expanded="true" data-group="settings">
<span class="ots-nav-icon fa fa-cog" aria-hidden="true"></span>
<span class="ots-nav-text">{% trans "Settings" %}</span>
<span class="sidebar-group-caret fa fa-chevron-down" aria-hidden="true"></span>
@@ -327,8 +327,8 @@
{% set countViewable = currentUser.featureEnabledCount(["report.view", "report.scheduling", "report.saving"]) %}
{% if countViewable > 0 %}
<li class="sidebar-group">
<a class="sidebar-group-toggle" href="#" aria-expanded="true">
<li class="sidebar-group is-open">
<a class="sidebar-group-toggle" href="#" aria-expanded="true" data-group="reporting">
<span class="ots-nav-icon fa fa-bar-chart" aria-hidden="true"></span>
<span class="ots-nav-text">{% trans "Reporting" %}</span>
<span class="sidebar-group-caret fa fa-chevron-down" aria-hidden="true"></span>
@@ -366,8 +366,8 @@
{% set countViewable = currentUser.featureEnabledCount(["log.view", "sessions.view", "auditlog.view", "fault.view"]) %}
{% if countViewable > 0 %}
<li class="sidebar-group">
<a class="sidebar-group-toggle" href="#" aria-expanded="true">
<li class="sidebar-group is-open">
<a class="sidebar-group-toggle" href="#" aria-expanded="true" data-group="advanced">
<span class="ots-nav-icon fa fa-shield" aria-hidden="true"></span>
<span class="ots-nav-text">{% trans "Advanced" %}</span>
<span class="sidebar-group-caret fa fa-chevron-down" aria-hidden="true"></span>
@@ -414,8 +414,8 @@
{% set countViewable = currentUser.featureEnabledCount(["developer.edit"]) %}
{% if countViewable > 0 %}
<li class="sidebar-group">
<a class="sidebar-group-toggle" href="#" aria-expanded="true">
<li class="sidebar-group is-open">
<a class="sidebar-group-toggle" href="#" aria-expanded="true" data-group="developer">
<span class="ots-nav-icon fa fa-code" aria-hidden="true"></span>
<span class="ots-nav-text">{% trans "Developer" %}</span>
<span class="sidebar-group-caret fa fa-chevron-down" aria-hidden="true"></span>
@@ -435,25 +435,4 @@
</ul>
</div>
<div class="sidebar-footer">
<div class="sidebar-user">
<div class="user-avatar-lg" aria-hidden="true">
{{ currentUser.userName|slice(0, 1)|upper }}
</div>
<div class="user-details">
<div class="user-role">
<i class="fa fa-shield" aria-hidden="true"></i>
{% if currentUser.isSuperAdmin() %}
{% trans "Super Admin" %}
{% else %}
{% trans "User" %}
{% endif %}
</div>
<div class="user-name">{{ currentUser.userName }}</div>
</div>
</div>
<button class="sidebar-theme-toggle" type="button" aria-label="{% trans "Toggle theme" %}">
<i class="fa fa-sun-o" aria-hidden="true"></i>
</button>
</div>
</div>

View File

@@ -10,25 +10,59 @@
sidebarCollapsed: 'otsTheme:sidebarCollapsed'
};
/**
* Measure sidebar width and set CSS variable for layout
*/
function updateSidebarWidth() {
const sidebar = document.querySelector('.ots-sidebar');
if (!sidebar) return;
const collapsed = sidebar.classList.contains('collapsed');
const base = collapsed ? 88 : sidebar.offsetWidth || sidebar.getBoundingClientRect().width || 240;
const padding = 5;
const value = Math.max(88, Math.round(base + padding));
document.documentElement.style.setProperty('--ots-sidebar-width', `${value}px`);
}
/**
* Initialize sidebar toggle functionality
*/
function initSidebarToggle() {
const toggleBtn = document.querySelector('[data-action="toggle-sidebar"]');
const sidebar = document.querySelector('.ots-sidebar');
const collapseBtn = document.querySelector('.sidebar-collapse-btn');
const body = document.body;
if (!toggleBtn || !sidebar) return;
if (!sidebar) return;
toggleBtn.addEventListener('click', function(e) {
e.preventDefault();
sidebar.classList.toggle('active');
});
if (toggleBtn) {
toggleBtn.addEventListener('click', function(e) {
e.preventDefault();
sidebar.classList.toggle('active');
});
}
if (collapseBtn) {
const isCollapsed = localStorage.getItem(STORAGE_KEYS.sidebarCollapsed) === 'true';
if (isCollapsed) {
sidebar.classList.add('collapsed');
body.classList.add('ots-sidebar-collapsed');
}
collapseBtn.addEventListener('click', function(e) {
e.preventDefault();
const nowCollapsed = !sidebar.classList.contains('collapsed');
sidebar.classList.toggle('collapsed');
body.classList.toggle('ots-sidebar-collapsed', nowCollapsed);
localStorage.setItem(STORAGE_KEYS.sidebarCollapsed, nowCollapsed ? 'true' : 'false');
updateSidebarWidth();
});
}
// Close sidebar when clicking outside on mobile
document.addEventListener('click', function(e) {
if (window.innerWidth <= 768) {
const isClickInsideSidebar = sidebar.contains(e.target);
const isClickOnToggle = toggleBtn.contains(e.target);
const isClickOnToggle = toggleBtn && toggleBtn.contains(e.target);
if (!isClickInsideSidebar && !isClickOnToggle && sidebar.classList.contains('active')) {
sidebar.classList.remove('active');
@@ -37,6 +71,65 @@
});
}
/**
* Initialize sidebar section collapse/expand functionality
*/
function initSidebarSectionToggles() {
const groupToggles = document.querySelectorAll('.sidebar-group-toggle');
groupToggles.forEach(toggle => {
const group = toggle.closest('.sidebar-group');
const submenu = group ? group.querySelector('.sidebar-submenu') : null;
const caret = toggle.querySelector('.sidebar-group-caret');
if (submenu) {
const isOpen = group.classList.contains('is-open');
submenu.style.display = isOpen ? 'block' : 'none';
toggle.setAttribute('aria-expanded', isOpen.toString());
}
toggle.addEventListener('click', function(e) {
e.preventDefault();
const group = toggle.closest('.sidebar-group');
const submenu = group ? group.querySelector('.sidebar-submenu') : null;
if (!submenu) return;
const isOpen = group.classList.contains('is-open');
group.classList.toggle('is-open', !isOpen);
toggle.setAttribute('aria-expanded', (!isOpen).toString());
submenu.style.display = isOpen ? 'none' : 'block';
});
if (caret) {
caret.addEventListener('click', function(e) {
e.preventDefault();
e.stopPropagation();
toggle.click();
});
}
});
// Capture-phase handler to override any conflicting listeners
document.addEventListener('click', function(e) {
const caret = e.target.closest('.sidebar-group-caret');
const toggle = e.target.closest('.sidebar-group-toggle');
const target = toggle || (caret ? caret.closest('.sidebar-group-toggle') : null);
if (!target) return;
e.preventDefault();
e.stopPropagation();
const group = target.closest('.sidebar-group');
const submenu = group ? group.querySelector('.sidebar-submenu') : null;
if (!submenu) return;
const isOpen = group.classList.contains('is-open');
group.classList.toggle('is-open', !isOpen);
target.setAttribute('aria-expanded', (!isOpen).toString());
submenu.style.display = isOpen ? 'none' : 'block';
}, true);
}
/**
* Initialize dropdown menus
*/
@@ -224,6 +317,7 @@
} else {
sidebar.classList.add('mobile');
}
updateSidebarWidth();
});
}
@@ -353,6 +447,7 @@
*/
function init() {
initSidebarToggle();
initSidebarSectionToggles();
initDropdowns();
initSearch();
initPageInteractions();
@@ -360,6 +455,8 @@
enhanceTables();
makeResponsive();
initChartSafeguard();
updateSidebarWidth();
window.addEventListener('resize', updateSidebarWidth);
}
// Wait for DOM to be ready