-
Notifications
You must be signed in to change notification settings - Fork 4.2k
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
add theme package #56669
base: trunk
Are you sure you want to change the base?
add theme package #56669
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
/** | ||
* Internal dependencies | ||
*/ | ||
|
||
import { defaultTheme } from '../theme.js'; | ||
import { themeToCss } from '../utils.js'; | ||
|
||
printStylesheet( defaultTheme ); | ||
|
||
function printStylesheet( theme ) { | ||
const css = themeToCss( theme ); | ||
|
||
const contents = [ | ||
`/* Generated by WordPress */`, | ||
'\n\n', | ||
':root {', | ||
'\n', | ||
css, | ||
'\n', | ||
'}', | ||
]; | ||
|
||
return contents; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,131 @@ | ||
/** | ||
* External dependencies | ||
*/ | ||
import { colord, extend } from 'colord'; | ||
import a11yPlugin from 'colord/plugins/a11y'; | ||
import namesPlugin from 'colord/plugins/names'; | ||
|
||
extend( [ namesPlugin, a11yPlugin ] ); | ||
|
||
const LIGHT_VALUES = [ 100, 98, 95, 92, 89, 87, 83, 73, 55, 48, 39, 13 ]; | ||
const DARK_VALUES = [ 1, 11, 16, 19, 22, 18, 29, 38, 43, 73, 80, 93 ]; | ||
export const PRIMARY_DEFAULT = '#3858e9'; | ||
|
||
// map showing which lightness in scale each use case should use | ||
const COLOR_MAP = { | ||
bg: { | ||
default: 2, | ||
hover: 3, | ||
active: 4, | ||
input: { | ||
default: 0, | ||
disabled: 0, | ||
}, | ||
muted: 1, | ||
strong: { | ||
default: 8, | ||
hover: 9, | ||
}, | ||
}, | ||
text: { | ||
default: 10, | ||
hover: 11, | ||
strong: 11, | ||
inverse: { | ||
default: 1, | ||
strong: 0, | ||
}, | ||
muted: 9, | ||
}, | ||
border: { | ||
default: 5, | ||
disabled: 4, | ||
input: 6, | ||
strong: { | ||
default: 6, | ||
hover: 7, | ||
}, | ||
muted: 4, | ||
hover: 6, | ||
}, | ||
}; | ||
|
||
// generates a color palette based on a primary color | ||
export const generateColors = ( { | ||
color = PRIMARY_DEFAULT, | ||
fun = 0, | ||
isDark = false, | ||
} ) => { | ||
const neutral = generateNeutralColors( { color, fun, isDark } ); | ||
const primary = generatePrimaryColors( { | ||
color, | ||
bg: neutral.bg.default, | ||
isDark, | ||
} ); | ||
|
||
return { | ||
primary, | ||
neutral, | ||
}; | ||
}; | ||
|
||
const generateNeutralColors = ( { | ||
color = PRIMARY_DEFAULT, | ||
fun = 0, | ||
isDark = false, | ||
} ) => { | ||
const base = colord( color ).toHsl(); | ||
const lightValues = isDark ? DARK_VALUES : LIGHT_VALUES; | ||
const colors = lightValues.map( ( value ) => | ||
colord( { ...base, s: fun, l: value } ).toHex() | ||
); | ||
return mapColors( colors, COLOR_MAP ); | ||
}; | ||
|
||
const generatePrimaryColors = ( { | ||
color = PRIMARY_DEFAULT, | ||
bg, | ||
isDark = false, | ||
} ) => { | ||
const base = colord( color ).toHsl(); | ||
const lightValues = isDark ? DARK_VALUES : LIGHT_VALUES; | ||
|
||
// if the color given has enough contrast agains the background, use that as the solid background colour and adjust the surrounding scale to proportionally move with it | ||
const length = lightValues.length; | ||
// Calculate the difference between the new value and the old value | ||
const diff = base.l - lightValues[ 8 ]; | ||
// Calculate the weight for adjusting values. Closer to base colour should adjust more. | ||
const weight = ( index ) => 1 - Math.abs( 8 - index ) / ( length - 1 ); | ||
// Adjust all values in the array based on their weight | ||
let adjustedArray = [ ...lightValues ]; | ||
if ( colord( bg ).isReadable( base ) ) { | ||
adjustedArray = lightValues.map( ( value, index ) => { | ||
const adjustment = diff * weight( index ); | ||
return index === 8 ? base.l : value + adjustment; | ||
} ); | ||
} | ||
|
||
// convert colours to hex and set min and max lightness values | ||
const colors = adjustedArray.map( ( value ) => | ||
colord( { | ||
...base, | ||
l: Math.min( Math.max( parseInt( value ), 0 ), 100 ), | ||
} ).toHex() | ||
); | ||
|
||
return mapColors( colors, COLOR_MAP ); | ||
}; | ||
|
||
// maps a color map to a color palette | ||
const mapColors = ( mapFromArray, mapToObject ) => { | ||
const map = {}; | ||
Object.keys( mapToObject ).forEach( ( alias ) => { | ||
const color = mapToObject[ alias ]; | ||
if ( typeof color === 'object' ) { | ||
map[ alias ] = mapColors( mapFromArray, color ); | ||
} else { | ||
map[ alias ] = mapFromArray[ parseInt( color ) ]; | ||
} | ||
} ); | ||
return map; | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
export { ThemeProvider } from './provider'; | ||
export { defaultTheme } from './theme'; | ||
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
{ | ||
"name": "@wordpress/theme", | ||
"version": "1.0.0", | ||
"description": "A collection of tokens that make up a WordPress theme.", | ||
"author": "The WordPress Contributors", | ||
"license": "GPL-2.0-or-later", | ||
"keywords": [ | ||
"theme", | ||
"variables", | ||
"styles" | ||
], | ||
"homepage": "https://github.com/WordPress/gutenberg", | ||
"repository": { | ||
"type": "git", | ||
"url": "git+https://github.com/WordPress/gutenberg.git", | ||
"directory": "packages/theme" | ||
}, | ||
"bugs": { | ||
"url": "https://github.com/WordPress/gutenberg/issues" | ||
}, | ||
"files": [ | ||
"index.js", | ||
"style.scss" | ||
], | ||
"main": "index.js", | ||
"style": "style.scss", | ||
"dependencies": { | ||
"@wordpress/element": "file:../element", | ||
"colord": "^2.7.0" | ||
}, | ||
"publishConfig": { | ||
"access": "public" | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
/** | ||
* WordPress dependencies | ||
*/ | ||
import { createElement } from '@wordpress/element'; | ||
|
||
/** | ||
* Internal dependencies | ||
*/ | ||
import { generateColors } from './color'; | ||
import { themeToCss } from './utils'; | ||
|
||
// lightweight way to add styles to a class name | ||
const toHash = ( str ) => { | ||
let i = 0, | ||
out = 11; | ||
while ( i < str.length ) out = ( 101 * out + str.charCodeAt( i++ ) ) >>> 0; //eslint-disable-line no-bitwise | ||
return 'wp-' + out; | ||
}; | ||
|
||
const addStyle = ( target, className, cssText ) => { | ||
const style = document.createElement( 'style' ); | ||
style.id = className; | ||
style.append( cssText ); | ||
target.append( style ); | ||
}; | ||
|
||
const merge = ( compiled, target ) => { | ||
const name = toHash( compiled ); | ||
if ( ! document.getElementById( name ) ) { | ||
addStyle( target, name, `.${ name } { ${ compiled }}` ); | ||
} | ||
return name; | ||
}; | ||
|
||
// theme provider component that generates a theme and adds appropriate tokens to the head | ||
export const ThemeProvider = ( { | ||
as = 'div', | ||
color, | ||
fun, | ||
isDark, | ||
Comment on lines
+38
to
+40
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. To avoid a lot of props in the {
primary: string;
fun: number;
isDark: boolean;
} |
||
...props | ||
} ) => { | ||
const { className, children, ...rest } = props; | ||
const styles = themeToCss( { | ||
color: generateColors( { | ||
color, | ||
fun, | ||
isDark, | ||
} ), | ||
} ); | ||
const name = merge( styles, document.head ); | ||
return createElement( | ||
as, | ||
{ | ||
className: [ name, className ].join( ' ' ), | ||
...rest, | ||
}, | ||
children | ||
); | ||
}; |
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure it's a good idea to commit a file that could be generated at build time? |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
:root { | ||
--wp-theme-color-neutral-bg-surface: var(--wp-theme-color-neutral-1); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Looking at the naming convention, what would be other values that could be used instead of "neutral"? Would "primary" be such an alternative? If so, why aren't there any |
||
--wp-theme-color-neutral-bg-input: var(--wp-theme-color-neutral-1); | ||
--wp-theme-color-neutral-text-inverse-strong: var(--wp-theme-color-neutral-1); | ||
--wp-theme-color-neutral-bg-input-disabled: var(--wp-theme-color-neutral-1); | ||
--wp-theme-color-neutral-bg-muted: var(--wp-theme-color-neutral-2); | ||
--wp-theme-color-neutral-text-inverse: var(--wp-theme-color-neutral-2); | ||
--wp-theme-color-neutral-bg: var(--wp-theme-color-neutral-3); | ||
--wp-theme-color-neutral-bg-hover: var(--wp-theme-color-neutral-4); | ||
--wp-theme-color-neutral-bg-active: var(--wp-theme-color-neutral-5); | ||
--wp-theme-color-neutral-border: var(--wp-theme-color-neutral-6); | ||
--wp-theme-color-neutral-border-disabled: var(--wp-theme-color-neutral-6); | ||
--wp-theme-color-neutral-border-input: var(--wp-theme-color-neutral-7); | ||
--wp-theme-color-neutral-border-strong: var(--wp-theme-color-neutral-7); | ||
--wp-theme-color-neutral-border-hover: var(--wp-theme-color-neutral-7); | ||
--wp-theme-color-neutral-border-strong-hover: var(--wp-theme-color-neutral-8); | ||
--wp-theme-color-neutral-bg-strong: var(--wp-theme-color-neutral-9); | ||
--wp-theme-color-neutral-bg-strong-hover: var(--wp-theme-color-neutral-10); | ||
--wp-theme-color-neutral-text-muted: var(--wp-theme-color-neutral-10); | ||
--wp-theme-color-neutral-text: var(--wp-theme-color-neutral-11); | ||
--wp-theme-color-neutral-text-hover: var(--wp-theme-color-neutral-12); | ||
--wp-theme-color-neutral-text-strong: var(--wp-theme-color-neutral-12); | ||
--wp-theme-color-neutral-bg-inverse: var(--wp-theme-color-neutral-12); | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
/** | ||
* Internal dependencies | ||
*/ | ||
import { generateColors } from './color'; | ||
|
||
// // theme object | ||
export const defaultTheme = { | ||
// shadows: {...}, | ||
// spacing: { ... }, | ||
// borderRadius: { ... }, | ||
// fonts: { ... }, | ||
// fontSizes: { ... }, | ||
// fontWeights: { ... }, | ||
// lineHeights: { ... }, | ||
colors: generateColors( {} ), | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
// flattens the theme object to a single level | ||
function flattenTheme( obj, parent, res = {} ) { | ||
for ( const key in obj ) { | ||
const propName = parent ? parent + '-' + key : key; | ||
if ( typeof obj[ key ] === 'object' ) { | ||
flattenTheme( obj[ key ], propName, res ); | ||
} else { | ||
res[ propName.replace( '-default', '' ) ] = obj[ key ]; | ||
} | ||
} | ||
return res; | ||
} | ||
|
||
// converts a theme object to a CSS string containing CSS variables | ||
export const themeToCss = ( theme ) => { | ||
const flattenedTheme = flattenTheme( theme ); | ||
return Object.entries( flattenedTheme ) | ||
.map( ( [ key, value ] ) => `--wp-theme-${ key }: ${ value };` ) | ||
.join( '\n' ); | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These exports should probably be private, so that we can safely iterate over the APIs of the package while we're actively developing it
Also, why is the
defaultTheme
an export? Wouldn't consumers of the theme just need the CSS variables ?