Skip to content

Commit

Permalink
feat(labs): Theming (react) (#272)
Browse files Browse the repository at this point in the history
* feat: Proof of concept of theming

* refactor: Move all theming into a new folder in core

* feat(core): Add the ability to override default Canvas theme

* feat(core): Iterate on theme structure

* feat(banner): Implement theming

* feat(core): Add story for default theme

* feat(core): Add helper to get theme if ThemeProvider doesn't exist

* chore(avatar): Remove theme test component

* feat: Add theme as a knob to customize in real time

* fix: Add keys to resolve warning in storybook

* docs(core): Add to CanvasProvider docs

* docs(core): Update provider documenation

* feat(core): Add breakpoints to theme

* refactor(labs): Move theming to labs

* chore: Restore InputProviderDecorator

* docs: Update docs in core and labs core to reflect the move

* fix(banner): Remove unnecessary console log

* feat(labs): Add support for window theme object

* docs(labs): Add breakpoints to theming docs

* refactor(labs): Rename getTheme to useTheme so it's more hook-like

* fix(labs): Lock the keys of breakpoints with as const

* refactor(labs): Expand on the theme object

* feat(labs): Add color expansion support to theme object

* fix(labs): Fix type error in theme story

* feat(labs): Prevent error from chroma on invalid input color

* fix(labs): Only expand main color if the others aren't defined

* feat(labs): Use enum for color direction to make shift fn more readable

* fix(labs): Remove unnecessary breakpoints index signature

* chore: Cleanup after rebase

* refactor: Move useTheme into its own file

* fix(labs): Allow createCanvasTheme to be called without a palette

* docs(labs): Fix readme typo

* refactor(labs): Decouple window theme from CanvasProvider

* docs(labs): Document theme breakpoint functionality

* feat(labs): Add styled function to avoid explicitly useTheme() call

* fix(labs): Add missing window global after it got removed

* test(labs): Add tests for useTheme and createCanvasTheme

* chore: Undo formatting changes in storybook config

* fix(labs): Use lodash merge instead of deepmerge

* fix(labs): Add missing deps

* docs: Update documentation

* refactor(labs): Roll our own ThemeProvider

* chore: Remove emotion-theming dep since we're no longer using it

* fix: Lock emotion-theming at 10.0.10

* refactor(labs): Update window.wdCanvas.theme to match existing usage

* chore: Update chroma-js version

* feat(core): Make InputProvider noop if it's nested within another

* fix(core): Add missing initializer to InputProvider

* refactor(banner): Remove theme functionality from Banner for now

* chore: Revert changes to avatar and banner
  • Loading branch information
anicholls authored Dec 6, 2019
1 parent e1300ac commit 929de08
Show file tree
Hide file tree
Showing 26 changed files with 1,075 additions and 57 deletions.
4 changes: 2 additions & 2 deletions .storybook/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {toId} from '@storybook/router';
import ReactDOM from 'react-dom';

import {commonColors, typeColors, fontFamily} from '../modules/core/react';
import {InputProviderDecorator, FontsDecorator} from '../utils/storybook';
import {CanvasProviderDecorator, FontsDecorator} from '../utils/storybook';

const reqMDXWelcome = require.context('./stories', true, /\.mdx$/);
const reqMDX = require.context('../modules', true, /\.mdx$/);
Expand All @@ -31,8 +31,8 @@ function loadStories() {
}

addDecorator(withKnobs);
addDecorator(InputProviderDecorator);
addDecorator(FontsDecorator);
addDecorator(CanvasProviderDecorator);

/** If the string contains a phrase, prefix it. This is useful for making ordering sections */
const prefix = (phrase, prefix) => value => (value.indexOf(phrase) > -1 ? prefix + value : value);
Expand Down
1 change: 0 additions & 1 deletion jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ module.exports = {
'!**/header/**/lib/Header.tsx',
'!**/common/**/ControlledComponentWrapper.tsx',
'!**/common/**/InputProviderDecorator.tsx',
'!**/common/**/SectionDecorator.tsx',
'!**/index.{ts,tsx,js,jsx}',
'!**/stories*.{ts,tsx,js,jsx}',
],
Expand Down
58 changes: 58 additions & 0 deletions modules/_labs/core/react/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,13 @@
<img src="https://img.shields.io/badge/UNSTABLE-alpha-orange" alt="UNSTABLE: Alpha" />
</a> This component is work in progress and currently in pre-release.

Includes:

- [Type](#type)
- [Margin & Padding Spacing](#margin-padding-spacing)
- [Providers](#providers)
- [Theming](#theming)

## Type

This new type hierarchy is in the process of being introduced into our products. It relies on larger
Expand Down Expand Up @@ -83,3 +90,54 @@ const Box = styled('div')(space)
padding-left: 40px;
*/
```

## Providers

Providers are higher order (wrapping) components used to provide global configuration to Canvas
components.

### Canvas Provider

This provider includes all of the Canvas Providers below. This is the way most consumers should use
the provider. This provider is required for our theming capabilities, so you can find more
information in the [theming documentation](./lib/theming/README.md).

**We strongly encourage you to use this in your application to wrap all Canvas components.**

```tsx
import * as React from 'react';
import {CanvasProvider} from '@workday/canvas-kit-react';

<CanvasProvider>{/* All your components containing any Canvas components */}</CanvasProvider>;
```

#### Storybook Decorator

We provide a [storybook decorator](../../utils/storybook/CanvasProviderDecorator.tsx) to wrap your
stories in a `CanvasProvider` (including `InputProvider`) automatically.

Add this decorator to your `/.storybook/config.js` configuration file to apply to all stories:

```js
import {CanvasProviderDecorator} from '../utils/storybook';

addDecorator(CanvasProviderDecorator);
```

Or, add it to stories individually:

```js
import {CanvasProviderDecorator} from '../../../../utils/storybook';

storiesOf('My Story', module)
.addDecorator(CanvasProviderDecorator)
.add('All', () => <YourJSX />);
```

### Input Provider

See the [@workday/canvas-kit-react-core docs](../../../core/react/README.md#input-provider)

## Theming

Theming documentation has its own README. You can find it [here](./lib/theming/README.md)
19 changes: 18 additions & 1 deletion modules/_labs/core/react/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,23 @@
import type from './lib/type';
import space from './lib/space';
import CanvasProvider from './lib/CanvasProvider';
import {breakpoints, CanvasBreakpoints, BreakpointKey} from './lib/theming/breakpoints';
import createCanvasTheme from './lib/theming/createCanvasTheme';
import styled from './lib/theming/styled';
import useTheme from './lib/theming/useTheme';

export default type;
export {type, space};
export {
breakpoints,
type,
space,
BreakpointKey,
CanvasBreakpoints,
CanvasProvider,
createCanvasTheme,
styled,
useTheme,
};
export * from './lib/type';
export * from './lib/theming/types';
export * from './lib/theming/theme';
24 changes: 24 additions & 0 deletions modules/_labs/core/react/lib/CanvasProvider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import * as React from 'react';
import {InputProvider} from '@workday/canvas-kit-react-core';
import {CanvasTheme} from './theming/types';
import {ThemeProvider, defaultCanvasTheme} from './theming/theme';

export interface CanvasProviderProps {
theme: CanvasTheme;
}

export default class CanvasProvider extends React.Component<CanvasProviderProps> {
static defaultProps = {
theme: defaultCanvasTheme,
};

render() {
const {children, theme} = this.props;

return (
<ThemeProvider value={theme}>
<InputProvider>{children}</InputProvider>
</ThemeProvider>
);
}
}
253 changes: 253 additions & 0 deletions modules/_labs/core/react/lib/theming/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,253 @@
# Canvas Kit Theming

Canvas Kit Core contains wrappers and types to enabling theming of Canvas components.

## Installation

```sh
yarn add @workday/canvas-kit-labs-react-core
```

or

```sh
yarn add @workday/canvas-kit-labs-react-core
```

## Usage

Wrap all of your Canvas components with the `CanvasProvider` higher order component. Usually this
would go in the highest level component of your application. This includes an
[`InputProvider`](../../README.md#input-provider) and includes all global configuration needed for
our Canvas Components.

```tsx
import * as React from 'react';
import {CanvasProvider} from '@workday/canvas-kit-labs-react-core';

<CanvasProvider>{/* All your components containing any Canvas components */}</CanvasProvider>;
```

## Component Props

### Required

> None
### Optional

#### `theme: CanvasTheme`

> The theme to be used throughout the children of the `CanvasProvider` component.
Default: `defaultCanvasTheme`

## Theme Object

The Canvas theme is based on the following object:

```ts
export const defaultCanvasTheme: CanvasTheme = {
palette: {
primary: {
lightest: colors.blueberry100,
light: colors.blueberry200,
main: colors.blueberry400,
dark: colors.blueberry500,
darkest: colors.blueberry600,
contrast: colors.frenchVanilla100,
},
alert: {
lightest: colors.cantaloupe100,
light: colors.cantaloupe300,
main: colors.cantaloupe400,
dark: colors.cantaloupe500,
darkest: colors.cantaloupe600,
contrast: colors.frenchVanilla100,
},
error: {
lightest: colors.cinnamon100,
light: colors.cinnamon300,
main: colors.cinnamon500,
dark: colors.cinnamon600,
darkest: '#80160E',
contrast: colors.frenchVanilla100,
},
success: {
lightest: colors.greenApple100,
light: colors.greenApple300,
main: colors.greenApple600,
dark: '',
darkest: '',
contrast: colors.frenchVanilla100,
},
neutral: {
lightest: colors.soap200,
light: colors.soap300,
main: colors.soap600,
dark: colors.licorice300,
darkest: colors.licorice400,
contrast: colors.frenchVanilla100,
},
common: {
focusOutline: colors.blueberry400,
},
},
breakpoints: {
values: {
zero: 0,
s: 600,
m: 960,
l: 1280,
xl: 1920,
},
up,
down,
between,
only,
},
};
```

Any changes will be reflected across all supported components. You are also able to consume for your
own use cases.

## Custom Theme

The `CanvasProvider` accepts a full or partial theme object to give a branded look to the component
library. Pass your theme into `createCanvasTheme` and use the return value for the `theme` prop.

If you only set a `main` color, the rest of the respective palette will be automatically generated
(note text `contrast` color will always return white if not specified).

Example:

```tsx
import {
CanvasProvider,
PartialCanvasTheme,
createCanvasTheme,
} from '@workday/canvas-kit-labs-react-core';

const theme: PartialCanvasTheme = {
palette: {
primary: {
main: colors.cantaloupe400,
},
},
};

<CanvasProvider theme={createCanvasTheme(theme)}>
{/* Your app with Canvas components */}
</CanvasProvider>;
```

### Nesting Theme Providers

It is possible to set a theme for a specific component or set of components within your React tree.
This is generally discouraged for consistency reasons, but may be required in some contexts (a green
`Switch` component for example). To do this, you must use the `ThemeProvider` component. The inner
theme will override the outer theme.

```tsx
import * as React from 'react';
import {CanvasProvider, CanvasTheme, ThemeProvider} from '@workday/canvas-kit-labs-react-core';
import {Switch} from '@workday/canvas-kit-react-switch';

const theme: PartialCanvasTheme = {
palette: {
primary: {
main: colors.greenApple400,
},
},
};

<CanvasProvider>
{/* All your components containing any Canvas components */}
<ThemeProvider value={createCanvasTheme(theme)}>
<Switch checked={true} />
</ThemeProvider>
</CanvasProvider>;
```

### Context Alternative

If, for whatever reason, you're not able to use React Contexts, we offer an alternative. A good
example of this is an app with many small React trees.

Simply set your theme on the window object like so:

```tsx
// If using typescript, you will need to declare this on the window object
declare global {
interface Window {
workday: {
canvas: {
theme?: CanvasTheme;
};
};
}
}

const theme: PartialCanvasTheme = {
palette: {
primary: {
main: colors.greenApple400,
},
},
};

window.workday.canvas.theme = createCanvasTheme(theme);
```

Note if any of the window object hasn't been defined, you will need to change your assignment. For
example:

```tsx
window.workday = {
canvas: {
theme: createCanvasTheme(theme);
}
}
```

If the theme is not available via a context, Canvas Kit components will attempt to pull it from this
variable before falling back to the default theme.

## Breakpoints

Our breakpoint system is customized within the theme object. `theme.breakpoints.values` contains the
various widths that our components adjust at:

| Name | Size (px) |
| ------ | --------- |
| `zero` | 0 |
| `s` | 600 |
| `m` | 960 |
| `l` | 1280 |
| `xl` | 1920 |

There are also several functions to help with generating media queries:

#### `up: (key: BreakpointFnParam) => string`

> Returns a media query reflecting your specified size and up. Works with the enum (e.g.
> BreakpointKey.m) or the string (e.g. 'm'). Example: theme.breakpoints.up(BreakpointKey.m) =>
> '@media (min-width:960px)'
#### `down: (key: BreakpointFnParam) => string`

> Returns a media query reflecting your specified size and down. Works with the enum or the string
> (e.g. 'm'). Example: theme.breakpoints.down(BreakpointKey.m) => '@media (max-width:1279.5px)'
#### `between: (start: BreakpointFnParam, end: BreakpointFnParam) => string`

> Returns a media query reflecting the sizes between your specified breakpoints. Works with the enum
> or the string (e.g. 'm'). Example: theme.breakpoints.between(BreakpointKey.m, BreakpointKey.l) =>
> '@media (min-width:960px) and (max-width:1919.5px)'
#### `only: (key: BreakpointFnParam) => string`

> Returns a media query reflecting the size swithin your specified breakpoint. Works with the enum
> or the string (e.g. 'm'). Example: theme.breakpointsonly(BreakpointKey.m) => '@media
> (min-width:960px) and (max-width:1279.5px)'
Loading

0 comments on commit 929de08

Please sign in to comment.