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

feat(labs): Theming (react) #272

Merged
merged 53 commits into from
Dec 6, 2019
Merged
Show file tree
Hide file tree
Changes from 43 commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
99ea29f
feat: Proof of concept of theming
anicholls Oct 15, 2019
ad33cf3
refactor: Move all theming into a new folder in core
anicholls Oct 15, 2019
44f4260
feat(core): Add the ability to override default Canvas theme
anicholls Oct 15, 2019
9e1cff6
feat(core): Iterate on theme structure
anicholls Oct 18, 2019
2fa4f7a
feat(banner): Implement theming
anicholls Oct 18, 2019
7517a24
feat(core): Add story for default theme
anicholls Oct 18, 2019
a2783c6
feat(core): Add helper to get theme if ThemeProvider doesn't exist
anicholls Oct 18, 2019
db2ba94
chore(avatar): Remove theme test component
anicholls Oct 22, 2019
862104e
feat: Add theme as a knob to customize in real time
anicholls Oct 22, 2019
74a264f
fix: Add keys to resolve warning in storybook
anicholls Oct 22, 2019
540c33c
docs(core): Add to CanvasProvider docs
anicholls Oct 22, 2019
5c848b5
docs(core): Update provider documenation
anicholls Oct 22, 2019
615e874
feat(core): Add breakpoints to theme
anicholls Oct 24, 2019
cb1a662
refactor(labs): Move theming to labs
anicholls Oct 24, 2019
fd5370e
chore: Restore InputProviderDecorator
anicholls Oct 24, 2019
e144f89
docs: Update docs in core and labs core to reflect the move
anicholls Oct 24, 2019
8016e88
fix(banner): Remove unnecessary console log
anicholls Oct 24, 2019
0c7c47b
docs(labs): Add breakpoints to theming docs
anicholls Oct 28, 2019
97814e7
feat(labs): Add support for window theme object
anicholls Oct 28, 2019
34748ee
refactor(labs): Rename getTheme to useTheme so it's more hook-like
anicholls Oct 29, 2019
3c1645b
fix(labs): Lock the keys of breakpoints with as const
anicholls Oct 30, 2019
ac510aa
refactor(labs): Expand on the theme object
anicholls Oct 30, 2019
1f28948
feat(labs): Add color expansion support to theme object
anicholls Oct 31, 2019
1addd9e
fix(labs): Fix type error in theme story
anicholls Nov 1, 2019
1da1279
feat(labs): Prevent error from chroma on invalid input color
anicholls Nov 1, 2019
b78bb5b
fix(labs): Only expand main color if the others aren't defined
anicholls Nov 1, 2019
15706bc
feat(labs): Use enum for color direction to make shift fn more readable
anicholls Nov 1, 2019
a0b7487
fix(labs): Remove unnecessary breakpoints index signature
anicholls Nov 1, 2019
c69da38
chore: Cleanup after rebase
anicholls Nov 19, 2019
2d80f98
refactor: Move useTheme into its own file
anicholls Nov 21, 2019
30934a1
fix(labs): Allow createCanvasTheme to be called without a palette
anicholls Nov 21, 2019
b84e3da
docs(labs): Fix readme typo
anicholls Nov 21, 2019
60c333f
refactor(labs): Decouple window theme from CanvasProvider
anicholls Nov 21, 2019
7ebbb17
docs(labs): Document theme breakpoint functionality
anicholls Nov 21, 2019
8b3b000
feat(labs): Add styled function to avoid explicitly useTheme() call
anicholls Nov 22, 2019
796486c
fix(labs): Add missing window global after it got removed
anicholls Nov 22, 2019
8a26547
test(labs): Add tests for useTheme and createCanvasTheme
anicholls Nov 22, 2019
b6b527e
chore: Undo formatting changes in storybook config
anicholls Nov 26, 2019
6de7c2e
fix(labs): Use lodash merge instead of deepmerge
anicholls Dec 3, 2019
6eff5a2
fix(labs): Add missing deps
anicholls Dec 3, 2019
376332a
docs: Update documentation
anicholls Dec 3, 2019
778eaab
refactor(labs): Roll our own ThemeProvider
anicholls Dec 3, 2019
05f40cb
chore: Remove emotion-theming dep since we're no longer using it
anicholls Dec 3, 2019
fe15672
fix: Lock emotion-theming at 10.0.10
anicholls Dec 3, 2019
01d336d
refactor(labs): Update window.wdCanvas.theme to match existing usage
anicholls Dec 4, 2019
28844c6
chore: Update chroma-js version
anicholls Dec 5, 2019
6c2d525
feat(core): Make InputProvider noop if it's nested within another
anicholls Dec 5, 2019
821c1de
Merge branch 'master' into theming
anicholls Dec 5, 2019
2da8610
fix(core): Add missing initializer to InputProvider
anicholls Dec 6, 2019
ad34a51
Merge branch 'master' into theming
anicholls Dec 6, 2019
fe60406
Merge branch 'master' into theming
anicholls Dec 6, 2019
fe2aea0
refactor(banner): Remove theme functionality from Banner for now
anicholls Dec 6, 2019
251aee0
chore: Revert changes to avatar and banner
anicholls Dec 6, 2019
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
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);

addParameters({
options: {
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>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have we tested multiple instances of InputProvider?

If you're using CanvasProvider in different subtrees, it'll always come with an InputProvider and could potentially be unnecessarily nested. I'm not sure our InputProvider noops or does some performance optimization if it's already been used higher up the tree?

Two possible solutions (I'm sure there are more):

  1. A boolean prop sets whether or not InputProvider wraps around the children or is omitted completely (what would it default to?)
  2. A removal of CanvasProvider altogether and just recommending people to nest the InputProvider in ThemeProvider if it's meant to go in the top level tree. Because if we were to do something like <CanvasProvider inputProvider={false}>...</CanvasProvider> then what is CanvasProvider really doing at this point other than providing a default theme?

Copy link
Contributor Author

@anicholls anicholls Dec 5, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Multiple instances of InputProvider will work fine from a consumer level, but I can't speak to the performance implications of doing that.

We could easily add a check in InputProvider to noop if one exists higher in the tree. This would be my vote rather than adding prop complexity to CanvasProvider or removing CanvasProvider completely.

I realize CanvasProvider is not doing much right now. However, there are two reasons I would like to keep it:

  • Future proofing. We will generally always need some sort of top-level component. I assume in the future we will need to add more to it. When we do, it's more likely that consumers will get that future functionality for free.
  • Reduces the complexity from the consumers end. They don't really need to know about the theme and input provider if they're not using it.
<CanvasProvider>
  // My App
</CanvasProvider>

is much simpler and hides our noise, compared to:

<ThemeProvider>
  <InputProvider>
    <FutureStuff>
      // My App
    </FutureStuff>
  </InputProvider>
</ThemeProvider>

Anyway, let me know if you agree. I will probably add that functionality to InputProvider either way.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@lychyi noop added to InputProvider in 6c2d525

</ThemeProvider>
);
}
}
240 changes: 240 additions & 0 deletions modules/_labs/core/react/lib/theming/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,240 @@
# 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
anicholls marked this conversation as resolved.
Show resolved Hide resolved

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 {
wdCanvas: {
theme?: CanvasTheme;
};
}
}

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

window.wdCanvas.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