From 3977c2c84e7d4f08f998f6de8e3c7fe069ec6ac1 Mon Sep 17 00:00:00 2001 From: Alexandru Pavaloi Date: Fri, 5 Jun 2020 14:56:38 +0300 Subject: [PATCH] custom-properties: extend ThemeProvider to insert CSS Custom Props --- packages/custom-properties/README.md | 118 +++++++++++++++--- packages/custom-properties/package.json | 8 +- packages/custom-properties/src/index.ts | 38 +++++- .../test/__snapshots__/test.js.snap | 20 +++ packages/custom-properties/test/test.js | 28 ++++- 5 files changed, 192 insertions(+), 20 deletions(-) diff --git a/packages/custom-properties/README.md b/packages/custom-properties/README.md index 716ed56bd..10b5182a7 100644 --- a/packages/custom-properties/README.md +++ b/packages/custom-properties/README.md @@ -1,8 +1,7 @@ # @theme-ui/custom-properties -Generate CSS custom properties for use with Theme UI. +Extend [ThemeUI](https://theme-ui.com)'s core functionality with CSS Custom Properties. -https://theme-ui.com ## Installation @@ -10,31 +9,118 @@ https://theme-ui.com yarn add @theme-ui/custom-properties ``` +## API -## Usage +### toCustomProperties -Transform your Theme UI compliant theme config with the library: +Transform your Theme UI compliant theme to an object of CSS Custom Properties. -```js -const toCustomProperties = require('@theme-ui/custom-properties') -const theme = require('../theme'); +**Type**: `Function` -module.exports = () => { - const customProperties = toCustomProperties(theme, '🍭'); +**Parameters**: +1. theme - The theme ui specification object +2. prefix - An optional string prefix for the css custom property (_optional_) - return customProperties; +**Returns**: `Object` +```js +// Example response +{ + '--color-primary': '#2980b9', + '--color-secondary': '#f7df1e', + '--fontSize-0': 12, + ' -fontSize-1': 14, + '--fontSize-2': 16, + '--fontSize-3': 24, + '--fontSize-4': 32, + '--fontSize-5': 48, + '--fontSize-6': 64 } ``` +**Example**: +```js +import toCustomProperties from '@theme-ui/custom-properties'; +import theme from '../theme'; -## Parameters +const customProperties = toCustomProperties(theme, '🍭'); +console.log(customProperties); +``` -The @theme-ui/custom-properties function takes two parameters: +### withCustomProperties +Extend the base `ThemeProvider` to allow native styling by using CSS Custom Properties. -```js -toCustomProperties( $theme, $prefix ); +**Type**: `Function` + +**Parameters**: +1. prefix - An optional string prefix for the css custom property (_optional_) +2. className - An optional class name to add onto the wrapper. All CSS Custom Properties will be defined on this element. + +**Returns** a React Component which extends the default `ThemeProvider` by adding CSS Custom Properties to the wrapper element. + +For example: + +```jsx +const ExtendedThemeProvider = withCustomProperties('app-name', 'extended-theme-provider'); + +ReactDOM.render( + +

Hello world!

+
, + root + ); ``` -1. theme - The theme ui specification object -1. prefix - An optional prefix for the css custom property _optional_ +will render: + +```jsx +
+

Hello world!

