diff --git a/docs/manifest.json b/docs/manifest.json
index a44e36d2ee77a..f9cadda442375 100644
--- a/docs/manifest.json
+++ b/docs/manifest.json
@@ -1319,6 +1319,12 @@
"markdown_source": "../packages/format-library/README.md",
"parent": "packages"
},
+ {
+ "title": "@wordpress/global-styles",
+ "slug": "packages-global-styles",
+ "markdown_source": "../packages/global-styles/README.md",
+ "parent": "packages"
+ },
{
"title": "@wordpress/hooks",
"slug": "packages-hooks",
diff --git a/lib/demo-block-templates/front-page.html b/lib/demo-block-templates/front-page.html
index 627b7ad76aecf..dc47f8b3a1ab8 100644
--- a/lib/demo-block-templates/front-page.html
+++ b/lib/demo-block-templates/front-page.html
@@ -1,3 +1,43 @@
+
+
Mauris consequat augue quis risus sollicitudin commodo. In hac habitasse platea dictumst. Pellentesque massa sem,
+ ultrices non tempus ac, aliquet ac nunc. Sed porttitor ac mi a malesuada. Donec blandit vel arcu blandit
+ scelerisque. Etiam euismod blandit leo a maximus.
+
+
+
+
@@ -9,12 +49,12 @@
-
-
-
-
-
-
+
+
+
+
+
+
@@ -22,7 +62,7 @@
-
+
@@ -34,7 +74,8 @@
-
With full-site editing you can modify all visual aspects of the site using the block editor.
+
With full-site editing you can modify all
+ visual aspects of the site using the block editor.
@@ -47,19 +88,19 @@
-
-
-
Footer
-
-
+
+
+
Footer
+
+
diff --git a/package-lock.json b/package-lock.json
index bb70539a83817..616a613b70c65 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -10121,6 +10121,7 @@
"@wordpress/editor": "file:packages/editor",
"@wordpress/element": "file:packages/element",
"@wordpress/escape-html": "file:packages/escape-html",
+ "@wordpress/global-styles": "file:packages/global-styles",
"@wordpress/i18n": "file:packages/i18n",
"@wordpress/icons": "file:packages/icons",
"@wordpress/is-shallow-equal": "file:packages/is-shallow-equal",
@@ -10414,6 +10415,7 @@
"@wordpress/data": "file:packages/data",
"@wordpress/editor": "file:packages/editor",
"@wordpress/element": "file:packages/element",
+ "@wordpress/global-styles": "file:packages/global-styles",
"@wordpress/hooks": "file:packages/hooks",
"@wordpress/i18n": "file:packages/i18n",
"@wordpress/media-utils": "file:packages/media-utils",
@@ -10547,6 +10549,16 @@
"lodash": "^4.17.15"
}
},
+ "@wordpress/global-styles": {
+ "version": "file:packages/global-styles",
+ "requires": {
+ "@babel/runtime": "^7.8.3",
+ "@wordpress/block-editor": "file:packages/block-editor",
+ "@wordpress/components": "file:packages/components",
+ "@wordpress/element": "file:packages/element",
+ "@wordpress/i18n": "file:packages/i18n"
+ }
+ },
"@wordpress/hooks": {
"version": "file:packages/hooks",
"requires": {
diff --git a/package.json b/package.json
index 9da8cdf2b75b6..ce68707a66cf4 100644
--- a/package.json
+++ b/package.json
@@ -47,6 +47,7 @@
"@wordpress/format-library": "file:packages/format-library",
"@wordpress/hooks": "file:packages/hooks",
"@wordpress/html-entities": "file:packages/html-entities",
+ "@wordpress/global-styles": "file:packages/global-styles",
"@wordpress/i18n": "file:packages/i18n",
"@wordpress/icons": "file:packages/icons",
"@wordpress/is-shallow-equal": "file:packages/is-shallow-equal",
diff --git a/packages/block-editor/README.md b/packages/block-editor/README.md
index 1cbc41293ca13..e52a6f00cdfdc 100644
--- a/packages/block-editor/README.md
+++ b/packages/block-editor/README.md
@@ -303,6 +303,10 @@ _Returns_
- `string`: String with the class corresponding to the fontSize passed. The class is generated by appending 'has-' followed by fontSizeSlug in kebabCase and ending with '-font-size'.
+
# **ifBlockEditSelected**
+
+Undocumented declaration.
+
# **InnerBlocks**
_Related_
diff --git a/packages/block-editor/src/components/block-edit/index.js b/packages/block-editor/src/components/block-edit/index.js
index 3057ff3b4cf72..e1552e2c8ad7b 100644
--- a/packages/block-editor/src/components/block-edit/index.js
+++ b/packages/block-editor/src/components/block-edit/index.js
@@ -12,7 +12,11 @@ import { Component } from '@wordpress/element';
* Internal dependencies
*/
import Edit from './edit';
-import { BlockEditContextProvider, useBlockEditContext } from './context';
+import {
+ BlockEditContextProvider,
+ useBlockEditContext,
+ ifBlockEditSelected,
+} from './context';
class BlockEdit extends Component {
constructor() {
@@ -67,4 +71,4 @@ class BlockEdit extends Component {
}
export default BlockEdit;
-export { useBlockEditContext };
+export { useBlockEditContext, ifBlockEditSelected };
diff --git a/packages/block-editor/src/components/index.js b/packages/block-editor/src/components/index.js
index 044c46a75e96d..6d96a70d4636a 100644
--- a/packages/block-editor/src/components/index.js
+++ b/packages/block-editor/src/components/index.js
@@ -10,7 +10,11 @@ export { default as Autocomplete } from './autocomplete';
export { default as BlockAlignmentToolbar } from './block-alignment-toolbar';
export { default as BlockBreadcrumb } from './block-breadcrumb';
export { default as BlockControls } from './block-controls';
-export { default as BlockEdit, useBlockEditContext } from './block-edit';
+export {
+ default as BlockEdit,
+ useBlockEditContext,
+ ifBlockEditSelected,
+} from './block-edit';
export { default as BlockFormatControls } from './block-format-controls';
export { default as BlockIcon } from './block-icon';
export { default as BlockNavigationDropdown } from './block-navigation/dropdown';
diff --git a/packages/block-library/package.json b/packages/block-library/package.json
index 23613d3fb6202..c0d56a7dd8d16 100644
--- a/packages/block-library/package.json
+++ b/packages/block-library/package.json
@@ -41,6 +41,7 @@
"@wordpress/editor": "file:../editor",
"@wordpress/element": "file:../element",
"@wordpress/escape-html": "file:../escape-html",
+ "@wordpress/global-styles": "file:../global-styles",
"@wordpress/i18n": "file:../i18n",
"@wordpress/icons": "file:../icons",
"@wordpress/is-shallow-equal": "file:../is-shallow-equal",
diff --git a/packages/block-library/src/heading/edit.js b/packages/block-library/src/heading/edit.js
index c1811738ea2e7..0de30e361c7b9 100644
--- a/packages/block-library/src/heading/edit.js
+++ b/packages/block-library/src/heading/edit.js
@@ -12,7 +12,7 @@ import HeadingToolbar from './heading-toolbar';
* WordPress dependencies
*/
import { __ } from '@wordpress/i18n';
-import { PanelBody } from '@wordpress/components';
+import { PanelBody, RangeControl } from '@wordpress/components';
import { createBlock } from '@wordpress/blocks';
import {
AlignmentToolbar,
@@ -23,6 +23,12 @@ import {
} from '@wordpress/block-editor';
import { useRef } from '@wordpress/element';
+import {
+ GlobalStylesControls,
+ GlobalStylesPanelBody,
+ useGlobalStylesState,
+} from '@wordpress/global-styles';
+
function HeadingEdit( {
attributes,
setAttributes,
@@ -31,6 +37,7 @@ function HeadingEdit( {
className,
} ) {
const ref = useRef();
+ const { headingFontWeight, setStyles } = useGlobalStylesState();
const { TextColor, InspectorControlsColorPanel } = __experimentalUseColors(
[ { name: 'textColor', property: 'color' } ],
{
@@ -61,6 +68,20 @@ function HeadingEdit( {
} }
/>
+
+
+
+ setStyles( { headingFontWeight: nextValue } )
+ }
+ min={ 100 }
+ max={ 900 }
+ step={ 100 }
+ />
+
+
{ __( 'Level' ) }
diff --git a/packages/block-library/src/heading/style.scss b/packages/block-library/src/heading/style.scss
new file mode 100644
index 0000000000000..ef5b9012edc78
--- /dev/null
+++ b/packages/block-library/src/heading/style.scss
@@ -0,0 +1,31 @@
+.wp-gs {
+ h1,
+ h2,
+ h3,
+ h4,
+ h5,
+ h6 {
+ color: var(--wp-color--text);
+ font-weight: var(--wp-heading--fontWeight);
+ line-height: var(--wp-typography--titleLineHeight);
+ }
+}
+
+.wp-gs h1 {
+ font-size: var(--wp-typography--fontSizeH1);
+}
+.wp-gs h2 {
+ font-size: var(--wp-typography--fontSizeH2);
+}
+.wp-gs h3 {
+ font-size: var(--wp-typography--fontSizeH3);
+}
+.wp-gs h4 {
+ font-size: var(--wp-typography--fontSizeH4);
+}
+.wp-gs h5 {
+ font-size: var(--wp-typography--fontSizeH5);
+}
+.wp-gs h6 {
+ font-size: var(--wp-typography--fontSizeH6);
+}
diff --git a/packages/block-library/src/paragraph/edit.js b/packages/block-library/src/paragraph/edit.js
index 944a3952dd616..400b65dc290f2 100644
--- a/packages/block-library/src/paragraph/edit.js
+++ b/packages/block-library/src/paragraph/edit.js
@@ -7,7 +7,13 @@ import classnames from 'classnames';
* WordPress dependencies
*/
import { __, _x } from '@wordpress/i18n';
-import { PanelBody, ToggleControl, ToolbarGroup } from '@wordpress/components';
+import {
+ ColorControl,
+ PanelBody,
+ RangeControl,
+ ToggleControl,
+ ToolbarGroup,
+} from '@wordpress/components';
import {
AlignmentToolbar,
BlockControls,
@@ -22,6 +28,12 @@ import { compose } from '@wordpress/compose';
import { useSelect } from '@wordpress/data';
import { useEffect, useState, useRef } from '@wordpress/element';
+import {
+ GlobalStylesControls,
+ GlobalStylesPanelBody,
+ useGlobalStylesState,
+} from '@wordpress/global-styles';
+
/**
* Browser dependencies
*/
@@ -81,6 +93,11 @@ function ParagraphBlock( {
setFontSize,
} ) {
const { align, content, dropCap, placeholder, direction } = attributes;
+ const {
+ paragraphColor,
+ paragraphLineHeight,
+ setStyles,
+ } = useGlobalStylesState();
const ref = useRef();
const dropCapMinimumHeight = useDropCapMinimumHeight( dropCap, [
@@ -124,6 +141,27 @@ function ParagraphBlock( {
}
/>
+
+
+
+ setStyles( { paragraphColor: nextValue } )
+ }
+ />
+
+ setStyles( { paragraphLineHeight: value } )
+ }
+ />
+
+
@@ -90,6 +98,20 @@ export default function QuoteEdit( {
/>
) }
+
+
+
+ setStyles( { quoteFontSize: nextValue } )
+ }
+ min={ 10 }
+ max={ 50 }
+ step={ 1 }
+ />
+
+
>
);
}
diff --git a/packages/block-library/src/quote/style.scss b/packages/block-library/src/quote/style.scss
index c1206d68958f7..9ff7209112f78 100644
--- a/packages/block-library/src/quote/style.scss
+++ b/packages/block-library/src/quote/style.scss
@@ -17,3 +17,9 @@
}
}
}
+
+.wp-gs .wp-block-quote {
+ p {
+ font-size: var(--wp-quote--fontSize);
+ }
+}
diff --git a/packages/block-library/src/style.scss b/packages/block-library/src/style.scss
index bd8ab8a093bb5..07e35a726f861 100644
--- a/packages/block-library/src/style.scss
+++ b/packages/block-library/src/style.scss
@@ -11,6 +11,7 @@
@import "./columns/style.scss";
@import "./cover/style.scss";
@import "./embed/style.scss";
+@import "./heading/style.scss";
@import "./file/style.scss";
@import "./gallery/style.scss";
@import "./image/style.scss";
diff --git a/packages/components/src/color-control/index.js b/packages/components/src/color-control/index.js
new file mode 100644
index 0000000000000..a9c62562d3a87
--- /dev/null
+++ b/packages/components/src/color-control/index.js
@@ -0,0 +1,106 @@
+/**
+ * External dependencies
+ */
+import colorize from 'tinycolor2';
+import classnames from 'classnames';
+import { noop } from 'lodash';
+/**
+ * WordPress dependencies
+ */
+import { useState, useCallback } from '@wordpress/element';
+import { compose, withInstanceId } from '@wordpress/compose';
+
+/**
+ * Internal dependencies
+ */
+import BaseControl from '../base-control';
+import ColorPicker from '../color-picker';
+import Dropdown from '../dropdown';
+
+import {
+ ControlContainer,
+ ControlWrapper,
+ ColorSwatch,
+ ColorLabel,
+} from './styles/color-control-styles';
+
+function BaseColorControl( {
+ className,
+ instanceId,
+ label,
+ value = 'black',
+ onChange = noop,
+ ...props
+} ) {
+ const [ isFocused, setIsFocused ] = useState( false );
+ const [ isOpen, setIsOpen ] = useState( false );
+
+ // TODO: Add derived prop/controlled hook to manage state
+ const [ color, setColor ] = useState( toColor( value ) );
+
+ const handleOnChange = ( nextColor ) => {
+ setColor( nextColor );
+ onChange( nextColor );
+ };
+
+ const renderToggle = useCallback(
+ ( { isOpen: isOpenProp, onToggle } ) => {
+ setIsOpen( isOpenProp );
+ return (
+ setIsFocused( false ) }
+ onFocus={ () => setIsFocused( true ) }
+ style={ { backgroundColor: color } }
+ onClick={ onToggle }
+ />
+ );
+ },
+ [ color ]
+ );
+
+ const renderContent = useCallback(
+ () => (
+
+ handleOnChange( nextColor.hex )
+ }
+ disableAlpha
+ />
+ ),
+ [ color ]
+ );
+
+ const classes = classnames( 'components-color-control', className );
+ const id = `inspector-color-control-${ instanceId }`;
+ const isFocusedOrOpen = isFocused || isOpen;
+
+ return (
+
+
+
+
+
+ { color }
+
+
+
+
+ );
+}
+
+function toColor( color ) {
+ return colorize( color ).toHexString();
+}
+
+export default compose( [ withInstanceId ] )( BaseColorControl );
diff --git a/packages/components/src/color-control/stories/index.js b/packages/components/src/color-control/stories/index.js
new file mode 100644
index 0000000000000..0e93187751fc9
--- /dev/null
+++ b/packages/components/src/color-control/stories/index.js
@@ -0,0 +1,25 @@
+/**
+ * External dependencies
+ */
+import styled from '@emotion/styled';
+
+/**
+ * Internal dependencies
+ */
+import ColorControl from '../';
+
+export default { title: 'Components/ColorControl', component: ColorControl };
+
+export const _default = () => {
+ return (
+
+
+
+ );
+};
+
+const Wrapper = styled.div`
+ padding: 40px;
+ margin-left: auto;
+ width: 250px;
+`;
diff --git a/packages/components/src/color-control/styles/color-control-styles.js b/packages/components/src/color-control/styles/color-control-styles.js
new file mode 100644
index 0000000000000..ddbae7db2cee9
--- /dev/null
+++ b/packages/components/src/color-control/styles/color-control-styles.js
@@ -0,0 +1,57 @@
+/**
+ * External dependencies
+ */
+import { css } from '@emotion/core';
+import styled from '@emotion/styled';
+
+/**
+ * Internal dependencies
+ */
+import { color } from '../../utils/colors';
+
+export const ControlWrapper = styled.div``;
+
+const containerFocus = ( { isFocused } ) => {
+ if ( ! isFocused ) return '';
+
+ return css`
+ border: 1px solid ${color( 'blue.medium.focus' )};
+ box-shadow: 0 0 0 1px ${color( 'blue.medium.focus' )};
+ `;
+};
+
+export const ControlContainer = styled.div`
+ align-items: center;
+ border-radius: 2px;
+ border: 1px solid ${color( 'lightGray.600' )};
+ box-sizing: border-box;
+ display: flex;
+ height: 36px;
+ overflow: hidden;
+ max-width: 110px;
+
+ ${containerFocus};
+`;
+
+export const ColorSwatch = styled.button`
+ appearance: none;
+ border: none;
+ border-right: 1px solid ${color( 'lightGray.600' )};
+ box-sizing: border-box;
+ cursor: pointer;
+ display: block;
+ height: 36px;
+ outline: none;
+ width: 36px;
+
+ &:focus {
+ outline: none;
+ }
+`;
+
+export const ColorLabel = styled.div`
+ box-sizing: border-box;
+ padding: 4px 8px;
+ width: 72px;
+ font-size: 12px;
+`;
diff --git a/packages/components/src/index.js b/packages/components/src/index.js
index 5a78b26d4733e..216bc7edbee28 100644
--- a/packages/components/src/index.js
+++ b/packages/components/src/index.js
@@ -26,6 +26,7 @@ export { default as CardMedia } from './card/media';
export { default as CheckboxControl } from './checkbox-control';
export { default as ClipboardButton } from './clipboard-button';
export { default as ColorIndicator } from './color-indicator';
+export { default as ColorControl } from './color-control';
export { default as ColorPalette } from './color-palette';
export { default as ColorPicker } from './color-picker';
export { default as CustomSelectControl } from './custom-select-control';
diff --git a/packages/edit-site/package.json b/packages/edit-site/package.json
index 8d25b17abbaa3..db954d80ebb88 100644
--- a/packages/edit-site/package.json
+++ b/packages/edit-site/package.json
@@ -29,6 +29,7 @@
"@wordpress/data": "file:../data",
"@wordpress/editor": "file:../editor",
"@wordpress/element": "file:../element",
+ "@wordpress/global-styles": "file:../global-styles",
"@wordpress/hooks": "file:../hooks",
"@wordpress/i18n": "file:../i18n",
"@wordpress/media-utils": "file:../media-utils",
diff --git a/packages/edit-site/src/components/editor/index.js b/packages/edit-site/src/components/editor/index.js
index 83a0f498a35fc..1b3e92e51339c 100644
--- a/packages/edit-site/src/components/editor/index.js
+++ b/packages/edit-site/src/components/editor/index.js
@@ -9,6 +9,7 @@ import {
navigateRegions,
} from '@wordpress/components';
import { EntityProvider } from '@wordpress/core-data';
+import { GlobalStylesStateProvider } from '@wordpress/global-styles';
/**
* Internal dependencies
@@ -29,21 +30,23 @@ function Editor( { settings } ) {
[]
);
return template ? (
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
) : null;
}
diff --git a/packages/edit-site/src/components/sidebar/index.js b/packages/edit-site/src/components/sidebar/index.js
index 8b310e80d0231..dcf54be757faa 100644
--- a/packages/edit-site/src/components/sidebar/index.js
+++ b/packages/edit-site/src/components/sidebar/index.js
@@ -3,6 +3,11 @@
*/
import { createSlotFill, Panel } from '@wordpress/components';
import { __ } from '@wordpress/i18n';
+import { GlobalStylesPanel } from '@wordpress/global-styles';
+
+/**
+ * Internal dependencies
+ */
const { Slot: InspectorSlot, Fill: InspectorFill } = createSlotFill(
'EditSiteSidebarInspector'
@@ -16,6 +21,9 @@ function Sidebar() {
aria-label={ __( 'Site editor advanced settings.' ) }
tabIndex="-1"
>
+
+
+
diff --git a/packages/global-styles/README.md b/packages/global-styles/README.md
new file mode 100644
index 0000000000000..5d765ae85542e
--- /dev/null
+++ b/packages/global-styles/README.md
@@ -0,0 +1,3 @@
+# Global Styles
+
+(Experimental) Global Styles Library
diff --git a/packages/global-styles/package.json b/packages/global-styles/package.json
new file mode 100644
index 0000000000000..fbfe8bb34a2bf
--- /dev/null
+++ b/packages/global-styles/package.json
@@ -0,0 +1,33 @@
+{
+ "name": "@wordpress/global-styles",
+ "version": "0.0.1",
+ "private": true,
+ "description": "Global Styles module for WordPress.",
+ "author": "The WordPress Contributors",
+ "license": "GPL-2.0-or-later",
+ "keywords": [
+ "wordpress"
+ ],
+ "homepage": "https://github.com/WordPress/gutenberg/tree/master/packages/global-styles/README.md",
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/WordPress/gutenberg.git",
+ "directory": "packages/global-styles"
+ },
+ "bugs": {
+ "url": "https://github.com/WordPress/gutenberg/issues"
+ },
+ "main": "build/index.js",
+ "module": "build-module/index.js",
+ "react-native": "src/index",
+ "dependencies": {
+ "@babel/runtime": "^7.8.3",
+ "@wordpress/block-editor": "../block-editor",
+ "@wordpress/components": "../components",
+ "@wordpress/element": "../element",
+ "@wordpress/i18n": "../i18n"
+ },
+ "publishConfig": {
+ "access": "public"
+ }
+}
diff --git a/packages/global-styles/src/controls/color-controls.js b/packages/global-styles/src/controls/color-controls.js
new file mode 100644
index 0000000000000..9b23a423737e9
--- /dev/null
+++ b/packages/global-styles/src/controls/color-controls.js
@@ -0,0 +1,42 @@
+/**
+ * WordPress dependencies
+ */
+import { __ } from '@wordpress/i18n';
+import { ColorControl } from '@wordpress/components';
+
+/**
+ * Internal dependencies
+ */
+import { GlobalStylesPanelBody } from '../global-styles-panel-body';
+import { useGlobalStylesState } from '../store';
+
+export default function ColorControls() {
+ const {
+ textColor,
+ backgroundColor,
+ primaryColor,
+ setStyles,
+ } = useGlobalStylesState();
+
+ return (
+
+ setStyles( { textColor: value } ) }
+ />
+
+ setStyles( { backgroundColor: value } )
+ }
+ />
+ setStyles( { primaryColor: value } ) }
+ />
+
+ );
+}
diff --git a/packages/global-styles/src/controls/index.js b/packages/global-styles/src/controls/index.js
new file mode 100644
index 0000000000000..b09df4b6b9842
--- /dev/null
+++ b/packages/global-styles/src/controls/index.js
@@ -0,0 +1,2 @@
+export { default as ColorControls } from './color-controls';
+export { default as TypographyControls } from './typography-controls';
diff --git a/packages/global-styles/src/controls/typography-controls.js b/packages/global-styles/src/controls/typography-controls.js
new file mode 100644
index 0000000000000..25ee5ad2033c6
--- /dev/null
+++ b/packages/global-styles/src/controls/typography-controls.js
@@ -0,0 +1,61 @@
+/**
+ * WordPress dependencies
+ */
+import { __ } from '@wordpress/i18n';
+import { RangeControl } from '@wordpress/components';
+
+/**
+ * Internal dependencies
+ */
+import { GlobalStylesPanelBody } from '../global-styles-panel-body';
+import { useGlobalStylesState } from '../store';
+
+export default function TypographyControls() {
+ const {
+ fontSize,
+ fontScale,
+ lineHeight,
+ fontWeight,
+ setStyles,
+ } = useGlobalStylesState();
+
+ return (
+
+ setStyles( { fontSize: value } ) }
+ />
+ setStyles( { fontScale: value } ) }
+ />
+ setStyles( { lineHeight: value } ) }
+ />
+ setStyles( { fontWeight: value } ) }
+ />
+
+ );
+}
diff --git a/packages/global-styles/src/global-styles-controls.js b/packages/global-styles/src/global-styles-controls.js
new file mode 100644
index 0000000000000..3d6c36b5eb44c
--- /dev/null
+++ b/packages/global-styles/src/global-styles-controls.js
@@ -0,0 +1,11 @@
+/**
+ * Internal dependencies
+ */
+import { Fill } from './slot';
+import { isEditSite } from './utils';
+
+export function GlobalStylesControls( { children } ) {
+ if ( ! isEditSite() ) return null;
+
+ return { children } ;
+}
diff --git a/packages/global-styles/src/global-styles-panel-body.js b/packages/global-styles/src/global-styles-panel-body.js
new file mode 100644
index 0000000000000..78963132a4536
--- /dev/null
+++ b/packages/global-styles/src/global-styles-panel-body.js
@@ -0,0 +1,14 @@
+/**
+ * WordPress dependencies
+ */
+import { PanelBody } from '@wordpress/components';
+/**
+ * Internal dependencies
+ */
+import { isEditSite } from './utils';
+
+export function GlobalStylesPanelBody( props ) {
+ if ( ! isEditSite() ) return null;
+
+ return ;
+}
diff --git a/packages/global-styles/src/global-styles-panel.js b/packages/global-styles/src/global-styles-panel.js
new file mode 100644
index 0000000000000..d5d8c19ccf39f
--- /dev/null
+++ b/packages/global-styles/src/global-styles-panel.js
@@ -0,0 +1,17 @@
+/**
+ * Internal dependencies
+ */
+
+import { Slot } from './slot';
+
+import { ColorControls, TypographyControls } from './controls';
+
+export function GlobalStylesPanel() {
+ return (
+ <>
+
+
+
+ >
+ );
+}
diff --git a/packages/global-styles/src/index.js b/packages/global-styles/src/index.js
new file mode 100644
index 0000000000000..2db07eb4412bd
--- /dev/null
+++ b/packages/global-styles/src/index.js
@@ -0,0 +1,5 @@
+export * from './store';
+export * from './global-styles-controls';
+export * from './global-styles-panel';
+export * from './global-styles-panel-body';
+export * from './utils';
diff --git a/packages/global-styles/src/renderer.js b/packages/global-styles/src/renderer.js
new file mode 100644
index 0000000000000..d9135b98abc6f
--- /dev/null
+++ b/packages/global-styles/src/renderer.js
@@ -0,0 +1,87 @@
+/**
+ * WordPress dependencies
+ */
+import { useEffect, useLayoutEffect } from '@wordpress/element';
+
+/**
+ * TODO: Replace everything below with client-side style rendering mechanism
+ */
+
+export function useRenderedGlobalStyles( styles = {} ) {
+ useGlobalStylesEnvironment();
+ const generatedStyles = compileStyles( styles );
+
+ useEffect( () => {
+ const styleNodeId = 'wp-global-styles-tag';
+ let styleNode = document.getElementById( styleNodeId );
+
+ if ( ! styleNode ) {
+ styleNode = document.createElement( 'style' );
+ styleNode.id = styleNodeId;
+ document
+ .getElementsByTagName( 'head' )[ 0 ]
+ .appendChild( styleNode );
+ }
+
+ styleNode.innerText = generatedStyles;
+ }, [ generatedStyles ] );
+}
+
+function useGlobalStylesEnvironment() {
+ useLayoutEffect( () => {
+ // Adding a slight async delay to give the Gutenberg editor time to render.
+ window.requestAnimationFrame( () => {
+ // Getting the Gutenberg editor content wrapper DOM node.
+ const editorNode = document.getElementsByClassName(
+ 'editor-styles-wrapper'
+ )[ 0 ];
+
+ const targetNode = editorNode || document.documentElement;
+
+ if ( ! targetNode.classList.contains( 'wp-gs' ) ) {
+ targetNode.classList.add( 'wp-gs' );
+ }
+ } );
+ }, [] );
+}
+
+function flattenObject( ob ) {
+ const toReturn = {};
+
+ for ( const i in ob ) {
+ if ( ! ob.hasOwnProperty( i ) ) continue;
+
+ if ( typeof ob[ i ] === 'object' ) {
+ const flatObject = flattenObject( ob[ i ] );
+ for ( const x in flatObject ) {
+ if ( ! flatObject.hasOwnProperty( x ) ) continue;
+
+ toReturn[ i + '.' + x ] = flatObject[ x ];
+ }
+ } else {
+ toReturn[ i ] = ob[ i ];
+ }
+ }
+ return toReturn;
+}
+
+function compileStyles( styles = {} ) {
+ const flattenedStyles = { ...flattenObject( styles ) };
+ const html = [];
+ html.push( ':root {' );
+
+ for ( const key in flattenedStyles ) {
+ const value = flattenedStyles[ key ];
+ if ( value ) {
+ const style = `--wp-${ key.replace( /\./g, '--' ) }: ${ value };`;
+ html.push( style );
+ }
+ }
+ html.push( '}' );
+
+ html.push(
+ '.editor-styles-wrapper { background-color: var(--wp-color--background); }'
+ );
+
+ return html.join( '\n' );
+}
diff --git a/packages/global-styles/src/slot.js b/packages/global-styles/src/slot.js
new file mode 100644
index 0000000000000..83d2bd48b44db
--- /dev/null
+++ b/packages/global-styles/src/slot.js
@@ -0,0 +1,10 @@
+/**
+ * WordPress dependencies
+ */
+import { createSlotFill } from '@wordpress/components';
+import { ifBlockEditSelected } from '@wordpress/block-editor';
+
+export const GlobalStylesSlot = createSlotFill( '__GLOBAL_STYLES_SLOT__' );
+export const { Slot, Fill: BaseFill } = GlobalStylesSlot;
+
+export const Fill = ifBlockEditSelected( BaseFill );
diff --git a/packages/global-styles/src/store.js b/packages/global-styles/src/store.js
new file mode 100644
index 0000000000000..764ef9ec4e7b7
--- /dev/null
+++ b/packages/global-styles/src/store.js
@@ -0,0 +1,132 @@
+/**
+ * WordPress dependencies
+ */
+import { useState, useContext, createContext } from '@wordpress/element';
+
+/**
+ * Internal dependencies
+ */
+import { useRenderedGlobalStyles } from './renderer';
+
+/**
+ * TODO: Replace everything below with wp.data store mechanism
+ */
+
+export const GlobalStylesContext = createContext( {} );
+export const useGlobalStylesState = () => useContext( GlobalStylesContext );
+
+export function GlobalStylesStateProvider( { children } ) {
+ const state = useGlobalStylesStore();
+
+ return (
+
+ { children }
+
+ );
+}
+
+export function useGlobalStylesDataState() {
+ const initialState = {
+ fontSize: 16,
+ fontWeight: 400,
+ headingFontWeight: 600,
+ fontScale: 1.2,
+ lineHeight: 1.5,
+ quoteFontSize: 24,
+ textColor: '#000000',
+ backgroundColor: '#ffffff',
+ primaryColor: '#0000ff',
+ paragraphColor: null,
+ paragraphLineHeight: null,
+ };
+
+ const [ state, _setState ] = useState( initialState );
+
+ const setState = ( nextState = {} ) => {
+ const mergedState = { ...state, ...nextState };
+ _setState( mergedState );
+ };
+
+ return [ state, setState ];
+}
+
+export function useGlobalStylesStore() {
+ // TODO: Replace with data/actions from wp.data
+ const [ styleState, setStyles ] = useGlobalStylesDataState();
+ const {
+ fontSize,
+ fontScale,
+ lineHeight,
+ fontWeight,
+ headingFontWeight,
+ paragraphColor,
+ paragraphLineHeight,
+ quoteFontSize,
+ textColor,
+ backgroundColor,
+ primaryColor,
+ } = styleState;
+
+ const styles = {
+ color: {
+ text: textColor,
+ background: backgroundColor,
+ primary: primaryColor,
+ },
+ typography: {
+ ...generateFontSizes( { fontSize, fontScale } ),
+ ...generateLineHeight( { lineHeight } ),
+ fontScale,
+ fontWeight,
+ },
+ heading: {
+ fontWeight: headingFontWeight,
+ },
+ quote: {
+ fontSize: toPx( quoteFontSize ),
+ },
+ paragraph: {
+ color: paragraphColor,
+ lineHeight: paragraphLineHeight,
+ },
+ };
+
+ useRenderedGlobalStyles( styles );
+
+ return {
+ ...styleState,
+ setStyles,
+ };
+}
+
+/**
+ * NOTE: Generators for extra computed values.
+ */
+
+function generateLineHeight( { lineHeight = 1.5 } ) {
+ return {
+ lineHeight,
+ titleLineHeight: ( lineHeight * 0.8 ).toFixed( 2 ),
+ };
+}
+
+function generateFontSizes( { fontSize = 16, fontScale = 1.2 } ) {
+ const toScaledPx = ( size ) => {
+ const value = ( Math.pow( fontScale, size ) * fontSize ).toFixed( 2 );
+ return toPx( value );
+ };
+
+ return {
+ fontSize: `${ fontSize }px`,
+ fontSizeH1: toScaledPx( 5 ),
+ fontSizeH2: toScaledPx( 4 ),
+ fontSizeH3: toScaledPx( 3 ),
+ fontSizeH4: toScaledPx( 2 ),
+ fontSizeH5: toScaledPx( 1 ),
+ fontSizeH6: toScaledPx( 0.5 ),
+ };
+}
+
+function toPx( value ) {
+ return `${ value }px`;
+}
diff --git a/packages/global-styles/src/utils.js b/packages/global-styles/src/utils.js
new file mode 100644
index 0000000000000..b7edfd6161dee
--- /dev/null
+++ b/packages/global-styles/src/utils.js
@@ -0,0 +1,3 @@
+export function isEditSite() {
+ return window.location.search.indexOf( 'page=gutenberg-edit-site' ) >= 0;
+}
diff --git a/storybook/test/__snapshots__/index.js.snap b/storybook/test/__snapshots__/index.js.snap
index e0afcc5aa3495..41a5c3f50d53c 100644
--- a/storybook/test/__snapshots__/index.js.snap
+++ b/storybook/test/__snapshots__/index.js.snap
@@ -1933,6 +1933,98 @@ exports[`Storyshots Components/ClipboardButton Default 1`] = `
`;
+exports[`Storyshots Components/ColorControl Default 1`] = `
+.emotion-8 {
+ padding: 40px;
+ margin-left: auto;
+ width: 250px;
+}
+
+.emotion-4 {
+ -webkit-align-items: center;
+ -webkit-box-align: center;
+ -ms-flex-align: center;
+ align-items: center;
+ border-radius: 2px;
+ border: 1px solid #d7dade;
+ box-sizing: border-box;
+ display: -webkit-box;
+ display: -webkit-flex;
+ display: -ms-flexbox;
+ display: flex;
+ height: 36px;
+ overflow: hidden;
+ max-width: 110px;
+}
+
+.emotion-0 {
+ -webkit-appearance: none;
+ -moz-appearance: none;
+ appearance: none;
+ border: none;
+ border-right: 1px solid #d7dade;
+ box-sizing: border-box;
+ cursor: pointer;
+ display: block;
+ height: 36px;
+ outline: none;
+ width: 36px;
+}
+
+.emotion-0:focus {
+ outline: none;
+}
+
+.emotion-2 {
+ box-sizing: border-box;
+ padding: 4px 8px;
+ width: 72px;
+ font-size: 12px;
+}
+
+
+`;
+
exports[`Storyshots Components/ColorIndicator Default 1`] = `