From 037e80fd743ed83c80df80e889cee74816eac790 Mon Sep 17 00:00:00 2001 From: Saleh Abdel Motaal Date: Sat, 19 Oct 2019 16:01:20 -0400 Subject: [PATCH 1/8] feat: Enhanced Dark Mode prototype This adds a new `utils/DarkModeController` controller wrapped in a new `components/controls` component. --- src/components/controls.tsx | 70 ++++++++++++++++ src/components/layout.tsx | 2 + src/util/DarkModeController.js | 141 +++++++++++++++++++++++++++++++++ 3 files changed, 213 insertions(+) create mode 100644 src/components/controls.tsx create mode 100644 src/util/DarkModeController.js diff --git a/src/components/controls.tsx b/src/components/controls.tsx new file mode 100644 index 0000000000..09b49f43ca --- /dev/null +++ b/src/components/controls.tsx @@ -0,0 +1,70 @@ +import React from 'react'; +import { css } from '@emotion/core'; +import DarkModeController from '../util/DarkModeController'; + +const controlsStyles = { + header: css/* scss */ ` + position: fixed; + padding: 0.25ch 1ch; + margin-bottom: 1ch; + margin-top: -1ch; + right: 0; + z-index: 999; + box-sizing: border-box; + will-change: transform; + + display: grid; + grid-auto-flow: column dense; + grid-gap: 1ch; + align-items: center; + + opacity: 0.9; + color: #999; + background: linear-gradient(to right, #9993, #9993), #333f46; + border-radius: 1ch; + + font-family: initial; + /* -apple-system', system-ui, 'Segoe UI', Roboto, + Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;*/ + font-size: 16px; + font-weight: 200; + min-height: 2em; + line-height: normal; + white-space: normal; + text-size-adjust: 100%; + text-shadow: #333f46 0px 0.875px 0px; + user-select: none; + `, +}; + +interface Props { + lightModeIcon?: string; + darkModeIcon?: string; + controller?: DarkModeController; +} + +const Controls = ({ + lightModeIcon = 'wb_sunny', + darkModeIcon = 'nights_stay', + controller = new DarkModeController(document.body), +}: Props) => ( +
+ +
+); + +export default Controls; diff --git a/src/components/layout.tsx b/src/components/layout.tsx index 4b29e1e34f..1e9a838c72 100644 --- a/src/components/layout.tsx +++ b/src/components/layout.tsx @@ -2,6 +2,7 @@ import 'prismjs/plugins/line-numbers/prism-line-numbers.css'; import 'prismjs/themes/prism-okaidia.css'; import React from 'react'; import Header from './header'; +import Controls from './controls'; import '../styles/tokens.css'; import '../styles/layout.css'; import '../styles/mobile.css'; @@ -26,6 +27,7 @@ const Layout = ({
+ {children} ); diff --git a/src/util/DarkModeController.js b/src/util/DarkModeController.js new file mode 100644 index 0000000000..0a0aca2d67 --- /dev/null +++ b/src/util/DarkModeController.js @@ -0,0 +1,141 @@ +// @ts-check + +export default class DarkModeController { + static get timeout() { + const value = Symbol.for('dark-mode.toggler.timeout'); + Object.defineProperty(this, 'timeout', { value, writable: false }); + return value; + } + + static get resetting() { + const value = Symbol.for('dark-mode.toggler.resetting'); + Object.defineProperty(this, 'resetting', { value, writable: false }); + return value; + } + + static get prefersLightMode() { + const value = matchMedia('(prefers-color-scheme: light)'); + Object.defineProperty(this, 'prefersLightMode', { value, writable: false }); + return value; + } + + static get prefersDarkMode() { + const value = matchMedia('(prefers-color-scheme: dark)'); + Object.defineProperty(this, 'prefersDarkMode', { value, writable: false }); + return value; + } + + /** @param {HTMLElement} [target] */ + constructor(target) { + this.target = target || document.body; + + Object.defineProperties(this, { + [DarkModeController.timeout]: { + value: /** @type {number|undefined} */ (undefined), + writable: true, + }, + [DarkModeController.resetting]: { + value: /** @type {boolean|undefined} */ (undefined), + writable: true, + }, + state: { + value: /** @type {DarkModeState|undefined} */ (undefined), + writable: true, + }, + prefers: { + value: /** @type {PrefersColorSchemes|undefined} */ (undefined), + writable: true, + }, + enable: { value: this.enable.bind(this), writable: false }, + disable: { value: this.disable.bind(this), writable: false }, + toggle: { value: this.toggle.bind(this), writable: false }, + onPointerDown: { value: this.onPointerDown.bind(this), writable: false }, + onPointerUp: { value: this.onPointerUp.bind(this), writable: false }, + }); + + ((prefersDarkMode, prefersLightMode) => { + localStorage.darkMode === 'enabled' + ? ((this.state = 'enabled'), this.enable()) + : localStorage.darkMode === 'disabled' + ? ((this.state = 'disabled'), this.disable()) + : this.toggle( + prefersDarkMode.matches === true || + prefersLightMode.matches !== true, + !!(localStorage.darkMode = this.state = 'auto') + ); + prefersDarkMode.addListener( + ({ matches = false }) => + matches === true && this.toggle(!!matches, true) + ); + prefersLightMode.addListener( + ({ matches = false }) => matches === true && this.toggle(!matches, true) + ); + })(DarkModeController.prefersDarkMode, DarkModeController.prefersLightMode); + + Object.preventExtensions(this); + } + + /** + * @param {DarkModeState|boolean} [state] + * @param {boolean} [auto] + */ + async toggle(state, auto) { + const { classList } = this.target; + + if (auto === true) { + if (state === true) this.prefers = 'dark'; + else if (state === false) this.prefers = 'light'; + if (this.state !== 'auto') return; + } + + state = + state === 'auto' + ? ((auto = true), this.prefers !== 'light') + : state == null + ? !classList.contains('dark-mode') + : !!state; + + this.state = localStorage.darkMode = auto + ? 'auto' + : state + ? 'enabled' + : 'disabled'; + + state + ? (classList.add('dark-mode'), classList.remove('light-mode')) + : (classList.add('light-mode'), classList.remove('dark-mode')); + } + + /** @param {boolean} [auto] */ + enable(auto) { + this.toggle(true, auto); + } + + /** @param {boolean} [auto] */ + disable(auto) { + this.toggle(false, auto); + } + + onPointerDown() { + clearTimeout(this[DarkModeController.timeout]); + this[DarkModeController.timeout] = setTimeout(() => { + this.toggle('auto'); + this[DarkModeController.resetting] = true; + console.log('Reset dark mode!'); + }, 2000); + } + + onPointerUp() { + this[DarkModeController.timeout] = clearTimeout( + this[DarkModeController.timeout] + ); + this[DarkModeController.resetting] === true + ? (this[DarkModeController.resetting] = false) + : this.toggle(); + } +} + +Object.preventExtensions(DarkModeController); + +/** @typedef {'auto'|'enabled'|'disabled'} DarkModeState */ +/** @typedef {'light'|'dark'} PrefersColorSchemes */ From 9aca8eb747421891fb21a8cc2f2610bff63c9fc5 Mon Sep 17 00:00:00 2001 From: Saleh Abdel Motaal Date: Sat, 19 Oct 2019 16:26:33 -0400 Subject: [PATCH 2/8] style: Lint --- src/components/controls.tsx | 13 +++++++++---- src/util/DarkModeController.js | 6 +++++- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/src/components/controls.tsx b/src/components/controls.tsx index 09b49f43ca..115186a9d2 100644 --- a/src/components/controls.tsx +++ b/src/components/controls.tsx @@ -51,17 +51,22 @@ const Controls = ({
diff --git a/src/util/DarkModeController.js b/src/util/DarkModeController.js index 0a0aca2d67..bc1294d80c 100644 --- a/src/util/DarkModeController.js +++ b/src/util/DarkModeController.js @@ -1,5 +1,7 @@ // @ts-check +/*eslint-disable */ + export default class DarkModeController { static get timeout() { const value = Symbol.for('dark-mode.toggler.timeout'); @@ -121,7 +123,7 @@ export default class DarkModeController { this[DarkModeController.timeout] = setTimeout(() => { this.toggle('auto'); this[DarkModeController.resetting] = true; - console.log('Reset dark mode!'); + // console.log('Reset dark mode!'); }, 2000); } @@ -139,3 +141,5 @@ Object.preventExtensions(DarkModeController); /** @typedef {'auto'|'enabled'|'disabled'} DarkModeState */ /** @typedef {'light'|'dark'} PrefersColorSchemes */ + +/* eslint-enable */ From 8b8a398492b7fcf08e135c62d62449d8479c919c Mon Sep 17 00:00:00 2001 From: Saleh Abdel Motaal Date: Sat, 19 Oct 2019 17:02:55 -0400 Subject: [PATCH 3/8] fix: Resolve build-ci bugs --- src/components/controls.tsx | 2 +- src/util/DarkModeController.js | 28 ++++++++++++++++++++++------ 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/src/components/controls.tsx b/src/components/controls.tsx index 115186a9d2..e88392c6db 100644 --- a/src/components/controls.tsx +++ b/src/components/controls.tsx @@ -46,7 +46,7 @@ interface Props { const Controls = ({ lightModeIcon = 'wb_sunny', darkModeIcon = 'nights_stay', - controller = new DarkModeController(document.body), + controller = new DarkModeController(), }: Props) => (
diff --git a/src/util/DarkModeController.js b/src/util/DarkModeController.js index bc1294d80c..fb1e260cff 100644 --- a/src/util/DarkModeController.js +++ b/src/util/DarkModeController.js @@ -16,22 +16,33 @@ export default class DarkModeController { } static get prefersLightMode() { - const value = matchMedia('(prefers-color-scheme: light)'); + const value = + (typeof matchMedia === 'function' && + matchMedia('(prefers-color-scheme: light)')) || + undefined; Object.defineProperty(this, 'prefersLightMode', { value, writable: false }); return value; } static get prefersDarkMode() { - const value = matchMedia('(prefers-color-scheme: dark)'); + const value = + (typeof matchMedia === 'function' && + matchMedia('(prefers-color-scheme: dark)')) || + undefined; Object.defineProperty(this, 'prefersDarkMode', { value, writable: false }); return value; } /** @param {HTMLElement} [target] */ constructor(target) { - this.target = target || document.body; - Object.defineProperties(this, { + target: { + value: + /** @type {HTMLElement|undefined} */ (target || + (typeof document === 'object' && document.body) || + undefined), + writable: false, + }, [DarkModeController.timeout]: { value: /** @type {number|undefined} */ (undefined), writable: true, @@ -55,7 +66,8 @@ export default class DarkModeController { onPointerUp: { value: this.onPointerUp.bind(this), writable: false }, }); - ((prefersDarkMode, prefersLightMode) => { + ((prefersDarkMode, prefersLightMode, localStorage) => { + if (!localStorage || !prefersDarkMode || !prefersLightMode) return; localStorage.darkMode === 'enabled' ? ((this.state = 'enabled'), this.enable()) : localStorage.darkMode === 'disabled' @@ -72,7 +84,11 @@ export default class DarkModeController { prefersLightMode.addListener( ({ matches = false }) => matches === true && this.toggle(!matches, true) ); - })(DarkModeController.prefersDarkMode, DarkModeController.prefersLightMode); + })( + DarkModeController.prefersDarkMode, + DarkModeController.prefersLightMode, + (typeof localStorage === 'object' && localStorage) || undefined + ); Object.preventExtensions(this); } From cbda851b8bf75df18a2cecf541ec9794f0cd3e69 Mon Sep 17 00:00:00 2001 From: Saleh Abdel Motaal Date: Sat, 19 Oct 2019 17:04:48 -0400 Subject: [PATCH 4/8] fix: Subtle CSS aspects --- src/components/controls.tsx | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/src/components/controls.tsx b/src/components/controls.tsx index e88392c6db..a55eb48089 100644 --- a/src/components/controls.tsx +++ b/src/components/controls.tsx @@ -19,22 +19,27 @@ const controlsStyles = { align-items: center; opacity: 0.9; - color: #999; - background: linear-gradient(to right, #9993, #9993), #333f46; - border-radius: 1ch; + color: var(--color-text-accent, #999); + background-color: var(--black9, #9993); + border-top-left-radius: 1ch; + border-bottom-left-radius: 1ch; - font-family: initial; - /* -apple-system', system-ui, 'Segoe UI', Roboto, - Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;*/ - font-size: 16px; - font-weight: 200; - min-height: 2em; - line-height: normal; + min-width: max-content; + width: 0; white-space: normal; text-size-adjust: 100%; text-shadow: #333f46 0px 0.875px 0px; user-select: none; `, + button: css/* scss */ ` + color: inherit; + border: none; + width:max-content; + display: contents; + `, + controls: css/* scss */ ` + color: inherit; + `, }; interface Props { @@ -49,11 +54,11 @@ const Controls = ({ controller = new DarkModeController(), }: Props) => (
-
+
+

+ Version 10.15.3 + - + + What’s new + + / + + + Get Current + +

+ + +
`; diff --git a/test/templates/__snapshots__/learn.test.tsx.snap b/test/templates/__snapshots__/learn.test.tsx.snap index ae210445a8..1e944fd4b8 100644 --- a/test/templates/__snapshots__/learn.test.tsx.snap +++ b/test/templates/__snapshots__/learn.test.tsx.snap @@ -1,63 +1,65 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`Learn Template renders correctly 1`] = ` - - + + -
+
- + previous={ + Object { + "slug": "test-slug", + "title": "test-title", + } + } + relativePath="test-path" + title="test-title" + /> + + `; From e4cdb66457e7c8b7bb5ad86db0fbd23f6f9e350a Mon Sep 17 00:00:00 2001 From: Saleh Abdel Motaal Date: Sat, 19 Oct 2019 18:00:50 -0400 Subject: [PATCH 7/8] refactor: Rewire Enhanced Dark Mode feature into existing button --- src/components/header.tsx | 18 ++++++++++++++++-- src/components/layout.tsx | 7 ++++--- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/src/components/header.tsx b/src/components/header.tsx index f96cee335c..9f3b01197b 100644 --- a/src/components/header.tsx +++ b/src/components/header.tsx @@ -2,6 +2,7 @@ import { Link } from 'gatsby'; import React from 'react'; import logoLight from '../images/logos/nodejs-logo-light-mode.svg'; import logoDark from '../images/logos/nodejs-logo-dark-mode.svg'; +import DarkModeController from '../util/DarkModeController'; const activeStyleTab = { fontWeight: 'var(--font-weight-semibold)', @@ -9,7 +10,11 @@ const activeStyleTab = { borderBottom: 'var(--space-04) inset var(--color-text-accent)', }; -const Header = () => ( +interface Props { + darkModeController?: DarkModeController; +} + +const Header = ({ darkModeController }: Props) => (