Skip to content

Commit

Permalink
feat!: Update useTheme hook (#2120)
Browse files Browse the repository at this point in the history
Fixes: #923 

- We've added `getTheme` so that you can have access to theme inside of `styled` function or class components.
- We've updated `useTheme` to add a warning if being used outside of a context. 
- We've removed `getCanvasTheme` and `useCanvasTheme`.

[category:Components]

### BREAKING CHANGES
- We've removed `getCanvasTheme` and `useCanvasTheme`.
- Use `useTheme` when you have a `CanvasProvider` or you're inside of a functional component
- Use `getTheme` when inside a styled component and you need access to theme.

Co-authored-by: @mannycarrera4 <[email protected]>
  • Loading branch information
RayRedGoose and mannycarrera4 authored Apr 7, 2023
1 parent 143ec66 commit 61d8137
Show file tree
Hide file tree
Showing 11 changed files with 207 additions and 82 deletions.
25 changes: 19 additions & 6 deletions modules/docs/mdx/9.0-UPGRADE-GUIDE.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,15 @@ any questions.
- [Layout and Column](#layout-and-column)
- [Stack](#stack-hstack-vstack)
- [Component Updates](#component-updates)
- [Buttons](#buttons)
- [Toast](#toast)
- [Buttons](#buttons) - [Toast](#toast)
- [Utility Updates](#utility-updates)
- [focusRing](#focusring)
- [useThemedRing](#useThemedRing)
- [useThemeRTL](#useThemeRTL)
- [useTheme and getTheme](#useTheme-and-getTheme)
- [useCanvasTheme and getCanvasTheme](#useCanvasTheme-and-getCanvasTheme)
- [useThemedRing](#useThemedRing)
- [useThemeRTL](#useThemeRTL)
- [Token Updates](#token-updates)
- [Depth](#depth)
- [Depth](#depth)

## Codemod

Expand Down Expand Up @@ -175,6 +176,17 @@ We're removing memoization from focus ring. The `memoize` argument passed to `fo
longer valid and we've removed the exported `memoizedFocusRing`. There is no codemod for this
change. We couldn't find any example of `memoize` or `memoizedFocusRing` being used.

### useTheme and getTheme

We've updated `useTheme` by adding error handling if this hook has been used outside a functional
component. Also, `getTheme` method has been added to access a theme from `styled` or class
components instead of `useTheme`.

### useCanvasTheme and getCanvasTheme

`useCanvasTheme` and `getCanvasTheme` have been removed and can be safely replaced by `useTheme`
and `getTheme`.

### useThemedRing

We've promoted `useThemedRing` from our Labs package to our Main package. You can use this utility
Expand All @@ -184,7 +196,8 @@ to theme focus rings.

### useThemeRTL

We've [soft-deprecated](#soft-deprecation) `useThemeRTL` from our Labs package. Although you may still use this utility, we encourage consumers to use
We've [soft-deprecated](#soft-deprecation) `useThemeRTL` from our Labs package. Although you may
still use this utility, we encourage consumers to use
[CSS logical properties](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Logical_Properties).

## Token Updates
Expand Down
4 changes: 2 additions & 2 deletions modules/react/action-bar/lib/ActionBarList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {commonColors, colors, space} from '@workday/canvas-kit-react/tokens';
import {
createSubcomponent,
ExtractProps,
getCanvasTheme,
getTheme,
styled,
StyledType,
} from '@workday/canvas-kit-react/common';
Expand Down Expand Up @@ -37,7 +37,7 @@ export interface ActionBarListProps<T = any>
}

const ResponsiveList = styled(Flex)<ActionBarListProps & StyledType>(({theme}) => {
const canvasTheme = getCanvasTheme(theme);
const {canvas: canvasTheme} = getTheme(theme);
return {
[canvasTheme.breakpoints.down('s')]: {
padding: space.s,
Expand Down
97 changes: 97 additions & 0 deletions modules/react/common/lib/theming/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,103 @@ window.workday = {
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
Expand Down
40 changes: 0 additions & 40 deletions modules/react/common/lib/theming/getCanvasTheme.ts

This file was deleted.

1 change: 0 additions & 1 deletion modules/react/common/lib/theming/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,4 @@ export * from './theme';
export * from './useTheme';
export * from './useThemedRing';
export * from './useIsRTL';
export * from './getCanvasTheme';
export * from './getObjectProxy';
101 changes: 79 additions & 22 deletions modules/react/common/lib/theming/useTheme.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
// refactor for v5
/// <reference types="@types/node" />

import {useTheme as useEmotionTheme} from '@emotion/react';
import {
defaultCanvasTheme,
Expand All @@ -12,12 +15,61 @@ const getFilledTheme = (theme: PartialEmotionCanvasTheme) => ({
});

/**
* Hook function to get the correct theme object.
* @param {Object=} theme - The theme object returned from the emotion ThemeContext
* (through ThemeProvider). The Canvas Kit theme is namespaced within this variable under the `canvas` key.
* Function to get the correct theme object for `styled` and class components
* or to be used outside component.
* @param {Object=} theme - The theme object with the Canvas Kit theme.
* It should be namespaced within this variable under the `canvas` key.
* Value of `canvas` property is any partial or full theme object to overwtite default theme.
*
* @returns An object containing updated or default Canvas Kit theme under `canvas` key.
*
* The passed partial theme object will be merged with the default Canvas theme
* (using memoized createCanvasTheme()) to establish any missing fields that have
* not been defined by the consumer's theme object.
*
* If theme is not passed, the function will try to retrieve it from the window object.
* If window theme is not found, it will return the default Canvas theme.
*
* @example
* 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
* }
* }
*/
export function getTheme(theme?: PartialEmotionCanvasTheme): EmotionCanvasTheme {
if (theme?.canvas) {
return getFilledTheme(theme);
}

const windowTheme = typeof window !== 'undefined' && (window as any)?.workday?.canvas?.theme;

if (windowTheme) {
return getFilledTheme({canvas: windowTheme});
}

return {canvas: defaultCanvasTheme};
}

/**
* Hook function to get the correct theme object for functional components.
* @param {Object=} theme - The theme object with the Canvas Kit theme.
* It should be namespaced within this variable under the `canvas` key.
* Value of `canvas` property is any partial or full theme object to overwtite default theme.
*
* @returns An object containing updated or default Canvas Kit theme under `canvas` key.
*
* NOTE: If you are using a class component, you MUST pass the theme.
* If not passed, the function will try to pull the theme from ThemeContext.
* NOTE: If theme is not passed, the function will try to pull the theme from ThemeContext.
* If that does not work, it will try to retrieve it from the window object.
* As a last resort, it will return the default Canvas theme.
*
Expand All @@ -28,26 +80,31 @@ const getFilledTheme = (theme: PartialEmotionCanvasTheme) => ({
* Providing the default theme here is currently a work around for when no
* ThemeProvider or context exists.
* Tracked on https://github.com/emotion-js/emotion/issues/1193.
*
* @example
* export const ErrorMessage = () => {
* const theme = useTheme();
* return (
* <Subtext size="large" color={theme.canvas.palette.error.main}>
* );
* }
*/
export function useTheme(theme?: PartialEmotionCanvasTheme): EmotionCanvasTheme {
if (theme && theme.canvas) {
return getFilledTheme(theme);
}

try {
// eslint-disable-next-line react-hooks/rules-of-hooks
const contextTheme = useEmotionTheme() as EmotionCanvasTheme;
if (contextTheme && contextTheme.canvas) {
return getFilledTheme(contextTheme);
if (!theme) {
try {
// eslint-disable-next-line react-hooks/rules-of-hooks
const contextTheme = useEmotionTheme() as EmotionCanvasTheme;
if (contextTheme?.canvas) {
return getFilledTheme(contextTheme);
}
} catch (e) {
if (process && process.env.NODE_ENV === 'development') {
console.warn(
'useTheme: Context not supported or invalid. Please consider using `getTheme` function instead for `styled` or class components.'
);
}
}
} catch (e) {
// Context not supported or invalid (probably called from within a class component)
}

const windowTheme = typeof window !== 'undefined' && (window as any)?.workday?.canvas?.theme;
if (windowTheme) {
return getFilledTheme({canvas: windowTheme});
}

return {canvas: defaultCanvasTheme};
return getTheme(theme);
}
5 changes: 2 additions & 3 deletions modules/react/common/stories/examples/ResponsiveViewport.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,9 @@ import * as React from 'react';
import {Box, Grid} from '@workday/canvas-kit-react/layout';
import styled from '@emotion/styled';
import {type, space, colors, borderRadius} from '@workday/canvas-kit-react/tokens';
import {useTheme} from '@workday/canvas-kit-react/common';
import {getTheme} from '@workday/canvas-kit-react/common';

// eslint-disable-next-line react-hooks/rules-of-hooks
const theme = useTheme();
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)'
Expand Down
4 changes: 2 additions & 2 deletions modules/react/modal/lib/ModalBody.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import * as React from 'react';
import {
createSubcomponent,
ExtractProps,
getCanvasTheme,
getTheme,
styled,
StyledType,
} from '@workday/canvas-kit-react/common';
Expand All @@ -15,7 +15,7 @@ import {useModalModel} from './hooks';
export interface ModalBodyProps extends ExtractProps<typeof Popup.Body, never> {}

const ResponsiveModalBody = styled(Popup.Body)<ModalBodyProps & StyledType>(({theme}) => {
const canvasTheme = getCanvasTheme(theme);
const {canvas: canvasTheme} = getTheme(theme);
return {
[canvasTheme.breakpoints.down('s')]: {
marginBottom: space.zero,
Expand Down
4 changes: 2 additions & 2 deletions modules/react/modal/lib/ModalCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {
ExtractProps,
StyledType,
styled,
getCanvasTheme,
getTheme,
} from '@workday/canvas-kit-react/common';
import {space} from '@workday/canvas-kit-react/tokens';
import {Popup} from '@workday/canvas-kit-react/popup';
Expand All @@ -15,7 +15,7 @@ import {useModalCard, useModalModel} from './hooks';
export interface ModalCardProps extends ExtractProps<typeof Popup.Card, never> {}

const ResponsiveModalCard = styled(Popup.Card)<ModalCardProps & StyledType>(({theme}) => {
const canvasTheme = getCanvasTheme(theme);
const {canvas: canvasTheme} = getTheme(theme);
return {
margin: space.xl,
[canvasTheme.breakpoints.down('s')]: {
Expand Down
Loading

0 comments on commit 61d8137

Please sign in to comment.