Skip to content

Commit

Permalink
custom-properties: extend ThemeProvider to insert CSS Custom Props
Browse files Browse the repository at this point in the history
  • Loading branch information
Alexandru Pavaloi committed Jun 7, 2020
1 parent d533060 commit 3977c2c
Show file tree
Hide file tree
Showing 5 changed files with 192 additions and 20 deletions.
118 changes: 102 additions & 16 deletions packages/custom-properties/README.md
Original file line number Diff line number Diff line change
@@ -1,40 +1,126 @@
# @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

```
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(
<ExtendedThemeProvider theme={theme}>
<p> Hello world! </p>
</ExtendedThemeProvider>,
root
);
```

1. theme - The theme ui specification object
1. prefix - An optional prefix for the css custom property _optional_
will render:

```jsx
<div class="extended-theme-provider">
<p> Hello world! </p>
</div>
```

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(
<ExtendedThemeProvider theme={theme}>
<p> Hello world! </p> // red on a blue background

<ExtendedThemeProvider theme={subTheme}>
<p> Hello Aliens! </p> // orange on a blue background
</ExtendedThemeProvider>

</ExtendedThemeProvider>,
root
);
```

```css
p {
color: var(--app-name-color-primary);
background: var(--app-name-color-secondary);
}
```
8 changes: 6 additions & 2 deletions packages/custom-properties/package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand All @@ -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",
Expand Down
38 changes: 37 additions & 1 deletion packages/custom-properties/src/index.ts
Original file line number Diff line number Diff line change
@@ -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) => {
Expand Down Expand Up @@ -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<HTMLDivElement>(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)
)
}
}
20 changes: 20 additions & 0 deletions packages/custom-properties/test/__snapshots__/test.js.snap
Original file line number Diff line number Diff line change
@@ -1,5 +1,25 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`adds a specific className on the wrapping element of ThemeProvider 1`] = `
<div
className="🍧"
>
<p>
Hello world!
</p>
</div>
`;

exports[`adds the default className on the wrapping element of ThemeProvider 1`] = `
<div
className="theme-ui-provider"
>
<p>
Hello world!
</p>
</div>
`;

exports[`transforms a theme config to CSS custom properties 1`] = `
Object {
"--color-accent": "#609",
Expand Down
28 changes: 27 additions & 1 deletion packages/custom-properties/test/test.js
Original file line number Diff line number Diff line change
@@ -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: {
Expand Down Expand Up @@ -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(
<ExtendedThemeProvider theme={theme}>
<p> Hello world! </p>
</ExtendedThemeProvider>
)

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(
<ExtendedThemeProvider theme={theme}>
<p> Hello world! </p>
</ExtendedThemeProvider>
)

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

0 comments on commit 3977c2c

Please sign in to comment.