Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Components: Add create-styles #30509

Merged
merged 10 commits into from
Apr 8, 2021
28 changes: 28 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions packages/components/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
"@babel/runtime": "^7.13.10",
"@emotion/core": "^10.1.1",
"@emotion/css": "^10.0.22",
"@emotion/hash": "^0.8.0",
"@emotion/native": "^10.0.22",
"@emotion/styled": "^10.0.23",
"@wordpress/a11y": "file:../a11y",
Expand All @@ -50,12 +51,15 @@
"@wp-g2/styles": "^0.0.160",
"@wp-g2/utils": "^0.0.160",
"classnames": "^2.2.5",
"create-emotion": "^10.0.27",
"dom-scroll-into-view": "^1.2.1",
"downshift": "^6.0.15",
"gradient-parser": "^0.1.5",
"highlight-words-core": "^1.2.2",
"hoist-non-react-statics": "^3.3.2",
"lodash": "^4.17.19",
"memize": "^1.1.0",
"mitt": "^2.1.0",
"moment": "^2.22.1",
"re-resizable": "^6.4.0",
"react-dates": "^17.1.1",
Expand All @@ -64,6 +68,7 @@
"react-use-gesture": "^9.0.0",
"reakit": "^1.3.5",
"rememo": "^3.0.0",
"styled-griddie": "^0.1.3",
"tinycolor2": "^1.4.2",
"uuid": "^8.3.0"
},
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# create-compiler

This module creates the Emotion instance that backs the style system. It integrates plugins and creates the core `css` function that wraps Emotion's `css` function adding support for breakpoint values on each property.

## Breakpoint values

Breakpoint values are supported by passing an array of values to a CSS property. For example:

```js
css({
width: [300, 500, 700],
});
```

This will dynamically respond to breakpoints and render the appropriate width for each `min-width`. The breakpoints are documented in the code in [`utils.js`](./utils.js).

## Plugins

`createCompiler` supports passing certain parameters to plugins. Plugin initialization should be contained to [`plugins/index.js`](./plugins/index.js).

The individual plugins are documented in [`plugins/README.md`](./plugins/README.md).

## Custom iframe support

Emotion by default does not support iframe styling. This style system solves this by implementing a custom `sheet.insert` that exposes a `sheet.insert` event which can be listened to by style providers to receive styles from outside of the current iframe.

## Interplated Components

`css` also supports passing style system-connected components as selectors in the same style as `styled-components`. It does this _without any Babel transformations_. Interpolated components are transformed to a special interpolated class name by the `css` function. Components are given an interpolation class name (prefixed by `ic-`) by either the `contextConnect` hook or by `styled` itself. `css` then detects when a component has been passed in and transorms it into a CSS selector.

For example:

```js
const Text = styled.div`
color: red;
`;

const greenText = css`
${Text} {
color: green;
}
`;
```

Now any child `Text` of a component that applies the `greenText` generated class name will be targeted with the `color: green` styles.

Psueudo selectors against the interpolated component are possible as well:

