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(react): add Theme component, useTheme hook #9201

Merged
merged 8 commits into from
Jul 22, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
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({});
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';
joshblack marked this conversation as resolved.
Show resolved Hide resolved

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);
}
}
}
}