Git Product home page Git Product logo

react-css-themr's Introduction

npm version Build Status NPM Status

React CSS Themr

Easy theming and composition for CSS Modules.

$ npm install --save react-css-themr

Note: Feedback and contributions on the docs are highly appreciated.

Why?

When you use CSS Modules to style your components, a classnames object is usually imported from the same component. Since css classes are scoped by default, there is no easy way to make your component customizable for the outside world.

The approach

Taking ideas from future-react-ui and react-themeable, a component should be shipped without styles. This means we can consider the styles as an injectable dependency. In CSS Modules you can consider the imported classnames object as a theme for a component. Therefore, every styled component should define a classname API to be used in the rendering function.

The most immediate way of providing a classname object is via props. In case you want to import a component with a theme already injected, you have to write a higher order component that does the job. This is ok for your own components, but for ui-kits like React Toolbox or Belle, you'd have to write a wrapper for every single component you want to use. In this fancy, you can understand the theme as a set of related classname objects for different components. It makes sense to group them together in a single object and move it through the component tree using a context. This way, you can provide a theme either via context, hoc or props.

The approach of react-css-themr consists of a provider and a decorator. The provider sets a context theme. The decorator adds to your components the logic to figure out which theme should be used or how should it be composed, depending on configuration, context and props.

Combining CSS modules

There are three possible sources for your component. Sorted by priority: context, configuration and props. Any of them can be missing. In case multiple themes are present, you may want to compose the final classnames object in three different ways:

  • Override: the theme object with the highest priority is the one used.
  • Softly merging: theme objects are merged but if a key is present in more than one object, the final value corresponds to the theme with highest priority.
  • Deeply merging: theme objects are merged and if a key is present in more than one object, the values for each objects are concatenated.

You can choose whatever you want. We consider the last one as the most flexible so it's selected by default.

How does it work?

Say you have a Button component you want to make themeable. You should pass a unique name identifier that will be used to retrieve its theme from context in case it is present.

// Button.js
import React, { Component } from 'react';
import { themr } from 'react-css-themr';

@themr('MyThemedButton')
class Button extends Component {
  render() {
    const { theme, icon, children } = this.props;
    return (
      <button className={theme.button}>
        { icon ? <i className={theme.icon}>{icon}</i> : null}
        <span className={theme.content}>{children}</span>
      </button>
    )
  }
}

export default Button;

The component is defining an API for theming that consists of three classnames: button, icon and content. Now, a component can use a button with a success theme like:

import Button from './Button';
import successTheme from './SuccessButton.css';

export default (props) => (
  <div {...props}>
    <p>Do you like it?</p>
    <Button theme={successTheme}>Yeah!</Button>
  </div>
);

Default theming

If you use a component with a base theme, you may want to import the component with the theme already injected. Then you can compose its style via props with another theme object. In this case the base css will always be bundled:

// SuccessButton.js
import React, { Component } from 'react';
import { themr } from 'react-css-themr';
import successTheme from './SuccessButton.css';

@themr('MySuccessButton', successTheme)
class Button extends Component {
  render() {
    const { theme, icon, children } = this.props;
    return (
      <button className={theme.button}>
        { icon ? <i className={theme.icon}>{icon}</i> : null}
        <span className={theme.content}>{children}</span>
      </button>
    )
  }
}

export default Button;

Imagine you want to make the success button uppercase for a specific case. You can include the classname mixed with other classnames:

import React from 'react';
import SuccessButton from 'SuccessButon';
import style from './Section.css';

export default () => (
  <section className={style.section}>
    <SuccessButton theme={style}>Yai!</SuccessButton>
  </section>
);

And being Section.css something like:

.section { border: 1px solid red; }
.button  { text-transform: uppercase; }

The final classnames object for the Button component would include class values from SuccessButton.css and Section.css so it would be uppercase!

Context theming

Although context theming is not limited to ui-kits, it's very useful to avoid declaring hoc for every component. For example, in react-toolbox, you can define a context theme like:

import React from 'react';
import { render } from 'react-dom';
import { ThemeProvider } from 'react-css-themr';
import App from './app'

const contextTheme = {
  RTButton: require('react-toolbox/lib/button/style.scss'),
  RTDialog: require('react-toolbox/lib/dialog/style.scss')
};

const content = (
  <ThemeProvider theme={contextTheme}>
    <App />
  </ThemeProvider>
);

render(content, document.getElementById('app'));

The main idea is to inject classnames objects for each component via context. This way you can have the whole theme in a single place and forget about including styles in every require. Any component Button or Dialog from will use the provided styles in the context.

API

<ThemeProvider theme>

Makes available a theme context to use in styled components. The shape of the theme object consists of an object whose keys are identifiers for styled components provided with the themr function with each theme as the corresponding value. Useful for ui-kits.

themr(Identifier, [defaultTheme], [options])

Returns a function to wrap a component and make it themeable.

The returned component accepts a theme, composeTheme, innerRef and mapThemrProps props apart from the props of the original component. They former two are used to provide a theme to the component and to configure the style composition, which can be configured via options too. innerRef is used to pass a ref callback to the decorated component and mapThemrProps is a function that can be used to map properties to the decorated component. The function arguments are:

  • Identifier (String) used to provide a unique identifier to the component that will be used to get a theme from context.
  • [defaultTheme] (Object) is classname object resolved from CSS modules. It will be used as the default theme to calculate a new theme that will be passed to the component.
  • [options] (Object) If specified it allows to customize the behavior:
    • [composeTheme = 'deeply'] (String) allows to customize the way themes are merged or to disable merging completely. The accepted values are deeply to deeply merge themes, softly to softly merge themes and false to disable theme merging.
    • [mapThemrProps = (props, theme) => ({ ref, theme })] (Function) allows to customize how properties are passed down to the decorated component. By default, themr extracts all own properties passing down just innerRef as ref and the generated theme as theme. If you are decorating a component that needs to map the reference or any other custom property, this function is called with all properties given to the component plus the generated theme in the second parameter. It should return the properties you want to pass.

