From 031abec4f78864cac157dc1698fea084c0346339 Mon Sep 17 00:00:00 2001
From: Sid Vishnoi <8426945+sidvishnoi@users.noreply.github.com>
Date: Wed, 6 Mar 2024 16:43:42 +0530
Subject: [PATCH] feat: dark mode (#3236)
---
src/styles/algorithms.css.js | 22 +++++++++++-
src/styles/caniuse.css.js | 3 +-
src/styles/datatype.css.js | 4 +--
src/styles/dfn-panel.css.js | 9 +++++
src/styles/examples.css.js | 12 -------
src/styles/highlight.css.js | 58 +++++++++++++++++++++++++-------
src/styles/issues-notes.css.js | 4 +++
src/styles/mdn-annotation.css.js | 11 +++++-
src/styles/respec.css.js | 5 ---
src/styles/ui.css.js | 37 +++++++++++---------
src/styles/var.css.js | 6 ++++
src/styles/webidl.css.js | 5 +++
src/type-helper.d.ts | 3 ++
src/w3c/style.js | 27 ++++++++++++++-
tests/spec/w3c/style-spec.js | 43 ++++++++++++++++++++++-
15 files changed, 196 insertions(+), 53 deletions(-)
diff --git a/src/styles/algorithms.css.js b/src/styles/algorithms.css.js
index f33bd48f53..908380d598 100644
--- a/src/styles/algorithms.css.js
+++ b/src/styles/algorithms.css.js
@@ -4,9 +4,29 @@ const css = String.raw;
// Prettier ignore only to keep code indented from level 0.
// prettier-ignore
export default css`
+:root {
+ --assertion-border: #aaa;
+ --assertion-bg: #eee;
+ --assertion-text: black;
+}
+
.assert {
- background: #eee;
border-left: 0.5em solid #aaa;
padding: 0.3em;
+ border-color: #aaa;
+ border-color: var(--assertion-border);
+ background: #eee;
+ background: var(--assertion-bg);
+ color: black;
+ color: var(--assertion-text);
+}
+
+/* There's no way to adapt this to "manual" theme toggle yet. */
+@media (prefers-color-scheme: dark) {
+ :root {
+ --assertion-border: #444;
+ --assertion-bg: var(--borderedblock-bg);
+ --assertion-text: var(--text);
+ }
}
`;
diff --git a/src/styles/caniuse.css.js b/src/styles/caniuse.css.js
index 3666ff670b..5296bf49c7 100644
--- a/src/styles/caniuse.css.js
+++ b/src/styles/caniuse.css.js
@@ -40,7 +40,7 @@ export default css`
}
.caniuse-type span {
- background-color: white;
+ background-color: var(--bg, white);
padding: 0 0.4em;
}
@@ -73,6 +73,7 @@ export default css`
img.caniuse-browser {
filter: drop-shadow(0px 0px .1cm #666666);
+ background: transparent;
}
.caniuse-cell span.browser-version {
diff --git a/src/styles/datatype.css.js b/src/styles/datatype.css.js
index 36186134c6..c6acc02678 100644
--- a/src/styles/datatype.css.js
+++ b/src/styles/datatype.css.js
@@ -25,14 +25,14 @@ var[data-type]::before {
border-width: 4px 6px 0 6px;
border-style: solid;
border-color: transparent;
- border-top-color: #000;
+ border-top-color: #222;
}
/* actual text */
var[data-type]::after {
content: attr(data-type);
transform: translateX(-50%) translateY(-100%);
- background: #000;
+ background: #222;
text-align: center;
/* additional styling */
font-family: "Dank Mono", "Fira Code", monospace;
diff --git a/src/styles/dfn-panel.css.js b/src/styles/dfn-panel.css.js
index 7055557c95..f2beb9389e 100644
--- a/src/styles/dfn-panel.css.js
+++ b/src/styles/dfn-panel.css.js
@@ -22,9 +22,13 @@ dfn {
font-family: "Helvetica Neue", sans-serif;
font-size: small;
background: #fff;
+ background: var(--indextable-hover-bg, #fff);
color: black;
+ color: var(--text, black);
box-shadow: 0 1em 3em -0.4em rgba(0, 0, 0, 0.3),
0 0 1px 1px rgba(0, 0, 0, 0.05);
+ box-shadow: 0 1em 3em -0.4em var(--tocsidebar-shadow, rgba(0, 0, 0, 0.3)),
+ 0 0 1px 1px var(--tocsidebar-shadow, rgba(0, 0, 0, 0.05));
border-radius: 2px;
}
/* Triangle/caret */
@@ -39,10 +43,13 @@ dfn {
border: 10px solid transparent;
border-top: 0;
border-bottom: 10px solid #fff;
+ border-bottom-color: var(--indextable-hover-bg, #fff);
top: 0;
}
.dfn-panel:not(.docked) > .caret::before {
border-bottom: 9px solid #a2a9b1;
+ /* TODO: need slightly darker shade */
+ border-bottom-color: var(--indextable-hover-bg, #a2a9b1);
}
.dfn-panel * {
@@ -52,11 +59,13 @@ dfn {
.dfn-panel b {
display: block;
color: #000;
+ color: var(--text, #000);
margin-top: 0.25em;
}
.dfn-panel ul a[href] {
color: #333;
+ color: var(--text, #333);
}
.dfn-panel > div {
diff --git a/src/styles/examples.css.js b/src/styles/examples.css.js
index 07ecd2e444..0206f1498b 100644
--- a/src/styles/examples.css.js
+++ b/src/styles/examples.css.js
@@ -24,21 +24,9 @@ div.illegal-example p {
color: black;
}
-:is(aside,div).example {
- border-left-width: 0.5em;
- border-left-style: solid;
- border-color: #e0cb52;
- background: #fcfaee;
-}
-
aside.example div.example {
border-left-width: 0.1em;
border-color: #999;
background: #fff;
}
-
-
-.example pre {
- background-color: rgba(0, 0, 0, 0.03);
-}
`;
diff --git a/src/styles/highlight.css.js b/src/styles/highlight.css.js
index 2a5cb3c5e9..3f52eb7e71 100644
--- a/src/styles/highlight.css.js
+++ b/src/styles/highlight.css.js
@@ -1,18 +1,7 @@
/*
-Adapted from Atom One Light by Daniel Gamage for ReSpec, with better color contrast
+One Light for ReSpec, with better color contrast
+Adapted from Atom One Light by Daniel Gamage (https://github.com/highlightjs/highlight.js/blob/c0b6ddbaaf7/src/styles/atom-one-light.css>
Original One Light Syntax theme from https://github.com/atom/one-light-syntax
-base: #fafafa
-mono-1: #383a42
-mono-2: #686b77
-mono-3: #a0a1a7
-hue-1: #0184bb
-hue-2: #4078f2
-hue-3: #a626a4
-hue-4: #50a14f
-hue-5: #e45649
-hue-5-2: #c91243
-hue-6: #986801
-hue-6-2: #c18401
*/
const css = String.raw;
@@ -20,17 +9,53 @@ const css = String.raw;
// Prettier ignore only to keep code indented from level 0.
// prettier-ignore
export default css`
+.hljs {
+ --base: #fafafa;
+ --mono-1: #383a42;
+ --mono-2: #686b77;
+ --mono-3: #717277;
+ --hue-1: #0b76c5;
+ --hue-2: #336ae3;
+ --hue-3: #a626a4;
+ --hue-4: #42803c;
+ --hue-5: #ca4706;
+ --hue-5-2: #c91243;
+ --hue-6: #986801;
+ --hue-6-2: #9a6a01;
+}
+
+/* There's no way to adapt this to "manual" theme toggle yet. */
+@media (prefers-color-scheme: dark) {
+ .hljs {
+ --base: #282c34;
+ --mono-1: #abb2bf;
+ --mono-2: #818896;
+ --mono-3: #5c6370;
+ --hue-1: #56b6c2;
+ --hue-2: #61aeee;
+ --hue-3: #c678dd;
+ --hue-4: #98c379;
+ --hue-5: #e06c75;
+ --hue-5-2: #be5046;
+ --hue-6: #d19a66;
+ --hue-6-2: #e6c07b;
+ }
+}
+
.hljs {
display: block;
overflow-x: auto;
padding: 0.5em;
color: #383a42;
+ color: var(--mono-1, #383a42);
background: #fafafa;
+ background: var(--base, #fafafa);
}
.hljs-comment,
.hljs-quote {
color: #717277;
+ color: var(--mono-3, #717277);
font-style: italic;
}
@@ -38,6 +63,7 @@ export default css`
.hljs-keyword,
.hljs-formula {
color: #a626a4;
+ color: var(--hue-3, #a626a4);
}
.hljs-section,
@@ -46,11 +72,13 @@ export default css`
.hljs-deletion,
.hljs-subst {
color: #ca4706;
+ color: var(--hue-5, #ca4706);
font-weight: bold;
}
.hljs-literal {
color: #0b76c5;
+ color: var(--hue-1, #0b76c5);
}
.hljs-string,
@@ -59,11 +87,13 @@ export default css`
.hljs-attribute,
.hljs-meta-string {
color: #42803c;
+ color: var(--hue-4, #42803c);
}
.hljs-built_in,
.hljs-class .hljs-title {
color: #9a6a01;
+ color: var(--hue-6-2, #9a6a01);
}
.hljs-attr,
@@ -75,6 +105,7 @@ export default css`
.hljs-selector-pseudo,
.hljs-number {
color: #986801;
+ color: var(--hue-6, #986801);
}
.hljs-symbol,
@@ -84,6 +115,7 @@ export default css`
.hljs-selector-id,
.hljs-title {
color: #336ae3;
+ color: var(--hue-2, #336ae3);
}
.hljs-emphasis {
diff --git a/src/styles/issues-notes.css.js b/src/styles/issues-notes.css.js
index 5296c04288..179fd55635 100644
--- a/src/styles/issues-notes.css.js
+++ b/src/styles/issues-notes.css.js
@@ -31,9 +31,13 @@ span.warning {
.warning {
border-color: #f11;
+ border-color: var(--warning-border, #f11);
border-width: 0.2em;
border-style: solid;
background: #fbe9e9;
+ background: var(--warning-bg, #fbe9e9);
+ color: black;
+ color: var(--text, black);
}
.warning-title:before {
diff --git a/src/styles/mdn-annotation.css.js b/src/styles/mdn-annotation.css.js
index 9052dd3b85..2b19a1db68 100644
--- a/src/styles/mdn-annotation.css.js
+++ b/src/styles/mdn-annotation.css.js
@@ -25,8 +25,15 @@ export default css`
min-width: 25ch;
max-width: 32ch;
background: #fff;
- box-shadow: 0 1em 3em -0.4em rgba(0, 0, 0, 0.3),
+ background: var(--indextable-hover-bg, #fff);
+ color: black;
+ color: var(--indextable-hover-text, black);
+ box-shadow:
+ 0 1em 3em -0.4em rgba(0, 0, 0, 0.3),
0 0 1px 1px rgba(0, 0, 0, 0.05);
+ box-shadow:
+ 0 1em 3em -0.4em var(--tocsidebar-shadow, rgba(0, 0, 0, 0.3)),
+ 0 0 1px 1px var(--tocsidebar-shadow, rgba(0, 0, 0, 0.05));
border-radius: 2px;
z-index: 11;
margin-bottom: 0.4em;
@@ -41,7 +48,9 @@ export default css`
.mdn summary span {
font-family: zillaslab, Palatino, "Palatino Linotype", serif;
color: #fff;
+ color: var(--bg, #fff);
background-color: #000;
+ background-color: var(--text, #000);
display: inline-block;
padding: 3px;
}
diff --git a/src/styles/respec.css.js b/src/styles/respec.css.js
index 1b2337db7e..5db7128e31 100644
--- a/src/styles/respec.css.js
+++ b/src/styles/respec.css.js
@@ -86,11 +86,6 @@ a[href].orcid > svg {
text-decoration: none;
}
-a .secno,
-a .figno {
- color: #000;
-}
-
ul.tof,
ol.tof {
list-style: none outside none;
diff --git a/src/styles/ui.css.js b/src/styles/ui.css.js
index b3312b8b28..c7b2658c5e 100644
--- a/src/styles/ui.css.js
+++ b/src/styles/ui.css.js
@@ -30,9 +30,12 @@ export default css`
.respec-info-button {
height: 2.4em;
background: #fff;
+ background: var(--bg, #fff);
color: rgb(120, 120, 120);
+ color: var(--tocnav-normal-text, rgb(120, 120, 120));
border: 1px solid #ccc;
box-shadow: 1px 1px 8px 0 rgba(100, 100, 100, 0.5);
+ box-shadow: 1px 1px 8px 0 var(--tocsidebar-shadow, rgba(100, 100, 100, 0.5));
padding: 0.2em 0em;
}
@@ -141,7 +144,8 @@ export default css`
margin: 0;
padding: 0;
font-family: sans-serif;
- background: #fff;
+ background: var(--bg, #fff);
+ color: var(--text, black);
box-shadow: 1px 1px 8px 0 rgba(100, 100, 100, 0.5);
width: 200px;
display: none;
@@ -169,8 +173,8 @@ export default css`
.respec-save-button:link {
padding-top: 16px;
- color: rgb(240, 240, 240);
- background: rgb(42, 90, 168);
+ color: var(--def-text, white);
+ background: var(--def-bg, rgb(42, 90, 168));
justify-self: stretch;
height: 1cm;
text-decoration: none;
@@ -181,8 +185,8 @@ export default css`
}
.respec-save-button:link:hover {
- color: white;
- background: rgb(42, 90, 168);
+ color: var(--def-text, white);
+ background: var(--defrow-border, rgb(42, 90, 168));
padding: 0;
margin: 0;
border: 0;
@@ -190,7 +194,8 @@ export default css`
}
.respec-save-button:link:focus {
- background: #193766;
+ background: var(--tocnav-active-bg, #193766);
+ color: var(--tocnav-active-text, black);
}
#respec-ui button:focus,
@@ -297,8 +302,10 @@ export default css`
position: fixed;
z-index: 11000;
top: 10%;
- background: #fff;
+ background: var(--bg, #fff);
+ color: var(--text, black);
border: 5px solid #666;
+ border-color: var(--tocsidebar-shadow, #666);
min-width: 20%;
padding: 0;
max-height: 80%;
@@ -313,19 +320,16 @@ export default css`
.respec-modal h3 {
margin: 0;
padding: 0.2em;
+ left: 0 !important;
text-align: center;
- color: black;
- background: linear-gradient(
- to bottom,
- rgba(238, 238, 238, 1) 0%,
- rgba(238, 238, 238, 1) 50%,
- rgba(204, 204, 204, 1) 100%
- );
+ background: var(--tocsidebar-shadow, #ddd);
+ color: var(--text, black);
font-size: 1em;
}
#respec-menu button.respec-option {
- background: white;
+ background: var(--bg, white);
+ color: var(--text, black);
border: none;
width: 100%;
text-align: left;
@@ -334,7 +338,8 @@ export default css`
}
#respec-menu button.respec-option:hover {
- background-color: #eeeeee;
+ background-color: var(--tocnav-hover-bg, #eee);
+ color: var(--tocnav-hover-text, black);
}
.respec-cmd-icon {
diff --git a/src/styles/var.css.js b/src/styles/var.css.js
index f3e8c90d94..71b838f922 100644
--- a/src/styles/var.css.js
+++ b/src/styles/var.css.js
@@ -14,6 +14,12 @@ var.respec-hl {
box-shadow: 0 0 0px 2px var(--bg-color);
}
+@media (prefers-color-scheme: dark) {
+ var.respec-hl {
+ filter: saturate(0.9) brightness(0.9)
+ }
+}
+
/* highlight colors
https://github.com/w3c/tr-design/issues/152
*/
diff --git a/src/styles/webidl.css.js b/src/styles/webidl.css.js
index f1393e0602..f4db208983 100644
--- a/src/styles/webidl.css.js
+++ b/src/styles/webidl.css.js
@@ -11,6 +11,7 @@ pre.idl {
pre.idl > code {
color: black;
+ color: var(--text, black);
}
@media print {
@@ -23,7 +24,10 @@ pre.idl > code {
display: block;
width: 150px;
background: #8ccbf2;
+ background: var(--def-border, #8ccbf2);
color: #fff;
+ /* TODO: need a better color here */
+ color: var(--defrow-border, #fff);
font-family: sans-serif;
font-weight: bold;
margin: -1em 0 1em -1em;
@@ -35,6 +39,7 @@ pre.idl > code {
margin-left: 0.3cm;
text-decoration: none;
border-bottom: none;
+ color: inherit;
}
.idlID {
diff --git a/src/type-helper.d.ts b/src/type-helper.d.ts
index 821a927d5f..dee96b79b7 100644
--- a/src/type-helper.d.ts
+++ b/src/type-helper.d.ts
@@ -134,6 +134,9 @@ interface Conf {
noToc: boolean;
/** Disables injecting ReSpec styles */
noReSpecCSS?: boolean;
+ /** Inject dark mode styles */
+ darkMode?: boolean;
+
/** Indicates whether the document is a preview */
isPreview?: boolean;
/** The pull request number, if applicable */
diff --git a/src/w3c/style.js b/src/w3c/style.js
index 413c41badc..7407b49889 100644
--- a/src/w3c/style.js
+++ b/src/w3c/style.js
@@ -42,7 +42,12 @@ function createResourceHints() {
},
{
hint: "preload", // all specs include on base.css.
- href: "https://www.w3.org/StyleSheets/TR/2021/base.css",
+ href: getStyleUrl("base.css").href,
+ as: "style",
+ },
+ {
+ hint: "preload",
+ href: getStyleUrl("dark.css").href,
as: "style",
},
{
@@ -108,6 +113,26 @@ export function run(conf) {
);
// Make sure the W3C stylesheet is the last stylesheet, as required by W3C Pub Rules.
sub("beforesave", styleMover(finalStyleURL));
+
+ if (conf.darkMode) {
+ const darkModeStyleUrl = getStyleUrl("dark.css");
+ document.head.appendChild(
+ html``
+ );
+ document.head.appendChild(
+ html``
+ );
+ // As required by W3C Pub Rules.
+ sub("beforesave", styleMover(darkModeStyleUrl));
+ }
}
/** @param {Conf} conf */
diff --git a/tests/spec/w3c/style-spec.js b/tests/spec/w3c/style-spec.js
index eeb798597a..6f412b216a 100644
--- a/tests/spec/w3c/style-spec.js
+++ b/tests/spec/w3c/style-spec.js
@@ -1,6 +1,11 @@
"use strict";
-import { flushIframes, makeRSDoc, makeStandardOps } from "../SpecHelper.js";
+import {
+ flushIframes,
+ getExportedDoc,
+ makeRSDoc,
+ makeStandardOps,
+} from "../SpecHelper.js";
const statuses = [
{
@@ -191,6 +196,42 @@ describe("W3C - Style", () => {
});
}
+ it("should add W3C stylesheet at the end", async () => {
+ const ops = makeStandardOps({});
+ const doc = await getExportedDoc(await makeRSDoc(ops));
+ const url = "https://www.w3.org/StyleSheets/TR/2021/base";
+ const elem = doc.querySelector(`link[href^='${url}'][rel="stylesheet"]`);
+ expect(elem).toBeTruthy();
+ expect(elem.nextElementSibling).toBeFalsy();
+ });
+
+ it("should add dark mode stylesheet", async () => {
+ const ops = makeStandardOps({ darkMode: true });
+ const doc = await makeRSDoc(ops);
+ const url = "https://www.w3.org/StyleSheets/TR/2021/dark.css";
+ const elem = doc.querySelector(`link[href^='${url}'][rel="stylesheet"]`);
+ expect(elem).toBeTruthy();
+ expect(elem.href).toBe(url);
+ expect(elem.getAttribute("media")).toBe("(prefers-color-scheme: dark)");
+ });
+
+ it("should add W3C darkmode stylesheet at the end", async () => {
+ const ops = makeStandardOps({ darkMode: true });
+ const doc = await getExportedDoc(await makeRSDoc(ops));
+ const linkBase = doc.querySelector(
+ `link[href^='https://www.w3.org/StyleSheets/TR/2021/base'][rel="stylesheet"]`
+ );
+ expect(linkBase).toBeTruthy();
+ expect(linkBase.nextElementSibling).toBeTruthy();
+
+ const linkDarkMode = doc.querySelector(
+ `link[href^='https://www.w3.org/StyleSheets/TR/2021/dark.css'][rel="stylesheet"]`
+ );
+ expect(linkDarkMode).toBeTruthy();
+ expect(linkDarkMode.nextElementSibling).toBeFalsy();
+ expect(linkBase.nextElementSibling).toBe(linkDarkMode);
+ });
+
it("shouldn't include fixup.js when noToc is set", async () => {
const ops = makeStandardOps();
const newProps = {