Skip to content
This repository was archived by the owner on Mar 14, 2024. It is now read-only.

Commit

Permalink
Theme switcher (#5908)
Browse files Browse the repository at this point in the history
* Basic implementation of theme toggle

* Change the toggle data attribute because data-theme created clashes with the toggle itself

Although they were rather amusing

* Add basic, temp UI to the toggle until we get some proper UI

* Switch the theme toggle to use light dom and a switch control

* Lean into the media query more and don't store that preference by default

It makes more sense only to store the preference when the user interacts with the switch
  • Loading branch information
Andy-set-studio authored Aug 4, 2021
1 parent ed6c4a6 commit e242019
Show file tree
Hide file tree
Showing 8 changed files with 145 additions and 3 deletions.
86 changes: 86 additions & 0 deletions src/lib/components/ThemeToggle/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/*
* Copyright 2021 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

/**
* Element that allows a user to toggle the current theme.
* @extends {HTMLElement}
* @final
*/
class ThemeToggle extends HTMLElement {
constructor() {
super();

this.STORAGE_KEY = 'user-color-scheme';
this.COLOR_MODE_KEY = '--color-mode';
}

connectedCallback() {
this.toggleSwitch = this.querySelector('[role="switch"]');

if (this.toggleSwitch) {
// On change, calculate the new setting, toggle state changes and store in storage
this.toggleSwitch.addEventListener('change', () => {
const setting = this.toggleSwitch.checked ? 'dark' : 'light';
this.applySetting(setting);
localStorage.setItem(this.STORAGE_KEY, setting);
});

this.applySetting();
}
}

applySetting(passedSetting) {
// Attempts to load the setting from local storage
const currentSetting =
passedSetting || localStorage.getItem(this.STORAGE_KEY);

if (currentSetting) {
this.setToggleSwitchStatus(currentSetting);
window.applyThemeSetting(currentSetting);
}
// If no storage setting, we set up media query-based state change
else {
// Set the checkbox to on if we're already in dark preference
if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
this.setToggleSwitchStatus('dark');
}

// Listen for changes to the preference and set checkbox state accordingly
window
.matchMedia('(prefers-color-scheme: dark)')
.addEventListener('change', (evt) => {
this.setToggleSwitchStatus(evt.matches ? 'dark' : 'light');
});
}
}

// Sets the correct aria checked role and checked state
setToggleSwitchStatus(currentSetting) {
const isDarkMode = currentSetting === 'dark';
this.toggleSwitch.setAttribute(
'aria-checked',
isDarkMode ? 'true' : 'false',
);

this.toggleSwitch.checked = isDarkMode;
}
}

if ('customElements' in window) {
customElements.define('theme-toggle', ThemeToggle);
}

export default ThemeToggle;
1 change: 1 addition & 0 deletions src/lib/components/base.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,6 @@ import './Header';
import './LanguageSelect';
import './NavigationDrawer';
import './SnackbarContainer';
import './ThemeToggle';
import './Search';
import './SearchResults';
20 changes: 20 additions & 0 deletions src/scss/blocks/_theme-toggle.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/// THIS IS VERY MUCH A TEMPORARY
/// BIT OF CSS UNTIL WE SLING SOME
/// PROPER UI DESIGN ON THE THEME TOGGLE
.theme-toggle {
display: flex;
align-items: center;
gap: 1rem;

button {
border: 2px solid var(--color-stroke);
padding: 0.5rem 1rem;

@include apply-utility('bg', 'action-bg');
@include apply-utility('color', 'action-text');
}

[aria-pressed='true'] {
border-color: get-color('core-primary-glare');
}
}
4 changes: 4 additions & 0 deletions src/scss/next.scss
Original file line number Diff line number Diff line change
Expand Up @@ -276,13 +276,17 @@ a:active {
background: var(--color-core-text);
}


/// COMPOSITIONS
@import 'compositions/auto-grid';
@import 'compositions/cluster';
@import 'compositions/repel';
@import 'compositions/sidebar';
@import 'compositions/switcher';

/// BLOCK CLASSES
@import 'blocks/theme-toggle';

/// UTILITY CLASSES
@import 'utilities/flow';
@import 'utilities/focus-ring';
Expand Down
11 changes: 10 additions & 1 deletion src/site/_data/design/themes.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
*/
module.exports = {
colorKeys: {
MODE: 'mode',
CORE_TEXT: 'core-text',
CORE_BG: 'core-bg',
MID_TEXT: 'mid-text',
Expand All @@ -32,6 +33,7 @@ module.exports = {
},
getDark() {
return {
MODE: 'dark',
CORE_TEXT: 'shades-light',
CORE_BG: 'shades-dim',
MID_TEXT: 'shades-gray-glare',
Expand All @@ -55,6 +57,7 @@ module.exports = {
},
getLight() {
return {
MODE: 'light',
CORE_TEXT: 'shades-dark',
CORE_BG: 'shades-light-bright',
MID_TEXT: 'shades-gray',
Expand Down Expand Up @@ -91,9 +94,15 @@ module.exports = {
{
name: 'dark-toggle',
key: 'prefix',
value: '[data-theme="dark"]',
value: '[data-user-theme="dark"]',
tokens: this.getDark(),
},
{
name: 'light-toggle',
key: 'prefix',
value: '[data-user-theme="light"]',
tokens: this.getLight(),
},
];
},
};
4 changes: 2 additions & 2 deletions src/site/_data/design/tokens.json
Original file line number Diff line number Diff line change
Expand Up @@ -130,8 +130,8 @@
},
{
"name": "Light",
"hex": "#f3f3f3",
"hsl": "hsl(0, 0%, 95%)"
"hex": "#f9f9f9",
"hsl": "hsl(0, 0%, 97%)"
},
{
"name": "Light Bright",
Expand Down
12 changes: 12 additions & 0 deletions src/site/_includes/partials/head.njk
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,18 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">

{% if CSS_ORIGIN === 'next' %}
<script>
// A global function that the theme toggle can use to apply the current theme.
window.applyThemeSetting = function(override) {
const currentSetting = override || localStorage.getItem('user-color-scheme');
const currentAttribute = document.documentElement.getAttribute('data-user-theme');
if (currentSetting && currentSetting !== currentAttribute) {
document.documentElement.setAttribute('data-user-theme', currentSetting);
}
};
window.applyThemeSetting();
</script>
{# hashForProd() will cache bust the css in dev with a random string. #}
<link rel="stylesheet" href="{{ helpers.hashForProd('/css/next.css') }}">
{% else %}
Expand Down
10 changes: 10 additions & 0 deletions src/site/content/en/design-system/temp-styleguide.njk
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,21 @@ permalink: '/design-system/temp-styleguide/index.html'
---

<main class="wrapper flow">

<h1>Global CSS Temp styleguide</h1>
<p>This is just a temporary dumping ground for the Global CSS to save time, working with the design system which still relies on the old CSS.</p>

<hr />

<h2>Toggle theme</h2>
<theme-toggle>
<!-- As part of the patterns, we'll build out a switch block and use that here -->
<label for="theme-toggle">
Dark mode
<input type="checkbox" role="switch" id="theme-toggle" />
</label>
</theme-toggle>

<h2>Headings</h2>

<h1>Heading level 1</h1>
Expand Down

0 comments on commit e242019

Please sign in to comment.