-
Notifications
You must be signed in to change notification settings - Fork 4.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add global styles sidebar at edit site screen (#24250)
- Loading branch information
Showing
11 changed files
with
758 additions
and
175 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
96 changes: 96 additions & 0 deletions
96
packages/edit-site/src/components/editor/global-styles-provider.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
/** | ||
* External dependencies | ||
*/ | ||
import { set, get } from 'lodash'; | ||
|
||
/** | ||
* WordPress dependencies | ||
*/ | ||
import { | ||
createContext, | ||
useContext, | ||
useEffect, | ||
useMemo, | ||
} from '@wordpress/element'; | ||
import { useEntityProp } from '@wordpress/core-data'; | ||
|
||
/** | ||
* Internal dependencies | ||
*/ | ||
import getGlobalStyles from './global-styles-renderer'; | ||
|
||
const GlobalStylesContext = createContext( { | ||
/* eslint-disable no-unused-vars */ | ||
getProperty: ( context, path ) => {}, | ||
setProperty: ( context, newValues ) => {}, | ||
globalContext: {}, | ||
/* eslint-enable no-unused-vars */ | ||
} ); | ||
|
||
export const useGlobalStylesContext = () => useContext( GlobalStylesContext ); | ||
|
||
export default ( { children, baseStyles, contexts } ) => { | ||
const [ content, setContent ] = useEntityProp( | ||
'postType', | ||
'wp_global_styles', | ||
'content' | ||
); | ||
|
||
const userStyles = useMemo( | ||
() => ( content ? JSON.parse( content ) : {} ), | ||
[ content ] | ||
); | ||
|
||
const nextValue = useMemo( | ||
() => ( { | ||
contexts, | ||
getProperty: ( context, path ) => | ||
get( userStyles?.[ context ]?.styles, path ), | ||
setProperty: ( context, newValues ) => { | ||
const newContent = { ...userStyles }; | ||
Object.keys( newValues ).forEach( ( key ) => { | ||
set( | ||
newContent, | ||
`${ context }.styles.${ key }`, | ||
newValues[ key ] | ||
); | ||
} ); | ||
setContent( JSON.stringify( newContent ) ); | ||
}, | ||
} ), | ||
[ contexts, content ] | ||
); | ||
|
||
useEffect( () => { | ||
if ( | ||
typeof contexts !== 'object' || | ||
typeof baseStyles !== 'object' || | ||
typeof userStyles !== 'object' | ||
) { | ||
return; | ||
} | ||
|
||
const embeddedStylesheetId = 'global-styles-inline-css'; | ||
let styleNode = document.getElementById( embeddedStylesheetId ); | ||
|
||
if ( ! styleNode ) { | ||
styleNode = document.createElement( 'style' ); | ||
styleNode.id = embeddedStylesheetId; | ||
document | ||
.getElementsByTagName( 'head' )[ 0 ] | ||
.appendChild( styleNode ); | ||
} | ||
|
||
styleNode.innerText = getGlobalStyles( | ||
contexts, | ||
baseStyles, | ||
userStyles | ||
); | ||
}, [ contexts, baseStyles, content ] ); | ||
|
||
return ( | ||
<GlobalStylesContext.Provider value={ nextValue }> | ||
{ children } | ||
</GlobalStylesContext.Provider> | ||
); | ||
}; |
121 changes: 121 additions & 0 deletions
121
packages/edit-site/src/components/editor/global-styles-renderer.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,121 @@ | ||
/** | ||
* External dependencies | ||
*/ | ||
import { get } from 'lodash'; | ||
|
||
/** | ||
* Internal dependencies | ||
*/ | ||
import { | ||
STYLE_PROPS, | ||
PRESET_CATEGORIES, | ||
LINK_COLOR_DECLARATION, | ||
} from './utils'; | ||
|
||
const mergeTrees = ( baseData, userData ) => { | ||
// Deep clone from base data. | ||
// | ||
// We don't use cloneDeep from lodash here | ||
// because we know the data is JSON compatible, | ||
// see https://github.com/lodash/lodash/issues/1984 | ||
const mergedTree = JSON.parse( JSON.stringify( baseData ) ); | ||
|
||
const styleKeys = [ 'typography', 'color' ]; | ||
Object.keys( userData ).forEach( ( context ) => { | ||
styleKeys.forEach( ( key ) => { | ||
// Normalize object shape: make sure the key exists under styles. | ||
if ( ! mergedTree[ context ].styles?.[ key ] ) { | ||
mergedTree[ context ].styles[ key ] = {}; | ||
} | ||
|
||
// Merge data: base + user. | ||
mergedTree[ context ].styles[ key ] = { | ||
...mergedTree[ context ].styles[ key ], | ||
...userData[ context ]?.styles?.[ key ], | ||
}; | ||
} ); | ||
} ); | ||
|
||
return mergedTree; | ||
}; | ||
|
||
export default ( blockData, baseTree, userTree ) => { | ||
const styles = []; | ||
// Can this be converted to a context, as the global context? | ||
// See comment in the server. | ||
styles.push( LINK_COLOR_DECLARATION ); | ||
const tree = mergeTrees( baseTree, userTree ); | ||
|
||
/** | ||
* Transform given style tree into a set of style declarations. | ||
* | ||
* @param {Object} blockSupports What styles the block supports. | ||
* @param {Object} blockStyles Block styles. | ||
* | ||
* @return {Array} An array of style declarations. | ||
*/ | ||
const getBlockStylesDeclarations = ( blockSupports, blockStyles ) => { | ||
const declarations = []; | ||
Object.keys( STYLE_PROPS ).forEach( ( key ) => { | ||
if ( | ||
blockSupports.includes( key ) && | ||
get( blockStyles, STYLE_PROPS[ key ], false ) | ||
) { | ||
declarations.push( | ||
`${ key }: ${ get( blockStyles, STYLE_PROPS[ key ] ) }` | ||
); | ||
} | ||
} ); | ||
|
||
return declarations; | ||
}; | ||
|
||
/** | ||
* Transform given preset tree into a set of style declarations. | ||
* | ||
* @param {Object} blockPresets | ||
* | ||
* @return {Array} An array of style declarations. | ||
*/ | ||
const getBlockPresetsDeclarations = ( blockPresets ) => { | ||
const declarations = []; | ||
PRESET_CATEGORIES.forEach( ( category ) => { | ||
if ( blockPresets?.[ category ] ) { | ||
blockPresets[ category ].forEach( ( { slug, value } ) => | ||
declarations.push( | ||
`--wp--preset--${ category }--${ slug }: ${ value }` | ||
) | ||
); | ||
} | ||
} ); | ||
return declarations; | ||
}; | ||
|
||
const getBlockSelector = ( selector ) => { | ||
// Can we hook into the styles generation mechanism | ||
// so we can avoid having to increase the class specificity here | ||
// and remap :root? | ||
if ( ':root' === selector ) { | ||
selector = ''; | ||
} | ||
return `.editor-styles-wrapper.editor-styles-wrapper ${ selector }`; | ||
}; | ||
|
||
Object.keys( blockData ).forEach( ( context ) => { | ||
const blockSelector = getBlockSelector( blockData[ context ].selector ); | ||
const blockDeclarations = [ | ||
...getBlockStylesDeclarations( | ||
blockData[ context ].supports, | ||
tree[ context ].styles | ||
), | ||
...getBlockPresetsDeclarations( tree[ context ].presets ), | ||
]; | ||
if ( blockDeclarations.length > 0 ) { | ||
styles.push( | ||
`${ blockSelector } { ${ blockDeclarations.join( ';' ) } }` | ||
); | ||
} | ||
} ); | ||
|
||
return styles.join( '' ); | ||
}; |
Oops, something went wrong.