About

The project is originally authored by Javi Velasco as an effort of providing a better customization experience for React Toolbox. Any comments, improvements or feedback is highly appreciated.

Thanks to Nik Graf and Mark Dalgleish for their thoughts about theming and customization for React components.

License

This project is licensed under the terms of the MIT license.

react-css-themr's People

Contributors

ankurp avatar baodelta avatar c0 avatar chinnawatp avatar ctumolosus avatar denisborovikov avatar doejon avatar epilande avatar istarkov avatar javivelasco avatar landabaso avatar pleunv avatar raveclassic avatar tuckerconnelly avatar zverev avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

react-css-themr's Issues

Rerender component when update theme context?

Hi, I'm making an app base on react-toolbox. My app has 2 themes. I want to add a button to change between themes. My stack includes react-router, redux, redux-saga.
When user changes theme, my app will dispatch an action and return props to component and send to Theme Provider like below.

        let ttheme = this.props.data || {
                RTButton: require('./themes/mui/main/input.scss')
            };
        return (
            <ThemeProvider theme={ttheme}>
                <div>
                    {this.props.children}
                </div>
            </ThemeProvider>
        )

But when changed a theme, nothing happen. But when I change to another route. It seems the new theme is applied. So when I changed theme, ThemeProvider got a new them object, but it doesn't apply immediately. I guess because child component uses context so It won't update immediately.

Thank you for you plugin. Have a nice day

how to override styling in default css for a themed component

quotation from README, "There are three possible sources for your component. Sorted by priority: context, configuration and props" hints to me that theme in context has lowest priority, then configuration(local theme or default theme) with higher priority, right?

Given that I decorated my Button react component (@TheMr("MyButton", defaultTheme)) with default.css

.button { background-color: blue; color: white }
and then I've got a customer.css to customize that button:
.button { background-color: #003366; color: red }

the customer.css was imported and sent in as context theme to Button component.
I am wondering how I could override the default theme with the theme in context? It seems impossible at all because context them has lower priority than localTheme (default.css)

The other issue is that if { composeTheme: 'deeply' } is being used, both context theme and local theme(default.css) has background-color, the final visual background result of button is dependent/undetermined for it depends on the order of who comes after whom in bundled resource

Should context theme have higher priority then local theme so that it is easier to override the default one with context theme?

Thanks in advance!

Allow a theme composition function as option

Hi,
thanks for your work, this module has been a tremendous help in my projects!

Proposition

Having an option allowing to specify a function that will be used in place of getTheme
having contextTheme, confTheme and propsTheme as parameters and the resulting theme object as return value.
It would allow specifying manually the merge behavior.

Use cases:

  1. Change the order of priority between theme sources.

I have a project where i'd like to have this priority order configuration < context < props instead of context < configuration < props.
Configuration would be present in a component / component library as functional defaults / reset, context would allow to have consistent styles between elements throughout the app / site, and props would allow specific components to have different styles.

  1. CSS modules composition (example with tachyons)

Let's say i have a component which CSS module is defined by multiple composes statements:

header.context.css

.header {
  composes: f-subheadline f-headline-l from 'tachyons-type-scale';
  composes: measure-narrow from 'tachyons-typography';
  composes: lh-title from 'tachyons-line-height';
  composes: mt0 mb4 from 'tachyons-spacing';
}

I'd like to deeply merge it to only modify the bottom margin and inherit the rest of the properties (i could softly merge it but i'd have to rewrite all the other statements, thus defeating the purpose / advantages of this library's approach)

header.props.css

.header {
  composes: mb2 from 'tachyons-spacing';
}

The resulting classname would contain both mb4 and mb2 transformed classes, and the one written last in tachyons library would take precedence according to css specificity.

Having access to a custom compose function would allow defining rules such as:
If two or more properties from different sources begin with the same 2 letters, only keep the one which comes from the highest priority source

If i understand properly the caveat in this specific case would be making sure having a localIdentName
starting by [local] (webpack example) in development and production, because react-css-themr only gets the transformed final classNames. So only using a hash as a classname would forbid any direct comparison.

Css selector with attribute not working

Hi,
I am using the React-Toolbox and I understood that it uses the react-css-themr behind the scene.
I am trying to use a simple css selector with an attribute and it is not working.

My react code:

import React from 'react';
import PropTypes from 'prop-types';
import { AppBar } from 'react-toolbox/lib/app_bar';
import theme from './AppBar.css';

const PurpleAppBar = props => (

App Example

);
export default PurpleAppBar;

My css file:

.appBar {
background-color: blue;
}
.appBar[disabled] {
background-color: gray;
}
.appBar:hover {
opacity: 0.5;
}

The AppBar is blue and the hover is working. However since the disabled attribute/property is present I would expect the color to be gray.

Thanks.

[QUESTION] Move to Friends of React

Hi,

sometimes in live it happens that you can not effort spending more time on a project.
Because of newer projects, moving to new technologies or what ever. Guess all understand that but it
is a bit sad when all the energy the developer put into a project like this package is in vain when the project is not maintained anymore and the users get frustrated.

It happens quite often in open source projects and therefore other projects have organizations for that. Friend of symphony, friend of TYPO3, friend of [any project name].

I started now the friend of react organization and hope that we can join the forces to give projects like this a second life.

I would like to ask @javivelasco if he is willing to join. :)
It would be the first package and I hope that this Idea will also work for the react community.

