Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add dark mode support in twenty twenty-one theme #6874

20 changes: 0 additions & 20 deletions includes/admin/class-amp-template-customizer.php
Original file line number Diff line number Diff line change
Expand Up @@ -120,9 +120,6 @@ public static function init( WP_Customize_Manager $wp_customize, ReaderThemeLoad
$self->set_refresh_setting_transport();
$self->remove_cover_template_section();
$self->remove_homepage_settings_section();
if ( get_template() === 'twentytwentyone' ) {
add_action( 'customize_controls_print_footer_scripts', [ $self, 'add_dark_mode_toggler_button_notice' ] );
}
return $self;
}

Expand Down Expand Up @@ -235,23 +232,6 @@ protected function remove_homepage_settings_section() {
}
}

/**
* Add notice that the dark mode toggler button is not currently available on AMP pages.
*/
public function add_dark_mode_toggler_button_notice() {
$message = __( 'While dark mode works on AMP pages, the toggle button is not currently available. It appears here only for preview purposes.', 'amp' );
?>
<script>
wp.customize.control( 'respect_user_color_preference', function ( control ) {
control.notifications.add( new wp.customize.Notification( 'amp_dark_mode_toggler_availability_notice', {
message: <?php echo wp_json_encode( $message ); ?>,
type: 'info'
} ) );
} );
</script>
<?php
}

