diff --git a/core/includes/menu.inc b/core/includes/menu.inc
index 3ba3b5d..4fd9d8e 100644
--- a/core/includes/menu.inc
+++ b/core/includes/menu.inc
@@ -2355,6 +2355,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/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 56adbff..5f3c001 100644
--- a/core/modules/system/css/system.module.css
+++ b/core/modules/system/css/system.module.css
@@ -372,3 +372,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 c6ed012..6dfe597 100644
--- a/core/modules/system/css/system.theme.css
+++ b/core/modules/system/css/system.theme.css
@@ -437,15 +437,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 8437e4c..c6a6e71 100644
--- a/core/modules/system/system.module
+++ b/core/modules/system/system.module
@@ -1262,6 +1262,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',
@@ -1309,6 +1345,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 bbc7d16..a4fa2d6c 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 2a22f0b..2383fd3 100644
--- a/core/themes/seven/seven.theme
+++ b/core/themes/seven/seven.theme
@@ -34,12 +34,81 @@ function seven_library_info() {
* 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 1564606..e124e01 100644
--- a/core/themes/seven/style.css
+++ b/core/themes/seven/style.css
@@ -201,7 +201,7 @@ pre {
#branding {
overflow: hidden;
background-color: #e0e0d8;
- padding: 24px 0 12px;
+ padding: 24px 0 0;
}
/* This layout styling is a copy of #page.
* @TODO: Replace with reuseable layout classes.
@@ -270,151 +270,244 @@ pre {
/**
* Tabs.
*/
-ul.primary {
- float: right; /* LTR */
- border-bottom: none;
- text-transform: uppercase;
- font-size: 0.923em;
- margin: 0;
- padding-top: 0;
-}
-[dir="rtl"] ul.primary {
- float: left;
+.is-collapse-enabled .tabs,
+.is-horizontal .tabs {
+ position: relative;
}
-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;
+ background-color: #A6A6A6;
+ height: 1px;
+ position: absolute;
+ bottom: 0;
+ left: 0;
+ z-index: 10;
+ right: 0;
}
-[dir="rtl"] ul.primary li {
- float: right;
+
+/* Span the full width of the viewport */
+.branding__inner .is-horizontal .tabs:before,
+.branding__inner .is-collapse-enabled .tabs:before {
+ left: -2.5em;
+ right: -2.5em;
}
-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