I thank @javivelasco and all the contributors for all the work.

https://github.com/FriendsOfReactJS (Logo can be improved later on ;) )

Deeply merging enhancement

Deeply merging: theme objects are merged and if a key is present in more than one object, the values for each objects are concatenated.

While this does offer the most flexibility, I think it can be improved to override styles set by parent themes/defaults.

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

Take this example. I think it would be cool to see specific nested styles defined in global overwritten if also defined by specialCase.

@javivelasco If I am understanding correctly, this might be pending work on postcss-apply?

Post Edit:

Note current work arounds are to use selectors that are more specific and/or override using !important.

Add theme to childContext

Let's say I have some two components, ChildComponent

@themr('ChildComponent')
class ChildComponent extends Component {
  render() {
    const { theme } = this.props;
    return (
      <div className={theme.child}></div>
    )
  }
}

and ParentComponent

@themr('ParentComponent')
class ParentComponent extends Component {
  render() {
    const { theme } = this.props;
    return (
      <div className={theme.parent}>
        <ChildComponent theme={theme} />
      </div>
    )
  }
}

For the theme prop of ParentComponent to be applied to ChildComponent, it has to be explicitly passed down. It would be a breaking change, but could Themed define getChildContext so that the theme of ParentComponent is automatically part of the theme of ChildComponent?

Question: pure rendering support

Hi!
First, thanks for this project, I was pleasantly surprised to know that css classnames concatination is already done after implementing it myself :) So I'll better go with this project.

I have a question about pure rendering optimization. As you always inject new theme object to props pure rendering checking is no longer working using shallowEqual.

There's of course a way to manually check both props without theme and only theme in shouldComponentUpdate but that would be rather tedious to implement in each themed component.
On the other way I believe it's not responsibility of this library to check shouldComponentUpdate.

So it's clear that to use both themr and pure rendering one should write specific mixin/decorator.
I suggest to inject a Symbol to ThemedComponent prototype and export it from the library. Then when decorating a themed component this symbol will indicate that pure decorator should also check props.theme.

I understand that shouldComponentUpdate could just relay on props.theme existance but that would be too restrictive because I want to support a case when props.theme is set not in themr.

There's also another way to investigate - usage of Immutable.Map for the theme, then shallow equality check could just compare two theme objects.

What do you think?
Maybe there's an easier way?

Typescript compiler fails when using react css themr with error Default export of the module has or is using private name 'TReactCSSThemrTheme'

Typescript Version : 2.4.2
React CSS Themr: 2.1.2

Replication Steps

  1. Create a component that uses themr.
    themr('Identifier')(ComponentName);
  2. Run the Typescript compiler

It will throw the following error
error TS4082: Default export of the module has or is using private name 'TReactCSSThemrTheme'.
error TS4082: Default export of the module has or is using private name 'ThemedComponentClass'.

Invalid prop `theme` of type `string` supplied to `ThemedHeader`, expected `object`.

I love this idea, but I got this error when following the README.md doc:

Warning: Failed prop type: Invalid prop `theme` of type `string` supplied to `ThemedHeader`, expected `object`.
    in ThemedHeader (created by App)
    in App
import defaultTheme from './Header.css'

@themr('ThemedHeader', defaultTheme)
class Header extends Component {
  render() {
    const { theme, logo, children } = this.props
    return (
      <div className={theme['header']}>
        { logo ? <i className={theme['icon']}>{logo}</i> : <Logo />}
        <Menu items={[1, 2, 3]} />
        <span>{children}</span>
      </div>
    )
  }
}

Error happened when pass theme as prop in parent component:

import styles from './App.css'

render() {
  <div>
    <Header theme={styles['header']}/>
  </div>
}
.header {
    background-color: darkgrey;
    padding: 5px;
    color: darkgreen;
}

Feature Request: Provide `themeName` prop for components wrapped by `themr`

Having to deal with the style object generated by module bundlers is typically unfriendly, for all the reasons outlined in react-css-modules#whats-the-problem.

It'd be nice if styling was done through a similar approach to what react-css-modules provides by way of a themeName prop that gets inspected by the HOC and is automatically injected into that children component's classNames. Having a similar API also provides more hints to the user as to which one they should be wrapping their components in: if its styles are meant to be overriden or injected, use themr, otherwise use react-css-modules. However, it's a little bit awkward in that themr could almost be seen as a superset of react-css-modules, but I think there's something to be gained by limiting the themeability of a component through react-css-modules and I see at least one clear use case where I'd use both together -- inline child components:

import React from 'react';
import CSSModules from 'react-css-modules';
import { themr } from 'react-css-themr';
import List from './List';
import theme from './table.css';

class CustomList extends React.Component {
    render () {
        let itemTemplate = CSSModules((name) => (
            <li styleName='item-template'>{name}</li>;
        ), this.props.theme); // or even subset of theme (ie. { 'item-template': this.props.theme['item-template'] })

        return <List themeName="list" itemTemplate={itemTemplate} />;
    }
}

export default themr("ListComponent", theme)(CustomList);
// As an aside, it'd also be nice to expose a normal function operator,
// see https://github.com/gajus/react-css-modules/blob/master/src/index.js#L47

Thoughts? Also pulling in @gajus since he might have some thoughts to share.