/**
* Init Customizer preview for legacy.
*
Expand Down
123 changes: 83 additions & 40 deletions includes/sanitizers/class-amp-core-theme-sanitizer.php
Original file line number Diff line number Diff line change
Expand Up @@ -84,37 +84,32 @@ protected static function get_theme_features_config( $theme_slug, $args = [] ) {
switch ( $theme_slug ) {
case 'twentytwentyone':
$config = [
'dequeue_scripts' => [
'dequeue_scripts' => [
'twenty-twenty-one-responsive-embeds-script',
'twenty-twenty-one-primary-navigation-script',
],
'remove_actions' => [
'remove_actions' => [
'wp_print_footer_scripts' => [
'twenty_twenty_one_skip_link_focus_fix', // Unnecessary since part of the AMP runtime.
],
'wp_footer' => [
'twentytwentyone_add_ie_class',
'twenty_twenty_one_supports_js', // AMP is essentially no-js, with any interactivity added explicitly via amp-bind.
[
'Twenty_Twenty_One_Dark_Mode',
'the_switch',
10,
],
],
],
'amend_twentytwentyone_styles' => [],
'amend_twentytwentyone_styles' => [],
'amend_twentytwentyone_sub_menu_toggles' => [],
'add_twentytwentyone_mobile_modal' => [],
'add_twentytwentyone_sub_menu_fix' => [],
'add_twentytwentyone_mobile_modal' => [],
'add_twentytwentyone_sub_menu_fix' => [],
'add_twentytwentyone_dark_mode_toggle' => [],
'amend_twentytwentyone_dark_mode_styles' => [],
];

// Dark mode button toggle is only supported in the Customizer for now.
// A notice is added to the Customizer control in AMP_Template_Customizer::add_dark_mode_toggler_button_notice() via AMP_Template_Customizer::init().
if ( is_customize_preview() ) {
// Make dark mode toggle AMP compatible.
$config['add_twentytwentyone_dark_mode_toggle'] = [];
} else {
// Amend the dark mode stylesheet to only apply its rules when the user's system supports dark mode.
$config['amend_twentytwentyone_dark_mode_styles'] = [];
// Prevent the dark mode toggle and its accompanying script from being inlined.
$config['remove_actions']['wp_footer'][] = [ 'Twenty_Twenty_One_Dark_Mode', 'the_switch', 10 ];
}

return $config;

// Twenty Twenty.
Expand Down Expand Up @@ -1890,11 +1885,8 @@ public function add_twentytwenty_toggles() {
}

/**
* Amend the Twenty Twenty-One dark mode stylesheet to only apply the relevant rules when the user has requested
* the system use a dark color theme.
*
* Note: Dark mode will only be available when the user's system supports it. The dark mode toggle is not available
* on the frontend as yet since there is no feasible AMP-compatible way to store and unserialize user's preferences.
* Amend the Twenty Twenty-One dark mode stylesheet to only apply the relevant rules when the user has enables
* dark mode support from the customizer options.
*/
public static function amend_twentytwentyone_dark_mode_styles() {
add_action(
Expand Down Expand Up @@ -1924,13 +1916,11 @@ static function() {

$styles = file_get_contents( $dark_mode_css_file ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents

// Restrict rules to only when the user has requested the system use a dark color theme.
$new_styles = str_replace( '@media only screen', '@media only screen and (prefers-color-scheme: dark)', $styles );
// Allow for rules to override the light theme related rules.
$new_styles = str_replace( '.is-dark-theme.is-dark-theme', ':root', $new_styles );
$new_styles = str_replace( '.respect-color-scheme-preference.is-dark-theme body', '.respect-color-scheme-preference:not(._) body', $new_styles );
$styles = str_replace( '.is-dark-theme.is-dark-theme', 'body.is-dark-theme', $styles );
$styles = str_replace( '.respect-color-scheme-preference.is-dark-theme body', '.respect-color-scheme-preference body.is-dark-theme', $styles );

wp_add_inline_style( $theme_style_handle, $new_styles );
wp_add_inline_style( $theme_style_handle, $styles );
},
11
);
Expand Down Expand Up @@ -1966,6 +1956,15 @@ static function() {

$styles = file_get_contents( $css_file ); //phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents

/*
* To support dark theme mode.
*
* The AMP provide a global API "AMP.toggleTheme()" to support dark theme by toggling a class in the body element.
* However, For dark mode in the "Twenty twenty one" theme, The class need to be toggled in HTML and body element because of CSS variables set at the root level.
* In AMP we can not toggle class in HTML element. So here we are changing the location where CSS variables are defined from `:root` to the body element.
*/
$styles = str_replace( ':root {', 'body {', $styles );

// Append extra rules needed for nav menus according to changes made to the document during sanitization.
$styles .= '
/* Trap keyboard navigation within mobile menu when it\'s open */
Expand Down Expand Up @@ -2022,32 +2021,76 @@ static function() {

/**
* Make the dark mode toggle in the Twenty Twenty-One theme AMP compatible.
*
* Note: This is only shown within the Customizer preview for now, as there is no feasible way of persisting and
* unserializing the user's preference when they switch to dark (or light) mode.
*/
public function add_twentytwentyone_dark_mode_toggle() {
// First check if dark mode is enabled.
$should_respect_color_scheme = get_theme_mod( 'respect_user_color_preference', false );

if ( ! $should_respect_color_scheme ) {
return;
}

$button = $this->dom->getElementById( 'dark-mode-toggler' );

if ( ! $button ) {
return;
// Create button element for dark mode toggle.
// Dark mode toggle button html and js has been stripped due to removing `the_switch` function.
$button = AMP_DOM_Utils::create_node(
$this->dom,
'button',
[
'id' => 'dark-mode-toggler',
'class' => 'fixed-bottom',
]
);
}

$button->nodeValue = __( 'Dark Mode:', 'amp' );

// Create span tag to show `On` for dark mode.
$button_text_on = $this->dom->createElement( 'span' );
$button_text_on->setAttribute( 'class', 'dark-mode-button-on' );
$button_text_on->nodeValue = __( 'On', 'amp' );

// Create span tag to show `Off` for dark mode.
$button_text_off = $this->dom->createElement( 'span' );
$button_text_off->setAttribute( 'class', 'dark-mode-button-off' );
$button_text_off->nodeValue = __( 'Off', 'amp' );

// Add button_text_{On, Off} to button.
$button->appendChild( $button_text_on );
$button->appendChild( $button_text_off );

// Add button to body.
$this->dom->body->appendChild( $button );

$style = $this->dom->createElement( 'style' );
$style->textContent = '.no-js #dark-mode-toggler { display: block; }';
$style->textContent = sprintf( // We need to add these styles to show On and Off to the user.
'
.no-js #dark-mode-toggler { display: block; }
#dark-mode-toggler > span {
margin-%s: 5px;
}
.dark-mode-button-on {
display: none;
}
body.is-dark-theme .dark-mode-button-on {
display: inline-block;
}
body.is-dark-theme .dark-mode-button-off {
display: none;
}
',
is_rtl() ? 'right' : 'left'
);
$this->dom->head->appendChild( $style );

$toggle_class = 'is-dark-theme';
$state_id = str_replace( '-', '_', $toggle_class );

$body_id = $this->dom->getElementId( $this->dom->body );
$document_id = $this->dom->getElementId( $this->dom->documentElement );

AMP_DOM_Utils::add_amp_action( $button, 'tap', "AMP.setState({{$state_id}: !{$state_id}})" );
AMP_DOM_Utils::add_amp_action( $button, 'tap', "{$body_id}.toggleClass(class='{$toggle_class}')" );
AMP_DOM_Utils::add_amp_action( $button, 'tap', "{$document_id}.toggleClass(class='{$toggle_class}')" );
// Add data-prefers-dark-mode-class in body to use toggleTheme component.
$this->dom->body->setAttribute( 'data-prefers-dark-mode-class', $toggle_class );

$button->setAttribute( 'data-amp-bind-aria-pressed', "{$state_id} ? 'true' : 'false'" );
AMP_DOM_Utils::add_amp_action( $button, 'tap', 'AMP.toggleTheme()' );
}

/**
Expand Down
10 changes: 10 additions & 0 deletions includes/sanitizers/class-amp-style-sanitizer.php
Original file line number Diff line number Diff line change
Expand Up @@ -581,6 +581,16 @@ static function ( $match ) {
}
}

// If using the toggleTheme component, get the theme's dark mode class.
// See usage of toggleTheme in <https://github.com/ampproject/amphtml/pull/36958>.
$dark_mode_class = $this->dom->body->getAttribute( 'data-prefers-dark-mode-class' );

// Prevent dark mode class from being tree-shaken.
if ( $dark_mode_class ) {
$class_names[] = $dark_mode_class;
}


$this->used_class_names = array_fill_keys( $class_names, true );
return $this->used_class_names;
}
Expand Down
30 changes: 25 additions & 5 deletions tests/php/test-class-amp-core-theme-sanitizer.php
Original file line number Diff line number Diff line change
Expand Up @@ -532,9 +532,8 @@ public function test_amend_twentytwentyone_dark_mode_styles() {
$after = implode( '', $extra['after'] );

$replacements = [
'@media only screen' => '@media only screen and (prefers-color-scheme: dark)',
'.is-dark-theme.is-dark-theme' => ':root',
'.respect-color-scheme-preference.is-dark-theme body' => '.respect-color-scheme-preference:not(._) body',
'.is-dark-theme.is-dark-theme' => 'body.is-dark-theme',
'.respect-color-scheme-preference.is-dark-theme body' => '.respect-color-scheme-preference body.is-dark-theme',
];
foreach ( $replacements as $search => $replacement ) {
$this->assertStringNotContainsString( "$search {", $after );
Expand Down Expand Up @@ -674,16 +673,37 @@ public function test_add_twentytwentyone_dark_mode_toggle() {
$dom = AMP_DOM_Utils::get_dom_from_content( $html );
$sanitizer = new AMP_Core_Theme_Sanitizer( $dom );

add_filter( 'theme_mod_respect_user_color_preference', '__return_true' );

$sanitizer->add_twentytwentyone_dark_mode_toggle();

$this->assertEquals(
'.no-js #dark-mode-toggler { display: block; }',
sprintf(
'
.no-js #dark-mode-toggler { display: block; }
#dark-mode-toggler > span {
margin-%s: 5px;
}
.dark-mode-button-on {
display: none;
}
body.is-dark-theme .dark-mode-button-on {
display: inline-block;
}
body.is-dark-theme .dark-mode-button-off {
display: none;
}
',
is_rtl() ? 'right' : 'left'
),
$dom->head->getElementsByTagName( 'style' )->item( 0 )->textContent
);

$this->assertEquals(
'<button id="dark-mode-toggler" on="tap:AMP.setState({is_dark_theme: !is_dark_theme}),i-amp-0.toggleClass(class=\'is-dark-theme\'),i-amp-1.toggleClass(class=\'is-dark-theme\')" data-amp-bind-aria-pressed="is_dark_theme ? \'true\' : \'false\'">Toggle dark mode</button>',
'<button id="dark-mode-toggler" on="tap:AMP.toggleTheme()">Dark Mode:<span class="dark-mode-button-on">On</span><span class="dark-mode-button-off">Off</span></button>',
$dom->saveHTML( $dom->getElementById( 'dark-mode-toggler' ) )
);

remove_filter( 'theme_mod_respect_user_color_preference', '__return_true' );
}
}
19 changes: 0 additions & 19 deletions tests/php/test-class-amp-template-customizer.php
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,6 @@ public function test_init_reader_theme_with_amp() {
$this->assertFalse( has_action( 'customize_controls_print_footer_scripts', [ $instance, 'print_legacy_controls_templates' ] ) );
$this->assertFalse( has_action( 'customize_preview_init', [ $instance, 'init_legacy_preview' ] ) );
$this->assertFalse( has_action( 'customize_controls_enqueue_scripts', [ $instance, 'add_legacy_customizer_scripts' ] ) );
$this->assertFalse( has_action( 'customize_controls_print_footer_scripts', [ $instance, 'add_dark_mode_toggler_button_notice' ] ) );

foreach ( [ $header_video_setting, $external_header_video_setting ] as $setting ) {
$this->assertEquals( 'refresh', $setting->transport );
Expand Down Expand Up @@ -227,24 +226,6 @@ public function test_set_reader_preview_url() {
$this->assertEquals( home_url( '/foo/' ), $wp_customize->get_preview_url() );
}

/**
* @covers AMP_Template_Customizer::init()
* @covers AMP_Template_Customizer::add_dark_mode_toggler_button_notice()
*/
public function test_init_for_twentytwentyone() {
if ( ! wp_get_theme( 'twentytwentyone' )->exists() ) {
$this->markTestSkipped();
}
switch_theme( 'twentytwentyone' );

$wp_customize = $this->get_customize_manager();
$instance = AMP_Template_Customizer::init( $wp_customize );
$this->assertEquals( 10, has_action( 'customize_controls_print_footer_scripts', [ $instance, 'add_dark_mode_toggler_button_notice' ] ) );

$output = get_echo( [ $instance, 'add_dark_mode_toggler_button_notice' ] );
$this->assertStringContainsString( 'wp.customize.control', $output );
}

/**
* @covers AMP_Template_Customizer::init()
* @covers AMP_Template_Customizer::set_refresh_setting_transport()
Expand Down