+
+``` + +Then in CSS we can do something like: + +```css +p { + color: var(--app-name-color-primary); + background: var(--app-name-color-secondary); +} +``` +These CSS Custom Properties are in total sync with the theme. Also, sub-theming works as expected. + +```jsx +const theme = { + colors: { + primary: 'red', + secondary: 'blue' + } +}; + +const subTheme = { + colors: { + primary: 'orange' + } +}; + +const ExtendedThemeProvider = withCustomProperties('app-name'); + +ReactDOM.render( + +

Hello world!

// red on a blue background + + +

Hello Aliens!

// orange on a blue background +
+ +
, + root +); +``` + +```css +p { + color: var(--app-name-color-primary); + background: var(--app-name-color-secondary); +} +``` \ No newline at end of file diff --git a/packages/custom-properties/package.json b/packages/custom-properties/package.json index 3ba856372..bce1a004d 100644 --- a/packages/custom-properties/package.json +++ b/packages/custom-properties/package.json @@ -1,7 +1,7 @@ { "name": "@theme-ui/custom-properties", "description": "Generate CSS custom properties for use with Theme UI", - "version": "0.4.0-alpha.3", + "version": "0.5.0-alpha.1", "source": "src/index.ts", "main": "dist/index.js", "module": "dist/index.esm.js", @@ -16,7 +16,11 @@ "access": "public" }, "dependencies": { - "pluralize": "^8.0.0" + "pluralize": "^8.0.0", + "@theme-ui/core": "^0.4.0-alpha.3" + }, + "peerDependencies": { + "react": "^16.11.0" }, "devDependencies": { "@theme-ui/css": "^0.4.0-alpha.3", diff --git a/packages/custom-properties/src/index.ts b/packages/custom-properties/src/index.ts index 3dffea093..b95a82e7b 100644 --- a/packages/custom-properties/src/index.ts +++ b/packages/custom-properties/src/index.ts @@ -1,11 +1,13 @@ +import React, { useEffect, useRef } from 'react' import pluralize from 'pluralize' import { Theme } from '@theme-ui/css' +import { ThemeProviderProps, useThemeUI, ThemeProvider } from '@theme-ui/core' interface CustomProperties { [key: string]: string | number } -export default (theme: Theme, prefix?: string) => { +export default function toCustomProperties(theme: Theme, prefix?: string) { const customProperties: CustomProperties = {} const generateProperties = (object: object, previousKey?: string) => { @@ -36,3 +38,37 @@ export default (theme: Theme, prefix?: string) => { return customProperties } + +export function withCustomProperties( + prefix?: string, + className: string = 'theme-ui-provider' +) { + return function customThemeProvider(props: ThemeProviderProps) { + const ref = useRef(null) + const outerTheme = useThemeUI().theme + + useEffect(() => { + if (!ref.current) { + return + } + + const theme = typeof props.theme === 'function' + ? props.theme(outerTheme) + : props.theme + const cssProperties = toCustomProperties(theme, prefix) + + Object.entries(cssProperties).forEach(([key, value]) => { + ref.current!.style.setProperty(key, value.toString()) + }) + }) + + return React.createElement( + 'div', + { + ref, + className, + }, + React.createElement(ThemeProvider, props) + ) + } +} diff --git a/packages/custom-properties/test/__snapshots__/test.js.snap b/packages/custom-properties/test/__snapshots__/test.js.snap index 1cac57cf8..a93295943 100644 --- a/packages/custom-properties/test/__snapshots__/test.js.snap +++ b/packages/custom-properties/test/__snapshots__/test.js.snap @@ -1,5 +1,25 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`adds a specific className on the wrapping element of ThemeProvider 1`] = ` +
+

+ Hello world! +

+
+`; + +exports[`adds the default className on the wrapping element of ThemeProvider 1`] = ` +
+

+ Hello world! +

+
+`; + exports[`transforms a theme config to CSS custom properties 1`] = ` Object { "--color-accent": "#609", diff --git a/packages/custom-properties/test/test.js b/packages/custom-properties/test/test.js index 392a29c51..c2415cd23 100644 --- a/packages/custom-properties/test/test.js +++ b/packages/custom-properties/test/test.js @@ -1,4 +1,6 @@ -import toCustomProperties from '../src' +import React from 'react' +import renderer from 'react-test-renderer' +import toCustomProperties, { withCustomProperties } from '../src' const theme = { colors: { @@ -45,3 +47,27 @@ it('transforms a theme config to CSS custom properties with prefix', () => { expect(result).toMatchSnapshot() }) + +it('adds the default className on the wrapping element of ThemeProvider', () => { + const ExtendedThemeProvider = withCustomProperties('🍭') + const themeProvider = renderer.create( + +

Hello world!

+
+ ) + + let tree = themeProvider.toJSON() + expect(tree).toMatchSnapshot() +}) + +it('adds a specific className on the wrapping element of ThemeProvider', () => { + const ExtendedThemeProvider = withCustomProperties('🍭', '🍧') + const themeProvider = renderer.create( + +

Hello world!

+
+ ) + + let tree = themeProvider.toJSON() + expect(tree).toMatchSnapshot() +})