diff --git a/core/misc/jquery.collapse.js b/core/misc/jquery.collapse.js new file mode 100644 index 0000000..5a21a66 --- /dev/null +++ b/core/misc/jquery.collapse.js @@ -0,0 +1,98 @@ +/** + * @file + */ + +;( function( $, window, document, undefined ) { + 'use strict'; + + var pluginName = 'collapse'; + var defaults = { + target: false, + trigger: false, + initState: 'closed' + }; + + function Collapse( element, options ) { + this.options = $.extend( {}, defaults, options ); + this.element = element; + this.$element = $(element); + this.$target = null; + this.$trigger = null; + + this._defaults = defaults; + this._name = pluginName; + + this.init(); + } + + Collapse.prototype.init = function() { + var self = this; + var opts = self.options; + + if (opts.target) { + if ( opts.target.jquery ) { + this.$target = opts.target; + } else { + this.$target = this.$element.withinOrUnique(opts.target); + } + } + + if (opts.trigger) { + if ( opts.trigger.jquery ) { + this.$trigger = opts.trigger; + } else { + this.$trigger = this.$element.withinOrUnique(opts.trigger); + } + } else { + this.$trigger = this.$element; + } + + this.refresh(); + + if (this.options.initState !== 'closed') { + this.$target.addClass('is-open'); + } + + this.$trigger.on('click', function(event) { + self.toggle(event); + }); + + this.$element.on('transitionend webkitTransitionEnd', function() { + if ( self.$target.hasClass('is-closing') ) { + self.$target.removeClass('is-closing is-open'); + self.$target[0].style.removeProperty('max-height'); + } + }); + }; + + Collapse.prototype.refresh = function() { + this.$target.data( 'intrinsicHeight', this.$target.intrinsic('height') + 'px' ); + }; + + Collapse.prototype.toggle = function(event) { + event.preventDefault(); + + if ( this.$target.hasClass('is-open') ) { + if ( Modernizr.csstransitions ) { + this.$target.css('max-height', '0').addClass('is-closing'); + } + else { + this.$target.removeClass('is-open'); + } + } else { + if ( Modernizr.csstransitions ) { + this.$target.css( 'max-height', this.$target.data('intrinsicHeight') ); + } + this.$target.addClass('is-open'); + } + }; + + $.fn[pluginName] = function( options ) { + return this.each( function() { + if ( ! $.data(this, 'plugin_' + pluginName) ) { + $.data( this, 'plugin_' + pluginName, new Collapse(this, options) ); + } + }); + }; + +})( jQuery, window, document ); diff --git a/core/misc/jquery.dom-utils.js b/core/misc/jquery.dom-utils.js new file mode 100644 index 0000000..641006b --- /dev/null +++ b/core/misc/jquery.dom-utils.js @@ -0,0 +1,59 @@ +/** + * @file + * Lowish-level DOM utilities for UI components + */ +;( function( $, w, undefined ) { + 'use strict'; + + // Find a given selector only within the current scope, except ids, which + // are found anywhere. + $.fn.withinOrUnique = function(selector) { + if ( selector.charAt(0) === '#' ) { + return $(selector); + } else { + return this.find(selector); + } + }; + + $.extend({ + kebabCase: function(string) { + return string.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase(); + }, + + safeTrim: function(object) { + if (typeof object === 'string') { + return $.trim(object); + } + return object; + } + }); + + // Parse options passed via data-attribute component initialization and + // turn them into an object. + $.fn.dataOptions = function(pluginName, namespace) { + var options = {}; + var optPair; + var key; + var value; + var dataPrefix = 'data-' + ( typeof namespace === 'string' ? namespace + '-' : '' ); + var optionsArray = ($(this).attr(dataPrefix + $.kebabCase(pluginName)) || ':').split(';'); + + // parse options + for (var i = optionsArray.length - 1; i >= 0; i--) { + optPair = optionsArray[i].split(':'); + key = optPair[0]; + value = optPair[1]; + + if (/true/i.test(value)) { value = true; } + if (/false/i.test(value)) { value = false; } + if ($.isNumeric(value)) { value = parseFloat(value); } + + if (optPair.length === 2 && key.length > 0) { + options[$.safeTrim(key)] = $.safeTrim(value); + } + } + + return options; + }; + +})( jQuery, window ); diff --git a/core/misc/jquery.intrinsic.js b/core/misc/jquery.intrinsic.js new file mode 100644 index 0000000..d803652 --- /dev/null +++ b/core/misc/jquery.intrinsic.js @@ -0,0 +1,51 @@ +/** + * @file + * Measure an element’s intrinsic width or height when neither constrained by + * a container nor forced full width as in 'display: block'. + */ +;( function( $, document, undefined ) { + 'use strict'; + + // Style block applied momentarily in order to measure the element. + // + // 1. Shrink-wrap the element. Block display would give us the width of the + // container, not the element’s intrinsic width. + // 2. Preventative measure. The styles should be reverted before the browser’s + // UI thread updates. + // + // We avoid 'position: absolute' because this causes the element to wrap if + // it’s wider than the viewport, regardless of the width of and . + // + var tempElementCSS = { + display: 'table', /* 1 */ + visibility: 'hidden', /* 2 */ + width: 'auto', + height: 'auto', + maxWidth: 'none', + maxHeight: 'none' + }; + + // Style block applied momentarily to the body in order to ensure the + // element’s layout area isn’t constrained. + // + var tempBodyCSS = { + width: '999em', + height: '999em' + }; + + $.fn.intrinsic = function(dimension) { + // The measured element may be a plain object or jQuery. + var element = this instanceof jQuery ? this[0] : this; + var measurement; + + // Use jQuery’s internal swap() method to temporarily apply the styles, then + // measure the element’s width() or height(). + $.swap( document.body, tempBodyCSS, function() { + $.swap( element, tempElementCSS, function() { + measurement = $(element)[dimension](); + }); + }); + + return measurement; + }; +})( jQuery, document ); diff --git a/core/misc/jquery.nav-tabs.js b/core/misc/jquery.nav-tabs.js new file mode 100644 index 0000000..9d0f21d --- /dev/null +++ b/core/misc/jquery.nav-tabs.js @@ -0,0 +1,70 @@ +/** + * @file + */ + +;( function( $, window, document, undefined ) { + 'use strict'; + + var pluginName = 'navTabs'; + var defaults = { + target: '.js-nav-tabs__target', + trigger: '.js-nav-tabs__trigger', + collapsible: false + }; + + function NavTabs( element, dataOptions, options ) { + this.options = $.extend( {}, defaults, dataOptions, options ); + this.element = element; + this.$element = $(element); + + this._defaults = defaults; + this._name = pluginName; + + this.init(); + } + + NavTabs.prototype.init = function() { + + if (this.options.collapsible) { + this.$element.collapse({ + target: this.options.target, + trigger: this.options.trigger + }); + this.$element.addClass('is-collapse-enabled'); + } + + $(window).on('resize.navTabs', $.proxy(this, 'refresh')); + + this.$element.addClass('position-container is-horizontal-enabled'); + this.refreshAll(); + }; + + NavTabs.prototype.refreshAll = function() { + this.intrinsicWidth = this.$element.addClass('is-horizontal').intrinsic('width'); + this.$element.removeClass('is-horizontal'); + this.refresh(); + }; + + NavTabs.prototype.refresh = function() { + if ( this.$element.parent().width() > this.intrinsicWidth ) { + this.$element.addClass('is-horizontal'); + } else { + this.$element.removeClass('is-horizontal'); + } + }; + + NavTabs.prototype.destroy = function() { + window.off('resize.navTabs'); + this.removeData('plugin_' + pluginName); + }; + + $.fn[pluginName] = function( options ) { + return this.each( function() { + if ( ! $.data(this, 'plugin_' + pluginName) ) { + var dataOptions = $(this).dataOptions(pluginName, 'drupal'); + $.data( this, 'plugin_' + pluginName, new NavTabs(this, dataOptions, options) ); + } + }); + }; + +})( jQuery, window, document ); diff --git a/core/modules/system/css/system.theme.css b/core/modules/system/css/system.theme.css index 19bb45f..5e15a64 100644 --- a/core/modules/system/css/system.theme.css +++ b/core/modules/system/css/system.theme.css @@ -372,10 +372,7 @@ ul.inline li { /** * Markup generated by theme_menu_local_tasks(). */ -div.tabs { - margin: 1em 0; -} -ul.tabs { +.tabs { list-style: none; margin: 0 0 0.5em; padding: 0; diff --git a/core/modules/system/system.module b/core/modules/system/system.module index 6a1f766..eb9aecc 100644 --- a/core/modules/system/system.module +++ b/core/modules/system/system.module @@ -1425,6 +1425,42 @@ function system_library_info() { ), ); + // jQuery DOM Utitlities. + $libraries['jquery.dom-utils'] = array( + 'title' => 'jQuery DOM Utitlities', + 'version' => '1.0', + 'js' => array( + 'core/misc/jquery.dom-utils.js' => array(), + ), + 'dependencies' => array( + array('system', 'jquery') + ), + ); + + // jQuery Collapse. + $libraries['jquery.collapse'] = array( + 'title' => 'jQuery Collapse', + 'version' => '1.0', + 'js' => array( + 'core/misc/jquery.collapse.js' => array(), + ), + 'dependencies' => array( + array('system', 'jquery') + ), + ); + + // jQuery Intrinsic Measurements. + $libraries['jquery.intrinsic'] = array( + 'title' => 'Instric Measurements', + 'version' => '1.0', + 'js' => array( + 'core/misc/jquery.intrinsic.js' => array(), + ), + 'dependencies' => array( + array('system', 'jquery') + ), + ); + // jQuery Form Plugin. $libraries['jquery.form'] = array( 'title' => 'jQuery Form Plugin', @@ -1472,6 +1508,22 @@ function system_library_info() { ), ); + // Navigation Tabs. + $libraries['jquery.nav-tabs'] = array( + 'title' => 'Navigation Tabs', + 'version' => '1.0', + 'js' => array( + 'core/misc/jquery.nav-tabs.js' => array(), + ), + 'dependencies' => array( + array('system', 'jquery'), + array('system', 'drupal'), + array('system', 'jquery.intrinsic'), + array('system', 'jquery.collapse'), + array('system', 'jquery.dom-utils'), + ), + ); + // Vertical Tabs. $libraries['drupal.vertical-tabs'] = array( 'title' => 'Vertical Tabs', diff --git a/core/themes/seven/js/nav-tabs.js b/core/themes/seven/js/nav-tabs.js new file mode 100644 index 0000000..f396e12 --- /dev/null +++ b/core/themes/seven/js/nav-tabs.js @@ -0,0 +1,17 @@ +(function ($, Drupal) { + +"use strict"; + +/** + * Initialise the tabs JS. + */ +Drupal.behaviors.navTabs = { + attach: function (context, settings) { + var tabs = $(context).find('[data-drupal-nav-tabs]'); + if ( tabs.length ) { + $(tabs).navTabs(); + } + } +}; + +})(jQuery, Drupal); diff --git a/core/themes/seven/seven.theme b/core/themes/seven/seven.theme index 0e33a77..66d1441 100644 --- a/core/themes/seven/seven.theme +++ b/core/themes/seven/seven.theme @@ -39,6 +39,38 @@ function seven_preprocess_page(&$vars) { } /** + * Overrides theme_custom_block_add_list(). + * + * Returns HTML for primary and secondary local tasks. + * + **/ +function seven_menu_local_tasks(&$variables) { + $output = ''; + + if (!empty($variables['primary'])) { + drupal_add_library('system', 'jquery.nav-tabs', FALSE); + drupal_add_js(drupal_get_path('theme', 'seven') . '/js/nav-tabs.js'); + $variables['primary']['#prefix'] = '

' . t('Primary tabs') . '

'; + $variables['primary']['#prefix'] .= ''; + $output .= drupal_render($variables['primary']); + } + if (!empty($variables['secondary'])) { + $variables['secondary']['#prefix'] = '

' . t('Secondary tabs') . '

'; + $variables['secondary']['#prefix'] .= ''; + $output .= drupal_render($variables['secondary']); + } + + return $output; +} + + + +/** * Displays the list of available node types for node creation. */ function seven_node_add_list($variables) { diff --git a/core/themes/seven/style-rtl.css b/core/themes/seven/style-rtl.css index 70770d1..c80726b 100644 --- a/core/themes/seven/style-rtl.css +++ b/core/themes/seven/style-rtl.css @@ -41,12 +41,6 @@ ol { text-align: left; } -/** - * Page title. - */ -#branding h1.page-title { - float: right; -} /** * Tabs. diff --git a/core/themes/seven/style.css b/core/themes/seven/style.css index 052d0f1..f6f47b1 100644 --- a/core/themes/seven/style.css +++ b/core/themes/seven/style.css @@ -229,132 +229,119 @@ pre { /** * Tabs. */ -ul.primary { - float: right; /* LTR */ - border-bottom: none; - text-transform: uppercase; - font-size: 0.923em; +.tabs.primary { + clear: both; margin: 0; - padding-top: 0; } -ul.primary li { - float: left; /* LTR */ - list-style: none; - height: 2.60em; - margin: 0 2px; -} -ul.primary li a:link, -ul.primary li a.active, -ul.primary li a:active, -ul.primary li a:visited, -ul.primary li a:hover, -ul.primary li.active a { +.tabs.primary li { + position: relative; display: block; - float: left; /* LTR */ - padding: 0.615em 18px; - background-color: #a6a7a2; - color: #000; + overflow: hidden; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + background-color: #f2f2f0; + color: #0074bd; + margin-top: -1px; + width: 100%; /* 1 */ + border: 1px solid #bfbfbf; + text-overflow: ellipsis; + white-space: nowrap; +} +.tabs.primary li:first-child { + border-radius: 4px 4px 0 0; +} +.tabs.primary li:last-child { + border-radius: 0 0 4px 4px; +} +.tabs.primary li.active { + z-index: 15; + border-color: #a6a6a6; + background-color: #ffffff; + color: #004f80; +} +.tabs.primary a { + background-color: transparent; + display: block; + padding: 9px 2em 7px 1em; +} +.tabs.primary a:hover, +.tabs.primary a:focus { + color: #008ee6; + text-decoration: none; + background-color: #fafaf7; +} +.tabs.primary a:after { + content: '>'; + float: right; + font-size: 18px; font-weight: bold; - border-width: 1px 1px 0 1px; - border-style: solid; - border-color: #a6a7a2; - border-radius: 8px 8px 0 0; + line-height: 1em; } -ul.primary li.active a, -ul.primary li.active a.active, -ul.primary li.active a:active, -ul.primary li.active a:visited { - background-color: #fff; - border-color: #c9cac4; +.tabs.primary a.active:after { + color: #CCC; } -ul.primary li a:hover { - color: #fff; + +.tabs.secondary { + border: 1px solid #d9d8d4; + border-radius: 2px; + display: block; + margin-top: 24px; + margin-top: 1.5rem; } -ul.primary li.active a:hover { - color: #000; +.tabs.secondary li { + display: block; + padding: 0; + position: relative; + margin: 0 0 0 -1px; + color: #0074bd; + font-size: 14px; + font-size: 0.875rem; } -.tabs-secondary { - clear: both; +.tabs.secondary li + li { + border-top: 1px solid #d9d8d4; } -ul.secondary { - float: right; /* LTR */ - font-size: 0.923em; - padding: 0 3px 5px; - line-height: 1.385em; - overflow: hidden; - background-color: #fff; +.tabs.secondary li.active { + color: #004f80; } -ul.secondary li { - margin: 0 5px; - float: none; /* LTR */ +.tabs.secondary li:focus, +.tabs.secondary li:hover { + border-left-color: #008ee6; + color: #008ee6; } -ul.secondary li a { - background-color: #ddd; - color: #000; - display: inline-block; +/** + * Use generated content to render the vertical active bar so it can overlap + * adjacent borders. + */ +.tabs.secondary li:before { + content: ''; + display: block; + position: absolute; + top: -1px; + left: 0; + bottom: -1px; + width: 2px; + background-color: transparent; + z-index: 60; } -ul.secondary li a, -ul.secondary li a:hover, -ul.secondary li.active a, -ul.secondary li.active a.active { - padding: 2px 10px; - border-radius: 7px; +.tabs.secondary li.active:before { + background-color: #004f80; } -ul.secondary li a:hover, -ul.secondary li.active a, -ul.secondary li.active a.active { - color: #fff; - background: #666; +.tabs.secondary li:focus:before, +.tabs.secondary li:hover:before { + background-color: #008ee6; } +.tabs.secondary a { + background-color: transparent; + padding: 7px 13px 5px; + text-decoration: none; +} + + #content { clear: left; } -@media screen and (max-width:56.538em) { /* 735px */ - .touch #branding { - padding-right: 0; - position: relative; - } - .touch ul.primary { - clear: both; - float: none; - margin-bottom: -3px; - overflow-x: scroll; - -webkit-overflow-scrolling: touch; - white-space: nowrap; - padding-right: 40px; - } - .touch #branding:after { - background-image: -moz-linear-gradient(360deg, rgba(224, 224, 216, 0), #E0E0D8 80%); - background-image: -o-linear-gradient(360deg, rgba(224, 224, 216, 0), #E0E0D8 80%); - background-image: -webkit-linear-gradient(360deg, rgba(224, 224, 216, 0), #E0E0D8 80%); - background-image: linear-gradient(360deg, rgba(224, 224, 216, 0), #E0E0D8 80%); - content: ' '; - display: block; - float: right; - height: 40px; - width: 80px; - position: relative; - right: 0; - top: -40px; - margin-bottom: -40px; - } - .touch ul.primary li { - float: none; - white-space: nowrap; - } - .touch ul.primary li a:link, - .touch ul.primary li a.active, - .touch ul.primary li a:active, - .touch ul.primary li a:visited, - .touch ul.primary li a:hover, - .touch ul.primary li.active a { - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - box-sizing: border-box; - text-align: center; - width: 100%; - } -} + /** * Page layout. diff --git a/core/themes/seven/templates/page.html.twig b/core/themes/seven/templates/page.html.twig index 3b8294e..206fd0e 100644 --- a/core/themes/seven/templates/page.html.twig +++ b/core/themes/seven/templates/page.html.twig @@ -75,12 +75,12 @@

{{ title }}

{% endif %} {{ title_suffix }} - {% if primary_local_tasks %} - {{ primary_local_tasks }} - {% endif %}
+ {% if primary_local_tasks %} + {{ primary_local_tasks }} + {% endif %} {% if secondary_local_tasks %} {% endif %}