diff --git a/lang/en/moodle.php b/lang/en/moodle.php index 22297f8ae56..90266b87ea7 100644 --- a/lang/en/moodle.php +++ b/lang/en/moodle.php @@ -1352,6 +1352,7 @@ $string['movefilestohere'] = 'Move files to here'; $string['movefull'] = 'Move {$a} to this location'; $string['movehere'] = 'Move to here'; $string['moveleft'] = 'Move left'; +$string['moremenu'] = 'More'; $string['moveright'] = 'Move right'; $string['movesection'] = 'Move section {$a}'; $string['moveselectedcategoriesto'] = 'Move selected categories to'; diff --git a/lib/amd/build/moremenu.min.js b/lib/amd/build/moremenu.min.js new file mode 100644 index 00000000000..e327b536683 --- /dev/null +++ b/lib/amd/build/moremenu.min.js @@ -0,0 +1,2 @@ +define ("core/moremenu",["exports","jquery"],function(a,b){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.default=void 0;b=function(a){return a&&a.__esModule?a:{default:a}}(b);var c={regions:{moredropdown:"[data-region=\"moredropdown\"]",morebutton:"[data-region=\"morebutton\"]"},classes:{dropdownitem:"dropdown-item",dropdownmoremenu:"dropdownmoremenu",dropdowntoggle:"dropdown-toggle",hidden:"d-none",active:"active",nav:"nav",navlink:"nav-link",observed:"observed"},attributes:{menu:"[role=\"menu\"]"}},d=6,e=function(a){var b=a.parentNode.offsetHeight+1,f=a.querySelector(c.regions.moredropdown),g=a.querySelector(c.regions.morebutton),h=a.querySelector("."+c.classes.dropdowntoggle);if(a.offsetHeight>b||a.children.length>d){g.classList.remove(c.classes.hidden);var i=Array.from(a.children).reverse();i.forEach(function(e){if(!e.classList.contains(c.classes.dropdownmoremenu)){if(a.offsetHeight>b||a.children.length>d){var g=a.removeChild(e),i=g.querySelector("."+c.classes.navlink);if(i&&!i.hasAttribute("role")){i.setAttribute("role","menuitem")}if(i.classList.contains(c.classes.active)){h.classList.add(c.classes.active)}i.classList.remove(c.classes.navlink);i.classList.add(c.classes.dropdownitem);f.prepend(g)}}})}else{if("children"in f){var j=Array.from(f.children);j.forEach(function(e){if(a.offsetHeightb){e(a)}}a.parentNode.classList.add(c.classes.observed)},f=function(a){e(a);window.addEventListener("resize",function(){e(a)});var d=function(a){var b=a.target.parentNode.querySelector(c.attributes.menu);if(b){b.classList.toggle("show")}a.stopPropagation()};(0,b.default)("."+c.classes.dropdownmoremenu).on("show.bs.dropdown",function(){var b=a.querySelector(c.regions.moredropdown);b.querySelectorAll(".dropdown").forEach(function(a){a.removeEventListener("click",d,!0);a.addEventListener("click",d,!0)})})};a.default=f;return a.default}); +//# sourceMappingURL=moremenu.min.js.map diff --git a/lib/amd/build/moremenu.min.js.map b/lib/amd/build/moremenu.min.js.map new file mode 100644 index 00000000000..3ce9876691e --- /dev/null +++ b/lib/amd/build/moremenu.min.js.map @@ -0,0 +1 @@ +{"version":3,"sources":["../src/moremenu.js"],"names":["Selectors","regions","moredropdown","morebutton","classes","dropdownitem","dropdownmoremenu","dropdowntoggle","hidden","active","nav","navlink","observed","attributes","menu","maxMenuItems","autoCollapse","maxHeight","parentNode","offsetHeight","moreDropdown","querySelector","moreButton","dropdownToggle","children","length","classList","remove","menuNodes","Array","from","reverse","forEach","item","contains","lastNode","removeChild","navLink","hasAttribute","setAttribute","add","prepend","currentAttribute","getAttribute","removeAttribute","insertBefore","window","addEventListener","toggledropdown","e","innerMenu","target","toggle","stopPropagation","on","querySelectorAll","dropdown","removeEventListener"],"mappings":"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","sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Moves wrapping navigation items into a more menu.\n *\n * @module core/moremenu\n * @package core\n * @copyright 2021 Moodle\n * @author Bas Brands \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport $ from 'jquery';\n/**\n * Moremenu selectors.\n */\nconst Selectors = {\n regions: {\n moredropdown: '[data-region=\"moredropdown\"]',\n morebutton: '[data-region=\"morebutton\"]'\n },\n classes: {\n dropdownitem: 'dropdown-item',\n dropdownmoremenu: 'dropdownmoremenu',\n dropdowntoggle: 'dropdown-toggle',\n hidden: 'd-none',\n active: 'active',\n nav: 'nav',\n navlink: 'nav-link',\n observed: 'observed',\n },\n attributes: {\n menu: '[role=\"menu\"]'\n }\n};\n\nconst maxMenuItems = 6;\n/**\n * Auto Collapse navigation items that wrap into a dropdown menu.\n *\n * @param {HTMLElement} menu The navbar container.\n */\nconst autoCollapse = menu => {\n\n const maxHeight = menu.parentNode.offsetHeight + 1;\n\n const moreDropdown = menu.querySelector(Selectors.regions.moredropdown);\n const moreButton = menu.querySelector(Selectors.regions.morebutton);\n\n const dropdownToggle = menu.querySelector('.' + Selectors.classes.dropdowntoggle);\n\n // If the menuitems wrap and the menu height is larger than the height of the\n // parent. Or if the number if menuitems is larger than the maximum menu items\n // allowed then start pushing navlinks into the moreDropdown.\n if (menu.offsetHeight > maxHeight || menu.children.length > maxMenuItems) {\n\n moreButton.classList.remove(Selectors.classes.hidden);\n\n const menuNodes = Array.from(menu.children).reverse();\n menuNodes.forEach(item => {\n if (!item.classList.contains(Selectors.classes.dropdownmoremenu)) {\n // After moving the menuitems into the moreDropdown check again\n // if the menuheight is still larger then the height of the parent or if the\n // menu still has more items than maxMenuItems.\n if (menu.offsetHeight > maxHeight || menu.children.length > maxMenuItems) {\n const lastNode = menu.removeChild(item);\n const navLink = lastNode.querySelector('.' + Selectors.classes.navlink);\n if (navLink && !navLink.hasAttribute('role')) {\n // Adding the menuitem role so the dropdown includes the\n // Accessibility improvements from theme/boost/amd/src/aria.js\n navLink.setAttribute('role', 'menuitem');\n }\n\n // If there are navLinks that contain an active link in the moreDropdown\n // make the dropdownToggle in the moreButton active.\n if (navLink.classList.contains(Selectors.classes.active)) {\n dropdownToggle.classList.add(Selectors.classes.active);\n }\n\n // Change the styling of the navLink to a dropdownitem and push it into\n // the moreDropdown.\n navLink.classList.remove(Selectors.classes.navlink);\n navLink.classList.add(Selectors.classes.dropdownitem);\n moreDropdown.prepend(lastNode);\n }\n }\n });\n } else {\n // If the the menu height is smaller than the height of the parent and there are\n // less than the maximum items in the menu, then try returning navlinks to the menu.\n\n if ('children' in moreDropdown) {\n const menuNodes = Array.from(moreDropdown.children);\n menuNodes.forEach(item => {\n\n if (menu.offsetHeight < maxHeight && menu.children.length < maxMenuItems) {\n const lastNode = moreDropdown.removeChild(item);\n const navLink = lastNode.querySelector('.' + Selectors.classes.dropdownitem);\n if (navLink) {\n const currentAttribute = navLink.getAttribute('role');\n if (currentAttribute === 'menuitem') {\n navLink.removeAttribute('role');\n }\n }\n\n // Stop displaying the active state on the dropdownToggle if\n // the active navlink is removed.\n if (navLink.classList.contains(Selectors.classes.active)) {\n dropdownToggle.classList.remove(Selectors.classes.active);\n }\n navLink.classList.remove(Selectors.classes.dropdownitem);\n navLink.classList.add(Selectors.classes.navlink);\n menu.insertBefore(lastNode, moreButton);\n }\n });\n\n // If there are no more menuNodes in the dropdown we can hide the moreButton.\n if (menuNodes.length === 0) {\n moreButton.classList.add(Selectors.classes.hidden);\n }\n }\n\n if (menu.offsetHeight > maxHeight) {\n autoCollapse(menu);\n }\n }\n menu.parentNode.classList.add(Selectors.classes.observed);\n};\n\n/**\n * Initialise the more menus.\n *\n * @param {HTMLElement} menu The navbar moremenu.\n */\nexport default menu => {\n autoCollapse(menu);\n\n // When the screen size changes make sure the menu still fits.\n window.addEventListener('resize', () => {\n autoCollapse(menu);\n });\n\n const toggledropdown = e => {\n const innerMenu = e.target.parentNode.querySelector(Selectors.attributes.menu);\n if (innerMenu) {\n innerMenu.classList.toggle('show');\n }\n e.stopPropagation();\n };\n\n // If there are dropdowns in the MoreMenu, add a new\n // eventlistener to show the contents on click and prevent the\n // moreMenu from closing.\n $('.' + Selectors.classes.dropdownmoremenu).on('show.bs.dropdown', function() {\n const moreDropdown = menu.querySelector(Selectors.regions.moredropdown);\n moreDropdown.querySelectorAll('.dropdown').forEach((dropdown) => {\n dropdown.removeEventListener('click', toggledropdown, true);\n dropdown.addEventListener('click', toggledropdown, true);\n });\n });\n};\n"],"file":"moremenu.min.js"} \ No newline at end of file diff --git a/lib/amd/src/moremenu.js b/lib/amd/src/moremenu.js new file mode 100644 index 00000000000..57b283fb254 --- /dev/null +++ b/lib/amd/src/moremenu.js @@ -0,0 +1,174 @@ +// This file is part of Moodle - http://moodle.org/ +// +// Moodle is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Moodle is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Moodle. If not, see . + +/** + * Moves wrapping navigation items into a more menu. + * + * @module core/moremenu + * @package core + * @copyright 2021 Moodle + * @author Bas Brands + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +import $ from 'jquery'; +/** + * Moremenu selectors. + */ +const Selectors = { + regions: { + moredropdown: '[data-region="moredropdown"]', + morebutton: '[data-region="morebutton"]' + }, + classes: { + dropdownitem: 'dropdown-item', + dropdownmoremenu: 'dropdownmoremenu', + dropdowntoggle: 'dropdown-toggle', + hidden: 'd-none', + active: 'active', + nav: 'nav', + navlink: 'nav-link', + observed: 'observed', + }, + attributes: { + menu: '[role="menu"]' + } +}; + +const maxMenuItems = 6; +/** + * Auto Collapse navigation items that wrap into a dropdown menu. + * + * @param {HTMLElement} menu The navbar container. + */ +const autoCollapse = menu => { + + const maxHeight = menu.parentNode.offsetHeight + 1; + + const moreDropdown = menu.querySelector(Selectors.regions.moredropdown); + const moreButton = menu.querySelector(Selectors.regions.morebutton); + + const dropdownToggle = menu.querySelector('.' + Selectors.classes.dropdowntoggle); + + // If the menuitems wrap and the menu height is larger than the height of the + // parent. Or if the number if menuitems is larger than the maximum menu items + // allowed then start pushing navlinks into the moreDropdown. + if (menu.offsetHeight > maxHeight || menu.children.length > maxMenuItems) { + + moreButton.classList.remove(Selectors.classes.hidden); + + const menuNodes = Array.from(menu.children).reverse(); + menuNodes.forEach(item => { + if (!item.classList.contains(Selectors.classes.dropdownmoremenu)) { + // After moving the menuitems into the moreDropdown check again + // if the menuheight is still larger then the height of the parent or if the + // menu still has more items than maxMenuItems. + if (menu.offsetHeight > maxHeight || menu.children.length > maxMenuItems) { + const lastNode = menu.removeChild(item); + const navLink = lastNode.querySelector('.' + Selectors.classes.navlink); + if (navLink && !navLink.hasAttribute('role')) { + // Adding the menuitem role so the dropdown includes the + // Accessibility improvements from theme/boost/amd/src/aria.js + navLink.setAttribute('role', 'menuitem'); + } + + // If there are navLinks that contain an active link in the moreDropdown + // make the dropdownToggle in the moreButton active. + if (navLink.classList.contains(Selectors.classes.active)) { + dropdownToggle.classList.add(Selectors.classes.active); + } + + // Change the styling of the navLink to a dropdownitem and push it into + // the moreDropdown. + navLink.classList.remove(Selectors.classes.navlink); + navLink.classList.add(Selectors.classes.dropdownitem); + moreDropdown.prepend(lastNode); + } + } + }); + } else { + // If the the menu height is smaller than the height of the parent and there are + // less than the maximum items in the menu, then try returning navlinks to the menu. + + if ('children' in moreDropdown) { + const menuNodes = Array.from(moreDropdown.children); + menuNodes.forEach(item => { + + if (menu.offsetHeight < maxHeight && menu.children.length < maxMenuItems) { + const lastNode = moreDropdown.removeChild(item); + const navLink = lastNode.querySelector('.' + Selectors.classes.dropdownitem); + if (navLink) { + const currentAttribute = navLink.getAttribute('role'); + if (currentAttribute === 'menuitem') { + navLink.removeAttribute('role'); + } + } + + // Stop displaying the active state on the dropdownToggle if + // the active navlink is removed. + if (navLink.classList.contains(Selectors.classes.active)) { + dropdownToggle.classList.remove(Selectors.classes.active); + } + navLink.classList.remove(Selectors.classes.dropdownitem); + navLink.classList.add(Selectors.classes.navlink); + menu.insertBefore(lastNode, moreButton); + } + }); + + // If there are no more menuNodes in the dropdown we can hide the moreButton. + if (menuNodes.length === 0) { + moreButton.classList.add(Selectors.classes.hidden); + } + } + + if (menu.offsetHeight > maxHeight) { + autoCollapse(menu); + } + } + menu.parentNode.classList.add(Selectors.classes.observed); +}; + +/** + * Initialise the more menus. + * + * @param {HTMLElement} menu The navbar moremenu. + */ +export default menu => { + autoCollapse(menu); + + // When the screen size changes make sure the menu still fits. + window.addEventListener('resize', () => { + autoCollapse(menu); + }); + + const toggledropdown = e => { + const innerMenu = e.target.parentNode.querySelector(Selectors.attributes.menu); + if (innerMenu) { + innerMenu.classList.toggle('show'); + } + e.stopPropagation(); + }; + + // If there are dropdowns in the MoreMenu, add a new + // eventlistener to show the contents on click and prevent the + // moreMenu from closing. + $('.' + Selectors.classes.dropdownmoremenu).on('show.bs.dropdown', function() { + const moreDropdown = menu.querySelector(Selectors.regions.moredropdown); + moreDropdown.querySelectorAll('.dropdown').forEach((dropdown) => { + dropdown.removeEventListener('click', toggledropdown, true); + dropdown.addEventListener('click', toggledropdown, true); + }); + }); +}; diff --git a/lib/outputrenderers.php b/lib/outputrenderers.php index 09c0fb748a2..9c5d98c9a26 100644 --- a/lib/outputrenderers.php +++ b/lib/outputrenderers.php @@ -3808,6 +3808,20 @@ EOD; return $content; } + /** + * Renders a navigation bar into a "more menu" navigation bar + * + * @param array $content + * @param string $navbarstyle navbar-nav or nav-tabs + * @return string + */ + public function more_menu($content, $navbarstyle) { + return $this->render_from_template('core/moremenu', (object) [ + 'nodearray' => $content, + 'navbarstyle' => $navbarstyle + ]); + } + /** * Renders theme links for switching between default and other themes. * diff --git a/lib/templates/moremenu.mustache b/lib/templates/moremenu.mustache new file mode 100644 index 00000000000..0b63ff6c355 --- /dev/null +++ b/lib/templates/moremenu.mustache @@ -0,0 +1,66 @@ +{{! + This file is part of Moodle - http://moodle.org/ + + Moodle is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Moodle is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Moodle. If not, see . +}} +{{! + @template core/moremenu + + The More menu. + + Example context (json): + { + "nodecollection": { + "children": [ + { + "text": "Home", + "action": "/index.php?redirect=0", + "active": "true" + }, + { + "text": "Dashboard", + "action": "/my" + }, + { + "text": "Courses", + "action": "/course" + }, + { + "text": "Site Administration", + "action": "/admin/search.php" + } + ] + } + } +}} + +{{#js}} +require(['core/moremenu'], function(moremenu) { + var moreMenu = document.querySelector('#moremenu-{{ uniqid }}'); + moremenu(moreMenu); +}); +{{/js}} diff --git a/lib/templates/moremenu_children.mustache b/lib/templates/moremenu_children.mustache new file mode 100644 index 00000000000..4e10461f972 --- /dev/null +++ b/lib/templates/moremenu_children.mustache @@ -0,0 +1,56 @@ +{{! + This file is part of Moodle - http://moodle.org/ + + Moodle is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Moodle is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Moodle. If not, see . +}} +{{! + @template core/moremenu_children + + The More menu children + + Example context (json): + { + "divider": "", + "haschildren": "", + "uniqid": "Unique string", + "text": "Moodle community", + "children": "", + "title": "Moodle community", + "url": "https://moodle.org" + } +}} +{{#haschildren}} + +{{/haschildren}} +{{^haschildren}} + +{{/haschildren}} diff --git a/theme/boost/layout/columns2.php b/theme/boost/layout/columns2.php index 384158d94e5..3498b762ca8 100644 --- a/theme/boost/layout/columns2.php +++ b/theme/boost/layout/columns2.php @@ -42,6 +42,11 @@ $hasblocks = strpos($blockshtml, 'data-block=') !== false; $buildregionmainsettings = !$PAGE->include_region_main_settings_in_header_actions(); // If the settings menu will be included in the header then don't add it here. $regionmainsettingsmenu = $buildregionmainsettings ? $OUTPUT->region_main_settings_menu() : false; + +$primary = new core\navigation\output\primary($PAGE); +$renderer = $PAGE->get_renderer('core'); +$primarymenu = $primary->export_for_template($renderer); + $templatecontext = [ 'sitename' => format_string($SITE->shortname, true, ['context' => context_course::instance(SITEID), "escape" => false]), 'output' => $OUTPUT, @@ -50,11 +55,10 @@ $templatecontext = [ 'bodyattributes' => $bodyattributes, 'navdraweropen' => $navdraweropen, 'regionmainsettingsmenu' => $regionmainsettingsmenu, - 'hasregionmainsettingsmenu' => !empty($regionmainsettingsmenu) + 'hasregionmainsettingsmenu' => !empty($regionmainsettingsmenu), + 'primarymoremenu' => $OUTPUT->more_menu(array_merge($primarymenu['primary'], $primarymenu['custom']), 'navbar-nav'), ]; - $nav = $PAGE->flatnav; $templatecontext['flatnavigation'] = $nav; $templatecontext['firstcollectionlabel'] = $nav->get_collectionlabel(); echo $OUTPUT->render_from_template('theme_boost/columns2', $templatecontext); - diff --git a/theme/boost/scss/moodle.scss b/theme/boost/scss/moodle.scss index 9eb49651a86..9351930aba4 100644 --- a/theme/boost/scss/moodle.scss +++ b/theme/boost/scss/moodle.scss @@ -44,3 +44,5 @@ $breadcrumb-divider-rtl: "◀" !default; @import "moodle/toasts"; @import "moodle/navbar"; @import "moodle/reportbuilder"; +@import "moodle/moremenu"; +@import "moodle/primarynavigation"; diff --git a/theme/boost/scss/moodle/core.scss b/theme/boost/scss/moodle/core.scss index f5c33cb2c28..a33e8494ee9 100644 --- a/theme/boost/scss/moodle/core.scss +++ b/theme/boost/scss/moodle/core.scss @@ -378,10 +378,12 @@ img.resize { .action-menu .dropdown-toggle { text-decoration: none; + display: inline-block; } .action-menu { white-space: nowrap; + display: inline; } .block img.resize { diff --git a/theme/boost/scss/moodle/moremenu.scss b/theme/boost/scss/moodle/moremenu.scss new file mode 100644 index 00000000000..743fe123095 --- /dev/null +++ b/theme/boost/scss/moodle/moremenu.scss @@ -0,0 +1,36 @@ +.moremenu { + opacity: 0; + height: $moremenu-height; + &.observed { + opacity: 1; + } + .nav-link { + height: $moremenu-height; + display: flex; + align-items: center; + } + // Styling for dropdown menus inside the MoreButton. + .dropdownmoremenu > .dropdown-menu { + & > .dropdown-item { + padding: 0; + } + .dropdown-menu { + position: static; + padding: 0; + border: 0; + &.show { + display: block; + } + .dropdown-item { + background-color: $gray-100; + @include hover-focus() { + color: $dropdown-link-hover-color; + @include gradient-bg($dropdown-link-active-bg); + } + } + .dropdown-divider { + display: none; + } + } + } +} diff --git a/theme/boost/scss/moodle/primarynavigation.scss b/theme/boost/scss/moodle/primarynavigation.scss new file mode 100644 index 00000000000..e6f55f217a4 --- /dev/null +++ b/theme/boost/scss/moodle/primarynavigation.scss @@ -0,0 +1,8 @@ +.navbar.fixed-top { + .moremenu { + height: $navbar-height; + .nav-link { + height: $navbar-height; + } + } +} diff --git a/theme/boost/scss/moodle/variables.scss b/theme/boost/scss/moodle/variables.scss index 70f1be5febf..3b322a2e4ef 100644 --- a/theme/boost/scss/moodle/variables.scss +++ b/theme/boost/scss/moodle/variables.scss @@ -27,3 +27,5 @@ $course-content-maxwidth: 800px; $box-shadow-drawer-left: -0.25rem .25rem .8rem rgba($black, .025) !default; $box-shadow-drawer-right: 0 .25rem .8rem rgba($black, .025) !default; + +$moremenu-height: 40px !default; diff --git a/theme/boost/style/moodle.css b/theme/boost/style/moodle.css index d4d8668592d..0445fab5085 100644 --- a/theme/boost/style/moodle.css +++ b/theme/boost/style/moodle.css @@ -9987,10 +9987,12 @@ img.resize { width: 1em; } .action-menu .dropdown-toggle { - text-decoration: none; } + text-decoration: none; + display: inline-block; } .action-menu { - white-space: nowrap; } + white-space: nowrap; + display: inline; } .block img.resize { height: 0.9em; @@ -20036,6 +20038,36 @@ div.editor_atto_toolbar button .icon { text-overflow: clip; word-break: break-all; } +.moremenu { + opacity: 0; + height: 40px; } + .moremenu.observed { + opacity: 1; } + .moremenu .nav-link { + height: 40px; + display: flex; + align-items: center; } + .moremenu .dropdownmoremenu > .dropdown-menu > .dropdown-item { + padding: 0; } + .moremenu .dropdownmoremenu > .dropdown-menu .dropdown-menu { + position: static; + padding: 0; + border: 0; } + .moremenu .dropdownmoremenu > .dropdown-menu .dropdown-menu.show { + display: block; } + .moremenu .dropdownmoremenu > .dropdown-menu .dropdown-menu .dropdown-item { + background-color: #f8f9fa; } + .moremenu .dropdownmoremenu > .dropdown-menu .dropdown-menu .dropdown-item:hover, .moremenu .dropdownmoremenu > .dropdown-menu .dropdown-menu .dropdown-item:focus { + color: #fff; + background-color: #0f6fc5; } + .moremenu .dropdownmoremenu > .dropdown-menu .dropdown-menu .dropdown-divider { + display: none; } + +.navbar.fixed-top .moremenu { + height: 50px; } + .navbar.fixed-top .moremenu .nav-link { + height: 50px; } + body { -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } diff --git a/theme/boost/templates/navbar.mustache b/theme/boost/templates/navbar.mustache index 993d899b7a9..70e10705ab1 100644 --- a/theme/boost/templates/navbar.mustache +++ b/theme/boost/templates/navbar.mustache @@ -53,9 +53,9 @@ {{{ sitename }}} + {{{primarymoremenu}}} + diff --git a/theme/classic/style/moodle.css b/theme/classic/style/moodle.css index 958e79e1b87..3b6787d615d 100644 --- a/theme/classic/style/moodle.css +++ b/theme/classic/style/moodle.css @@ -10199,10 +10199,12 @@ img.resize { width: 1em; } .action-menu .dropdown-toggle { - text-decoration: none; } + text-decoration: none; + display: inline-block; } .action-menu { - white-space: nowrap; } + white-space: nowrap; + display: inline; } .block img.resize { height: 0.9em; @@ -20227,6 +20229,36 @@ div.editor_atto_toolbar button .icon { text-overflow: clip; word-break: break-all; } +.moremenu { + opacity: 0; + height: 40px; } + .moremenu.observed { + opacity: 1; } + .moremenu .nav-link { + height: 40px; + display: flex; + align-items: center; } + .moremenu .dropdownmoremenu > .dropdown-menu > .dropdown-item { + padding: 0; } + .moremenu .dropdownmoremenu > .dropdown-menu .dropdown-menu { + position: static; + padding: 0; + border: 0; } + .moremenu .dropdownmoremenu > .dropdown-menu .dropdown-menu.show { + display: block; } + .moremenu .dropdownmoremenu > .dropdown-menu .dropdown-menu .dropdown-item { + background-color: #f8f9fa; } + .moremenu .dropdownmoremenu > .dropdown-menu .dropdown-menu .dropdown-item:hover, .moremenu .dropdownmoremenu > .dropdown-menu .dropdown-menu .dropdown-item:focus { + color: #fff; + background-color: #0f6fc5; } + .moremenu .dropdownmoremenu > .dropdown-menu .dropdown-menu .dropdown-divider { + display: none; } + +.navbar.fixed-top .moremenu { + height: 50px; } + .navbar.fixed-top .moremenu .nav-link { + height: 50px; } + body { -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; }