@workday/canvas-kit-react
Version:
The parent module that contains all Workday Canvas Kit React components
564 lines (442 loc) • 15.8 kB
Markdown
# Canvas Kit Theming
Canvas Kit Common contains wrappers and types to enabling theming of Canvas components.
## Installation
```sh
yarn add @workday/canvas-kit-react/common
```
## 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-react/common';
<CanvasProvider>{/* All your components containing any Canvas components */}</CanvasProvider>;
```
## Component Props
### Required
> None
### Optional
#### `theme: PartialEmotionCanvasTheme`
> The theme to be used throughout the children of the `CanvasProvider` component.
Default: `{ canvas: 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: 320,
m: 768,
l: 1024,
xl: 1440,
},
up,
down,
between,
only,
},
direction: ContentDirection.LTR,
};
```
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 or a different
direction to the component library.
> Note: Our theme is namespaced under the `canvas` key within the Emotion `ThemeContext`.
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, EmotionCanvasTheme} from '@workday/canvas-kit-react/common';
const theme: EmotionCanvasTheme = {
canvas: {
palette: {
primary: {
main: colors.cantaloupe400,
},
},
},
};
<CanvasProvider theme={theme}>{/* Your app with Canvas components */}</CanvasProvider>;
```
### Bidirectionality
The `CanvasProvider` also provides support for bidirectionality, useful for RTL languages. The
direction, part of the theme, is set using `ContentDirection.LTR` or `ContentDirection.RTL`.
You can nest `CanvasProvider` if you need to set a different direction for some components in your
React tree (See below: Nesting CanvasProvider components).
`CanvasProvider` wraps your components with a `bdo` element that has the `dir` attribute set to the
value of the theme direction. Styled components using the
[Canvas `styled` function](https://github.com/Workday/canvas-kit/blob/master/modules/common/react/lib/theming/styled.ts)
will have their styles automatically flipped if dictated by the closest theme object.
```tsx
import {
CanvasProvider,
ContentDirection,
EmotionCanvasTheme,
} from '@workday/canvas-kit-react/common';
const rtlTheme: EmotionCanvasTheme = {
canvas: {
direction: ContentDirection.RTL,
},
};
<CanvasProvider theme={rtlTheme}>{/* Your app with Canvas components */}</CanvasProvider>;
```
### Nesting CanvasProvider components
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, or changing the direction of a set of components). To do this, you
can nest CanvasProvider components with a different theme.
```tsx
import * as React from 'react';
import {
CanvasProvider,
EmotionCanvasTheme,
PartialEmotionCanvasTheme,
ContentDirection,
} from '@workday/canvas-kit-react/common';
import {Switch} from '@workday/canvas-kit-react/switch';
const rtlTheme: EmotionCanvasTheme = {
canvas: {
direction: ContentDirection.RTL,
},
};
const theme: PartialEmotionCanvasTheme = {
canvas: {
palette: {
primary: {
main: colors.greenApple400,
},
},
direction: ContentDirection.LTR,
},
};
<CanvasProvider theme={rtlTheme}>
<CanvasProvider theme={theme}>
<Switch checked={true} />
{/* Content that should be LTR */}
</CanvasProvider>
</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
const theme: PartialEmotionCanvasTheme = {
canvas: {
palette: {
primary: {
main: colors.greenApple400,
},
},
},
};
window.workday.canvas.theme = 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: 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.
## Accessing the theme value
Canvas Kit provides two functions to access the current theme, `getTheme` and `useTheme`. If you
need to access the theme within a function component, use the hook, `useTheme`. If you need to
access the theme within a `styled` component, a class component, or outside a component, use
`getTheme`. The main difference is `useTheme` is intended to work within the `CanvasProvider` and
will check the `ThemeContext` for the theme first. Both functions check the window for the theme and
fall back to the default theme is nothing is found. Both functions will return a full theme object
in this shape:
**Return value**
```tsx
{
canvas: {
palette: {
// ...
},
breakpoints: {
// ...
},
direction: ContentDirection.LTR,
},
// other themes can be placed here
}
```
### getTheme
`getTheme` is a function that returns the full theme object with the Canvas Kit theme under the
`canvas` key. It should be used with `styled` components, class components, or outside components.
Below is an example of how to use `getTheme` to build responsive media query styles with the
breakpoint functions provided in the theme.
```tsx
import {getTheme} from '@workday/canvas-kit-react/common';
const theme = getTheme();
const {up, down} = theme.canvas.breakpoints;
const small = down('m'); // Returns '@media (max-width: 768px)'
const medium = up('m'); // Returns '@media (min-width: 768px)'
const styles = {
[small]: {
margin: space.m,
},
[medium]: {
margin: space.l,
},
};
```
### useTheme
`useTheme` is hook to get the full theme object. It should be used only with functional compoents
wrapped in ContextProvider. Function returns a theme object with the Canvas Kit theme under the
canvas key.
`useTheme` should be used only inside functional component otherwise it will show a warning if the
theme context value has not been found. In that case you will need to use `getTheme`.
Below is an example showing how to use `useTheme` in a function component to set `Subtext`'s color
to the error color provided by the theme.
```tsx
export const ErrorMessage = () => {
const theme = useTheme();
return (
<Subtext size="large" color={theme.canvas.palette.error.main}>
)
}
```
### Overwriting the theme
You can also use both functions to overwrite the theme object by providing a partial or full theme
object to overwrite the current theme. In the example below, we're setting a custom content
direction, which can be passed to either `useTheme` or `getTheme`. These functions will properly
merge your the partial theme with the default Canvas theme and return a complete theme object.
```tsx
import {ContentDirection, useTheme, getTheme} from '@workday/canvas-kit-react/common';
const customTheme = {
canvas: {
// set the content direction to right-to-left
direction: ContentDirection.RTL,
},
};
// Overwriting the theme with useTheme
const customTheme = useTheme(customTheme);
// Overwriting the theme with getTheme
const customTheme = getTheme(customTheme);
```
## Breakpoints
Breakpoints are used by media queries to conditionally apply or modify styles based on the viewport
width. This allows the UI to be responsive to various screen sizes.
### Values
The canvas theme object provides five breakpoint values that correspond to the min-widths of our
standard screen sizes.
| Name | Size (px) |
| ------ | --------- |
| `zero` | 0 |
| `s` | 320 |
| `m` | 768 |
| `l` | 1024 |
| `xl` | 1440 |
And these are our standard screen size ranges:
- `small` (320px - 767px) Used for mobile-sized screens
- `medium` (768px - 1023px) Used for tablet-sized screens
- `large` - (1024px - 1439px) Used for laptop and small desktop screens
- `extra-large` (≥1440px) Used for very large screens
> Note: Some applications may only require a subset of screen sizes and not use all breakpoints.
Our breakpoint system is customized within the theme object. `theme.canvas.breakpoints.values`.
```ts
import {useTheme} from '@workday/canvas-kit-react/common';
import {space} from '@workday/canvas-kit-react/tokens';
const {theme} = useTheme();
const {values} = theme.canvas.breakpoints;
const styles = {
[`@media (min-width: ${values.m}px)`]: {
padding: space.s,
},
};
```
### Functions
There are also several functions to help with generating media queries:
- [Up](#Up)
- [Down](#Down)
- [Between](#Between)
- [Only](#Only)
#### Up
_Returns a media query above the `min-width` for the range of a given breakpoint_
Given a `start` breakpoint key ("zero", "s", "m", "l", "xl"), this function returns a media query
(string) using a `min-width`.
```ts
import {useTheme} from '@workday/canvas-kit-react/common';
import {space} from '@workday/canvas-kit-react/tokens';
const theme = useTheme();
const {up} = theme.canvas.breakpoints;
const mediaQuery = up('l'); // Returns '@media (min-width: 1024px)'
const styles = {
[mediaQuery]: {
padding: space.m,
},
};
```
#### Down
_Returns a media query below the `max-width` for the range of a given breakpoint_
Given an `end` breakpoint key ("zero", "s", "m", "l", "xl"), this function returns a media query
(string) using a `max-width`.
> Note: This function subtracts `0.5px` from the next breakpoint value to prevent collisions. For
> example, `breakpoints.values.s`, has a `min-width` of `320px`, and the `max-width` is `767.5px`).
If the `xl` breakpoint is provided, this function returns a media query with only a `min-width` of
`0`, as seen in the second example below.
```ts
import {useTheme} from '@workday/canvas-kit-react/common';
import {space} from '@workday/canvas-kit-react/tokens';
const theme = useTheme();
const {down} = theme.canvas.breakpoints;
const mediaQuery = down('m'); // Returns '@media (max-width: 1023.5px)'
const styles = {
[mediaQuery]: {
padding: space.m,
},
};
```
This example uses the `xl` breakpoint and only adds a `min-width` of `0` to the media query.
```ts
import {useTheme} from '@workday/canvas-kit-react/common';
import {space} from '@workday/canvas-kit-react/tokens';
const theme = useTheme();
const {down} = theme.canvas.breakpoints;
const mediaQuery = down('xl'); // Returns '@media (min-width: 0)'
const styles = {
[mediaQuery]: {
padding: space.m,
},
};
```
#### Between
_Returns a media query between two given breakpoints_
Given `start` and `end` breakpoint keys ("zero", "s", "m", "l", "xl"), this function returns a media
query (string) using a min-width and max-width.
> Note: This function subtracts `0.5px` from the next breakpoint value to prevent collisions. For
> example, `breakpoints.values.s`, has a `min-width` of `320px`, and the `max-width` is `767.5px`).
If the `xl` breakpoint is provided, this function returns a media query with only a `min-width`, as
seen in the second example below.
```ts
import {useTheme} from '@workday/canvas-kit-react/common';
import {space} from '@workday/canvas-kit-react/tokens';
const theme = useTheme();
const {between} = theme.canvas.breakpoints;
// Returns '@media (min-width: 320px) and (max-width: 1023.5px)'
const mediaQuery = between('s', 'm');
const styles = {
[mediaQuery]: {
padding: space.s,
},
};
```
This example uses `xl` as the `end` breakpoint and only adds a min-width to the media query.
```ts
import {useTheme} from '@workday/canvas-kit-react/common';
import {space} from '@workday/canvas-kit-react/tokens';
const theme = useTheme();
const {between} = theme.canvas.breakpoints;
const mediaQuery = between('m', 'xl'); // Returns '@media (min-width: 768px)'
const styles = {
[mediaQuery]: {
padding: space.s,
},
};
```
#### Only
_Returns a media query with a `min-width` and `max-width` for a given breakpoint_
Given a breakpoint key ("zero", "s", "m", "l", "xl"), this function returns a media query (string)
using a `min-width` and `max-width`.
> Note: This function subtracts `0.5px` from the next breakpoint value to prevent collisions.For
> example, `breakpoints.values.s`, has a `min-width` of `320px`, and the `max-width` is `767.5px`).
If the `xl` breakpoint is provided, this function returns a media query with only a `min-width` of
`1440px`, as seen in the second example below.
```ts
import {useTheme} from '@workday/canvas-kit-react/common';
import {space} from '@workday/canvas-kit-react/tokens';
const theme = useTheme();
const {only} = theme.canvas.breakpoints;
const mediaQuery = only('s'); // Returns '@media (min-width: 320px) and (max-width: 767.5px)'
const styles = {
[mediaQuery]: {
padding: space.s,
},
};
```
This example uses the `xl` breakpoint and only adds a `min-width` of `1440px` to the media query.
```ts
import {useTheme} from '@workday/canvas-kit-react/common';
import {space} from '@workday/canvas-kit-react/tokens';
const theme = useTheme();
const {only} = theme.canvas.breakpoints;
const mediaQuery = only('m', 'xl'); // Returns '@media (min-width: 1440px)'
const styles = {
[mediaQuery]: {
padding: space.s,
},
};
```
## useIsRTL Hook
`useIsRTL` is a small hook to support right-to-left logic. If a theme exists in React context, the
component will use it automatically, or you can explicitly provide a theme object.
### Usage
```ts
// this will automatically pull the theme from context if it exists.
const isRTL = useIsRTL();
```
```ts
// or you can explicitly provide a (partial) theme object.
const RTLTheme = {
canvas: {
direction: ContentDirection.RTL,
},
};
const isRTL = useIsRTL(RTLTheme);
```