diff --git a/src/wp-admin/css/colors/_admin.scss b/src/wp-admin/css/colors/_admin.scss
index 2ce80049c3..eb869c6610 100644
--- a/src/wp-admin/css/colors/_admin.scss
+++ b/src/wp-admin/css/colors/_admin.scss
@@ -472,6 +472,11 @@ body.more-filters-opened .more-filters:focus:before {
color: $menu-highlight-text;
}
+.control-panel-back:focus,
+.control-panel-back:hover {
+ background-color: $menu-highlight-background;
+ color: $menu-highlight-text;
+}
/* jQuery UI Slider */
diff --git a/src/wp-admin/css/customize-controls.css b/src/wp-admin/css/customize-controls.css
index f966114735..a3ffbdf9c5 100644
--- a/src/wp-admin/css/customize-controls.css
+++ b/src/wp-admin/css/customize-controls.css
@@ -32,6 +32,11 @@ body {
display: block;
}
+#customize-controls .wp-full-overlay-sidebar-content {
+ overflow-y: auto;
+ overflow-x: hidden;
+}
+
#customize-info {
border: none;
border-top: 1px solid #ddd;
@@ -83,11 +88,6 @@ body {
line-height: 20px;
}
-#customize-theme-controls {
- -webkit-box-shadow: 0 1px 1px -1px rgba(0, 0, 0, 0.1);
- box-shadow: 0 1px 1px -1px rgba(0, 0, 0, 0.1);
-}
-
#customize-theme-controls .control-section {
border: none;
}
@@ -103,7 +103,7 @@ body {
background: white;
}
-#customize-theme-controls .control-section:hover .accordion-section-title,
+#customize-theme-controls .control-section:hover > .accordion-section-title,
#customize-theme-controls .control-section .accordion-section-title:hover,
#customize-theme-controls .control-section.open .accordion-section-title,
#customize-theme-controls .control-section .accordion-section-title:focus {
@@ -118,7 +118,7 @@ body {
background: #f5f5f5;
}
-#customize-theme-controls .control-section:hover .accordion-section-title::after,
+#customize-theme-controls .control-section:hover > .accordion-section-title::after,
#customize-theme-controls .control-section .accordion-section-title:hover::after,
#customize-theme-controls .control-section.open .accordion-section-title::after,
#customize-theme-controls .control-section .accordion-section-title:focus::after {
@@ -143,6 +143,138 @@ body {
margin: 0;
}
+.control-section.control-panel > .accordion-section-title:after {
+ content: "\f139";
+}
+
+.accordion-sub-container.control-panel-content {
+ display: none;
+ position: absolute;
+ left: 300px;
+ top: 0;
+ width: 300px;
+ border-top: 1px solid #ddd;
+ -webkit-transition: left ease-in-out .18s;
+ transition: left ease-in-out .18s;
+}
+
+.accordion-sub-container.control-panel-content.animating {
+ display: block;
+}
+
+.current-panel .accordion-sub-container.control-panel-content {
+ width: 100%;
+}
+
+.control-panel-back {
+ display: block;
+ position: fixed;
+ top: 0;
+ z-index: 99;
+ left: -48px;
+ width: 45px;
+ height: 45px;
+ padding-right: 2px;
+ background: #eee;
+ border-right: 1px solid #ddd;
+ cursor: pointer;
+ -webkit-transition: left ease-in-out .18s, color ease-in .1s;
+ transition: left ease-in-out .18s, color ease-in .1s;
+}
+
+.collapsed .control-panel-back {
+ display: none;
+}
+
+.control-panel-back:focus,
+.control-panel-back:hover {
+ background-color: #0074a2;
+ color: #fff;
+ outline: none;
+}
+
+.control-panel-back:before {
+ font: normal 29px/1 dashicons;
+ content: "\f340";
+ position: relative;
+ top: 9px;
+ left: 9px;
+}
+
+.current-panel .control-panel-back {
+ left: 0;
+}
+
+.current-panel > .accordion-section-title {
+ height: 22px;
+}
+
+#customize-header-actions a.back {
+ position: relative;
+ left: 0;
+ -webkit-transition: left ease-in-out .18s;
+ transition: left ease-in-out .18s;
+}
+
+.in-sub-panel #customize-header-actions a.back {
+ left: -120px;
+}
+
+.wp-full-overlay-sidebar .wp-full-overlay-header {
+ -webkit-transition: padding ease-in-out .18s;
+ transition: padding ease-in-out .18s;
+}
+
+.in-sub-panel .wp-full-overlay-sidebar .wp-full-overlay-header {
+ padding-left: 62px;
+}
+
+#customize-info,
+#customize-theme-controls > ul > .accordion-section {
+ position: relative;
+ left: 0;
+ -webkit-transition: left ease-in-out .18s;
+ transition: left ease-in-out .18s;
+}
+
+.in-sub-panel #customize-info,
+.in-sub-panel #customize-theme-controls > ul > .accordion-section {
+ left: -300px;
+ width: 300px;
+}
+
+.in-sub-panel #customize-theme-controls .accordion-section.current-panel {
+ width: 100%;
+}
+
+#customize-theme-controls .control-section.current-panel {
+ padding: 0;
+}
+
+#customize-theme-controls .control-section > h3.accordion-section-title {
+ position: relative;
+ left: 0;
+}
+
+#customize-theme-controls .control-section.current-panel > h3.accordion-section-title {
+ left: -300px;
+ -webkit-transition: left ease-in-out .18s;
+ transition: left ease-in-out .18s;
+}
+
+.control-section.control-panel .accordion-section-title .panel-title {
+ font-size: 20px;
+ font-weight: 200;
+ line-height: 24px;
+ display: block;
+ border: none;
+}
+
+.control-section.control-panel .preview-notice {
+ font-size: 13px;
+ line-height: 24px;
+}
+
.customize-control {
width: 100%;
float: left;
diff --git a/src/wp-admin/customize.php b/src/wp-admin/customize.php
index 1faf371cd9..29ae035599 100644
--- a/src/wp-admin/customize.php
+++ b/src/wp-admin/customize.php
@@ -142,8 +142,12 @@ do_action( 'customize_controls_print_scripts' );
sections() as $section )
+ foreach ( $wp_customize->panels() as $panel ) {
+ $panel->maybe_render();
+ }
+ foreach ( $wp_customize->sections() as $section ) {
$section->maybe_render();
+ }
?>
diff --git a/src/wp-admin/js/accordion.js b/src/wp-admin/js/accordion.js
index 7bc99ccca8..e2a6212b55 100644
--- a/src/wp-admin/js/accordion.js
+++ b/src/wp-admin/js/accordion.js
@@ -4,13 +4,26 @@
// Expand/Collapse on click
$( '.accordion-container' ).on( 'click keydown', '.accordion-section-title', function( e ) {
- if ( e.type === 'keydown' && 13 !== e.which ) // "return" key
- return;
+ if ( e.type === 'keydown' && 13 !== e.which ) { // "return" key
+ return;
+ }
+
e.preventDefault(); // Keep this AFTER the key filter above
accordionSwitch( $( this ) );
});
+ // Back to top level
+ $( '.accordion-container' ).on( 'click keydown', '.control-panel-back', function( e ) {
+ if ( e.type === 'keydown' && 13 !== e.which ) { // "return" key
+ return;
+ }
+
+ e.preventDefault(); // Keep this AFTER the key filter above
+
+ panelSwitch( $( this ) );
+ });
+
// Re-initialize accordion when screen options are toggled
$( '.hide-postbox-tog' ).click( function () {
accordionInit();
@@ -33,8 +46,14 @@
siblings = section.closest( '.accordion-container' ).find( '.open' ),
content = section.find( sectionContent );
- if ( section.hasClass( 'cannot-expand' ) )
+ if ( section.hasClass( 'cannot-expand' ) ) {
return;
+ }
+
+ if ( section.hasClass( 'control-panel' ) ) {
+ panelSwitch( section );
+ return;
+ }
if ( section.hasClass( 'open' ) ) {
section.toggleClass( 'open' );
@@ -49,6 +68,30 @@
accordionInit();
}
+ function panelSwitch( panel ) {
+ var position,
+ section = panel.closest( '.accordion-section' ),
+ container = section.closest( '.wp-full-overlay' ),
+ siblings = container.find( '.accordion-section.open' ),
+ content = section.find( '.control-panel-content' );
+
+ if ( section.hasClass( 'current-panel' ) ) {
+ section.toggleClass( 'current-panel' );
+ container.toggleClass( 'in-sub-panel' );
+ content.delay( 180 ).hide( 0, function() {
+ content.css( 'margin-top', 'inherit' ); // Reset
+ } );
+ } else {
+ siblings.removeClass( 'open' );
+ content.show( 0, function() {
+ position = content.offset().top;
+ content.css( 'margin-top', ( 45 - position ) );
+ section.toggleClass( 'current-panel' );
+ container.toggleClass( 'in-sub-panel' );
+ } );
+ }
+ }
+
// Initialize the accordion (currently just corner fixes)
accordionInit();
diff --git a/src/wp-includes/class-wp-customize-manager.php b/src/wp-includes/class-wp-customize-manager.php
index 42977cbf4e..7ec89ee904 100644
--- a/src/wp-includes/class-wp-customize-manager.php
+++ b/src/wp-includes/class-wp-customize-manager.php
@@ -45,6 +45,7 @@ final class WP_Customize_Manager {
public $widgets;
protected $settings = array();
+ protected $panels = array();
protected $sections = array();
protected $controls = array();
@@ -314,6 +315,17 @@ final class WP_Customize_Manager {
return $this->sections;
}
+ /**
+ * Get the registered panels.
+ *
+ * @since 4.0.0
+ *
+ * @return array
+ */
+ public function panels() {
+ return $this->panels;
+ }
+
/**
* Checks if the current theme is active.
*
@@ -647,6 +659,50 @@ final class WP_Customize_Manager {
unset( $this->settings[ $id ] );
}
+ /**
+ * Add a customize panel.
+ *
+ * @since 4.0.0
+ *
+ * @param WP_Customize_Panel|string $id Customize Panel object, or Panel ID.
+ * @param array $args Panel arguments.
+ */
+ public function add_panel( $id, $args = array() ) {
+ if ( is_a( $id, 'WP_Customize_Panel' ) ) {
+ $panel = $id;
+ }
+ else {
+ $panel = new WP_Customize_Panel( $this, $id, $args );
+ }
+
+ $this->panels[ $panel->id ] = $panel;
+ }
+
+ /**
+ * Retrieve a customize panel.
+ *
+ * @since 4.0.0
+ *
+ * @param string $id Panel ID.
+ * @return WP_Customize_Panel
+ */
+ public function get_panel( $id ) {
+ if ( isset( $this->panels[ $id ] ) ) {
+ return $this->panels[ $id ];
+ }
+ }
+
+ /**
+ * Remove a customize panel.
+ *
+ * @since 4.0.0
+ *
+ * @param string $id Panel ID.
+ */
+ public function remove_panel( $id ) {
+ unset( $this->panels[ $id ] );
+ }
+
/**
* Add a customize section.
*
@@ -749,7 +805,7 @@ final class WP_Customize_Manager {
}
/**
- * Prepare settings and sections.
+ * Prepare panels, sections, and controls.
*
* For each, check if required related components exist,
* whether the user has the necessary capabilities,
@@ -763,8 +819,9 @@ final class WP_Customize_Manager {
$controls = array();
foreach ( $this->controls as $id => $control ) {
- if ( ! isset( $this->sections[ $control->section ] ) || ! $control->check_capabilities() )
+ if ( ! isset( $this->sections[ $control->section ] ) || ! $control->check_capabilities() ) {
continue;
+ }
$this->sections[ $control->section ]->controls[] = $control;
$controls[ $id ] = $control;
@@ -778,13 +835,39 @@ final class WP_Customize_Manager {
$sections = array();
foreach ( $this->sections as $section ) {
- if ( ! $section->check_capabilities() || ! $section->controls )
+ if ( ! $section->check_capabilities() || ! $section->controls ) {
continue;
+ }
usort( $section->controls, array( $this, '_cmp_priority' ) );
- $sections[] = $section;
+
+ if ( ! $section->panel ) {
+ // Top-level section.
+ $sections[] = $section;
+ } else {
+ // This section belongs to a panel.
+ if ( isset( $this->panels [ $section->panel ] ) ) {
+ $this->panels[ $section->panel ]->sections[] = $section;
+ }
+ }
}
$this->sections = $sections;
+
+ // Prepare panels.
+ // Reversing makes uasort sort by time added when conflicts occur.
+ $this->panels = array_reverse( $this->panels );
+ uasort( $this->panels, array( $this, '_cmp_priority' ) );
+ $panels = array();
+
+ foreach ( $this->panels as $panel ) {
+ if ( ! $panel->check_capabilities() || ! $panel->sections ) {
+ continue;
+ }
+
+ usort( $panel->sections, array( $this, '_cmp_priority' ) );
+ $panels[] = $panel;
+ }
+ $this->panels = $panels;
}
/**
diff --git a/src/wp-includes/class-wp-customize-section.php b/src/wp-includes/class-wp-customize-section.php
index 2cfae71a63..1082647ac9 100644
--- a/src/wp-includes/class-wp-customize-section.php
+++ b/src/wp-includes/class-wp-customize-section.php
@@ -37,6 +37,15 @@ class WP_Customize_Section {
*/
public $priority = 10;
+ /**
+ * Panel in which to show the section, making it a sub-section.
+ *
+ * @since 4.0.0
+ * @access public
+ * @var string
+ */
+ public $panel = '';
+
/**
* Capability required for the section.
*
@@ -162,8 +171,12 @@ class WP_Customize_Section {
* @since 3.4.0
*/
protected function render() {
+ $classes = 'control-section accordion-section';
+ if ( $this->panel ) {
+ $classes .= ' control-subsection';
+ }
?>
-
+
title ); ?>
description ) ) : ?>
@@ -178,3 +191,77 @@ class WP_Customize_Section {
sections = array(); // Users cannot customize the $sections array.
+
+ return $this;
+ }
+
+ /**
+ * Render the panel, and the sections that have been added to it.
+ *
+ * @since 4.0.0
+ */
+ protected function render() {
+ ?>
+ -
+
title ); ?>
+ Back to Customize
+
+ -
+
+ ' . esc_html( $this->title ) . '' );
+ ?>
+
+ description ) ) : ?>
+
+ description; ?>
+
+
+
+ sections as $section ) {
+ $section->maybe_render();
+ }
+ ?>
+
+
+ manager->add_setting( $setting_id, $setting_args );
}
+ $this->manager->add_panel( 'widgets', array(
+ 'title' => __( 'Widgets' ),
+ 'description' => __( 'Widgets are independent sections of content that can be placed into widgetized areas provided by your theme (commonly called sidebars).' ),
+ ) );
+
foreach ( $sidebars_widgets as $sidebar_id => $sidebar_widget_ids ) {
if ( empty( $sidebar_widget_ids ) ) {
$sidebar_widget_ids = array();
@@ -458,10 +463,10 @@ final class WP_Customize_Widgets {
if ( $is_active_sidebar ) {
$section_args = array(
- /* translators: %s: sidebar name */
- 'title' => sprintf( __( 'Widgets: %s' ), $GLOBALS['wp_registered_sidebars'][$sidebar_id]['name'] ),
- 'description' => $GLOBALS['wp_registered_sidebars'][$sidebar_id]['description'],
- 'priority' => 1000 + array_search( $sidebar_id, array_keys( $wp_registered_sidebars ) ),
+ 'title' => $GLOBALS['wp_registered_sidebars'][ $sidebar_id ]['name'],
+ 'description' => $GLOBALS['wp_registered_sidebars'][ $sidebar_id ]['description'],
+ 'priority' => array_search( $sidebar_id, array_keys( $wp_registered_sidebars ) ),
+ 'panel' => 'widgets',
);
/**