mirror of
https://github.com/facebook/docusaurus.git
synced 2025-05-01 11:18:24 +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": {
|
||||
"assert": {
|
||||
"assertions": {
|
||||
"categories:accessibility": ["error", {"minScore": 1}]
|
||||
}
|
||||
},
|
||||
"collect": {
|
||||
"settings": {
|
||||
"skipAudits": [
|
||||
|
|
|
@ -6,16 +6,27 @@
|
|||
|
||||
/* You can override the default Infima variables here. */
|
||||
:root {
|
||||
--ifm-color-primary: #25c2a0;
|
||||
--ifm-color-primary-dark: rgb(33, 175, 144);
|
||||
--ifm-color-primary-darker: rgb(31, 165, 136);
|
||||
--ifm-color-primary-darkest: rgb(26, 136, 112);
|
||||
--ifm-color-primary-light: rgb(70, 203, 174);
|
||||
--ifm-color-primary-lighter: rgb(102, 212, 189);
|
||||
--ifm-color-primary-lightest: rgb(146, 224, 208);
|
||||
--ifm-color-primary: #2e8555;
|
||||
--ifm-color-primary-dark: #29784c;
|
||||
--ifm-color-primary-darker: #277148;
|
||||
--ifm-color-primary-darkest: #205d3b;
|
||||
--ifm-color-primary-light: #33925d;
|
||||
--ifm-color-primary-lighter: #359962;
|
||||
--ifm-color-primary-lightest: #3cad6e;
|
||||
--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 {
|
||||
background-color: rgba(0, 0, 0, 0.1);
|
||||
display: block;
|
||||
|
|
|
@ -15,16 +15,27 @@
|
|||
|
||||
/* You can override the default Infima variables here. */
|
||||
:root {
|
||||
--ifm-color-primary: #25c2a0;
|
||||
--ifm-color-primary-dark: rgb(33, 175, 144);
|
||||
--ifm-color-primary-darker: rgb(31, 165, 136);
|
||||
--ifm-color-primary-darkest: rgb(26, 136, 112);
|
||||
--ifm-color-primary-light: rgb(70, 203, 174);
|
||||
--ifm-color-primary-lighter: rgb(102, 212, 189);
|
||||
--ifm-color-primary-lightest: rgb(146, 224, 208);
|
||||
--ifm-color-primary: #2e8555;
|
||||
--ifm-color-primary-dark: #29784c;
|
||||
--ifm-color-primary-darker: #277148;
|
||||
--ifm-color-primary-darkest: #205d3b;
|
||||
--ifm-color-primary-light: #33925d;
|
||||
--ifm-color-primary-lighter: #359962;
|
||||
--ifm-color-primary-lightest: #3cad6e;
|
||||
--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 {
|
||||
background-color: rgb(72, 77, 91);
|
||||
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.
|
||||
|
||||
```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 {
|
||||
--ifm-color-primary: #25c2a0;
|
||||
--ifm-code-font-size: 95%;
|
||||
|
|
|
@ -38,6 +38,7 @@
|
|||
"@docusaurus/plugin-pwa": "2.0.0-beta.14",
|
||||
"@docusaurus/preset-classic": "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/utils": "2.0.0-beta.14",
|
||||
"@popperjs/core": "^2.10.2",
|
||||
|
|
|
@ -5,83 +5,97 @@
|
|||
* 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 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';
|
||||
|
||||
const COLOR_SHADES: Record<
|
||||
string,
|
||||
{
|
||||
adjustment: number;
|
||||
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 wcagContrast(foreground: string, background: string) {
|
||||
const contrast = Color(foreground).contrast(Color(background));
|
||||
// eslint-disable-next-line no-nested-ternary
|
||||
return contrast > 7 ? 'AAA 🏅' : contrast > 4.5 ? 'AA 👍' : 'Fail 🔴';
|
||||
}
|
||||
|
||||
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 [background, setBackground] = useState(DEFAULT_BACKGROUND_COLOR);
|
||||
const [shades, setShades] = useState(COLOR_SHADES);
|
||||
const color = Color(`#${baseColor}`);
|
||||
const adjustedColors = Object.keys(shades)
|
||||
.map((shade) => ({
|
||||
...shades[shade],
|
||||
variableName: shade,
|
||||
}))
|
||||
.map((value) => ({
|
||||
...value,
|
||||
hex: color.darken(value.adjustment).hex(),
|
||||
}));
|
||||
const [storage, setStorage] = useState(
|
||||
isDarkTheme ? darkStorage : lightStorage,
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
setStorage(isDarkTheme ? darkStorage : lightStorage);
|
||||
}, [isDarkTheme]);
|
||||
|
||||
// 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 (
|
||||
<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>
|
||||
{/* eslint-disable-next-line jsx-a11y/label-has-associated-control */}
|
||||
<label htmlFor="primary_color">
|
||||
|
@ -89,33 +103,69 @@ function ColorGenerator(): JSX.Element {
|
|||
</label>{' '}
|
||||
<input
|
||||
id="primary_color"
|
||||
className={styles.input}
|
||||
defaultValue={baseColor}
|
||||
onChange={(event) => {
|
||||
// Replace all the prefix '#' with an empty string.
|
||||
// For example, '#ccc' -> 'ccc', '##ccc' -> 'ccc'
|
||||
const colorValue = event.target.value.replace(/^#+/, '');
|
||||
|
||||
try {
|
||||
Color(`#${colorValue}`);
|
||||
setBaseColor(colorValue);
|
||||
} catch {
|
||||
// Don't update for invalid colors.
|
||||
type="text"
|
||||
className={clsx(styles.input, 'margin-right--sm')}
|
||||
value={inputColor}
|
||||
onChange={updateColor}
|
||||
/>
|
||||
<input
|
||||
type="color"
|
||||
className={styles.colorInput}
|
||||
// value has to always be a valid color, so baseColor instead of inputColor
|
||||
value={baseColor}
|
||||
onChange={updateColor}
|
||||
/>
|
||||
<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>
|
||||
<div>
|
||||
<table>
|
||||
<table className={styles.colorTable}>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>CSS Variable Name</th>
|
||||
<th>Hex</th>
|
||||
<th>Adjustment</th>
|
||||
<th>Contrast Rating</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{adjustedColors
|
||||
{getAdjustedColors(shades, baseColor)
|
||||
.sort((a, b) => a.displayOrder - b.displayOrder)
|
||||
.map((value) => {
|
||||
const {variableName, adjustment, adjustmentInput, hex} = value;
|
||||
|
@ -160,6 +210,14 @@ function ColorGenerator(): JSX.Element {
|
|||
/>
|
||||
)}
|
||||
</td>
|
||||
<td
|
||||
style={{
|
||||
fontSize: 'medium',
|
||||
backgroundColor: background,
|
||||
color: hex,
|
||||
}}>
|
||||
<b>{wcagContrast(hex, background)}</b>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
|
@ -170,11 +228,17 @@ function ColorGenerator(): JSX.Element {
|
|||
Replace the variables in <code>src/css/custom.css</code> with these new
|
||||
variables.
|
||||
</p>
|
||||
<CodeBlock className="css">
|
||||
{adjustedColors
|
||||
.sort((a, b) => a.codeOrder - b.codeOrder)
|
||||
.map((value) => `${value.variableName}: ${value.hex.toLowerCase()};`)
|
||||
.join('\n')}
|
||||
<CodeBlock className="language-css" title="/src/css/custom.css">
|
||||
{`${isDarkTheme ? "html[data-theme='dark']" : ':root'} {
|
||||
${getAdjustedColors(shades, baseColor)
|
||||
.sort((a, b) => a.codeOrder - b.codeOrder)
|
||||
.map((value) => ` ${value.variableName}: ${value.hex.toLowerCase()};`)
|
||||
.join('\n')}${
|
||||
background !== DEFAULT_BACKGROUND_COLOR
|
||||
? `\n --ifm-background-color: ${background};`
|
||||
: ''
|
||||
}
|
||||
}`}
|
||||
</CodeBlock>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -21,3 +21,17 @@
|
|||
font-size: var(--ifm-font-size-base);
|
||||
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-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-dark: hsl(var(--site-primary-hue-saturation), 41%);
|
||||
--ifm-color-primary-darker: hsl(var(--site-primary-hue-saturation), 38%);
|
||||
|
@ -27,16 +46,9 @@
|
|||
var(--site-primary-hue-saturation-light),
|
||||
73%
|
||||
);
|
||||
|
||||
--site-color-feedback-background: #fff;
|
||||
--site-color-favorite-background: #f6fdfd;
|
||||
--site-color-tooltip: #fff;
|
||||
--site-color-tooltip-background: #353738;
|
||||
--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-favorite-background: #1d1e1e;
|
||||
--site-color-checkbox-checked-bg: hsl(167deg 56% 73% / 10%);
|
||||
|
|
|
@ -39,6 +39,7 @@
|
|||
.announcementDark {
|
||||
background-color: #20232a;
|
||||
color: #fff;
|
||||
--ifm-link-color: hsl(var(--site-primary-hue-saturation), 45%);
|
||||
}
|
||||
|
||||
.announcementInner {
|
||||
|
@ -49,6 +50,8 @@
|
|||
.hero {
|
||||
background-color: #2b3137;
|
||||
padding: 48px;
|
||||
--ifm-color-primary: hsl(var(--site-primary-hue-saturation), 45%);
|
||||
--ifm-color-primary-dark: hsl(var(--site-primary-hue-saturation), 41%);
|
||||
}
|
||||
|
||||
.heroInner {
|
||||
|
@ -104,6 +107,11 @@
|
|||
margin-top: 24px;
|
||||
}
|
||||
|
||||
.indexCtas a,
|
||||
.indexCtas a:hover {
|
||||
color: black;
|
||||
}
|
||||
|
||||
.indexCtas a:last-of-type {
|
||||
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