I'll probably start working on this soon since I'm finally getting around to using this (awesome work btw, @javivelasco, a lot of this makes a lot of sense and solidified a lot of the ideas I had wanted to implement myself!) but the similarities to react-css-modules means I'll likely pull a lot from that repo or generalize it into some generic style utils.

Themr with pure functions

Does anyone have an example of using themr with pure function components vs. classes and how to provide a default theme and then override that theme from a parent container? I've searched through the readmes and issue log and I'm not finding anything solid as an example for this case.

Here's my thought on how it should work

/* defaultTheme.module.scss */
.button {
    background-color: blue;
    color: white;
}
/* Button.js */
import React from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import { themr } from 'react-css-themr';
import defaultTheme from './defaultTheme.module.scss';

const Button = ({ theme }) => {
    const classes = classNames({ [theme.button]: true });
    return <button className={ classes }>Button</button>;
};

Button.propTypes = {
    theme: PropTypes.any
};

export default themr('Button', defaultTheme)(Button);
/* parentTheme.module.scss */
.button {
    background-color: white;
    color: blue;
}
/* ParentContainer.js */
import React from 'react';
import { render } from 'react-dom';
import parentTheme from './parentTheme.module.scss';
import Button from './Button';

render(
    <Button theme={ parentTheme } />,
    document.getElementById('app');
);

Default theme seems to work fine and the button component displays the default styles no problem. However, nothing happens when the parent passes a theme down.

Add CSS example to MyThemedButton in documentation

I was hoping you could add the CSS below the first example in the documentation. I need to pass down the icon type via a prop but I'm unable to implement a BEM modifier using the context themr with CSS modules.

{ icon ? <i className={theme.icon}>{icon}</i> : null}

Maybe I'm missing something but how would I go about styling a modified icon, say .twitter? When I try this, {theme.icon} is exposed as .icon.

<Button icon="twitter" />

Thank you so much!

themeable() merging issues

In the nested theme case shown below:

const themeA = {};
const themeB = {
    nested: {
        bar: 'bar'
    }
};
themeable(themeA, themeB);

we get an error:
Uncaught Error: You are merging a string "" with an Object,Make sure you are passing the proper theme descriptors.

Document namespaced theme feature

Now it's possible to namespace a theme by using a themeNamespace property for the component. This avoids collisions between modules of different components using the same classname. It's a feature that it's working and it's published but not documented. We need to write a section detailing the API which is very simple!

Multiple class not working

Hi,
I am using the React-Toolbox and I understood that it uses the react-css-themr behind the scene.
I am trying to add a class dynamically to do the styling but it is not working as expected.

My react code:

import React from 'react';
import PropTypes from 'prop-types';
import { AppBar } from 'react-toolbox/lib/app_bar';
import theme from './AppBar.css';

const PurpleAppBar = props => (
<AppBar theme={theme} className="disabled"> App Example </AppBar>
);
export default PurpleAppBar;

My css file:

.appBar {
background-color: blue;
}
.appBar.disabled {
background-color: gray;
}
.appBar:hover {
opacity: 0.5;
}

The AppBar is blue and the hover is working. However since the disabled class is added I would expect the color to be gray.

Thanks.

Error "theme[key].indexOf is not a function" when using with isomorphic-style-loader

Hi,

