mirror of
https://github.com/facebook/docusaurus.git
synced 2025-05-02 03:37:48 +02:00
feat: update website & init template palette to pass WCAG test; include contrast check in ColorGenerator (#5822)
* docs: update website palette to pass WCAG test * Darker palette in light mode * Fix CodeBlock * Fix front page * Fix announcement color * Unify primary color * Add contrast check in website * Fix color input not updating * Use website for preview; allow changing background * Persist in localStorage * Fixes * Fix SSR * Edit dark mode separately * Fix light mode palette * Fix storage reset * Fix CSS * Fix * fix toggling when not on styling-layout * require 100% lighthouse accessibility score * use sessionStorage * refactor * tweak light color * update comments Co-authored-by: sebastienlorber <lorber.sebastien@gmail.com>
This commit is contained in:
parent
19fb337618
commit
abdcad7316
11 changed files with 378 additions and 109 deletions
5
.github/workflows/lighthousesrc.json
vendored
5
.github/workflows/lighthousesrc.json
vendored
|
@ -1,5 +1,10 @@
|
||||||
{
|
{
|
||||||
"ci": {
|
"ci": {
|
||||||
|
"assert": {
|
||||||
|
"assertions": {
|
||||||
|
"categories:accessibility": ["error", {"minScore": 1}]
|
||||||
|
}
|
||||||
|
},
|
||||||
"collect": {
|
"collect": {
|
||||||
"settings": {
|
"settings": {
|
||||||
"skipAudits": [
|
"skipAudits": [
|
||||||
|
|
|
@ -6,16 +6,27 @@
|
||||||
|
|
||||||
/* You can override the default Infima variables here. */
|
/* You can override the default Infima variables here. */
|
||||||
:root {
|
:root {
|
||||||
--ifm-color-primary: #25c2a0;
|
--ifm-color-primary: #2e8555;
|
||||||
--ifm-color-primary-dark: rgb(33, 175, 144);
|
--ifm-color-primary-dark: #29784c;
|
||||||
--ifm-color-primary-darker: rgb(31, 165, 136);
|
--ifm-color-primary-darker: #277148;
|
||||||
--ifm-color-primary-darkest: rgb(26, 136, 112);
|
--ifm-color-primary-darkest: #205d3b;
|
||||||
--ifm-color-primary-light: rgb(70, 203, 174);
|
--ifm-color-primary-light: #33925d;
|
||||||
--ifm-color-primary-lighter: rgb(102, 212, 189);
|
--ifm-color-primary-lighter: #359962;
|
||||||
--ifm-color-primary-lightest: rgb(146, 224, 208);
|
--ifm-color-primary-lightest: #3cad6e;
|
||||||
--ifm-code-font-size: 95%;
|
--ifm-code-font-size: 95%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* For readability concerns, you should choose a lighter palette in dark mode. */
|
||||||
|
html[data-theme='dark'] {
|
||||||
|
--ifm-color-primary: #25c2a0;
|
||||||
|
--ifm-color-primary-dark: #21af90;
|
||||||
|
--ifm-color-primary-darker: #1fa588;
|
||||||
|
--ifm-color-primary-darkest: #1a8870;
|
||||||
|
--ifm-color-primary-light: #29d5b0;
|
||||||
|
--ifm-color-primary-lighter: #32d8b4;
|
||||||
|
--ifm-color-primary-lightest: #4fddbf;
|
||||||
|
}
|
||||||
|
|
||||||
.docusaurus-highlight-code-line {
|
.docusaurus-highlight-code-line {
|
||||||
background-color: rgba(0, 0, 0, 0.1);
|
background-color: rgba(0, 0, 0, 0.1);
|
||||||
display: block;
|
display: block;
|
||||||
|
|
|
@ -15,16 +15,27 @@
|
||||||
|
|
||||||
/* You can override the default Infima variables here. */
|
/* You can override the default Infima variables here. */
|
||||||
:root {
|
:root {
|
||||||
--ifm-color-primary: #25c2a0;
|
--ifm-color-primary: #2e8555;
|
||||||
--ifm-color-primary-dark: rgb(33, 175, 144);
|
--ifm-color-primary-dark: #29784c;
|
||||||
--ifm-color-primary-darker: rgb(31, 165, 136);
|
--ifm-color-primary-darker: #277148;
|
||||||
--ifm-color-primary-darkest: rgb(26, 136, 112);
|
--ifm-color-primary-darkest: #205d3b;
|
||||||
--ifm-color-primary-light: rgb(70, 203, 174);
|
--ifm-color-primary-light: #33925d;
|
||||||
--ifm-color-primary-lighter: rgb(102, 212, 189);
|
--ifm-color-primary-lighter: #359962;
|
||||||
--ifm-color-primary-lightest: rgb(146, 224, 208);
|
--ifm-color-primary-lightest: #3cad6e;
|
||||||
--ifm-code-font-size: 95%;
|
--ifm-code-font-size: 95%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* For readability concerns, you should choose a lighter palette in dark mode. */
|
||||||
|
html[data-theme='dark'] {
|
||||||
|
--ifm-color-primary: #25c2a0;
|
||||||
|
--ifm-color-primary-dark: #21af90;
|
||||||
|
--ifm-color-primary-darker: #1fa588;
|
||||||
|
--ifm-color-primary-darkest: #1a8870;
|
||||||
|
--ifm-color-primary-light: #29d5b0;
|
||||||
|
--ifm-color-primary-lighter: #32d8b4;
|
||||||
|
--ifm-color-primary-lightest: #4fddbf;
|
||||||
|
}
|
||||||
|
|
||||||
.docusaurus-highlight-code-line {
|
.docusaurus-highlight-code-line {
|
||||||
background-color: rgb(72, 77, 91);
|
background-color: rgb(72, 77, 91);
|
||||||
display: block;
|
display: block;
|
||||||
|
|
|
@ -94,10 +94,6 @@ import CodeBlock from '@theme/CodeBlock';
|
||||||
When you scaffold your Docusaurus project with `create-docusaurus`, the website will be generated with basic Infima stylesheets and default styling. You can override Infima CSS variables globally.
|
When you scaffold your Docusaurus project with `create-docusaurus`, the website will be generated with basic Infima stylesheets and default styling. You can override Infima CSS variables globally.
|
||||||
|
|
||||||
```css title="/src/css/custom.css"
|
```css title="/src/css/custom.css"
|
||||||
/**
|
|
||||||
* You can override the default Infima variables here.
|
|
||||||
* Note: this is not a complete list of --ifm- variables.
|
|
||||||
*/
|
|
||||||
:root {
|
:root {
|
||||||
--ifm-color-primary: #25c2a0;
|
--ifm-color-primary: #25c2a0;
|
||||||
--ifm-code-font-size: 95%;
|
--ifm-code-font-size: 95%;
|
||||||
|
|
|
@ -38,6 +38,7 @@
|
||||||
"@docusaurus/plugin-pwa": "2.0.0-beta.14",
|
"@docusaurus/plugin-pwa": "2.0.0-beta.14",
|
||||||
"@docusaurus/preset-classic": "2.0.0-beta.14",
|
"@docusaurus/preset-classic": "2.0.0-beta.14",
|
||||||
"@docusaurus/remark-plugin-npm2yarn": "2.0.0-beta.14",
|
"@docusaurus/remark-plugin-npm2yarn": "2.0.0-beta.14",
|
||||||
|
"@docusaurus/theme-common": "2.0.0-beta.14",
|
||||||
"@docusaurus/theme-live-codeblock": "2.0.0-beta.14",
|
"@docusaurus/theme-live-codeblock": "2.0.0-beta.14",
|
||||||
"@docusaurus/utils": "2.0.0-beta.14",
|
"@docusaurus/utils": "2.0.0-beta.14",
|
||||||
"@popperjs/core": "^2.10.2",
|
"@popperjs/core": "^2.10.2",
|
||||||
|
|
|
@ -5,83 +5,97 @@
|
||||||
* LICENSE file in the root directory of this source tree.
|
* LICENSE file in the root directory of this source tree.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, {useState} from 'react';
|
import React, {useEffect, useState} from 'react';
|
||||||
|
import clsx from 'clsx';
|
||||||
import Color from 'color';
|
import Color from 'color';
|
||||||
import CodeBlock from '@theme/CodeBlock';
|
import CodeBlock from '@theme/CodeBlock';
|
||||||
|
import Admonition from '@theme/Admonition';
|
||||||
|
import Link from '@docusaurus/Link';
|
||||||
|
import {useColorMode} from '@docusaurus/theme-common';
|
||||||
|
|
||||||
|
import {
|
||||||
|
type ColorState,
|
||||||
|
COLOR_SHADES,
|
||||||
|
LIGHT_PRIMARY_COLOR,
|
||||||
|
DARK_PRIMARY_COLOR,
|
||||||
|
LIGHT_BACKGROUND_COLOR,
|
||||||
|
DARK_BACKGROUND_COLOR,
|
||||||
|
getAdjustedColors,
|
||||||
|
lightStorage,
|
||||||
|
darkStorage,
|
||||||
|
updateDOMColors,
|
||||||
|
} from '@site/src/utils/colorUtils';
|
||||||
import styles from './styles.module.css';
|
import styles from './styles.module.css';
|
||||||
|
|
||||||
const COLOR_SHADES: Record<
|
function wcagContrast(foreground: string, background: string) {
|
||||||
string,
|
const contrast = Color(foreground).contrast(Color(background));
|
||||||
{
|
// eslint-disable-next-line no-nested-ternary
|
||||||
adjustment: number;
|
return contrast > 7 ? 'AAA 🏅' : contrast > 4.5 ? 'AA 👍' : 'Fail 🔴';
|
||||||
adjustmentInput: string;
|
}
|
||||||
displayOrder: number;
|
|
||||||
codeOrder: number;
|
|
||||||
}
|
|
||||||
> = {
|
|
||||||
'--ifm-color-primary': {
|
|
||||||
adjustment: 0,
|
|
||||||
adjustmentInput: '0',
|
|
||||||
displayOrder: 3,
|
|
||||||
codeOrder: 0,
|
|
||||||
},
|
|
||||||
'--ifm-color-primary-dark': {
|
|
||||||
adjustment: 0.1,
|
|
||||||
adjustmentInput: '10',
|
|
||||||
displayOrder: 4,
|
|
||||||
codeOrder: 1,
|
|
||||||
},
|
|
||||||
'--ifm-color-primary-darker': {
|
|
||||||
adjustment: 0.15,
|
|
||||||
adjustmentInput: '15',
|
|
||||||
displayOrder: 5,
|
|
||||||
codeOrder: 2,
|
|
||||||
},
|
|
||||||
'--ifm-color-primary-darkest': {
|
|
||||||
adjustment: 0.3,
|
|
||||||
adjustmentInput: '30',
|
|
||||||
displayOrder: 6,
|
|
||||||
codeOrder: 3,
|
|
||||||
},
|
|
||||||
'--ifm-color-primary-light': {
|
|
||||||
adjustment: -0.1,
|
|
||||||
adjustmentInput: '-10',
|
|
||||||
displayOrder: 2,
|
|
||||||
codeOrder: 4,
|
|
||||||
},
|
|
||||||
'--ifm-color-primary-lighter': {
|
|
||||||
adjustment: -0.15,
|
|
||||||
adjustmentInput: '-15',
|
|
||||||
displayOrder: 1,
|
|
||||||
codeOrder: 5,
|
|
||||||
},
|
|
||||||
'--ifm-color-primary-lightest': {
|
|
||||||
adjustment: -0.3,
|
|
||||||
adjustmentInput: '-30',
|
|
||||||
displayOrder: 0,
|
|
||||||
codeOrder: 6,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const DEFAULT_PRIMARY_COLOR = '3578e5';
|
|
||||||
|
|
||||||
function ColorGenerator(): JSX.Element {
|
function ColorGenerator(): JSX.Element {
|
||||||
|
const {isDarkTheme, setDarkTheme, setLightTheme} = useColorMode();
|
||||||
|
const DEFAULT_PRIMARY_COLOR = isDarkTheme
|
||||||
|
? DARK_PRIMARY_COLOR
|
||||||
|
: LIGHT_PRIMARY_COLOR;
|
||||||
|
const DEFAULT_BACKGROUND_COLOR = isDarkTheme
|
||||||
|
? DARK_BACKGROUND_COLOR
|
||||||
|
: LIGHT_BACKGROUND_COLOR;
|
||||||
|
|
||||||
|
const [inputColor, setInputColor] = useState(DEFAULT_PRIMARY_COLOR);
|
||||||
const [baseColor, setBaseColor] = useState(DEFAULT_PRIMARY_COLOR);
|
const [baseColor, setBaseColor] = useState(DEFAULT_PRIMARY_COLOR);
|
||||||
|
const [background, setBackground] = useState(DEFAULT_BACKGROUND_COLOR);
|
||||||
const [shades, setShades] = useState(COLOR_SHADES);
|
const [shades, setShades] = useState(COLOR_SHADES);
|
||||||
const color = Color(`#${baseColor}`);
|
const [storage, setStorage] = useState(
|
||||||
const adjustedColors = Object.keys(shades)
|
isDarkTheme ? darkStorage : lightStorage,
|
||||||
.map((shade) => ({
|
);
|
||||||
...shades[shade],
|
|
||||||
variableName: shade,
|
useEffect(() => {
|
||||||
}))
|
setStorage(isDarkTheme ? darkStorage : lightStorage);
|
||||||
.map((value) => ({
|
}, [isDarkTheme]);
|
||||||
...value,
|
|
||||||
hex: color.darken(value.adjustment).hex(),
|
// Switch modes -> update state by stored values
|
||||||
}));
|
useEffect(() => {
|
||||||
|
const storedValues: ColorState = JSON.parse(storage.get() ?? '{}');
|
||||||
|
setInputColor(storedValues.baseColor ?? DEFAULT_PRIMARY_COLOR);
|
||||||
|
setBaseColor(storedValues.baseColor ?? DEFAULT_PRIMARY_COLOR);
|
||||||
|
setBackground(storedValues.background ?? DEFAULT_BACKGROUND_COLOR);
|
||||||
|
setShades(storedValues.shades ?? COLOR_SHADES);
|
||||||
|
}, [storage, DEFAULT_BACKGROUND_COLOR, DEFAULT_PRIMARY_COLOR]);
|
||||||
|
|
||||||
|
// State changes -> update DOM styles
|
||||||
|
useEffect(() => {
|
||||||
|
updateDOMColors({baseColor, background, shades});
|
||||||
|
storage.set(JSON.stringify({baseColor, background, shades}));
|
||||||
|
}, [baseColor, background, shades, storage]);
|
||||||
|
|
||||||
|
function updateColor(event: React.ChangeEvent<HTMLInputElement>) {
|
||||||
|
// Only prepend # when there isn't one.
|
||||||
|
// e.g. ccc -> #ccc, #ccc -> #ccc, ##ccc -> ##ccc,
|
||||||
|
const colorValue = event.target.value.replace(/^(?=[^#])/, '#');
|
||||||
|
setInputColor(colorValue);
|
||||||
|
|
||||||
|
try {
|
||||||
|
setBaseColor(Color(colorValue).hex());
|
||||||
|
} catch {
|
||||||
|
// Don't update for invalid colors.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
|
<Admonition type="tip">
|
||||||
|
<p>
|
||||||
|
Aim for at least{' '}
|
||||||
|
<Link href="https://www.w3.org/TR/WCAG20/#visual-audio-contrast-contrast">
|
||||||
|
WCAG-AA contrast ratio
|
||||||
|
</Link>{' '}
|
||||||
|
for the primary color to ensure readability. Use the Docusaurus
|
||||||
|
website itself to preview how your color palette would look like. You
|
||||||
|
can use alternative palettes in dark mode because one color
|
||||||
|
doesn't usually work in both light and dark mode.
|
||||||
|
</p>
|
||||||
|
</Admonition>
|
||||||
<p>
|
<p>
|
||||||
{/* eslint-disable-next-line jsx-a11y/label-has-associated-control */}
|
{/* eslint-disable-next-line jsx-a11y/label-has-associated-control */}
|
||||||
<label htmlFor="primary_color">
|
<label htmlFor="primary_color">
|
||||||
|
@ -89,33 +103,69 @@ function ColorGenerator(): JSX.Element {
|
||||||
</label>{' '}
|
</label>{' '}
|
||||||
<input
|
<input
|
||||||
id="primary_color"
|
id="primary_color"
|
||||||
className={styles.input}
|
type="text"
|
||||||
defaultValue={baseColor}
|
className={clsx(styles.input, 'margin-right--sm')}
|
||||||
onChange={(event) => {
|
value={inputColor}
|
||||||
// Replace all the prefix '#' with an empty string.
|
onChange={updateColor}
|
||||||
// For example, '#ccc' -> 'ccc', '##ccc' -> 'ccc'
|
/>
|
||||||
const colorValue = event.target.value.replace(/^#+/, '');
|
<input
|
||||||
|
type="color"
|
||||||
try {
|
className={styles.colorInput}
|
||||||
Color(`#${colorValue}`);
|
// value has to always be a valid color, so baseColor instead of inputColor
|
||||||
setBaseColor(colorValue);
|
value={baseColor}
|
||||||
} catch {
|
onChange={updateColor}
|
||||||
// Don't update for invalid colors.
|
/>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="clean-btn button button--primary margin-left--md"
|
||||||
|
onClick={() => {
|
||||||
|
if (isDarkTheme) {
|
||||||
|
setLightTheme();
|
||||||
|
} else {
|
||||||
|
setDarkTheme();
|
||||||
}
|
}
|
||||||
|
}}>
|
||||||
|
Edit {isDarkTheme ? 'light' : 'dark'} mode
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="clean-btn button button--secondary margin-left--md"
|
||||||
|
onClick={() => {
|
||||||
|
setInputColor(DEFAULT_PRIMARY_COLOR);
|
||||||
|
setBaseColor(DEFAULT_PRIMARY_COLOR);
|
||||||
|
setBackground(DEFAULT_BACKGROUND_COLOR);
|
||||||
|
setShades(COLOR_SHADES);
|
||||||
|
}}>
|
||||||
|
Reset
|
||||||
|
</button>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
{/* eslint-disable-next-line jsx-a11y/label-has-associated-control */}
|
||||||
|
<label htmlFor="background_color">
|
||||||
|
<strong className="margin-right--sm">Background:</strong>
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
id="background_color"
|
||||||
|
type="color"
|
||||||
|
className={clsx(styles.colorInput, 'margin-right--sm')}
|
||||||
|
value={background}
|
||||||
|
onChange={(e) => {
|
||||||
|
setBackground(e.target.value);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</p>
|
</p>
|
||||||
<div>
|
<div>
|
||||||
<table>
|
<table className={styles.colorTable}>
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>CSS Variable Name</th>
|
<th>CSS Variable Name</th>
|
||||||
<th>Hex</th>
|
<th>Hex</th>
|
||||||
<th>Adjustment</th>
|
<th>Adjustment</th>
|
||||||
|
<th>Contrast Rating</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{adjustedColors
|
{getAdjustedColors(shades, baseColor)
|
||||||
.sort((a, b) => a.displayOrder - b.displayOrder)
|
.sort((a, b) => a.displayOrder - b.displayOrder)
|
||||||
.map((value) => {
|
.map((value) => {
|
||||||
const {variableName, adjustment, adjustmentInput, hex} = value;
|
const {variableName, adjustment, adjustmentInput, hex} = value;
|
||||||
|
@ -160,6 +210,14 @@ function ColorGenerator(): JSX.Element {
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</td>
|
</td>
|
||||||
|
<td
|
||||||
|
style={{
|
||||||
|
fontSize: 'medium',
|
||||||
|
backgroundColor: background,
|
||||||
|
color: hex,
|
||||||
|
}}>
|
||||||
|
<b>{wcagContrast(hex, background)}</b>
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
@ -170,11 +228,17 @@ function ColorGenerator(): JSX.Element {
|
||||||
Replace the variables in <code>src/css/custom.css</code> with these new
|
Replace the variables in <code>src/css/custom.css</code> with these new
|
||||||
variables.
|
variables.
|
||||||
</p>
|
</p>
|
||||||
<CodeBlock className="css">
|
<CodeBlock className="language-css" title="/src/css/custom.css">
|
||||||
{adjustedColors
|
{`${isDarkTheme ? "html[data-theme='dark']" : ':root'} {
|
||||||
.sort((a, b) => a.codeOrder - b.codeOrder)
|
${getAdjustedColors(shades, baseColor)
|
||||||
.map((value) => `${value.variableName}: ${value.hex.toLowerCase()};`)
|
.sort((a, b) => a.codeOrder - b.codeOrder)
|
||||||
.join('\n')}
|
.map((value) => ` ${value.variableName}: ${value.hex.toLowerCase()};`)
|
||||||
|
.join('\n')}${
|
||||||
|
background !== DEFAULT_BACKGROUND_COLOR
|
||||||
|
? `\n --ifm-background-color: ${background};`
|
||||||
|
: ''
|
||||||
|
}
|
||||||
|
}`}
|
||||||
</CodeBlock>
|
</CodeBlock>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -21,3 +21,17 @@
|
||||||
font-size: var(--ifm-font-size-base);
|
font-size: var(--ifm-font-size-base);
|
||||||
padding: 0.5rem;
|
padding: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.colorInput {
|
||||||
|
position: relative;
|
||||||
|
border-color: var(--ifm-color-content-secondary);
|
||||||
|
border-radius: var(--ifm-global-radius);
|
||||||
|
border-style: solid;
|
||||||
|
border-width: var(--ifm-global-border-width);
|
||||||
|
height: 2.25rem;
|
||||||
|
top: 7px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.colorTable {
|
||||||
|
font-size: small;
|
||||||
|
}
|
||||||
|
|
|
@ -13,6 +13,25 @@
|
||||||
--site-primary-hue-saturation: 167, 68%;
|
--site-primary-hue-saturation: 167, 68%;
|
||||||
--site-primary-hue-saturation-light: 167, 56%; /* do we really need this extra one? */
|
--site-primary-hue-saturation-light: 167, 56%; /* do we really need this extra one? */
|
||||||
|
|
||||||
|
--ifm-color-primary: hsl(var(--site-primary-hue-saturation), 30%);
|
||||||
|
--ifm-color-primary-dark: hsl(var(--site-primary-hue-saturation), 26%);
|
||||||
|
--ifm-color-primary-darker: hsl(var(--site-primary-hue-saturation), 23%);
|
||||||
|
--ifm-color-primary-darkest: hsl(var(--site-primary-hue-saturation), 17%);
|
||||||
|
|
||||||
|
--ifm-color-primary-light: hsl(var(--site-primary-hue-saturation-light), 39%);
|
||||||
|
--ifm-color-primary-lighter: hsl(
|
||||||
|
var(--site-primary-hue-saturation-light),
|
||||||
|
47%
|
||||||
|
);
|
||||||
|
--ifm-color-primary-lightest: hsl(
|
||||||
|
var(--site-primary-hue-saturation-light),
|
||||||
|
58%
|
||||||
|
);
|
||||||
|
|
||||||
|
--ifm-color-feedback-background: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
html[data-theme='dark'] {
|
||||||
--ifm-color-primary: hsl(var(--site-primary-hue-saturation), 45%);
|
--ifm-color-primary: hsl(var(--site-primary-hue-saturation), 45%);
|
||||||
--ifm-color-primary-dark: hsl(var(--site-primary-hue-saturation), 41%);
|
--ifm-color-primary-dark: hsl(var(--site-primary-hue-saturation), 41%);
|
||||||
--ifm-color-primary-darker: hsl(var(--site-primary-hue-saturation), 38%);
|
--ifm-color-primary-darker: hsl(var(--site-primary-hue-saturation), 38%);
|
||||||
|
@ -27,16 +46,9 @@
|
||||||
var(--site-primary-hue-saturation-light),
|
var(--site-primary-hue-saturation-light),
|
||||||
73%
|
73%
|
||||||
);
|
);
|
||||||
|
|
||||||
--site-color-feedback-background: #fff;
|
|
||||||
--site-color-favorite-background: #f6fdfd;
|
|
||||||
--site-color-tooltip: #fff;
|
--site-color-tooltip: #fff;
|
||||||
--site-color-tooltip-background: #353738;
|
--site-color-tooltip-background: #353738;
|
||||||
--site-color-svgIcon-favorite: #e9669e;
|
--site-color-svgIcon-favorite: #e9669e;
|
||||||
--site-color-checkbox-checked-bg: hsl(167deg 56% 73% / 25%);
|
|
||||||
}
|
|
||||||
|
|
||||||
html[data-theme='dark'] {
|
|
||||||
--site-color-feedback-background: #f0f8ff;
|
--site-color-feedback-background: #f0f8ff;
|
||||||
--site-color-favorite-background: #1d1e1e;
|
--site-color-favorite-background: #1d1e1e;
|
||||||
--site-color-checkbox-checked-bg: hsl(167deg 56% 73% / 10%);
|
--site-color-checkbox-checked-bg: hsl(167deg 56% 73% / 10%);
|
||||||
|
|
|
@ -39,6 +39,7 @@
|
||||||
.announcementDark {
|
.announcementDark {
|
||||||
background-color: #20232a;
|
background-color: #20232a;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
|
--ifm-link-color: hsl(var(--site-primary-hue-saturation), 45%);
|
||||||
}
|
}
|
||||||
|
|
||||||
.announcementInner {
|
.announcementInner {
|
||||||
|
@ -49,6 +50,8 @@
|
||||||
.hero {
|
.hero {
|
||||||
background-color: #2b3137;
|
background-color: #2b3137;
|
||||||
padding: 48px;
|
padding: 48px;
|
||||||
|
--ifm-color-primary: hsl(var(--site-primary-hue-saturation), 45%);
|
||||||
|
--ifm-color-primary-dark: hsl(var(--site-primary-hue-saturation), 41%);
|
||||||
}
|
}
|
||||||
|
|
||||||
.heroInner {
|
.heroInner {
|
||||||
|
@ -104,6 +107,11 @@
|
||||||
margin-top: 24px;
|
margin-top: 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.indexCtas a,
|
||||||
|
.indexCtas a:hover {
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
|
|
||||||
.indexCtas a:last-of-type {
|
.indexCtas a:last-of-type {
|
||||||
margin: 20px 36px;
|
margin: 20px 36px;
|
||||||
}
|
}
|
||||||
|
|
39
website/src/theme/Toggle.tsx
Normal file
39
website/src/theme/Toggle.tsx
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import OriginalToggle from '@theme-original/Toggle';
|
||||||
|
import type {Props} from '@theme/Toggle';
|
||||||
|
import {
|
||||||
|
lightStorage,
|
||||||
|
darkStorage,
|
||||||
|
type ColorState,
|
||||||
|
updateDOMColors,
|
||||||
|
} from '@site/src/utils/colorUtils';
|
||||||
|
|
||||||
|
// The ColorGenerator modifies the DOM styles. The styles are persisted in
|
||||||
|
// session storage, and we need to apply the same style when toggling modes even
|
||||||
|
// when we are not on the styling-layout page. The only way to do this so far is
|
||||||
|
// by hooking into the Toggle component.
|
||||||
|
export default function Toggle(props: Props): JSX.Element {
|
||||||
|
return (
|
||||||
|
<OriginalToggle
|
||||||
|
{...props}
|
||||||
|
onChange={(e) => {
|
||||||
|
props.onChange(e);
|
||||||
|
const isDarkMode = e.target.checked;
|
||||||
|
const storage = isDarkMode ? darkStorage : lightStorage;
|
||||||
|
const colorState = JSON.parse(
|
||||||
|
storage.get() ?? 'null',
|
||||||
|
) as ColorState | null;
|
||||||
|
if (colorState) {
|
||||||
|
updateDOMColors(colorState);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
108
website/src/utils/colorUtils.ts
Normal file
108
website/src/utils/colorUtils.ts
Normal file
|
@ -0,0 +1,108 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import Color from 'color';
|
||||||
|
import {createStorageSlot} from '@docusaurus/theme-common';
|
||||||
|
|
||||||
|
// These values are shared between the Toggle component and the ColorGenerator
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stored in session storage
|
||||||
|
*/
|
||||||
|
export type ColorState = {
|
||||||
|
baseColor: string;
|
||||||
|
background: string;
|
||||||
|
shades: Shades;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type Shades = Record<
|
||||||
|
string,
|
||||||
|
{
|
||||||
|
adjustment: number;
|
||||||
|
adjustmentInput: string;
|
||||||
|
displayOrder: number;
|
||||||
|
codeOrder: number;
|
||||||
|
}
|
||||||
|
>;
|
||||||
|
export const COLOR_SHADES: Shades = {
|
||||||
|
'--ifm-color-primary': {
|
||||||
|
adjustment: 0,
|
||||||
|
adjustmentInput: '0',
|
||||||
|
displayOrder: 3,
|
||||||
|
codeOrder: 0,
|
||||||
|
},
|
||||||
|
'--ifm-color-primary-dark': {
|
||||||
|
adjustment: 0.1,
|
||||||
|
adjustmentInput: '10',
|
||||||
|
displayOrder: 4,
|
||||||
|
codeOrder: 1,
|
||||||
|
},
|
||||||
|
'--ifm-color-primary-darker': {
|
||||||
|
adjustment: 0.15,
|
||||||
|
adjustmentInput: '15',
|
||||||
|
displayOrder: 5,
|
||||||
|
codeOrder: 2,
|
||||||
|
},
|
||||||
|
'--ifm-color-primary-darkest': {
|
||||||
|
adjustment: 0.3,
|
||||||
|
adjustmentInput: '30',
|
||||||
|
displayOrder: 6,
|
||||||
|
codeOrder: 3,
|
||||||
|
},
|
||||||
|
'--ifm-color-primary-light': {
|
||||||
|
adjustment: -0.1,
|
||||||
|
adjustmentInput: '-10',
|
||||||
|
displayOrder: 2,
|
||||||
|
codeOrder: 4,
|
||||||
|
},
|
||||||
|
'--ifm-color-primary-lighter': {
|
||||||
|
adjustment: -0.15,
|
||||||
|
adjustmentInput: '-15',
|
||||||
|
displayOrder: 1,
|
||||||
|
codeOrder: 5,
|
||||||
|
},
|
||||||
|
'--ifm-color-primary-lightest': {
|
||||||
|
adjustment: -0.3,
|
||||||
|
adjustmentInput: '-30',
|
||||||
|
displayOrder: 0,
|
||||||
|
codeOrder: 6,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const LIGHT_PRIMARY_COLOR = '#2e8555';
|
||||||
|
export const DARK_PRIMARY_COLOR = '#25c2a0';
|
||||||
|
export const LIGHT_BACKGROUND_COLOR = '#ffffff';
|
||||||
|
export const DARK_BACKGROUND_COLOR = '#181920';
|
||||||
|
|
||||||
|
// sessionStorage allows resetting everything next time users visit the site
|
||||||
|
export const lightStorage = createStorageSlot('ifm-theme-colors-light', {
|
||||||
|
persistence: 'sessionStorage',
|
||||||
|
});
|
||||||
|
export const darkStorage = createStorageSlot('ifm-theme-colors-dark', {
|
||||||
|
persistence: 'sessionStorage',
|
||||||
|
});
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||||
|
export function getAdjustedColors(shades: Shades, baseColor: string) {
|
||||||
|
return Object.keys(shades).map((shade) => ({
|
||||||
|
...shades[shade],
|
||||||
|
variableName: shade,
|
||||||
|
hex: Color(baseColor).darken(shades[shade].adjustment).hex(),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function updateDOMColors({
|
||||||
|
shades,
|
||||||
|
baseColor,
|
||||||
|
background,
|
||||||
|
}: ColorState): void {
|
||||||
|
const root = document.documentElement;
|
||||||
|
getAdjustedColors(shades, baseColor).forEach((value) => {
|
||||||
|
root.style.setProperty(value.variableName, value.hex);
|
||||||
|
});
|
||||||
|
root.style.setProperty('--ifm-background-color', background);
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue