From a67867cff9cfe88a4f4852446d3cbb7e849e6484 Mon Sep 17 00:00:00 2001 From: DAK <40970507+dakahn@users.noreply.github.com> Date: Thu, 22 Jul 2021 11:29:43 -0500 Subject: [PATCH] feat(react): add `Theme` component, `useTheme` hook (#9201) * 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 Co-authored-by: Andrea N. Cardona --- .../src/components/Theme/Theme-story.scss | 16 ++++ .../src/components/Theme/Theme.mdx | 85 ++++++++++++++++ .../src/components/Theme/Theme.stories.js | 96 +++++++++++++++++++ .../components/Theme/__tests__/Theme-test.js | 40 ++++++++ .../src/components/Theme/index.js | 80 ++++++++++++++++ packages/carbon-react/src/index.js | 2 + packages/styles/scss/_zone.scss | 33 +++++++ 7 files changed, 352 insertions(+) create mode 100644 packages/carbon-react/src/components/Theme/Theme-story.scss create mode 100644 packages/carbon-react/src/components/Theme/Theme.mdx create mode 100644 packages/carbon-react/src/components/Theme/Theme.stories.js create mode 100644 packages/carbon-react/src/components/Theme/__tests__/Theme-test.js create mode 100644 packages/carbon-react/src/components/Theme/index.js create mode 100644 packages/styles/scss/_zone.scss diff --git a/packages/carbon-react/src/components/Theme/Theme-story.scss b/packages/carbon-react/src/components/Theme/Theme-story.scss new file mode 100644 index 000000000000..fb99752868e1 --- /dev/null +++ b/packages/carbon-react/src/components/Theme/Theme-story.scss @@ -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; +} diff --git a/packages/carbon-react/src/components/Theme/Theme.mdx b/packages/carbon-react/src/components/Theme/Theme.mdx new file mode 100644 index 000000000000..c49bc0d6fc51 --- /dev/null +++ b/packages/carbon-react/src/components/Theme/Theme.mdx @@ -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) + + + + +## Table of Contents + +- [Overview](#overview) + - [`useTheme`](#usetheme) +- [Component API](#component-api) + - [Theme as](#theme-as) +- [Feedback](#feedback) + + + + +## 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 + + + +``` + +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 + + + +### 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 + + + +``` + +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). diff --git a/packages/carbon-react/src/components/Theme/Theme.stories.js b/packages/carbon-react/src/components/Theme/Theme.stories.js new file mode 100644 index 000000000000..8e9ca9235038 --- /dev/null +++ b/packages/carbon-react/src/components/Theme/Theme.stories.js @@ -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 ( + <> + +
+

g100 theme

+
+
+ +
+

g90 theme

+
+
+ +
+

g10 theme

+
+
+ +
+

white theme

+
+
+ + ); +}; + +export const UseTheme = () => { + function Example() { + const { theme } = useTheme(); + return
The current theme is: {theme}
; + } + + return ( +
+ + + + +
+ ); +}; + +UseTheme.storyName = 'useTheme'; + +const PlaygroundStory = (args) => { + return ( + +
+

{args.theme} theme

+
+
+ ); +}; + +export const Playground = PlaygroundStory.bind({}); diff --git a/packages/carbon-react/src/components/Theme/__tests__/Theme-test.js b/packages/carbon-react/src/components/Theme/__tests__/Theme-test.js new file mode 100644 index 000000000000..66664f01c11f --- /dev/null +++ b/packages/carbon-react/src/components/Theme/__tests__/Theme-test.js @@ -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( + + test + + ); + + expect(screen.getByTestId('test')).toBeInTheDocument(); + }); + + it('should set the theme in context', () => { + function TestComponent({ id }) { + const { theme } = useTheme(); + return {theme}; + } + render( + + + + + + + ); + + expect(screen.getByTestId('default')).toHaveTextContent('white'); + expect(screen.getByTestId('nested')).toHaveTextContent('g100'); + }); +}); diff --git a/packages/carbon-react/src/components/Theme/index.js b/packages/carbon-react/src/components/Theme/index.js new file mode 100644 index 000000000000..dcbad8be4aae --- /dev/null +++ b/packages/carbon-react/src/components/Theme/index.js @@ -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 ( + + + {children} + + + ); +} + +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); +} diff --git a/packages/carbon-react/src/index.js b/packages/carbon-react/src/index.js index e41e40d2d392..26620ec00e79 100644 --- a/packages/carbon-react/src/index.js +++ b/packages/carbon-react/src/index.js @@ -204,3 +204,5 @@ export { Grid, Column, } from './components/Grid'; + +export { Theme, useTheme } from './components/Theme'; diff --git a/packages/styles/scss/_zone.scss b/packages/styles/scss/_zone.scss new file mode 100644 index 000000000000..755b3a457a8a --- /dev/null +++ b/packages/styles/scss/_zone.scss @@ -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); + } + } + } +}