diff --git a/core/includes/menu.inc b/core/includes/menu.inc index f289cbb..36ff691 100644 --- a/core/includes/menu.inc +++ b/core/includes/menu.inc @@ -2347,6 +2347,28 @@ function menu_local_tabs() { } /** + * Returns a renderable element for the primary tabs. + */ +function menu_local_primary_tabs() { + $build = array( + '#theme' => 'menu_local_tasks', + '#primary' => menu_primary_local_tasks(), + ); + return !empty($build['#primary']) || !empty($build['#secondary']) ? $build : array(); +} + +/** + * Returns a renderable element for the secondary tabs. + */ +function menu_local_secondary_tabs() { + $build = array( + '#theme' => 'menu_local_tasks', + '#secondary' => menu_secondary_local_tasks(), + ); + return !empty($build['#primary']) || !empty($build['#secondary']) ? $build : array(); +} + +/** * Returns HTML for primary and secondary local tasks. * * @param $variables diff --git a/core/misc/icons/0074BD/chevron-right.png b/core/misc/icons/0074BD/chevron-right.png new file mode 100755 index 0000000..9dc775a --- /dev/null +++ b/core/misc/icons/0074BD/chevron-right.png @@ -0,0 +1,5 @@ +PNG + + IHDRaIDAT8Ocd0R w;^ dj\\@,/0g|4@t@:@^#!oD!09/ + + ?2p4HJ82Am IENDB` \ No newline at end of file diff --git a/core/misc/icons/0074BD/chevron-right.svg b/core/misc/icons/0074BD/chevron-right.svg new file mode 100644 index 0000000..0f509a6 --- /dev/null +++ b/core/misc/icons/0074BD/chevron-right.svg @@ -0,0 +1 @@ + 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.module.css b/core/modules/system/css/system.module.css index 71dd4c6..3bae634 100644 --- a/core/modules/system/css/system.module.css +++ b/core/modules/system/css/system.module.css @@ -369,3 +369,60 @@ tr .ajax-progress-throbber .throbber { .align-justify { text-align: justify; } + +/* + * Remove browser styles, especially for and so on. + */ +.reset-appearance { + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + border: 0 none; + background: transparent; + padding: 0; + margin: 0; + line-height: inherit; +} + +/* + * Remove browser styles, especially for and so on. + */ +.reset-appearance { + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + border: 0 none; + background: transparent; + padding: 0; + margin: 0; + line-height: inherit; +} + +/* + * Contain positioned elements. + */ +.position-container { + position: relative; +} + +/** + * Block-level HTML5 display definition. + * + * Provides display values for browsers that don't recognize the new elements + * and therefore display them inline by default. + */ +article, +aside, +details, +figcaption, +figure, +footer, +header, +hgroup, +main, +menu, +nav, +section, +summary { + display: block; +} diff --git a/core/modules/system/css/system.theme.css b/core/modules/system/css/system.theme.css index 0b9af42..0accde9 100644 --- a/core/modules/system/css/system.theme.css +++ b/core/modules/system/css/system.theme.css @@ -441,15 +441,12 @@ 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; } -.tabs > li { +.tabs__tab { display: inline-block; margin-right: 0.3em; /* LTR */ } diff --git a/core/modules/system/system.module b/core/modules/system/system.module index 4625a82..84acf1b 100644 --- a/core/modules/system/system.module +++ b/core/modules/system/system.module @@ -1225,6 +1225,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', @@ -1272,6 +1308,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/modules/views_ui/css/views_ui.admin.theme.css b/core/modules/views_ui/css/views_ui.admin.theme.css index a203ba1..52490a2 100644 --- a/core/modules/views_ui/css/views_ui.admin.theme.css +++ b/core/modules/views_ui/css/views_ui.admin.theme.css @@ -457,20 +457,30 @@ td.group-title { * The tabs that switch between sections */ -ul#views-display-menu-tabs { +.views-displays .tabs.secondary { margin-right: 200px; + border: 0; } -ul#views-display-menu-tabs li { +.views-displays .tabs.secondary li { + background: transparent; margin-bottom: 5px; + border: 0; + padding: 0; + width: auto; } -ul#views-display-menu-tabs li.add ul.action-list li{ +.views-displays .tabs.secondary li.add ul.action-list li{ margin: 0; } -.views-displays .secondary a { +.views-displays .tabs.secondary li { + margin: 0 5px 0 6px; +} + +.views-displays .tabs.secondary a { border: 1px solid #cbcbcb; + border-radius: 7px; display: inline-block; font-size: small; line-height: 1.3333; @@ -480,65 +490,66 @@ ul#views-display-menu-tabs li.add ul.action-list li{ /** * Display a red border if the display doesn't validate. */ -.views-displays ul.secondary li.active a.active.error, -.views-displays .secondary a.error { +.views-displays .tabs.secondary li.active a.active.error, +.views-displays .tabs.secondary a.error { border: 2px solid #ed541d; padding: 1px 6px; } -.views-displays .secondary a:focus { +.views-displays .tabs.secondary a:focus { outline: none; } -.views-displays ul.secondary li a { +.views-displays .tabs.secondary li a { background-color: #fff; } -.views-displays ul.secondary li a:hover, -.views-displays ul.secondary li.active a, -.views-displays ul.secondary li.active a.active { +.views-displays .tabs.secondary li a:hover, +.views-displays .tabs.secondary li.active a, +.views-displays .tabs.secondary li.active a.active { background-color: #555; color: #fff; } -.views-displays .secondary .open > a { +.views-displays .tabs.secondary .open > a { background-color: #f1f1f1; border-bottom: 1px solid transparent; position: relative; } -.views-displays .secondary .open > a:hover { +.views-displays .tabs.secondary .open > a:hover { background-color: #f1f1f1; } -.views-displays .secondary .action-list li { +.views-displays .tabs.secondary .action-list li { background-color: #f1f1f1; border-color: #cbcbcb; border-style: solid; border-width: 0 1px; padding: 2px 9px; + } -.views-displays .secondary .action-list li:first-child { +.views-displays .tabs.secondary .action-list li:first-child { border-width: 1px 1px 0; } -.views-displays .secondary .action-list li.last { +.views-displays .tabs.secondary .action-list li.last { border-width: 0 1px 1px; } -.views-displays .secondary .action-list li:last-child { +.views-displays .tabs.secondary .action-list li:last-child { border-width: 0 1px 1px; } -.views-displays .secondary .action-list input.form-submit { +.views-displays .tabs.secondary .action-list input.form-submit { background: none repeat scroll 0 0 transparent; border: medium none; margin: 0; padding: 0; } -.views-displays .secondary .action-list li:hover { +.views-displays .tabs.secondary .action-list li:hover { background-color: #ddd; } diff --git a/core/themes/seven/js/nav-tabs.js b/core/themes/seven/js/nav-tabs.js new file mode 100644 index 0000000..12ba207 --- /dev/null +++ b/core/themes/seven/js/nav-tabs.js @@ -0,0 +1,20 @@ +(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 ) { + var notSmartPhone = window.matchMedia("(min-width: 300px)"); + if (notSmartPhone.matches) { + $(tabs).navTabs(); + } + } + } +}; + +})(jQuery, Drupal); diff --git a/core/themes/seven/seven.theme b/core/themes/seven/seven.theme index bfc87e2..ee6c37b 100644 --- a/core/themes/seven/seven.theme +++ b/core/themes/seven/seven.theme @@ -53,12 +53,81 @@ function seven_preprocess_html(&$variables) { * Implements hook_preprocess_HOOK() for page.tpl.php. */ function seven_preprocess_page(&$variables) { - $variables['primary_local_tasks'] = $variables['tabs']; - unset($variables['primary_local_tasks']['#secondary']); - $variables['secondary_local_tasks'] = array( - '#theme' => 'menu_local_tasks', - '#secondary' => isset($variables['tabs']['#secondary']) ? $variables['tabs']['#secondary'] : '', + if (isset($variables['tabs'])) { + $variables['primary_local_tasks'] = $variables['tabs']; + unset($variables['primary_local_tasks']['#secondary']); + $variables['secondary_local_tasks'] = array( + '#theme' => 'menu_local_tasks', + '#secondary' => isset($variables['tabs']['#secondary']) ? $variables['tabs']['#secondary'] : '', + ); + } +} + +/** + * Overrides theme_menu_local_tasks(). + * + * 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'])) { + drupal_add_library('system', 'jquery.nav-tabs', FALSE); + drupal_add_js(drupal_get_path('theme', 'seven') . '/js/nav-tabs.js'); + $variables['secondary']['#prefix'] = '

' . t('Secondary tabs') . '

'; + $variables['secondary']['#prefix'] .= ''; + $output .= drupal_render($variables['secondary']); + } + + return $output; +} + +/** + * Overrides theme_menu_local_task(). + * + * Returns HTML for a local task. + * + **/ +function seven_menu_local_task($variables) { + $link = $variables['element']['#link']; + $link += array( + 'localized_options' => array(), ); + $link_text = $link['title']; + + if (!empty($variables['element']['#active'])) { + // Add text to indicate active tab for non-visual users. + $active = '' . t('(active tab)') . ''; + + // If the link does not contain HTML already, check_plain() it now. + // After we set 'html'=TRUE the link will not be sanitized by l(). + if (empty($link['localized_options']['html'])) { + $link['title'] = check_plain($link['title']); + } + $link['localized_options']['html'] = TRUE; + $link_text = t('!local-task-title!active', array('!local-task-title' => $link['title'], '!active' => $active)); + } + $class = 'tabs__tab'; + if(!empty($variables['element']['#active'])) { + $class .= ' active'; + } + + return '
  • ' . l($link_text, $link['href'], $link['localized_options']) . '
  • '; } /** diff --git a/core/themes/seven/style.css b/core/themes/seven/style.css index c628c55..3fdef3e 100644 --- a/core/themes/seven/style.css +++ b/core/themes/seven/style.css @@ -258,151 +258,266 @@ pre { /** * Tabs. */ -ul.primary { - float: right; /* LTR */ - border-bottom: none; - text-transform: uppercase; - font-size: 0.923em; - margin: 0; - padding-top: 0; + .tabs { + display: none; + } +.no-js .tabs { + display: block; } -[dir="rtl"] ul.primary { - float: left; +.is-collapse-enabled .tabs, +.is-horizontal .tabs { + position: relative; + display: block; } -ul.primary li { - float: left; /* LTR */ - list-style: none; - height: 2.60em; - margin: 0 2px; +.is-collapse-enabled .tabs:before, +.is-horizontal .tabs:before { + content: ''; + display: block; + border-bottom: 1px solid #A6A6A6; + position: absolute; + bottom: 0; + left: 0; + z-index: 10; + right: 0; } -[dir="rtl"] ul.primary li { - float: right; +#branding .is-collapse-enabled .tabs:before, +#branding .is-horizontal .tabs:before { + left: -20px; + right: -20px; } -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 { + +/** + * Tab + * + * 1. Required by some elements such as