I am setting up some tests with mocha-webpack for my project which uses react-toolbox. Because the tests are run in node.js environment, I must use isomorphic-style-loader instead of style-loader (see webpack-contrib/style-loader#109). However, because isomorphic-style-loader adds _getCss() and _insertCss() to the imported style object, I get the error "theme[key].indexOf is not a function" from https://github.com/javivelasco/react-css-themr/blob/master/src/components/themr.js#L62:

theme[key] && style[key] && theme[key].indexOf(style[key]) === -1

It happens when key is _getCss or _insertCss and theme[key] is a function instead of a string. The fix is simple like this:

typeof theme[key] === 'string' && theme[key] && style[key] && theme[key].indexOf(style[key]) === -1

I think it is a safe check anyway. I can provide a PR if you want?

Is there anyway to import more than one css?

Hi,

Sometimes, there is need to import two or more css files, and I will give an example:
Says you have one <Feeds> component which contains many <FeedA /> and <FeedB />, and those feeds contains some title which you wanna style same in FeedA and FeedB, but they have also their unique css.

Thanks

Uncaught TypeError: originalValue.split is not a function with RT-beta-6

I'm getting:

Uncaught TypeError: originalValue.split is not a function after updating RT from beta-4 to beta-6...

This error comes from function themeable.
I debugged it a bit and it throws an error because it assumes that originalValue is a string.

However:typeof mixinValue === function, and typeof originalValue === function (when it throws).

EDIT: Now I see... These types are functions because I'm using isomorphic-style-loader.

The description of isomorphic-style-loader says it all:

An alternative CSS style loader, which works similarly to style-loader, but is optimized for isomorphic apps. In addition to what style-loader provides, it allow to render critical path CSS during server-side rendering (SSR), by adding two helper methods on to the styles object - ._insertCss() (injects CSS into the DOM) and ._getCss() (returns CSS string).

Theme css is overridden by default css

Hi,

I'm using this module with default css for component, and try to override it with theme css, but the default css is overriding the theme css:

// SuccessButton.js
import React, { Component } from 'react';
import { themr } from 'react-css-themr';
import * as successTheme from './SuccessButton.scss';

@themr('MySuccessButton', successTheme)
class Button extends Component {
  render() {
    const { theme, icon, children } = this.props;
    return (
      <button className={theme.button}>
        { icon ? <i className={theme.icon}>{icon}</i> : null}
        <span className={theme.content}>{children}</span>
      </button>
    )
  }
}

export default Button;

SuccessButton.scss the default component css

// App.js
import * as MySuccessButton from './theme-b/SuccessButton.scss';
...
const contextTheme = {
  MySuccessButton
};

const content = (
  <ThemeProvider theme={contextTheme}>
    <App />
  </ThemeProvider>
);

Chrome Inspector:
screen shot 2017-04-26 at 12 56 39 pm

(Second css is the theme-b/SuccessButton.scss, which should be the first)

Themr breaks shouldComponentUpdate shallow equal optimization

Themr breaks shouldComponentUpdate shallow equal optimization.
Every new render themr returns a new object reference for theme object, so all subsequent optimizations based on shallowEqual like pure recompose enhancer always returns true.
As nextProps.theme !== props.theme.

I'll create a PR to fix.

Context class order

Hi there!
First of all - thanks a lot for your library!
I was working now quite a bit on some issue I tried to solve.
Let's assume I do have a default button from react-toolbox/lib/button
that I'd love to style using ThemeProvider.
I do add my own styles through RTButton and expect default values to be overwritten.
They'll get rendered into something like class="test__button___hPsIx [...]".
Though when looking at the output, I get the default stylesheet set second in place (which will therefore overwrite my own styles set in test__button___hp...)
The whole class looks something like class="test__button___hPsIx theme__button___1iKuo " whereas second in place is the default lib/button style.

What's the easiest way to overwrite default styles without using !important all the time? Is there by chance a possibility to re-arrange class order - or some other way possible I'm not quite aware of?

Cheers
Korbi

When using ExtractTextPlugin it break the app

Trace: You are merging non-object // removed by extract-text-webpack-plugin with an object default
[0]     at /Users/idangozlan/myproject/node_modules/react-css-themr/lib/components/themr.js:265:23
[0]     at Array.forEach (native)
[0]     at merge (/Users/idangozlan/myproject/node_modules/react-css-themr/lib/components/themr.js:217:22)
[0]     at /Users/idangozlan/myproject/node_modules/react-css-themr/lib/components/themr.js:193:12
[0]     at Array.reduce (native)
[0]     at themeable (/Users/idangozlan/myproject/node_modules/react-css-themr/lib/components/themr.js:192:17)
[0]     at Themed.getTheme (/Users/idangozlan/myproject/node_modules/react-css-themr/lib/components/themr.js:139:156)
[0]     at Themed.calcTheme (/Users/idangozlan/myproject/node_modules/react-css-themr/lib/components/themr.js:145:36)
[0]     at new Themed (/Users/idangozlan/myproject/node_modules/react-css-themr/lib/components/themr.js:102:30)
[0]     at ReactCompositeComponentWrapper._constructComponentWithoutOwner (/Users/idangozlan/myproject/node_modules/react-dom/lib/ReactCompositeComponent.js:298:16) /
[0]     at /Users/idangozlan/myproject/src/server.js:341:17
[0]     at process._tickCallback (internal/process/next_tick.js:103:7)

Any special instructions to ExtractTextPlugin usage?

Index.d.ts outdated in NPM Package

The index.d.ts in the react-css-themr NPM package is not the same as the index.d.ts in this repository. The one in NPM package does not have an option for React.SFC<P> in the export function themr, whereas the the export function in this repo's index.ts has React.SFC<P>.

You will need to publish the package with the latest index.d.ts.

Only for CSS Modules/Class names?

Is the scope for this project just for sending in class names down for theme styles?

Just curious if things like style objects/inline styles/radium are also on your mind for this project, or if that would be out of the realm of what this decorator should handle.

Release for 1.7.2?

I just recently updated a large chunk of my dependencies to fix some issues I was having. Once I updated however, react-css-themr started breaking my build. After some investigation, it appears the issue has already been fixed with this commit.

Wondering when react-css-themr is going to get the hotfix uploaded to npm, or if there is an alternate solution for dealing with the undefined mixinValue variable.

Thanks

What the idea behind deep merging if theme class name contains in parent class

Thank you for this library, I have a small question about COMPOSE_DEEPLY logic.

Having function themeable

I got that merging style and theme objects has following logic.

const style = { a: 'xxx'};
const theme = { a: 'yyy'};
themeable(style, theme); // the result is {hello: "xxx yyy"}

const theme2 = { a: 'xxxzzz'};
themeable(style, theme2); // the result is {hello: "xxxzzz"}

so in first themeable call - classes are combined together but in the second call the theme class wins.

The only situation I see it's needed is to avoid class duplication if theme and style are the same css-module class names - and this will not work in all cases.

const style = { a: 'xxx aaa'};
const theme = { a: 'xxx zzz'};
themeable(style, theme); // the result is {hello: "xxx aaa xxx zzz"} xxx is duplicated

Is any other behaviors why it's needed? Please help to get the logic of this.

More control over merge ordering.

First off, thanks for an awesome project that's helping to solve the CSS Modules theming conundrum 👍 .

Although @themr('injectedStyles', localStyles) allows the injected stylesheet to override the component-local styles (where the override granularity can be increased if that's helpful), there are other more complex ordering strategies that can be useful depending on the project, so I'd really prefer to have specific control over both the number of stylesheets being merged, and the order in which they are merged.

For example, in my case I'd really like to invoke as @themr('siteTheme', localStyles, 'componentOverrides'). My motivation for wanting to do this is that I'd like the ability to very quickly theme an app we have using a site-theme that involves the cross-cutting concerns that affect all components, or go further and start theming specific components when we can justify spending more time, while still retaining the ability to just use the app as it is, with its default theme specified directly within the React components.

I'm not suggesting here that 'react-css-themr' also supports the particular function signature I've used above, but that any number of theme objects (string | object) can be provided as suits the end-developer. It's possible that this might also address @gharwood1's issue?

Context theming works when app is run but not working when app is built

Hi!

I use Context theming in my app, so that I can call a different layout dynamically, depending on a color passed as a get parameter in the url.

When I launch "npm start" to test my app it works perfectly, but when I use it with "npm run build", and visit the created page on my server, the app always loads the default theme, even when calling for a different color.

I am still a beginner in react, hope there is something I don't miss here. Any help would be great.

Code extract:


import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
/*import './index-green.css'
import './index-blue.css'*/
import { ThemeProvider } from 'react-css-themr';
import App from './App';
import registerServiceWorker from './registerServiceWorker';
import { persistStore } from 'redux-persist'
import 'core-js/fn/array/find';
import 'core-js/fn/array/find-index';
import 'core-js/fn/array/map';

import configureStore from './store/configureStore';
const store = configureStore();
persistStore(store, {
    blacklist: [
        'event',
        'polls',
        'chat',
        'userProfile',
        'homeSubPage'
    ]
});

let params = new URLSearchParams(window.location.search);
let color = params.get('color');

let contextTheme;

switch (color) {
  case "blue":
    contextTheme  = {
      theme1: require('./assets/index-green.css'),
      theme2: require('./assets/index-blue.css')
    };
    break;

  default:
  contextTheme  = {
    theme1: require('./assets/index-blue.css'),
    theme2: require('./assets/index-green.css')
  };
}

console.log("color is : "+ color); /* with npm start, blue has blue theme, but with npm run build, blue has green theme as well */

ReactDOM.render(
      <ThemeProvider theme={contextTheme}>
      <Provider store={store}>
        <App />
      </Provider>
      </ThemeProvider>

    , document.getElementById('root'));
registerServiceWorker();


And the scripts part in the package.json:



    "name": "widget2",
    "version": "0.1.0",
    "private": true,
    "dependencies": {
        "autosize": "^4.0.0",
        "core-js": "^2.5.1",
        "es6-promise": "^4.1.1",
        "isomorphic-fetch": "^2.2.1",
        "lodash": "^4.17.4",
        "masonry-layout": "^4.2.0",
        "moment": "^2.19.1",
        "node-sass-chokidar": "0.0.3",
        "normalize.css": "^7.0.0",
        "npm-run-all": "^4.0.2",
        "prevent-parent-scroll": "0.0.6",
        "react": "^15.6.1",
        "react-css-themr": "^2.1.2",
        "react-dom": "^15.6.1",
        "react-facebook-login": "^3.6.2",
        "react-lazyload": "^2.2.7",
        "react-redux": "^5.0.5",
        "react-scripts": "1.0.10",
        "react-transition-group": "^1.2.0",
        "redux": "^3.7.2",
        "redux-persist": "^4.10.1",
        "redux-thunk": "^2.2.0",
        "url-search-params": "^0.10.0"
    },
    "scripts": {
        "start-js": "react-scripts start",
        "start": "npm-run-all -p watch-css start-js",
        "build": "npm run build-css && react-scripts build",
        "test": "react-scripts test --env=jsdom",
        "eject": "react-scripts eject",
        "build-css": "node-sass-chokidar --include-path ./src --include-path ./node_modules src/ -o src/",
        "watch-css": "npm run build-css && node-sass-chokidar --include-path ./src --include-path ./node_modules src/ -o src/ --watch --recursive"
    },
    "homepage": "/widget2/build",
    "devDependencies": {
        "raw-loader": "^0.5.1"
    }
}

Do context theme support tree shaking ?

For example is have code like this

//component.js
import { Button, Radio } from 'my-library';

const Component = props => <div><Button></Button><Radio></div>;

//Theme.js
const contextTheme = {
  Button: require('my-library/button/theme1.css'),
  Radio: require('my-library/radio/theme1.css'),
  Other: require('my-library/other/theme1.css'),
};

<ThemeProvider them={contextTheme} >
     <Component></Component>
</ThemeProvider>

I'm using extract-text-plugin for webpack.
Will my final build contains CSS of other ass well ?

ThemeProvider update theme?

I have 2 themes.

Want to be able to switch between them inside the application via a profile menu.

Wrapped ThemeProvider and pass the new theme to it but children of ThemeProvider does not receive the new theme. Childrens render-functions triggers but the props are the same. Problem with context?

Merging non class name styles

I'm looking to create a HOC that can take string props, turn them into relevant styles and pass them in to be merged with the child component. I'm wondering if I can achieve this with themr.

Here is an example:

Child:

import styles from 'styles.scss'

@withPropsStyles
@themr(styles)
class Child extends Component {
  render () {
    return <p className={this.props.theme.wrapper}>{this.props.children}</p>
  }
}

HOC:

const withPropsStyles = ChildComponent => {
  return class extends Component {
    constructor () {
      const styles = {
        wrapper: {
          marginTop: this.props.marginTop 
        }
      }
    }
    render () {
      return <ChildComponent theme={styles.wrapper} />
    }
  }
}
export default themr(withPropsStyles, styles)

Loading:

<Child marginTop='2em' />

In reality, I want a consistent API across different components to tweak similar styles (I wouldn't pass actual values into props—like above—but modifiers), but the premise is the same.

  • Am I approaching this correctly?
  • Can I use the styleName decorator with themr?

Default theme is clobbered by parent theme

I love the idea of this module, but I'm having some trouble getting default themes working with deep merging... For example:

Themed component:

  @themr('MyThemedComponent', styles)
  class ThemedComponent extends React.Component {
    render() {
      const { theme } = this.props;

      return (
         <div>
            <button className={theme.firstButton} />
            <button className={theme.secondButton} />
         </div>
      )
    }
  }

Default theme:

.firstButton { color: red; }
.secondButton { color: green; }

At this point, everything is working fine and my component is showing its default theme of 1 red button and 1 green. The problem arises when trying to merge styles from a parent component...

Parent:

<ThemedComponent theme={theme} />

Parent's theme:

.secondButton { color: yellow; }

Now the problem here is that the ONLY style that is included is .secondButton. The .firstButton class from the default theme is completely missing... Any ideas of why this might be happening or things I could look into? I'd be happy to post my webpack config if that would help as well.

Inject parent class into children

Let's imagine we have a button component with a base theme.

// SuccessButton.js
import React, { Component } from 'react';
import { themr } from 'react-css-themr';
import successTheme from './SuccessButton.css';

@themr('MySuccessButton', successTheme)
class Button extends Component {
  render() {
    const { theme, icon, children } = this.props;
    return (
      <button className={theme.button}>
        { icon ? <i className={theme.icon}>{icon}</i> : null}
        <span className={theme.content}>{children}</span>
      </button>
    )
  }
}

export default Button;

And we want to extend its styles inside of our own component.

import React from 'react';
import SuccessButton from 'SuccessButon';
import style from './Section.css';

export default () => (
  <section className={style.section}>
    <div className={style.content}>
        <SuccessButton theme={style}>Yai!</SuccessButton>
    </div>
  </section>
);

Section.css

.content { border: 1px solid red; }
.button  { text-transform: uppercase; }

As you can see the problem here is the same name of content node. We can't use the same css file for styles of our own component and component we use inside if they have the same class names.

We may try to update our own component and move styles into two different files:

import React from 'react';
import SuccessButton from 'SuccessButton';
import style from './Section.css';
import styleButton from './SectionButton.css';

export default () => (
  <section className={style.section}>
    <div className={style.content}>
        <SuccessButton theme={styleButton}>Yai!</SuccessButton>
    </div>
  </section>
);

Section.css

.content { border: 1px solid red; }

SectionButton.css

.button  { text-transform: uppercase; }

It will work. But what if we want to apply some styles on SuccessButton that depends of styles of our section component. I.e. make button's background red when hovering section.

It's possible to do when styles of our own component and SuccessButton are in the same file but impossible when they are in different files as they have its own scope.

So my idea is to have ability to "inject" specific parent classes to appropriate child classes.

Update the last example:

import React from 'react';
import SuccessButton from 'SuccessButton';
import style from './Section.css';
import styleButton from './SectionButton.css';

export default () => (
  <section className={style.section}>
    <div className={style.content}>
        <SuccessButton theme={styleButton} mapClasses={{button: styles.redButton}}>Yai!</SuccessButton>
    </div>
  </section>
);

Section.css

.content { border: 1px solid red; }
.section:hover .redButton { background: red }

SectionButton.css

.button  { text-transform: uppercase; }

redButton class will be added to the button node and will be available from our component.

I use wrapper for themr now in my project to add support of mapClasses property.

import compose from 'recompose/compose';
import mapProps from 'recompose/mapProps';
import { themr } from 'react-css-themr';

const themrmap = (...args) => compose(
  themr(...args),
  mapProps(({ ...props, mapClasses, theme }) => ({
    ...props,
    theme: mapClasses
      ? Object.keys(mapClasses).reduce((p, c) => ({
        ...p,
        [c]: `${theme[c]} ${mapClasses[c]}`,
      }), theme)
      : theme,
  })),
);

export default themrmap;

Do you think you can use this idea in the themr? Sorry If my explanation is not clear enough. Feel free to ask any questions.

Avoid passing down own props

A child component doesn't need to receive composeTheme prop. People are getting warning from this and it should be extracted instead of passed 🤕.

Coming from here

Backport switch to `prop-types` package to the v1 branch to add compat with react 16

After updating to react 16, I encountered the following error:

TypeError: Cannot read property 'shape' of undefined
./node_modules/react-css-themr/lib/utils/themr-shape.js
node_modules/react-css-themr/lib/utils/themr-shape.js:7

Would be great to add a 1.8 release with the switch to the prop-types package to maintain compatibility for people like us stuck on toolbox v1 for now.

Has this project been abandoned?

I've noticed that quite a few issues out here have not been commented on by the maintainer and there are 8 pull requests that have been sitting waiting 6 months or more. Is there a more active fork or is there another alternative to react-css-themr that is better or more frequently updated.

For the record themr works well for me in how I'm using it today. But I worry with how quickly the market moves that it may become incompatible with some future release of React. I want to make sure I'm planning ahead.

Receiving/passing ref

Hi! To continue our conversation at #46

In 2.1.0 you added mapThemrProps with example:

function mapThemrProps(props, theme) {
  const { composeTheme, innerRef, mapThemrProps, themeNamespace, ...rest } = props;
  return { ...rest, withRef: innerRef, theme };
}

But withRef is usually boolean, for example in react-redux, so it doesn't work.

It was case, when @themr hoc wraps another hoc. But what if vice-versa, I have places where it's wrapped by @connect.

It used to be simple convention: In hoc's ref handler function it checked if underline instance also has getWrappedInstance and used it to take ref from it, so it's a chain when you can reach real component no matter how many hocs do you have on top of it

    saveComponentRef(instance) {
      this.instance = instance && instance.getWrappedInstance ? instance.getWrappedInstance() : instance;
    }

But now having somewhere withRef, somewhere innerRef or mapThemrProps I'm confused.
Feels like it has added complexity and problems instead of solving anything

Possible drop of support of using themr as decorator with TypeScript

Currently I'm trying to rewrite themr in TS to provide consistent up-to-date typings and I'm in the middle of the struggle started in #39
The problem is that switching from correct React.ComponentClass<P> to component constructor type (with new()) is completely broken with strictNullChecks on, here's my comment

@themr('foo') //error - Type 'null' is not assignable to type 'Element'
class FooClass extends React.Component<any, any> {
	render()/*: JSX.Element | null //ok when uncommented*/ {
		return <div>hi</div>;
	}
}

Moreover, decorating a class with @themr complains on every lifecycle method (like componentWillMount) of decorated class as TS can't cast it to ComponentClass. On the other hand, calling themr as a function on existing class works fine - seems like decorators are broken in TS.
Well, at least they are still behind experimental flag.

@mpodlasin @odensc Could you please check if you do use decorators instead of composing HOCs separately from actual component class or sfc which is more natural for react ecosystem in general especially when using recompose?

If it's ok for you and if it's worth dropping experimental decorators, I would open a PR with a fresh new themr fully in typescript.

The good news are that now I'm finally able to overwrite props in decorated component with their non-necessary versions, thanks to this comment
So that themed component does not require theme object to be passed via props but still checks its type if it is present.

UPDATE: still it will be possible and absolutely transparent to use themr as decorator for current ES6 users.

Default theming example

The README.md shows this example where a theme is passed in as a prop to override the default theme:

import style from './Section.css';
[...]
<SuccessButton theme={style.button}>Yai!</SuccessButton>

But this didn't work for me--since style is an object like { button: 'button-1jq92' }, style.button is a string and can't be used to override the default theme.

I had to do something like this:

<SuccessButton theme={ {button: style.button} }>Yai!</SuccessButton>

or just theme={style}.

Am I missing something, or is this a mistake in that example?

npm build diverges from repository

Hi!

I still have a problem with compiled sources (#29). Seems that npm's lib dir differs from the one I have after building the repo, so I have to npm link to get this work in IE :(

Here is my script to reproduce the issue:

#!/bin/bash

# clone the original repo and build
git clone [email protected]:javivelasco/react-css-themr.git
pushd react-css-themr
npm i
popd

# install package from npm to another directory
mkdir react-css-themr-npm
pushd react-css-themr-npm
echo {} > package.json
npm i react-css-themr
popd

# compare
diff react-css-themr-npm/node_modules/react-css-themr/lib/components/themr.js react-css-themr/lib/components/themr.js

And here is the output:

7,9c7
< var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol ? "symbol" : typeof obj; };
< 
< var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
---
> var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; };
12a11,12
> var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
> 
65c65
<   var options = arguments.length <= 2 || arguments[2] === undefined ? {} : arguments[2];
---
>   var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
69,72c69,71
<     var _DEFAULT_OPTIONS$opti = _extends({}, DEFAULT_OPTIONS, options);
< 
<     var optionComposeTheme = _DEFAULT_OPTIONS$opti.composeTheme;
<     var optionWithRef = _DEFAULT_OPTIONS$opti.withRef;
---
>     var _DEFAULT_OPTIONS$opti = _extends({}, DEFAULT_OPTIONS, options),
>         optionComposeTheme = _DEFAULT_OPTIONS$opti.composeTheme,
>         optionWithRef = _DEFAULT_OPTIONS$opti.withRef;
94c93
<         var _Object$getPrototypeO;
---
>         var _ref;
102c101
<         var _this = _possibleConstructorReturn(this, (_Object$getPrototypeO = Object.getPrototypeOf(Themed)).call.apply(_Object$getPrototypeO, [this].concat(args)));
---
>         var _this = _possibleConstructorReturn(this, (_ref = Themed.__proto__ || Object.getPrototypeOf(Themed)).call.apply(_ref, [this].concat(args)));
118,119c117,118
<           var themeNamespace = props.themeNamespace;
<           var theme = props.theme;
---
>           var themeNamespace = props.themeNamespace,
>               theme = props.theme;
167,169d165
<           var _props = this.props;
<           var composeTheme = _props.composeTheme;
<           var themeNamespace = _props.themeNamespace;
171c167,170
<           var props = _objectWithoutProperties(_props, ['composeTheme', 'themeNamespace']); //eslint-disable-line no-unused-vars
---
>           var _props = this.props,
>               composeTheme = _props.composeTheme,
>               themeNamespace = _props.themeNamespace,
>               props = _objectWithoutProperties(_props, ['composeTheme', 'themeNamespace']); //eslint-disable-line no-unused-vars
215c214
<   var original = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0];
---
>   var original = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};

Notice this line:
var _this = _possibleConstructorReturn(this, (_ref = Themed.__proto__ || Object.getPrototypeOf(Themed)).call.apply(_ref, [this].concat(args)));
It seems to be transpiled from super() call in Themed. The key thing is Themed.__proto__. Babel manually creates this property in it's inheritance method implementation for browsers that don't support Object.setPrototypeOf() so IE10 can only access parent Object from this property. Previous compiler version generates the following:
var _this = _possibleConstructorReturn(this, (_Object$getPrototypeO = Object.getPrototypeOf(Themed)).call.apply(_Object$getPrototypeO, [this].concat(args)));
This is what npm package contains.
In IE10 Object.getPrototypeOf() returns not we are expect and we have an error.

Please check the npm package. I don't actually understand why could it be diverged from the repo because package.json does have a prepublish script that seems to be correct.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.