Skip to content

Commit

Permalink
feat(react): add Theme component, useTheme hook (#9201)
Browse files Browse the repository at this point in the history
* feat(Theme): add theme package

* feat(themes): add zone support

* refactor(theme): update props and add docs

* chore(styles): update style order

* feat(carbon-react): add Theme, useTheme to exports

Co-authored-by: Josh Black <[email protected]>
Co-authored-by: Andrea N. Cardona <[email protected]>
  • Loading branch information
3 people authored Jul 22, 2021
1 parent 20bd64b commit a67867c
Show file tree
Hide file tree
Showing 7 changed files with 352 additions and 0 deletions.
16 changes: 16 additions & 0 deletions packages/carbon-react/src/components/Theme/Theme-story.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
//
// Copyright IBM Corp. 2018, 2018
//
// This source code is licensed under the Apache-2.0 license found in the
// LICENSE file in the root directory of this source tree.
//

@use '@carbon/styles/scss/themes';
@use '@carbon/styles/scss/theme';
@use '@carbon/styles/scss/zone';

.theme-section {
padding: 1rem;
background: theme.$background;
color: theme.$text-primary;
}
85 changes: 85 additions & 0 deletions packages/carbon-react/src/components/Theme/Theme.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import { Story, Props, Source, Preview } from '@storybook/addon-docs/blocks';

# Theme

[Source code](https://github.com/carbon-design-system/carbon/tree/main/packages/carbon-react/src/components/Theme)

<!-- prettier-ignore-start -->
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
## Table of Contents

- [Overview](#overview)
- [`useTheme`](#usetheme)
- [Component API](#component-api)
- [Theme as](#theme-as)
- [Feedback](#feedback)

<!-- END doctoc generated TOC please keep comment here to allow auto update -->
<!-- prettier-ignore-end -->

## Overview

The `Theme` component allows you to specify the theme for a page, or a portion
of a page. It is most often used to implement inline theming in order to specify
what theme a section of the page should render with.

The `Theme` component accepts a `theme` prop which allows you to specify the
theme. This value is propagated to the entire sub-tree of components and can be
accessed by any child component using the `useTheme` hook.

```jsx
<Theme theme="g100">
<ChildComponent />
</Theme>
```
The `Theme` component will render a wrapper element on which all of the values
of the given theme will be applied as CSS Custom Properties. These CSS Custom
Properties can be accessed directly, or you can use the Sass Variables provided
in the styles from the Carbon Design System to refer to these properties in your
styles.
### `useTheme`
The `useTheme` hook allows you to access the current theme in a component. This
can be useful if you need to conditionally render something based on the theme.
For example, if you have an illustration that needs to be rendered differently
in a light or dark theme.
You can use this hook in any component by calling `useTheme`:
```jsx
function ExampleComponent() {
const { theme } = useTheme();
if (theme === 'g100') {
// ...
}
}
```

## Component API

<Props />

### Theme as

You can configure the base element rendered by `Theme` using the `as` prop. For
example, if you would like the `Theme` component to render as a `section` you
could write the following:

```jsx
<Theme as="section" theme="g100">
<ChildComponent />
</Theme>
```

Similarly, you can provide any custom component to the `as` prop which the
`Theme` component will use.

## Feedback

Help us improve this component by providing feedback, asking questions on Slack,
or updating this file on
[GitHub](https://github.com/carbon-design-system/carbon/edit/main/packages/carbon-react/src/components/Theme/Theme.mdx).
96 changes: 96 additions & 0 deletions packages/carbon-react/src/components/Theme/Theme.stories.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
/**
* Copyright IBM Corp. 2016, 2018
*
* This source code is licensed under the Apache-2.0 license found in the
* LICENSE file in the root directory of this source tree.
*/

import './Theme-story.scss';
import React from 'react';
import { Theme, useTheme } from '../Theme';
import mdx from './Theme.mdx';

export default {
title: 'Components/Theme',
component: Theme,
parameters: {
controls: {
hideNoControlsWarning: true,
},
docs: {
page: mdx,
},
},
argTypes: {
as: {
table: {
disable: true,
},
},
children: {
table: {
disable: true,
},
},
theme: {
defaultValue: 'g10',
},
},
};

export const Default = () => {
return (
<>
<Theme theme="g100">
<section className="theme-section">
<p>g100 theme</p>
</section>
</Theme>
<Theme theme="g90">
<section className="theme-section">
<p>g90 theme</p>
</section>
</Theme>
<Theme theme="g10">
<section className="theme-section">
<p>g10 theme</p>
</section>
</Theme>
<Theme theme="white">
<section className="theme-section">
<p>white theme</p>
</section>
</Theme>
</>
);
};

export const UseTheme = () => {
function Example() {
const { theme } = useTheme();
return <div className="theme-section">The current theme is: {theme}</div>;
}

return (
<div>
<Example />
<Theme theme="g100">
<Example />
</Theme>
</div>
);
};

UseTheme.storyName = 'useTheme';

const PlaygroundStory = (args) => {
return (
<Theme {...args}>
<section className="theme-section">
<p>{args.theme} theme</p>
</section>
</Theme>
);
};

export const Playground = PlaygroundStory.bind({});
40 changes: 40 additions & 0 deletions packages/carbon-react/src/components/Theme/__tests__/Theme-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/**
* Copyright IBM Corp. 2016, 2018
*
* This source code is licensed under the Apache-2.0 license found in the
* LICENSE file in the root directory of this source tree.
*/

import React from 'react';
import { Theme, useTheme } from '../../Theme';
import { screen, render } from '@testing-library/react';

describe('Theme', () => {
it('should render the children passed in as a prop', () => {
render(
<Theme>
<span data-testid="test">test</span>
</Theme>
);

expect(screen.getByTestId('test')).toBeInTheDocument();
});

it('should set the theme in context', () => {
function TestComponent({ id }) {
const { theme } = useTheme();
return <span data-testid={id}>{theme}</span>;
}
render(
<Theme theme="white">
<TestComponent id="default" />
<Theme theme="g100">
<TestComponent id="nested" />
</Theme>
</Theme>
);

expect(screen.getByTestId('default')).toHaveTextContent('white');
expect(screen.getByTestId('nested')).toHaveTextContent('g100');
});
});
80 changes: 80 additions & 0 deletions packages/carbon-react/src/components/Theme/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/**
* Copyright IBM Corp. 2016, 2018
*
* This source code is licensed under the Apache-2.0 license found in the
* LICENSE file in the root directory of this source tree.
*/

import React from 'react';
import PropTypes from 'prop-types';
import cx from 'classnames';

const ThemeContext = React.createContext({
theme: 'white',
});

/**
* Specify the theme to be applied to a page, or a region in a page
*/
export function Theme({
as: BaseComponent = 'div',
children,
className: customClassName,
theme,
...rest
}) {
const className = cx(customClassName, {
'bx--white': theme === 'white',
'bx--g10': theme === 'g10',
'bx--g90': theme === 'g90',
'bx--g100': theme === 'g100',
});
const value = React.useMemo(() => {
return {
theme,
};
}, [theme]);

return (
<ThemeContext.Provider value={value}>
<BaseComponent {...rest} className={className}>
{children}
</BaseComponent>
</ThemeContext.Provider>
);
}

Theme.propTypes = {
/**
* Specify a custom component or element to be rendered as the top-level
* element in the component
*/
as: PropTypes.oneOfType([
PropTypes.func,
PropTypes.string,
PropTypes.elementType,
]),

/**
* Provide child elements to be rendered inside of `Theme`
*/
children: PropTypes.node,

/**
* Provide a custom class name to be used on the outermost element rendered by
* the component
*/
className: PropTypes.string,

/**
* Specify the theme
*/
theme: PropTypes.oneOf(['white', 'g10', 'g90', 'g100']),
};

/**
* Get access to the current theme
*/
export function useTheme() {
return React.useContext(ThemeContext);
}
2 changes: 2 additions & 0 deletions packages/carbon-react/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -204,3 +204,5 @@ export {
Grid,
Column,
} from './components/Grid';

export { Theme, useTheme } from './components/Theme';
33 changes: 33 additions & 0 deletions packages/styles/scss/_zone.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
//
// Copyright IBM Corp. 2018, 2018
//
// This source code is licensed under the Apache-2.0 license found in the
// LICENSE file in the root directory of this source tree.
//

@use 'sass:map';
@use 'sass:meta';
@use './config';
@use './themes';
@use './theme';
@use './utilities/custom-property';

/// Specify a Map of zones where the key will be used as part of the selector
/// and the value will be a map used to emit CSS Custom Properties for all color
/// values
$zones: (
white: themes.$white,
g10: themes.$g10,
g90: themes.$g90,
g100: themes.$g100,
) !default;

@each $name, $theme in $zones {
.#{config.$prefix}--#{'' + $name} {
@each $key, $value in $theme {
@if type-of($value) == color {
@include custom-property.declaration($key, $value);
}
}
}
}

0 comments on commit a67867c

Please sign in to comment.