Skip to content

Commit

Permalink
Extract CSS vars from RAC docs into reusable package (#5419)
Browse files Browse the repository at this point in the history
  • Loading branch information
devongovett committed Nov 16, 2023
1 parent 3688f07 commit 5dac45c
Show file tree
Hide file tree
Showing 50 changed files with 694 additions and 1,465 deletions.
2 changes: 1 addition & 1 deletion .parcelrc
Expand Up @@ -9,7 +9,7 @@
"bundle-text:*.svg": ["@parcel/transformer-svg", "@parcel/transformer-inline-string"],
"*.{md,mdx}": ["parcel-transformer-mdx-docs"],
"*.svg": ["@parcel/transformer-svg-react"],
"*.global.css": ["parcel-transformer-css-global", "..."],
"packages/@react-aria/example-theme/**/*.css": ["@parcel/transformer-css"],
"*.css": ["...", "parcel-transformer-css-env"],
"*.{js,mjs,jsm,jsx,es6,cjs,ts,tsx}": [
"@parcel/transformer-js",
Expand Down
5 changes: 4 additions & 1 deletion package.json
Expand Up @@ -187,7 +187,10 @@
"browserslist": "4.20.3"
},
"@parcel/transformer-css": {
"cssModules": true,
"cssModules": {
"global": true,
"exclude": ["**/*.global.css", "packages/@react-aria/example-theme/**"]
},
"drafts": {
"nesting": true
},
Expand Down
3 changes: 3 additions & 0 deletions packages/@react-aria/example-theme/README.md
@@ -0,0 +1,3 @@
# @react-aria/example-theme

This package is part of [react-spectrum](https://github.com/adobe/react-spectrum). See the repo for more details.
22 changes: 22 additions & 0 deletions packages/@react-aria/example-theme/package.json
@@ -0,0 +1,22 @@
{
"name": "@react-aria/example-theme",
"version": "1.0.0",
"description": "Spectrum UI components in React",
"license": "Apache-2.0",
"source": "src/index.css",
"dist": "dist/index.css",
"main": "dist/index.css",
"targets": {
"main": false,
"dist": {
"optimize": false
}
},
"repository": {
"type": "git",
"url": "https://github.com/adobe/react-spectrum"
},
"publishConfig": {
"access": "public"
}
}
118 changes: 118 additions & 0 deletions packages/@react-aria/example-theme/src/index.css
@@ -0,0 +1,118 @@
/* color themes for dark and light modes, generated with Leonardo.
* Light: https://leonardocolor.io/theme.html?name=Light&config=%7B%22baseScale%22%3A%22Gray%22%2C%22colorScales%22%3A%5B%7B%22name%22%3A%22Gray%22%2C%22colorKeys%22%3A%5B%22%23000000%22%5D%2C%22colorspace%22%3A%22RGB%22%2C%22ratios%22%3A%5B%22-1.12%22%2C%221.45%22%2C%222.05%22%2C%223.02%22%2C%224.54%22%2C%227%22%2C%2210.86%22%5D%2C%22smooth%22%3Afalse%7D%2C%7B%22name%22%3A%22Purple%22%2C%22colorKeys%22%3A%5B%22%235e30eb%22%5D%2C%22colorspace%22%3A%22RGB%22%2C%22ratios%22%3A%5B%22-1.12%22%2C%221.45%22%2C%222.05%22%2C%223.02%22%2C%224.54%22%2C%227%22%2C%2210.86%22%5D%2C%22smooth%22%3Afalse%7D%2C%7B%22name%22%3A%22Red%22%2C%22colorKeys%22%3A%5B%22%23e32400%22%5D%2C%22colorspace%22%3A%22RGB%22%2C%22ratios%22%3A%5B%22-1.12%22%2C%221.45%22%2C%222.05%22%2C%223.02%22%2C%224.54%22%2C%227%22%2C%2210.86%22%5D%2C%22smooth%22%3Afalse%7D%5D%2C%22lightness%22%3A98%2C%22contrast%22%3A1%2C%22saturation%22%3A100%2C%22formula%22%3A%22wcag2%22%7D */
:root {
--background-color: #f8f8f8;
--gray-50: #ffffff;
--gray-100: #d0d0d0;
--gray-200: #afafaf;
--gray-300: #8f8f8f;
--gray-400: #717171;
--gray-500: #555555;
--gray-600: #393939;
--purple-100: #d5c9fa;
--purple-200: #b8a3f6;
--purple-300: #997cf2;
--purple-400: #7a54ef;
--purple-500: #582ddc;
--purple-600: #3c1e95;
--red-100: #f7c4ba;
--red-200: #f29887;
--red-300: #eb664d;
--red-400: #de2300;
--red-500: #a81b00;
--red-600: #731200;
--highlight-hover: rgb(0 0 0 / 0.07);
--highlight-pressed: rgb(0 0 0 / 0.15);
}

/* Dark: https://leonardocolor.io/theme.html?name=Dark&config=%7B%22baseScale%22%3A%22Gray%22%2C%22colorScales%22%3A%5B%7B%22name%22%3A%22Gray%22%2C%22colorKeys%22%3A%5B%22%23000000%22%5D%2C%22colorspace%22%3A%22RGB%22%2C%22ratios%22%3A%5B%22-1.12%22%2C%221.45%22%2C%222.05%22%2C%223.02%22%2C%224.54%22%2C%227%22%2C%2210.86%22%5D%2C%22smooth%22%3Afalse%7D%2C%7B%22name%22%3A%22Purple%22%2C%22colorKeys%22%3A%5B%22%235e30eb%22%5D%2C%22colorspace%22%3A%22RGB%22%2C%22ratios%22%3A%5B%22-1.12%22%2C%221.45%22%2C%222.05%22%2C%223.02%22%2C%224.54%22%2C%227%22%2C%2210.86%22%5D%2C%22smooth%22%3Afalse%7D%2C%7B%22name%22%3A%22Red%22%2C%22colorKeys%22%3A%5B%22%23e32400%22%5D%2C%22colorspace%22%3A%22RGB%22%2C%22ratios%22%3A%5B%22-1.12%22%2C%221.45%22%2C%222.05%22%2C%223.02%22%2C%224.54%22%2C%227%22%2C%2210.86%22%5D%2C%22smooth%22%3Afalse%7D%5D%2C%22lightness%22%3A11%2C%22contrast%22%3A1%2C%22saturation%22%3A100%2C%22formula%22%3A%22wcag2%22%7D */
@media (prefers-color-scheme: dark) {
:root {
--background-color: #1d1d1d;
--gray-50: #101010;
--gray-100: #393939;
--gray-200: #4f4f4f;
--gray-300: #686868;
--gray-400: #848484;
--gray-500: #a7a7a7;
--gray-600: #cfcfcf;
--purple-100: #3c1e95;
--purple-200: #522acd;
--purple-300: #6f46ed;
--purple-400: #8e6ef1;
--purple-500: #b099f5;
--purple-600: #d5c8fa;
--red-100: #721200;
--red-200: #9c1900;
--red-300: #cc2000;
--red-400: #e95034;
--red-500: #f08c79;
--red-600: #f7c3ba;
--highlight-hover: rgb(255 255 255 / 0.1);
--highlight-pressed: rgb(255 255 255 / 0.2);
}
}

/* Semantic colors */
:root {
--focus-ring-color: var(--purple-400);
--text-color: var(--gray-600);
--text-color-base: var(--gray-500);
--text-color-hover: var(--gray-600);
--text-color-disabled: var(--gray-200);
--text-color-placeholder: var(--gray-400);
--link-color: var(--purple-500);
--link-color-secondary: var(--gray-500);
--link-color-pressed: var(--purple-600);
--border-color: var(--gray-300);
--border-color-hover: var(--gray-400);
--border-color-pressed: var(--gray-400);
--border-color-disabled: var(--gray-100);
--field-background: var(--gray-50);
--field-text-color: var(--gray-600);
--overlay-background: var(--gray-50);
--button-background: var(--gray-50);
--button-background-pressed: var(--background-color);
/* these colors are the same between light and dark themes
* to ensure contrast with the foreground color */
--highlight-background: #6f46ed; /* purple-300 from dark theme, 3.03:1 against background-color */
--highlight-background-pressed: #522acd; /* purple-200 from dark theme */
--highlight-background-invalid: #cc2000; /* red-300 from dark theme */
--highlight-foreground: white; /* 5.56:1 against highlight-background */
--highlight-foreground-pressed: #ddd;
--highlight-overlay: rgb(from #6f46ed r g b / 15%);
--invalid-color: var(--red-400);
--invalid-color-pressed: var(--red-500);
}

/* Windows high contrast mode overrides */
@media (forced-colors: active) {
:root {
--background-color: Canvas;
--focus-ring-color: Highlight;
--text-color: ButtonText;
--text-color-base: ButtonText;
--text-color-hover: ButtonText;
--text-color-disabled: GrayText;
--text-color-placeholder: ButtonText;
--link-color: LinkText;
--link-color-secondary: LinkText;
--link-color-pressed: LinkText;
--border-color: ButtonBorder;
--border-color-hover: ButtonBorder;
--border-color-pressed: ButtonBorder;
--border-color-disabled: GrayText;
--field-background: Field;
--field-text-color: FieldText;
--overlay-background: Canvas;
--button-background: ButtonFace;
--button-background-pressed: ButtonFace;
--highlight-background: Highlight;
--highlight-background-pressed: Highlight;
--highlight-background-invalid: LinkText;
--highlight-foreground: HighlightText;
--highlight-foreground-pressed: HighlightText;
--invalid-color: LinkText;
--invalid-color-pressed: LinkText;
}
}
20 changes: 0 additions & 20 deletions packages/dev/parcel-transformer-css-global/CSSGlobalTransformer.js

This file was deleted.

12 changes: 0 additions & 12 deletions packages/dev/parcel-transformer-css-global/package.json

This file was deleted.

52 changes: 2 additions & 50 deletions packages/dev/parcel-transformer-mdx-docs/MDXTransformer.js
Expand Up @@ -17,9 +17,7 @@ const {fragmentUnWrap, fragmentWrap} = require('./MDXFragments');
const yaml = require('js-yaml');
const dprint = require('dprint-node');
const t = require('@babel/types');
const lightningcss = require('lightningcss');
const fs = require('fs');
const path = require('path');
const processCSS = require('./processCSS');

const IMPORT_MAPPINGS = {
'@react-spectrum/theme-default': {
Expand All @@ -44,9 +42,6 @@ module.exports = new Transformer({
let preRelease = preReleaseParts ? preReleaseParts[0] : '';

let visit = (await import('unist-util-visit')).visit;
let unified = (await import('unified')).unified;
let remarkParse = (await import('remark-parse')).default;
let remarkMdx = (await import('remark-mdx')).default;
const extractExamples = () => (tree, file) => (
flatMap(tree, node => {
if (node.type === 'code') {
Expand Down Expand Up @@ -169,50 +164,7 @@ module.exports = new Transformer({
);

let appendCSS = () => async (tree) => {
let transformed = await lightningcss.bundleAsync({
filename: `${asset.filePath}.lightning`,
minify: true,
drafts: {
nesting: true
},
targets: {
chrome: 95 << 16,
safari: 15 << 16
},
resolver: {
resolve(specifier, parent) {
if (specifier.startsWith('.')) {
return path.resolve(path.dirname(parent), specifier);
}

let baseDir = process.env.DOCS_ENV === 'production' ? 'docs' : 'packages';
return path.resolve(options.projectRoot, baseDir, specifier);
},
read(filePath) {
if (filePath === `${asset.filePath}.lightning`) {
return cssCode.join('\n');
}
let result = '';
try {
let contents = fs.readFileSync(filePath, 'utf8');
// get all css blocks and concat
let ast = unified().use(remarkParse).use(remarkMdx).parse(contents);
visit(ast, 'code', node => {
if (node.lang !== 'css' || node?.meta?.includes('render=false') || node?.meta?.includes('hidden')) {
return;
}
let code = node.value;
code = code.replace(/@import.*/g, '');
result += code;
});
} catch (e) {
console.log(e);
}
return result;
}
}
});
let css = transformed.code.toString();
let css = await processCSS(cssCode, asset, options);

tree.children.push(
{
Expand Down
105 changes: 105 additions & 0 deletions packages/dev/parcel-transformer-mdx-docs/processCSS.js
@@ -0,0 +1,105 @@
/*
* Copyright 2020 Adobe. All rights reserved.
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. You may obtain a copy
* of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
* OF ANY KIND, either express or implied. See the License for the specific language
* governing permissions and limitations under the License.
*/

const lightningcss = require('lightningcss');
const fs = require('fs');
const path = require('path');

module.exports = async function processCSS(cssCode, asset, options) {
let visit = (await import('unist-util-visit')).visit;
let unified = (await import('unified')).unified;
let remarkParse = (await import('remark-parse')).default;
let remarkMdx = (await import('remark-mdx')).default;
let transformed = await lightningcss.bundleAsync({
filename: `${asset.filePath}.lightning`,
minify: true,
drafts: {
nesting: true
},
targets: {
chrome: 95 << 16,
safari: 15 << 16
},
resolver: {
resolve(specifier, parent) {
if (specifier.startsWith('.')) {
return path.resolve(path.dirname(parent), specifier);
}

if (path.extname(specifier) === '') {
// Assume this is a package.
specifier += '/src/index.css';
}

let baseDir = process.env.DOCS_ENV === 'production' ? 'docs' : 'packages';
return path.resolve(options.projectRoot, baseDir, specifier);
},
read(filePath) {
if (filePath === `${asset.filePath}.lightning`) {
return cssCode.join('\n');
}
asset.invalidateOnFileChange(filePath);

let result = '';
try {
let contents = fs.readFileSync(filePath, 'utf8');
if (path.extname(filePath) === '.css') {
return contents;
}
// get all css blocks and concat
let ast = unified().use(remarkParse).use(remarkMdx).parse(contents);
visit(ast, 'code', node => {
if (node.lang !== 'css' || node?.meta?.includes('render=false') || node?.meta?.includes('hidden')) {
return;
}
let code = node.value;
code = code.replace(/@import.*/g, '');
result += code;
});
} catch (e) {
console.log(e);
}
return result;
}
},
visitor: {
Rule: {
media(m) {
// Convert dark mode media query to use docs color scheme.
let mediaQueries = m.value.query.mediaQueries;
let condition = mediaQueries[0].condition;
let isDarkMode = mediaQueries.length === 1
&& condition.type === 'feature'
&& condition.value.type === 'plain'
&& condition.value.name === 'prefers-color-scheme'
&& condition.value.value.value === 'dark';
if (isDarkMode) {
return {
type: 'style',
value: {
// Generates this selector: `:root[style*="color-scheme: dark"]`
selectors: [[
{type: 'pseudo-class', kind: 'root'},
{type: 'attribute', name: 'style', operation: {operator: 'substring', value: 'color-scheme: dark'}}
]],
loc: m.value.loc,
declarations: m.value.rules[0].value.declarations
}
};
}
}
}
}
});

return transformed.code.toString();
};

0 comments on commit 5dac45c

Please sign in to comment.