From e41a82bebe9e7b8039f6902a6e6e2813e9a566cd Mon Sep 17 00:00:00 2001 From: Bogdan Kitura Date: Thu, 13 Nov 2025 16:01:33 +0100 Subject: [PATCH] 14260-Magefan-Extensions-Panel-Redesign [in progress] --- view/adminhtml/templates/menu-magefan.phtml | 222 ++++++++++++++++++-- view/adminhtml/web/css/source/_module.less | 194 +++++++++++++++-- 2 files changed, 381 insertions(+), 35 deletions(-) diff --git a/view/adminhtml/templates/menu-magefan.phtml b/view/adminhtml/templates/menu-magefan.phtml index 9c824cd..184444b 100644 --- a/view/adminhtml/templates/menu-magefan.phtml +++ b/view/adminhtml/templates/menu-magefan.phtml @@ -12,32 +12,222 @@ * @var $mfSecureRenderer \Magefan\Community\Api\SecureHtmlRendererInterface */ ?> + + + ul[role=menu]'); - if (submenu.classList.contains('_show') && submenuDiv && submenuList) { - submenuList.style.maxHeight = (document.documentElement.clientHeight - 120) + 'px'; + document.addEventListener('DOMContentLoaded', function () { + const menu = document.querySelector('#menu-magefan-community-elements'); + if (!menu) return; + + // Get background color for submenu + const bgSubmenu = document.querySelector('.admin__menu .level-0 > .submenu'); + let bgColor = '#4a4542' + if (bgSubmenu) { + bgColor = window.getComputedStyle(bgSubmenu).backgroundColor; + } + + + // 1. Processing ALL .level-1.parent add title + button close + const level1Parents = menu.querySelectorAll('.level-1.parent'); + + level1Parents.forEach(parent => { + // 1.1. Get group title and submenu + const groupTitleSpan = parent.querySelector('.submenu-group-title span'); + const submenu = parent.querySelector('.submenu'); + + // 1.2. Set background color for submenu + submenu.style.backgroundColor = bgColor; + if (!groupTitleSpan || !submenu) return; + + // 1.2.1. Get title text + const titleText = groupTitleSpan.textContent.trim(); + + // 1.3. Check if title already exists + if (submenu.querySelector('.submenu-item-title')) return; + + // 2. Create Title + const itemTitle = document.createElement('strong'); + itemTitle.className = 'submenu-item-title'; + itemTitle.textContent = titleText; + + // 3. Create Close button + const closeBtn = document.createElement('a'); + closeBtn.href = '#'; + closeBtn.className = 'action-close-submenu'; + + // 4. Add elements to end submenu + submenu.prepend(closeBtn); + submenu.prepend(itemTitle); + + // 5. Add event listeners + closeBtn.addEventListener('click', function () { + parent.classList.remove('active'); + }); + + // 6. Close active submenu on click outside submenu + menu.querySelector('.action-close').addEventListener('click', function () { + parent.classList.remove('active'); + }); + + // 7. Open submenu on click parent + parent.addEventListener('click', function () { + var isOpen = document.querySelector('#menu-magefan-community-elements .level-1.parent.active .submenu._show'); + if (isOpen) { + isOpen.classList.remove('_show'); + } + this.classList.add('active'); + const submenuParentList = this.querySelector('.submenu'); + if (submenuParentList) { + submenuParentList.classList.toggle('_show'); + } + }); + }); + + + // Removing columns + const submenuContainer = menu.querySelector('.level-0 > .submenu > ul[role=\"menu\"]'); + if (submenuContainer) { + + // Finds all elements with classes inside .parent.level-1 .level-0 > .submenu + const allLevel1Parents = Array.from(menu.querySelectorAll('.level-0 > .submenu .parent.level-1')); + + // Clean (delete .column and everything inside) + submenuContainer.innerHTML = ''; + + // Add all .parent.level-1 directly to submenu > ul + allLevel1Parents.forEach(parent => { + submenuContainer.appendChild(parent); + }); + + // Sorting without columns + const level1Items = Array.from(submenuContainer.querySelectorAll('.parent.level-1')); + level1Items.sort((a, b) => { + const textA = (a.querySelector('.submenu-group-title span')?.textContent || '').trim(); + const textB = (b.querySelector('.submenu-group-title span')?.textContent || '').trim(); + return textA.localeCompare(textB); + }); + level1Items.forEach(item => submenuContainer.appendChild(item)); + + + // SEARCH AND FILTER + if (level1Items.length > 16) { + // Create a search Container + const searchContainer = document.createElement('div'); + searchContainer.className = 'mf-menu-search-container'; + + // Create Input + const searchInput = document.createElement('input'); + searchInput.type = 'text'; + searchInput.placeholder = 'Search in the menu...'; + searchInput.className = 'mf-menu-search-input'; + + // Search button close + const searchClose = document.createElement('div'); + searchClose.className = 'action-search-close'; + // searchClose.addEventListener('click', function () { + // searchInput.value = ''; + // }); + + // Insert Input in Container + searchContainer.appendChild(searchInput); + + // Insert button close + searchContainer.appendChild(searchClose); + + // Insert search Container to top submenu Container + submenuContainer.parentElement.insertBefore(searchContainer, submenuContainer); + + // Search filtering + searchInput.addEventListener('input', function() { + const searchText = this.value.toLowerCase().trim(); + + // Show/hide close button + if (this.value.length > 0) { + searchClose.classList.add('_show'); + } else { + searchClose.classList.remove('_show'); + } + + level1Items.forEach(item => { + const titleSpan = item.querySelector('.submenu-group-title span'); + const titleText = titleSpan ? titleSpan.textContent.toLowerCase() : ''; + + // Filtering + if (searchText === '' || titleText.includes(searchText)) { + item.style.display = ''; // visible + } else { + item.style.display = 'none'; // hidden + } + + // Highlighting found text + if (searchText !== '' && titleText.includes(searchText)) { + titleSpan.innerHTML = titleSpan.textContent.replace( + new RegExp(searchText, 'gi'), + match => `\${match}` + ); + } else { + titleSpan.textContent = titleSpan.textContent; // clear the backlight + } + }); + }); + + // Click handler for close button + searchClose.addEventListener('click', function() { + // Clear input value + searchInput.value = ''; + + // Hide close button + this.classList.remove('_show'); + + // Show all items + level1Items.forEach(item => { + item.style.display = ''; + + // Clear highlighting + const titleSpan = item.querySelector('.submenu-group-title span'); + if (titleSpan) { + titleSpan.textContent = titleSpan.textContent; + } + }); + + // Set focus back to input + searchInput.focus(); + }); + } + } + + + // Observer for close active submenu + let observer = new MutationObserver(function (mutationsList) { + if (!menu.classList.contains('_show')) { + let activeSubmenu = menu.querySelector('.level-1.parent.active .submenu._show'); + if (activeSubmenu) { + activeSubmenu.classList.toggle('_show'); + } + } + + const submenuDiv = menu.querySelector('.submenu'); + const submenuList = menu.querySelector('.submenu > ul[role=menu]'); + if (menu.classList.contains('_show') && submenuDiv && submenuList) { + const search = menu.querySelector('.mf-menu-search-container') + let height = 0; + if (search) { + height = search.offsetHeight; + } + submenuList.style.maxHeight = (document.documentElement.clientHeight - 147 - height) + 'px'; submenuDiv.style.position = 'fixed'; submenuDiv.style.left = '88px'; document.body.style.overflowY = 'hidden'; } else { document.body.style.overflowY = 'auto'; } - } + }); + observer.observe(menu, { attributes: true, attributeFilter: ['class'] }); - let menuElement = document.querySelector('#menu-magefan-community-elements'); - if (menuElement) { - var observer = new MutationObserver(updateMenuHeight); - observer.observe(menuElement, { attributes: true }); - } }); " -?> + ?> renderTag('script', [], $script, false) ?> diff --git a/view/adminhtml/web/css/source/_module.less b/view/adminhtml/web/css/source/_module.less index f2a8738..efab975 100644 --- a/view/adminhtml/web/css/source/_module.less +++ b/view/adminhtml/web/css/source/_module.less @@ -1,33 +1,189 @@ + .admin__menu { - + // Magefan Community Menu Lvl-0 #menu-magefan-community-elements { - &._show { - > .submenu { - width: calc(100vw - 8.800000000000001rem); - > ul[role=menu] { - display: grid; - grid-template-columns: repeat(auto-fill, minmax(28rem, 1fr)); - overflow-x: hidden; - overflow-y: auto; - > .column { - display: contents; + overflow-x: visible; + > .submenu { + width: auto; + bottom: 0; + height: 100vh; + max-height: 100vh; + min-height: 770px; + min-width: 320px; + z-index: 698; + + // Submenu title + .submenu-title {} + + // Button Close + a.action-close-submenu { + position: absolute; + top: 0; + right: 0; + padding: 2.4rem 2.8rem; + z-index: 999; + &:before { + -webkit-font-smoothing: antialiased; + font-family: 'Admin Icons'; + font-style: normal; + font-weight: normal; + line-height: 1; + speak: none; + content: '\e62f'; + display: block; + color: #ffffff; + font-size: 1.7rem; + width: 20px; + height: 20px; + background-size: 26px; + margin: 0 auto .3rem auto; + transition: color 0.1s linear; + } + &:hover { + background-color: transparent; + } + } + + // Search + .mf-menu-search-container { + margin: 0 30px 30px; + position: relative; + } + .action-search-close { + position: absolute; + top: 50%; + transform: translateY(-50%); + right: 14px; + z-index: 10; + width: 15px; + height: 15px; + cursor: pointer; + opacity: 0; + visibility: hidden; + transition: all 0.2s ease; + } + .action-search-close._show { + opacity: 1; + visibility: visible; + } + .action-search-close:before { + -webkit-font-smoothing: antialiased; + font-family: 'Admin Icons'; + font-style: normal; + font-weight: normal; + line-height: 15px; + speak: none; + content: '\e62f'; + display: block; + width: 15px; + height: 15px; + color: #000000; + font-size: 15px; + } + // Input Search + .mf-menu-search-input { + width: 100%; + padding: 8px 30px 8px 40px; + color: #000; + border: 1px solid #292826; + border-radius: 4px; + font-size: 14px; + box-sizing: border-box; + background: #ffffff url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyMCIgaGVpZ2h0PSIyMCIgdmlld0JveD0iMCAwIDIwIDIwIiBmaWxsPSJub25lIj4NCiAgICA8cGF0aCBkPSJNMTcuNSAxNy41TDE0LjU4MzQgMTQuNTgzM00xNi42NjY3IDkuNTgzMzNDMTYuNjY2NyAxMy40OTU0IDEzLjQ5NTQgMTYuNjY2NyA5LjU4MzMzIDE2LjY2NjdDNS42NzEzMiAxNi42NjY3IDIuNSAxMy40OTU0IDIuNSA5LjU4MzMzQzIuNSA1LjY3MTMyIDUuNjcxMzIgMi41IDkuNTgzMzMgMi41QzEzLjQ5NTQgMi41IDE2LjY2NjcgNS42NzEzMiAxNi42NjY3IDkuNTgzMzNaIiBzdHJva2U9IiM0MTM2MmYiIHN0cm9rZS13aWR0aD0iMS42NjY2NyIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIj48L3BhdGg+DQo8L3N2Zz4=") no-repeat 2% 7px; + } + + // Submenu content + > ul[role='menu'] { + display: flex; + flex-direction: column; + //overflow-x: hidden; + overflow-y: auto; + min-height: 320px; + max-height: ~'calc(100vh - 90px)'; + position: relative; + margin-right: 10px; + scrollbar-color: rgb(110, 106, 106) transparent; + scrollbar-width: thin; + .level-1 { + display: flex; + flex-direction: column; + margin-bottom: 0; + cursor: pointer; + .submenu-group-title { + font-weight: normal; + margin: 0; + pointer-events: auto; + &:hover { + background: var(--mfav-menu-bc, #403934); + } + } + // Submenu 2 + .submenu { + display: none; + visibility: hidden; + //opacity: 0; + position: fixed; + top: 0; + left: 100%; + bottom: 0; + z-index: 698; + min-width: 310px; + transform: translateX(-100%); + transition-duration: .3s; + transition-property: transform, visibility; + transition-timing-function: ease-in-out; + color: #ffffff; + background: #4a4542; + box-shadow: 0 0 3px #000000; + padding: 2rem 0 0; + cursor: default; + strong.submenu-item-title { + display: block; + font-size: 2.2rem; + font-weight: 600; + margin-bottom: 4.2rem; + margin-left: 3rem; + margin-right: 5.8rem; + } + a.action-close { + padding: 2.4rem 2.8rem; + } > ul[role=menu] { - display: contents; - > li { - display: flex; - flex-direction: column; - min-width: 24.8rem; - border-bottom: 1px dotted rgba(255, 255, 255, 0.2); - margin-bottom: 2rem; - padding-bottom: 2.5rem; + display: flex; + flex-direction: column; + margin: 0 1.5rem; + } + } + &:hover { + //background-color: #0b182a; + } + &.active { + .submenu { + &._show { + display: block; + visibility: visible; + //opacity: 1; + transform: translateX(0); + transition-duration: .3s; + transition-property: transform, visibility; + transition-timing-function: ease-in-out; + z-index: 698; } } } } + + .column { + display: contents; + } + ul[role='menu'] { + display: contents; + } } } + // Logo menu > a { &:before { content: '';