diff --git a/cspell.json b/cspell.json index 85c0c33af..f9350085a 100644 --- a/cspell.json +++ b/cspell.json @@ -3,6 +3,7 @@ "$schema": "https://raw.githubusercontent.com/streetsidesoftware/cspell/master/cspell.schema.json", "words": [ "ACMR", + "activeline", "Alexa", "Algora", "Amjad", @@ -31,6 +32,7 @@ "blenda", "Bopomofo", "bottlenecked", + "Braad", "Breakell", "Brevik", "bunx", @@ -99,6 +101,7 @@ "glsl", "gofmt", "Gregor", + "guttermarker", "Hampus", "hardline", "hardlines", @@ -137,6 +140,7 @@ "Kasturi", "kddnewton", "Kearney", + "Kempson", "Khatri", "Konstantin", "l’objectif", diff --git a/website/playground/index.jsx b/website/playground/index.jsx index 382ebf746..ef86d50d9 100644 --- a/website/playground/index.jsx +++ b/website/playground/index.jsx @@ -11,6 +11,71 @@ const { ReactDOM: { createRoot }, } = window; +function getInitialTheme() { + const saved = localStorage.getItem("theme"); + if (saved) { + return saved; + } + + return window.matchMedia("(prefers-color-scheme: dark)").matches + ? "dark" + : "light"; +} + +function applyTheme(theme) { + document.documentElement.dataset.theme = theme; +} + +function ThemeToggle() { + const [theme, setTheme] = React.useState(getInitialTheme); + + const toggleTheme = () => { + const newTheme = theme === "dark" ? "light" : "dark"; + setTheme(newTheme); + localStorage.setItem("theme", newTheme); + applyTheme(newTheme); + }; + applyTheme(theme); + + return React.createElement( + "button", + { + onClick: toggleTheme, + className: "btn", + "aria-label": `Switch to ${theme === "dark" ? "light" : "dark"} theme`, + }, + theme === "dark" + ? React.createElement( + "svg", + { + class: "theme-icon", + viewBox: "0 0 24 24", + width: "14", + height: "14", + "aria-hidden": "true", + }, + React.createElement("path", { + fill: "currentColor", + d: "M9.37 5.51a7.4 7.4 0 009.12 9.12A7 7 0 119.37 5.51M12 3a9 9 0 108.9 7.64 5.39 5.39 0 01-9.8-3.14 5.4 5.4 0 012.26-4.4Q12.69 3 12 3", + }), + ) + : React.createElement( + "svg", + { + class: "theme-icon", + viewBox: "0 0 24 24", + width: "14", + height: "14", + "aria-hidden": "true", + }, + React.createElement("path", { + fill: "currentColor", + d: "M12 9a1 1 0 0 1 0 6 1 1 0 0 1 0-6m0-2a5 5 0 1 0 0 10 5 5 0 0 0 0-10M2 13h2a1 1 0 0 0 0-2H2a1 1 0 0 0 0 2m18 0h2a1 1 0 0 0 0-2h-2a1 1 0 0 0 0 2M11 2v2a1 1 0 0 0 2 0V2a1 1 0 0 0-2 0m0 18v2a1 1 0 0 0 2 0v-2a1 1 0 0 0-2 0M5.99 4.58a1 1 0 1 0-1.41 1.41l1.06 1.06a1 1 0 1 0 1.41-1.41zm12.37 12.37a1 1 0 1 0-1.41 1.41l1.06 1.06a1 1 0 1 0 1.41-1.41zm1.06-10.96a1 1 0 1 0-1.41-1.41l-1.06 1.06a1 1 0 1 0 1.41 1.41zM7.05 18.36a1 1 0 1 0-1.41-1.41l-1.06 1.06a1 1 0 1 0 1.41 1.41z", + }), + ), + ); +} + class App extends React.Component { constructor() { super(); @@ -62,6 +127,14 @@ function augmentOption(option) { return option; } +const linksContainer = document.querySelector(".links"); +if (linksContainer) { + const themeToggleContainer = document.createElement("div"); + linksContainer.appendChild(themeToggleContainer); + const themeToggleRoot = createRoot(themeToggleContainer); + themeToggleRoot.render(); +} + const container = document.getElementById("root"); const root = createRoot(container); root.render(); diff --git a/website/playground/panels.jsx b/website/playground/panels.jsx index 1d3d238a8..8d993db12 100644 --- a/website/playground/panels.jsx +++ b/website/playground/panels.jsx @@ -215,7 +215,7 @@ export function InputPanel(props) { matchBrackets={true} showCursorWhenSelecting={true} tabSize={4} - rulerColor="#eeeeee" + rulerColor="color-mix(in oklab, currentColor 10%, transparent)" {...props} /> ); @@ -226,7 +226,7 @@ export function OutputPanel(props) { ); diff --git a/website/playground/playground.css b/website/playground/playground.css index 3d0200663..10676e9ff 100644 --- a/website/playground/playground.css +++ b/website/playground/playground.css @@ -4,8 +4,16 @@ } html { - background-color: #fafafa; - color: #6a6a6a; + background-color: light-dark(#fafafa, #1b1b1d); + color: light-dark(#6a6a6a, #e3e3e3); + --color-border: light-dark(#ddd, #606770); + color-scheme: light dark; + &[data-theme="light"] { + color-scheme: light; + } + &[data-theme="dark"] { + color-scheme: dark; + } font-family: -apple-system, system-ui, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Fira Sans", "Droid Sans", "Helvetica Neue", Arial, @@ -87,10 +95,15 @@ header h1 { .links { display: flex; + gap: 8px; font-size: 16px; + align-items: center; } -.links > * + * { - margin-left: 15px; +.links > * { + display: flex; +} +.theme-icon { + padding-block: 2px; } .editors-container { @@ -110,7 +123,7 @@ header h1 { display: flex; flex: 1 1 100%; position: relative; - border-bottom: 1px solid #ddd; + border-bottom: 1px solid var(--color-border); } .disabled-output-panel { @@ -140,7 +153,7 @@ header h1 { .editor { flex-basis: 50%; - border-left: 1px solid #ddd; + border-left: 1px solid var(--color-border); margin-left: -1px; } } @@ -165,6 +178,83 @@ header h1 { bottom: 0; line-height: 1.6; font-family: Consolas, Monaco, "Andale Mono", "Ubuntu Mono", monospace; + + &:is([data-theme="dark"] *) { + /* Credit: Chris Kempson, Gerard Braad */ + background: #000000; + color: #eaeaea; + div.CodeMirror-selected { + background: #424242; + } + .CodeMirror-gutters { + background: #000000; + border-right: 0px; + } + .CodeMirror-guttermarker { + color: #e78c45; + } + .CodeMirror-guttermarker-subtle { + color: #777; + } + .CodeMirror-linenumber { + color: #424242; + } + .CodeMirror-cursor { + border-left: 1px solid #6a6a6a; + } + + span.cm-comment { + color: #d27b53; + } + span.cm-atom { + color: #a16a94; + } + span.cm-number { + color: #a16a94; + } + + span.cm-property, + span.cm-attribute { + color: #99cc99; + } + span.cm-keyword { + color: #d54e53; + } + span.cm-string { + color: #e7c547; + } + + span.cm-variable { + color: #b9ca4a; + } + span.cm-variable-2 { + color: #7aa6da; + } + span.cm-def { + color: #e78c45; + } + span.cm-bracket { + color: #eaeaea; + } + span.cm-tag { + color: #d54e53; + } + span.cm-link { + color: #a16a94; + } + span.cm-error { + background: #d54e53; + color: #6a6a6a; + } + + .CodeMirror-activeline-background { + background: #2a2a2a; + } + .CodeMirror-matchingbracket { + text-decoration: underline; + color: white !important; + } + } } .bottom-bar { @@ -203,8 +293,8 @@ header h1 { box-sizing: border-box; width: 225px; overflow-y: auto; - border-bottom: 1px solid #ddd; - border-right: 1px solid #ddd; + border-bottom: 1px solid var(--color-border); + border-right: 1px solid var(--color-border); flex: 0 1 auto; } @@ -222,7 +312,7 @@ header h1 { .sub-options { flex: 1; padding: 15px 0 10px; - border-bottom: 1px solid #ddd; + border-bottom: 1px solid var(--color-border); } .sub-options:last-child { @@ -264,39 +354,44 @@ input[type="number"] { display: inline-block; height: 18px; padding: 0 5px; - border: 1px solid #d1d2d3; border-radius: 0.25em; - background-image: linear-gradient(to bottom, #fafbfc, #e4ebf0); + background-image: linear-gradient( + to bottom, + light-dark(#fafbfc, #090a11), + light-dark(#e4ebf0, #000) + ); + color: light-dark(#24292e, #fff); + border: 1px solid var(--color-border); font-size: 11px; line-height: 18px; font-weight: 600; font-family: inherit; - color: #24292e; text-decoration: none; cursor: pointer; - outline: none; position: relative; } -.btn:focus { - border-color: #c8e1ff; +.btn:enabled:is(:hover, :active) { + border-color: light-dark(#afb1b2, #6f7b81); } -.btn:hover:enabled { - background-color: #e6ebf1; - background-image: linear-gradient(to bottom, #f0f3f6, #dce3ec); - border-color: #afb1b2; +.btn:enabled:hover { + background-color: light-dark(#e6ebf1, #2c3a44); + background-image: linear-gradient( + to bottom, + light-dark(#f0f3f6, #1a2b34), + light-dark(#dce3ec, #090a11) + ); } -.btn:active { - background-color: #e9ecef; +.btn:enabled:active { + background-color: light-dark(#e9ecef, #1a2b34); background-image: none; - border-color: #afb1b2; box-shadow: inset 0 0.15em 0.3em rgba(27, 31, 35, 0.15); } .btn:disabled { - color: #afb1b2; + opacity: 0.5; } .tooltip {