in website/src/components/ColorGenerator/index.tsx [37:300]
export default function ColorGenerator(): JSX.Element {
const {colorMode, setColorMode} = useColorMode();
const isDarkTheme = colorMode === 'dark';
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 [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}, isDarkTheme);
storage.set(JSON.stringify({baseColor, background, shades}));
}, [baseColor, background, shades, storage, isDarkTheme]);
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>
<Translate
id="colorGenerator.tip.body"
values={{
wcagLink: (
<Link href="https://www.w3.org/TR/WCAG20/#visual-audio-contrast-contrast">
<Translate id="colorGenerator.tip.body.wcagLink.label">
WCAG-AA contrast ratio
</Translate>
</Link>
),
}}>
{
"Aim for at least {wcagLink} 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."
}
</Translate>
</p>
</Admonition>
<p>
{/* eslint-disable-next-line jsx-a11y/label-has-associated-control */}
<label htmlFor="primary_color">
<strong className="margin-right--sm">
<Translate id="colorGenerator.inputs.primary.label">
Primary Color:
</Translate>
</strong>
</label>{' '}
<input
id="primary_color"
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={() => setColorMode(isDarkTheme ? 'light' : 'dark')}>
<Translate
id="colorGenerator.inputs.modeToggle.label"
values={{
colorMode: isDarkTheme ? (
<Translate id="colorGenerator.inputs.modeToggle.label.colorMode.light">
light
</Translate>
) : (
<Translate id="colorGenerator.inputs.modeToggle.label.colorMode.dark">
dark
</Translate>
),
}}>
{'Edit {colorMode} mode'}
</Translate>
</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);
}}>
<Translate id="colorGenerator.inputs.resetButton.label">
Reset
</Translate>
</button>
</p>
<p>
{/* eslint-disable-next-line jsx-a11y/label-has-associated-control */}
<label htmlFor="background_color">
<strong className="margin-right--sm">
<Translate id="colorGenerator.inputs.background.label">
Background:
</Translate>
</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 className={styles.colorTable}>
<thead>
<tr>
<th>
<Translate id="colorGenerator.table.heading1">
CSS Variable Name
</Translate>
</th>
<th>
<Translate
id="colorGenerator.table.heading2"
description="This column is the color's representation in hex">
Hex
</Translate>
</th>
<th>
<Translate
id="colorGenerator.table.heading3"
description="This column is the adjusted shades' adjustment values relative to the primary color">
Adjustment
</Translate>
</th>
<th>
<Translate
id="colorGenerator.table.heading4"
description="This column is WCAG contrast rating: AAA, AA, Fail">
Contrast Rating
</Translate>
</th>
</tr>
</thead>
<tbody>
{getAdjustedColors(shades, baseColor)
.sort((a, b) => a.displayOrder - b.displayOrder)
.map((value) => {
const {variableName, adjustment, adjustmentInput, hex} = value;
return (
<tr key={variableName}>
<td>
<code>{variableName}</code>
</td>
<td>
<span
className={styles.color}
style={{
backgroundColor: hex,
}}
/>
<code className="margin-left--sm">
{hex.toLowerCase()}
</code>
</td>
<td>
{variableName === '--ifm-color-primary' ? (
0
) : (
<input
aria-label={`${variableName} CSS variable name`}
className={styles.input}
type="number"
value={adjustmentInput}
onChange={(event) => {
const newValue = parseFloat(event.target.value);
setShades({
...shades,
[variableName]: {
...shades[variableName],
adjustmentInput: event.target.value,
adjustment: Number.isNaN(newValue)
? adjustment
: newValue / 100.0,
},
});
}}
/>
)}
</td>
<td
style={{
fontSize: 'medium',
backgroundColor: background,
color: hex,
}}>
<b>{wcagContrast(hex, background)}</b>
</td>
</tr>
);
})}
</tbody>
</table>
</div>
<p>
<Translate
id="colorGenerator.text"
values={{cssPath: <code>src/css/custom.css</code>}}>
{'Replace the variables in {cssPath} with these new variables.'}
</Translate>
</p>
<CodeBlock className="language-css" title="/src/css/custom.css">
{`${isDarkTheme ? "[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>
);
}