Skip to content
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

custom-properties: extend ThemeProvider to insert CSS Custom Props #982

Open
wants to merge 2 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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);
}
```
6 changes: 5 additions & 1 deletion packages/custom-properties/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,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.6.0-alpha.1",
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()
})