```js
const blueText = css`
${Text}:first-child {
color: blue;
}
`;
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
/**
* External dependencies
*/
import createEmotion from 'create-emotion';
import mitt from 'mitt';

/**
* Internal dependencies
*/
import { RootStore } from '../css-custom-properties';
import { createCSS } from './create-css';
import { createPlugins } from './plugins';
import { breakpoints, generateInterpolationName } from './utils';

const defaultOptions = {
key: 'css',
specificityLevel: 1,
rootStore: new RootStore(),
};

/* eslint-disable jsdoc/valid-types */
/**
* @typedef {import('create-emotion').Emotion & {
* breakpoints: typeof breakpoints,
* __events: import('mitt').Emitter,
* generateInterpolationName(): string,
* }} Compiler
*/
/* eslint-enable jsdoc/valid-types */

/**
* @typedef {import('create-emotion').Options & {
* key?: string,
* specificityLevel?: number,
* rootStore: import('../css-custom-properties').RootStore
* }} CreateCompilerOptions
*/

/**
* @param {CreateCompilerOptions} options
* @return {Compiler} The compiler.
*/
export function createCompiler( options ) {
const mergedOptions = {
...defaultOptions,
...options,
};

const { key, rootStore, specificityLevel } = mergedOptions;

const defaultPlugins = createPlugins( {
key,
specificityLevel,
rootStore,
} );

if ( options.stylisPlugins ) {
if ( Array.isArray( options.stylisPlugins ) ) {
mergedOptions.stylisPlugins = [
...defaultPlugins,
...options.stylisPlugins,
];
} else if ( typeof options.stylisPlugins !== 'undefined' ) {
// just a single plugin was passed in, as is allowed by emotion
mergedOptions.stylisPlugins = [
...defaultPlugins,
options.stylisPlugins,
];
} else {
mergedOptions.stylisPlugins = defaultPlugins;
}
} else {
mergedOptions.stylisPlugins = defaultPlugins;
}

/**
* We're creating a custom Emotion instance to ensure that the style system
* does not conflict with (potential) existing Emotion instances.
*
* We're also able to provide createEmotion with our custom Stylis plugins.
*/
const customEmotionInstance = {
...createEmotion( mergedOptions ),
/**
* Exposing the breakpoints used in the internal Style system.
*/
breakpoints,
/**
* An internal custom event emitter (pub/sub) for Emotion.
* This is currently used in <StyleFrameProvider />
* to subscribe to and sync style injection.
*/
__events: mitt(),
generateInterpolationName,
};

/**
* Enhance the base css function from Emotion to add features like responsive
* value handling and compiling an Array of css() calls.
*/
const { css } = customEmotionInstance;
customEmotionInstance.css = createCSS( css );

/**
* Modify the sheet.insert method to emit a `sheet.insert` event
* within the internal custom event emitter.
*/
const __insert = customEmotionInstance.sheet.insert;
customEmotionInstance.sheet.insert = (
/* eslint-disable jsdoc/valid-types */
/** @type {[rule: string]} */ ...args
) =>
/* eslint-enable jsdoc/valid-types */
{
__insert.apply( customEmotionInstance.sheet, [ ...args ] );
customEmotionInstance.__events.emit( 'sheet.insert', ...args );
youknowriad marked this conversation as resolved.
Show resolved Hide resolved
};

return customEmotionInstance;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/**
* External dependencies
*/
import { isPlainObject } from 'lodash';

/**
* Internal dependencies
*/
import { responsive } from './responsive';

/**
* @param {CSS} compile
* @return {CSS} The CSS function
*/
export function createCSS( compile ) {
/**
* An enhanced version of the compiler's (Emotion) CSS function.
* This enhanced CSS supports dynamic responsive (breakpoint-based) styles if
* the value is an array of values.
*
* @example
* ```js
* // The following will render a CSS rule where the widths will be:
* // 100px for mobile
* // 200px for tablet
* // 500px for desktop
* css({
* width: [100, 200, 500]
* })
* ```
* @param {Parameters<CSS>} args
* @return {ReturnType<CSS>} The compiled CSS className associated with the styles.
*/
function css( ...args ) {
const [ arg, ...rest ] = args;

if ( isPlainObject( arg ) ) {
return compile(
responsive( /** @type {ObjectInterpolation} */ ( arg ) )
);
}

if ( Array.isArray( arg ) ) {
for ( let i = 0, len = arg.length; i < len; i++ ) {
const n = arg[ i ];
if ( isPlainObject( n ) ) {
arg[ i ] = responsive(
/** @type {ObjectInterpolation} */ ( n )
);
}
}
return compile( ...[ arg, ...rest ] );
}

return compile( ...args );
}

// @ts-ignore No amount of zhuzhing will convince TypeScript that a function with the parameters and return type for CSS is in fact the same type
return css;
}

/* eslint-disable jsdoc/valid-types */
/** @typedef {import('create-emotion').Emotion['css']} CSS */
/** @typedef {import('create-emotion').ObjectInterpolation<unknown>} ObjectInterpolation */
/* eslint-enable jsdoc/valid-types */
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export * from './create-compiler';
export * from './create-css';
export * from './responsive';
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# plugins

This foler contains all the applied plugins in the style system.

**Nota bene**: All of the plugins can be removed once IE11 support is officially dropped.

## Extra Specificity

This plugin automatically compounds selector specificity by simply repeating the selector x number of times. For example, if a specificity of 3 is passed in, the plugin will transform:

```css
.css-abc123 {
color: red;
}
```

into:

```css
.css-abc123.css-abc123.css-abc123 {
color: red;
}
```

This is meant to prevent "hacks" from being applied to the component system via regular css selection (or rather to make it difficult/annoying to do so), forcing consumers to use the style system itself, for example, the `css` prop and theme variables, to apply custom styles.

It is currently set to a specificity of 1 to disable it. This may be reversed in the future. If it isn't reversed in the future, at some point we shoiuld just remove it.

## CSS Variable Fallback

The [`css-variables.js` ](./css-variables.js) plugin automatically generates fallback variables to support browsers that lack CSS variable support.

Given WordPress core is dropping IE11 support,we might be able to drop this plugin altogether.
Loading