Commit 65958f96 authored by effulgentsia's avatar effulgentsia
Browse files

Issue #3070493 by bnjmnm, lauriii: Introduce a mechanism to provide an...

Issue #3070493 by bnjmnm, lauriii: Introduce a mechanism to provide an alternate Claro design for the toolbar in the future
parent 030cc90d
......@@ -101,6 +101,7 @@ public function testToolbar() {
'config:block_list',
'config:shortcut.set.default',
'config:system.menu.admin',
'config:system.theme',
'config:user.role.authenticated',
'rendered',
'user:' . $this->rootUser->id(),
......
......@@ -60,6 +60,12 @@ public function onSave(ConfigCrudEvent $event) {
$this->cacheTagsInvalidator->invalidateTags(['rendered']);
}
// Library and template overrides potentially change for the default theme
// when the admin theme is changed.
if ($config_name === 'system.theme' && $event->isChanged('admin')) {
$this->cacheTagsInvalidator->invalidateTags(['library_info', 'theme_registry']);
}
// Theme-specific settings, check if this matches a theme settings
// configuration object (THEME_NAME.settings), in that case, clear the
// rendered cache tag.
......
......@@ -1250,3 +1250,57 @@ function system_modules_uninstalled($modules) {
}
}
}
/**
* Determines if Claro is the admin theme but not the active theme.
*
* @return bool
* TRUE if Claro is the admin theme but not the active theme.
*/
function _system_is_claro_admin_and_not_active() {
$admin_theme = \Drupal::configFactory()->get('system.theme')->get('admin');
$active_theme = \Drupal::theme()->getActiveTheme()->getName();
return $active_theme !== 'claro' && $admin_theme === 'claro';
}
/**
* Implements hook_library_info_alter().
*/
function system_library_info_alter(&$libraries, $extension) {
// If Claro is the admin theme but not the active theme, grant Claro the
// ability to override the toolbar library with its own assets.
if ($extension === 'toolbar' && _system_is_claro_admin_and_not_active()) {
require_once DRUPAL_ROOT . '/core/themes/claro/claro.theme';
claro_system_module_invoked_library_info_alter($libraries, $extension);
}
}
/**
* Implements hook_preprocess_toolbar().
*/
function system_preprocess_toolbar(array &$variables, $hook, $info) {
// When Claro is the admin theme, Claro overrides the active theme's if that
// active theme is not Claro. Because of these potential overrides, the
// toolbar cache should be invalidated any time the default or admin theme
// changes.
$variables['#cache']['tags'][] = 'config:system.theme';
// If Claro is the admin theme but not the active theme, still include Claro's
// toolbar preprocessing.
if (_system_is_claro_admin_and_not_active()) {
require_once DRUPAL_ROOT . '/core/themes/claro/claro.theme';
claro_preprocess_toolbar($variables, $hook, $info);
}
}
/**
* Implements hook_theme_registry_alter().
*/
function system_theme_registry_alter(array &$theme_registry) {
// If Claro is the admin theme but not the active theme, use Claro's toolbar
// templates.
if (_system_is_claro_admin_and_not_active()) {
require_once DRUPAL_ROOT . '/core/themes/claro/claro.theme';
claro_system_module_invoked_theme_registry_alter($theme_registry);
}
}
<?php
namespace Drupal\Tests\system\Functional\Theme;
use Drupal\Tests\BrowserTestBase;
/**
* Tests the loading of Claro assets on a non-Claro default theme.
*
* @group Theme
*/
class ToolbarClaroOverridesTest extends BrowserTestBase {
/**
* {@inheritdoc}
*/
protected static $modules = ['toolbar', 'test_page_test', 'shortcut', 'node'];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* The theme installer used in this test for enabling themes.
*
* @var \Drupal\Core\Extension\ThemeInstallerInterface
*/
protected $themeInstaller;
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->themeInstaller = $this->container->get('theme_installer');
$this->themeManager = $this->container->get('theme.manager');
$this->themeInstaller->install(['claro']);
// Create user with sufficient permissions to have the shortcut toolbar menu
// be available.
$this->drupalLogin($this->drupalCreateUser([
'access toolbar',
'access shortcuts',
'administer shortcuts',
'access content overview',
]));
}
/**
* Confirm Claro assets load on a non-Claro default theme.
*/
public function testClaroAssets() {
$default_stylesheets = [
'core/modules/toolbar/css/toolbar.module.css',
'core/modules/toolbar/css/toolbar.menu.css',
'core/modules/toolbar/css/toolbar.theme.css',
'core/modules/toolbar/css/toolbar.icons.theme.css',
];
$claro_stylesheets = [
'core/themes/claro/css/components/toolbar.module.css',
'core/themes/claro/css/state/toolbar.menu.css',
'core/themes/claro/css/theme/toolbar.theme.css',
'core/themes/claro/css/theme/toolbar.icons.theme.css',
];
$this->config('system.theme')->set('admin', 'stark')->save();
$this->drupalGet('test-page');
$this->assertSession()->statusCodeEquals(200);
$admin_theme = \Drupal::configFactory()->get('system.theme')->get('admin');
$default_theme = \Drupal::configFactory()->get('system.theme')->get('default');
$this->assertEquals('stark', $admin_theme);
$this->assertEquals('stark', $default_theme);
$head = $this->getSession()->getPage()->find('css', 'head')->getHtml();
// Confirm that Claro stylesheets are not loading, and the ones they would
// override were Claro enabled are still loading.
$stylesheet_positions = [];
foreach ($default_stylesheets as $stylesheet) {
$this->assertStringContainsString($stylesheet, $head);
$stylesheet_positions[] = strpos($head, $stylesheet);
}
$sorted_stylesheet_positions = $stylesheet_positions;
sort($sorted_stylesheet_positions);
$this->assertEquals($sorted_stylesheet_positions, $stylesheet_positions);
foreach ($claro_stylesheets as $stylesheet) {
$this->assertStringNotContainsString($stylesheet, $head);
}
// Confirm toolbar is not processed by claro_preprocess_toolbar().
$this->assertFalse($this->getSession()->getPage()->find('css', '#toolbar-administration')->hasAttribute('data-drupal-claro-processed-toolbar'));
// Confirm menu--toolbar.html.twig is not loaded from Claro.
$this->assertFalse($this->getSession()->getPage()->find('css', '.toolbar-menu')->hasClass('claro-toolbar-menu'));
$this->assertFalse($this->getSession()->getPage()->find('css', '.toolbar')->hasClass('claro-toolbar'));
$this->config('system.theme')->set('admin', 'claro')->save();
$this->drupalGet('test-page');
$this->assertSession()->statusCodeEquals(200);
$admin_theme = \Drupal::configFactory()->get('system.theme')->get('admin');
$default_theme = \Drupal::configFactory()->get('system.theme')->get('default');
$this->assertEquals('claro', $admin_theme);
$this->assertEquals('stark', $default_theme);
$head = $this->getSession()->getPage()->find('css', 'head')->getHtml();
// Confirm that Claro stylesheets are loading, and the ones they override
// are not loading.
$stylesheet_positions = [];
foreach ($claro_stylesheets as $stylesheet) {
$this->assertStringContainsString($stylesheet, $head);
$stylesheet_positions[] = strpos($head, $stylesheet);
}
$sorted_stylesheet_positions = $stylesheet_positions;
sort($sorted_stylesheet_positions);
$this->assertEquals($sorted_stylesheet_positions, $stylesheet_positions);
foreach ($default_stylesheets as $stylesheet) {
$this->assertStringNotContainsString($stylesheet, $head);
}
// Confirm toolbar is processed by claro_preprocess_toolbar().
$this->assertTrue($this->getSession()->getPage()->find('css', '#toolbar-administration')->hasAttribute('data-drupal-claro-processed-toolbar'));
// Confirm toolbar templates are loaded from Claro.
$this->assertTrue($this->getSession()->getPage()->find('css', '.toolbar')->hasClass('claro-toolbar'));
$this->assertTrue($this->getSession()->getPage()->find('css', '.toolbar-menu')->hasClass('claro-toolbar-menu'));
}
}
......@@ -89,6 +89,19 @@ libraries-override:
layout:
css/node.module.css: false
toolbar/toolbar:
css:
component:
css/toolbar.module.css: css/components/toolbar.module.css
theme:
css/toolbar.theme.css: css/theme/toolbar.theme.css
css/toolbar.icons.theme.css: css/theme/toolbar.icons.theme.css
toolbar/toolbar.menu:
css:
state:
css/toolbar.menu.css: css/state/toolbar.menu.css
views_ui/admin.styling:
css:
component:
......
......@@ -1596,3 +1596,62 @@ function claro_form_views_ui_add_handler_form_alter(array &$form, FormStateInter
unset($form['override']['controls']);
}
}
/**
* Implements hook_preprocess_toolbar().
*
* This is also called by system_preprocess_toolbar() in instances where Claro
* is the admin theme but not the active theme.
*
* @see system_preprocess_toolbar()
*/
function claro_preprocess_toolbar(&$variables, $hook, $info) {
$variables['attributes']['data-drupal-claro-processed-toolbar'] = TRUE;
}
/**
* Called by system.module via its hook_library_info_alter().
*
* If the active theme is not Claro, but Claro is the admin theme, this alters
* the toolbar library config so Claro's toolbar stylesheets are used.
*
* @see system_library_info_alter()
*/
function claro_system_module_invoked_library_info_alter(&$libraries, $extension) {
if ($extension === 'toolbar') {
$claro_info = \Drupal::service('theme_handler')->listInfo()['claro']->info;
$path_prefix = '/core/themes/claro/';
$claro_toolbar_overrides = $claro_info['libraries-override']['toolbar/toolbar'];
foreach ($claro_toolbar_overrides['css'] as $concern => $overrides) {
foreach ($claro_toolbar_overrides['css'][$concern] as $key => $value) {
$config = $libraries['toolbar']['css'][$concern][$key];
$libraries['toolbar']['css'][$concern][$path_prefix . $value] = $config;
unset($libraries['toolbar']['css'][$concern][$key]);
}
}
$claro_toolbar_menu_overrides = $claro_info['libraries-override']['toolbar/toolbar.menu'];
foreach ($claro_toolbar_menu_overrides['css'] as $concern => $overrides) {
foreach ($claro_toolbar_menu_overrides['css'][$concern] as $key => $value) {
$config = $libraries['toolbar.menu']['css'][$concern][$key];
$libraries['toolbar.menu']['css'][$concern][$path_prefix . $value] = $config;
unset($libraries['toolbar.menu']['css'][$concern][$key]);
}
}
}
}
/**
* Called by system.module via its hook_theme_registry_alter().
*
* If the active theme is not Claro, but Claro is the admin theme, this alters
* the registry so Claro's toolbar templates are used.
*
* @see system_theme_registry_alter()
*/
function claro_system_module_invoked_theme_registry_alter(array &$theme_registry) {
foreach (['toolbar', 'menu__toolbar'] as $registry_item) {
if (isset($theme_registry[$registry_item])) {
$theme_registry[$registry_item]['path'] = 'core/themes/claro/templates/navigation';
}
}
}
/*
* DO NOT EDIT THIS FILE.
* See the following change record for more information,
* https://www.drupal.org/node/3084859
* @preserve
*/
/**
* @file toolbar.module.css
*
*
* Aggressive resets so we can achieve a consistent look in hostile CSS
* environments.
*
* If Claro is the admin theme, this stylesheet will be used by the active theme
* even if the active theme is not Claro.
*/
#toolbar-administration,
#toolbar-administration * {
box-sizing: border-box;
}
#toolbar-administration {
margin: 0;
padding: 0;
vertical-align: baseline;
font-size: small;
line-height: 1;
}
@media print {
#toolbar-administration {
display: none;
}
}
.toolbar-loading #toolbar-administration {
overflow: hidden;
}
/**
* Very specific overrides for Drupal system CSS.
*/
.toolbar li,
.toolbar .item-list,
.toolbar .item-list li,
.toolbar .menu-item,
.toolbar .menu-item--expanded {
list-style-type: none;
list-style-image: none;
}
.toolbar .menu-item {
padding-top: 0;
}
.toolbar .toolbar-bar .toolbar-tab,
.toolbar .menu-item {
display: block;
}
.toolbar .toolbar-bar .toolbar-tab.hidden {
display: none;
}
.toolbar a {
display: block;
line-height: 1;
}
/**
* Administration menu.
*/
.toolbar .toolbar-bar,
.toolbar .toolbar-tray {
position: relative;
z-index: 1250;
}
.toolbar-horizontal .toolbar-tray {
position: fixed;
left: 0;
width: 100%;
}
/* Position the admin toolbar absolutely when the configured standard breakpoint
* is active. The toolbar container, that contains the bar and the trays, is
* position absolutely so that it scrolls with the page. Otherwise, on smaller
* screens, the components of the admin toolbar are positioned statically. */
.toolbar-oriented .toolbar-bar {
position: absolute;
top: 0;
right: 0;
left: 0;
}
.toolbar-oriented .toolbar-tray {
position: absolute;
right: 0;
left: 0;
}
/* .toolbar-loading is required by Toolbar JavaScript to pre-render markup
* style to avoid extra reflow & flicker. */
@media (min-width: 61em) {
.toolbar-loading.toolbar-horizontal .toolbar .toolbar-bar .toolbar-tab:last-child .toolbar-tray {
position: relative;
z-index: -999;
display: block;
visibility: hidden;
width: 1px;
}
.toolbar-loading.toolbar-horizontal .toolbar .toolbar-bar .toolbar-tab:last-child .toolbar-tray .toolbar-lining {
width: 999em;
}
.toolbar-loading.toolbar-horizontal .toolbar .toolbar-bar .home-toolbar-tab + .toolbar-tab .toolbar-tray {
display: block;
}
}
/* Layer the bar just above the trays and above contextual link triggers. */
.toolbar-oriented .toolbar-bar {
z-index: 502;
}
/* Position the admin toolbar fixed when the configured standard breakpoint is
* active. */
body.toolbar-fixed .toolbar-oriented .toolbar-bar {
position: fixed;
}
/* When the configured narrow breakpoint is active, the toolbar is sized to wrap
* around the trays in order to provide a context for scrolling tray content
* that is taller than the viewport. */
body.toolbar-tray-open.toolbar-fixed.toolbar-vertical .toolbar-oriented {
bottom: 0;
width: 240px;
width: 15rem;
}
/* Present the admin toolbar tabs horizontally as a default on user agents that
* do not understand media queries or on user agents where JavaScript is
* disabled. */
.toolbar-loading.toolbar-horizontal .toolbar .toolbar-tray .toolbar-menu > li,
.toolbar .toolbar-bar .toolbar-tab,
.toolbar .toolbar-tray-horizontal li {
float: left; /* LTR */
}
[dir="rtl"] .toolbar-loading.toolbar-horizontal .toolbar .toolbar-tray .toolbar-menu > li,
[dir="rtl"] .toolbar .toolbar-bar .toolbar-tab,
[dir="rtl"] .toolbar .toolbar-tray-horizontal li {
float: right;
}
/* Present the admin toolbar tabs vertically by default on user agents that
* that understand media queries. This will be the small screen default. */
@media only screen {
.toolbar .toolbar-bar .toolbar-tab,
.toolbar .toolbar-tray-horizontal li {
float: none; /* LTR */
}
[dir="rtl"] .toolbar .toolbar-bar .toolbar-tab,
[dir="rtl"] .toolbar .toolbar-tray-horizontal li {
float: none;
}
}
/* This min-width media query is meant to provide basic horizontal layout to
* the main menu tabs when JavaScript is disabled on user agents that understand
* media queries. */
@media (min-width: 16.5em) {
.toolbar .toolbar-bar .toolbar-tab,
.toolbar .toolbar-tray-horizontal li {
float: left; /* LTR */
}
[dir="rtl"] .toolbar .toolbar-bar .toolbar-tab,
[dir="rtl"] .toolbar .toolbar-tray-horizontal li {
float: right;
}
}
/* Present the admin toolbar tabs horizontally when the configured narrow
* breakpoint is active. */
.toolbar-oriented .toolbar-bar .toolbar-tab,
.toolbar-oriented .toolbar-tray-horizontal li {
float: left; /* LTR */
}
[dir="rtl"] .toolbar-oriented .toolbar-bar .toolbar-tab,
[dir="rtl"] .toolbar-oriented .toolbar-tray-horizontal li {
float: right;
}
/**
* Toolbar tray.
*/
.toolbar .toolbar-tray {
z-index: 501;
display: none;
}
.toolbar-oriented .toolbar-tray-vertical {
position: absolute;
left: -100%; /* LTR */
width: 240px;
width: 15rem;
}
[dir="rtl"] .toolbar-oriented .toolbar-tray-vertical {
right: -100%;
left: auto;
}
.toolbar .toolbar-tray-vertical > .toolbar-lining {
min-height: 100%;
}
/* Layer the links just above the toolbar-tray. */
.toolbar .toolbar-bar .toolbar-tab > .toolbar-icon {
position: relative;
z-index: 502;
}
/* Hide secondary menus when the tray is horizontal. */
.toolbar-oriented .toolbar-tray-horizontal .menu-item ul {
display: none;
}
/* When the configured standard breakpoint is active and the tray is in a
* vertical position, the tray does not scroll with the page. The contents of
* the tray scroll within the confines of the viewport.
*/
.toolbar .toolbar-tray-vertical.is-active,
body.toolbar-fixed .toolbar .toolbar-tray-vertical {
position: fixed;
overflow-x: hidden;
overflow-y: auto;
height: 100%;
}
.toolbar .toolbar-tray.is-active {
display: block;
}
/* Bring the tray into the viewport. By default it is just off-screen. */
.toolbar-oriented .toolbar-tray-vertical.is-active {
left: 0; /* LTR */
}
[dir="rtl"] .toolbar-oriented .toolbar-tray-vertical.is-active {
right: 0;
left: auto;
}
/* When the configured standard breakpoint is active, the tray appears to push
* the page content away from the edge of the viewport. */
body.toolbar-tray-open.toolbar-vertical.toolbar-fixed {
margin-left: 240px; /* LTR */
margin-left: 15rem; /* LTR */
}
@media print {
body.toolbar-tray-open.toolbar-vertical.toolbar-fixed {
margin-left: 0;
}
}
[dir="rtl"] body.toolbar-tray-open.toolbar-vertical.toolbar-fixed {
margin-right: 240px;
margin-right: 15rem;
margin-left: auto;
}
@media print {
[dir="rtl"] body.toolbar-tray-open.toolbar-vertical.toolbar-fixed {
margin-right: 0;
}
}
/**
* ToolBar tray orientation toggle.
*/
/* Hide the orientation toggle when the configured narrow breakpoint is not
* active. */
.toolbar .toolbar-tray .toolbar-toggle-orientation {
display: none;
}
/* Show the orientation toggle when the configured narrow breakpoint is
* active. */
.toolbar-oriented .toolbar-tray .toolbar-toggle-orientation {
display: block;
}
.toolbar-oriented .toolbar-tray-horizontal .toolbar-toggle-orientation {
position: absolute;
top: auto;
right: 0; /* LTR */
bottom: 0;
}
[dir="rtl"] .toolbar-oriented .toolbar-tray-horizontal .toolbar-toggle-orientation {
right: auto;
left: 0;
}
.toolbar-oriented .toolbar-tray-vertical .toolbar-toggle-orientation {
float: right; /* LTR */
width: 100%;
}
[dir="rtl"] .toolbar-oriented .toolbar-tray-vertical .toolbar-toggle-orientation {
float: left;
}
/**
* Toolbar home button toggle.
*/
.toolbar .toolbar-bar .home-toolbar-tab {
display: none;
}
.path-admin .toolbar-bar .home-toolbar-tab {
display: block;
}
/**
* @file toolbar.module.css
*
*
* Aggressive resets so we can achieve a consistent look in hostile CSS
* environments.
*
* If Claro is the admin theme, this stylesheet will be used by the active theme
* even if the active theme is not Claro.
*/