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

Dynamic theme #84

Open
Danilqa opened this issue Aug 29, 2019 · 2 comments
Open

Dynamic theme #84

Danilqa opened this issue Aug 29, 2019 · 2 comments

Comments

@Danilqa
Copy link

Danilqa commented Aug 29, 2019

Hi! Thank you for your wonderful job! :)

I haven't found approaches for creating a dynamic theme as we've usually done it in Style Component. What do you think about this solution? Does another more correct way exist?

export function ThemeProvider({ theme, children }) {
    return React.createElement(ThemeContext.Provider, { children, value: theme });
};

export function withTheme(component) {
    return props => (
        <ThemeContext.Consumer>
            {theme => React.createElement(component, { ...props, theme })}
        </ThemeContext.Consumer>
    );
}

Each component I wrap by withTheme and get need properties inside it.

@lttb
Copy link
Member

lttb commented Aug 30, 2019

Hi @Danilqa, thank you very much for the issue!

It's a very interesting topic, and I hope you will not mind if I try to dive a little deeper into the subject.

There are a couple of ways to apply some styles to the Component:

  • Component's modifiers (like, size, variant etc.)
  • Theming (primary/secondary colors, paddings and the other stuff declared in the theme variables)
  • Component styling

The first two are well known in the context of React applications, and the last one is something that we've introduced with reshadow (but of course it might be achievable somehow else) and I'll try to explain these parts better.

Component's modifiers

Component's modifiers - is a clear and simple way to implement the styling. We just need to write the Component's behaviour depends on that modifiers, and I won't focus on this approach at the moment (but with reshadow there are also some interesting things).

This is a great way to declare Component's own styling behaviour by design system, but it's important to mention that this approach is not really flexible, because if you need to change something on styling, you need to change Component's styles or change its API (add modifier or change modifier for example).

So, this is the basic idea and it's often combined with others.

Theming

With theme we can "detach" some styling parts from the Component to the theme and make styling more flexible.

There are also some limits, like Theme's API, because if you need to style something that's not supported by theme, you need to extend theme's API or, again, change Component's code.

It is also might be tricky sometimes to design the Theme API the best way.

Anyway, it looks like the most common way to apply different styles in React application, Context Theming Variables is a popular kind of implementation.

Context Theming Variables

This approach is very popular on styled-components or jss way. You can use any theme context provider for React, for example cssinjs/theming, or the custom implementation that was mentioned.

In fact, with reshadow it looks similar.

In example, with theming (on the codesandbox):

import {ThemeProvider} from 'theming';

const theme = {bg: 'white', fg: 'black'};

<ThemeProvider theme={theme}>
  <App />
</ThemeProvider>
import styled from 'reshadow'
import {withTheme} from 'theming'; 

const Button = ({theme, children, ...props}) => styled`
  button {
    color: ${theme.fg};
    background-color: ${theme.bg};
    border: 2px solid;
    padding: 0.25em 1em;
  }
`(
  <button {...props}>
    {children}
  </button>
)

export default withTheme(Button)

Dynamic values will fallback to the css custom properties and styles will be still extractable to the separate css files during the build time, without runtime styles parsing.

BTW, @reshadow/styled has theming support out of the box with the same API and approaches that styled-components has, but it's an experimental package.

Important to note, that because of css custom properties there is no IE11 support with dynamic values in the tagged template literal.

Context Theme

The previous approach might look natural and classic, but there are also some interesting things that might be nice to consider.

A theme might be not just about some set of different variables for sizes, fonts, colors and so on, but it also affects the style behaviour of the component. Moreover, it might be no real intersection between Component styles in different themes (imagine, if we'd like to have Material and iOS themes on the same core codebase, that have different layouts, animations, property combinations etc.). So we can imagine an example, that's not that easy to describe by theming variables.

But we can adjust the Component to the theme, for example, with styled-components:

Button/index.js

import styled, {css} from 'styled-components'

const themes = {
  light: props => css` /* light theme */ `,
  dark: props => css` /* dark theme */ `,
}

export const Button = styled.button`
  ${props => themes[props.theme]}
`

With reshadow it might be implemented this way:

import {ThemeProvider} from 'theming';

const theme = 'light';

<ThemeProvider theme={theme}>
  <App />
</ThemeProvider>

Button/themes.js

export * as light from './light.css'
export * as dark from './dark.css'

Button/index.js

import styled from 'reshadow'
import {withTheme} from 'theming'; 

import themes from './themes'

const Button = ({theme, children, ...props}) => styled(themes[theme])(
  <button {...props}>{children}</button>
)

export default withTheme(Button)

This is the basic example in an attempt to show an idea, but we can take a step forward and load themes in a lazy way by dynamic imports for example when the current theme is needed.

Thus, we can compose different styles, themes for Components in the isolated ways and share our components independently.

In contrast to styled-components example, css with reshadow is extractable and does not have any runtime processing as in the previous example, and it also supports IE11, because there is no need in css custom properties. It is also easy to style not only Components, but its elements with any attributes and modifiers.

Styling

If we'll take a look on the example with Context Theme, we can notice that our styles were detached from Component to the separate values. Let's take a little next step and inject our styles from props, for example:

Button/index.js

import styled from 'reshadow'

export const Button = ({styles, children, ...props}) => styled(styles)(
  <button {...props}>
    <content>{children}</content>
  </button>
)

With reshadow you can just write your markup and map the Component's state to it without carrying about the styling, and style the Component somewhere outside:

import {css} from 'reshadow'

const styles = css`
  button { ... }
  button[disabled] { ... }
  content { ... }
`

<Button styles={styles} ... />

It is possible to put the styles to DI and achieve the Styles Injection by the context. It is useful for styling components with the same UI logic and markup, but with really different styles, like this:

image

But that's another interesting topic 😄

I hope I've understandably described different dynamic theme approaches with reshadow, please feel free to ask for any clarification or help 🙂

@s-kobets
Copy link

How I think. It is not work, because when you calculate hash for class name will be other, if use local style in Button/index.js. Fix me, if I am not right.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants