javivelasco / react-css-themr

Easy theming and composition for CSS Modules.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

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

sohkai opened this issue · comments

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.

I read the entire docs and don't see how any of this is different from react-css-modules. react-css-modules already provides ways to overwrite component styles, e.g. https://github.com/gajus/react-css-modules#extending-component-styles

Can you provide an example solving a problem with/ without react-css-modules/ react-css-themr?

@gajus In cases where you have a component tree but only want to change a few component's styles, it gets much harder to apply that change through just react-css-modules since its styling is only available to its direct parent component. react-css-themr provides a way to restyle these components from the top level, or really, from any level, down depending on your usage of the ThemeProvider.

To be more concrete, let's say we had a List that had a bunch of ListItems, that each had a Button. The component tree would be something like:

List
  ListItem
    Button
  ListItem
    Button
  ...

If we wanted to only restyle the Button components, say, depending on which whitelabel or which user it is, you'd have to pass that style from the top all the way down with only react-css-modules if you didn't want your Button to be directly coupled with the logic for your styling. In some cases, this is impossible to realistically accomplish since it'd mean passing down all possible child component styles through each component, or, if you don't control some of the middle components, it can actually be just impossible (eg. if one of those middle components wraps your component in a HOC and that HOC doesn't pass through all the props).

With react-css-themr, you'd just have to declare which style you want your Button to use at the top and the Button will use that style, no matter how far down or how many "uncontrolled" layers there are.

@gajus In cases where you have a component tree but only want to change a few component's styles, it gets much harder to apply that change through just react-css-modules since its styling is only available to its direct parent component.

You can achieve theme inheritance using custom properties:

const Viewport = () => {
    const button = require('./button-theme.css');

    const themes = {
        button
    };

    return <App themes={themes} />;
};

const App = CSSModules((props) => {
    return <div styleName='app'>
        <Button styles={props.themes.button || null} />
    </div>;
});

const Button = CSSModules(() => {
    return <button type='button' styleName='button'>Click me</button>;
});

or using React context.

react-css-themr provides a way to restyle these components from the top level, or really, from any level, down depending on your usage of the ThemeProvider.
If we wanted to only restyle the Button components, say, depending on which whitelabel or which user it is [..]
With react-css-themr, you'd just have to declare which style you want your Button to use at the top and the Button will use that style, no matter how far down or how many "uncontrolled" layers there are. [..]

Can you produce a code example?

I have read the documentation, but I am unable to find the particular pattern that you are describing.

Furthermore, react-css-modules could add a custom method used to resolve values from the properties, React context or anywhere else, e.g.

const Viewport = () => {
    const button = require('./button-theme.css');

    const themes = {
        button
    };

    return <App themes={themes} />;
};

const App = CSSModules((props) => {
    return <div styleName='app'>
        <Button styles={props.themes.button || null} />
    </div>;
};

const Button do {
    /**
     * A custom function used to overwrite the default styles.
     */
    const resolveStyles = (props, defaultStyles) => {
        // A sample implementation.
        if (props.styles) {
            return props.styles;
        }

        if (props.themes && props.themes.button) {
            return props.themes.button;
        }

        return defaultStyles;
    };

    const styles = {};
    const options = {
        resolveStyles
    };

    CSSModules(() => {
        return <button type='button' styleName='button'>Click me</button>;
    }, styles, options);
};

Can you produce a code example?

I assume you mean how react-css-themr themes components through ThemeProvider? There's an example at context theming, but as an example:

import loggedOutButton from 'loggedOutButton.css';
import loggedInButton from 'loggedInButton.css';

const theme = isUserLoggedOn ? { Button: loggedOutButton }
                             : { Button: loggedInButton };

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

render(content, ...);

Now any Button in the app will be styled either with the logged out styles if the user's logged out, or the logged in styles if they're logged in. There's no need to do any prop pass-throughs or context managing yourself.

Yes, this is very similar to what you described with custom properties or context (all ThemeProvider actually does is add the theme to context). That's why I said it's somewhat awkward, because react-css-themr and react-css-modules can achieve very similar things but that the former is more of a superset of the latter because it provides theming capabilities out of the box, rather than having to figure it out yourself.

I suppose at the other end of the suggestion spectrum, the ability to theme could also be baked into react-css-modules rather than making react-css-themr's API closer to `react-css-modules. In any case though, I think it'd still be nice to be able to explicitly declare that a component can either be rethemed or not.

I suppose at the other end of the suggestion spectrum, the ability to theme could also be baked into react-css-modules rather than making react-css-themr's API closer to `react-css-modules. In any case though, I think it'd still be nice to be able to explicitly declare that a component can either be rethemed or not.

Theme-ing, in one way or another, is super important to react-css-modules. However, I think that react-css-modules should not provide implementation of theme-ing, it should only provide hooks required to implement theme-ing. Therefore I think that resolveStyles is quite an elegant solution.

If you'd like to discuss this further, please raise an issue in react-css-modules repository.

Hi guys! I couldn't read the whole thread yet but I wanted to drop a quick comment before going to sleep. First of all, congrats @gajus for the project, I looked at it long ago and it's a great work, I'm not familiar with the current state though. Thanks @sohkai for your kind words! :)

I've written this small module to be used with react-toolbox. It's quite difficult to customize components so what I wanted to do at first was just to provide a way to add classes to the component following an API.

For example, imagine a menu with a pointer that can be positioned in multiple places depending on some props. Typically the pointer will be done with pseudoelements and, in case you want to customize the background, for that specific class what you'd do is to change the border-color. Just one property. That's way instead of provide new styles for the menu, you just add classes using the theme property. The HOC appends them for you. I don't know if nowadays it's possible to do this in react-css-modules, but if it's I couldn't figure out before writing this.

Another example is to provide the whole theme in a single place. In react toolbox you should be able to import a Raw component and I wanted to give the ability to customize a full application without changing toolbox imports. This means you'd be importing a RAW toolbox component and then providing the theme somewhere else. The best way to do this without spreading props through the tree was by using contexts. So I came up with this idea of setting an object with the relation between component ids and theme objects.

More or less those are the ideas behind. If all of this can be done I'd happily move to react-css-modules :) Tomorrow I'll read better the messages and try to be more accurate in my answers. Thanks for your time checking on this lib!

@gajus Ah, I see why you proposed resolveStyles now. At first I was hesitant because it's on a component level (and therefore tedious), but I agree with you that providing hooks in this style is more flexible and a better API.

The HOC appends them for you. I don't know if nowadays it's possible to do this in react-css-modules, but if it's I couldn't figure out before writing this.

@javivelasco This isn't possible right now with react-css-modules since it doesn't resolve styles in the way you described, but with resolveStyles, this would just fall under that. I think most of what's in themr.js, and especially the themeable function that does the class merging, could go into resolveStyles to have react-css-modules resolve themes like you described.


@gajus @javivelasco Given that someone else other than react-css-modules should provide theming, do you think it's the right approach if this library (or maybe a new one since this is a rather different approach philosophically) wrapped around react-css-modules, provided a HOC that injected a default resolveStyles into react-css-modules's HOC, and included a ThemeProvider that could add themes to the context for the resolveStyles to pull from (basically ThemeProvider as-is)?

I think there's one issue here where someone is wondering if this library is meant just for css-modules or other methodologies; not sure what your plans are on that @javivelasco. One thought is that another HOC could be provided that only does the class name resolving without wrapping around react-css-modules.

resolveStyles is a function. There is no need (as far as I can think) for an additional HoC.

As you suggested, it would be a separate module altogether. I could list them under a new section in react-css-modules documentation.

I will experiment over the weekend with resolveStyles to discover useful patterns. If you already have suggestions for a module that could utilise resolveStyles, please share. I will happily contribute and work together to create a flexible API to integrate your requirements.

If I understand correctly, resolveStyles would be separately available on every component wrapped with react-css-modules's HOC? If I wanted a single way to resolve styles, I'd need a way to inject that into all those instances, right? I'd rather not have to import a resolveStyles function from a module and put that into every usage of react-css-module's HOC...

I think the one big thing that needs some attention in regards to resolveStyles is supporting the use case of "appending classes" (see themeable()). I think if this is done in resolveStyles, it should work without any problems on react-css-modules's side, but I'm not 100% sure.

If I understand correctly, resolveStyles would be separately available on every component wrapped with react-css-modules's HOC?

I was thinking more about providing at the time of wrapping the component using react-css-modules, such as in the earlier example:

const Button do {
    /**
     * A custom function used to overwrite the default styles.
     */
    const resolveStyles = (props, defaultStyles) => {
        // A sample implementation.
        if (props.styles) {
            return props.styles;
        }

        if (props.themes && props.themes.button) {
            return props.themes.button;
        }

        return defaultStyles;
    };

    const styles = {};
    const options = {
        resolveStyles
    };

    CSSModules(() => {
        return <button type='button' styleName='button'>Click me</button>;
    }, styles, options);
};

providing at the time of wrapping the component using react-css-modules

That's what I mean; I think it'd be tedious to do this over and over again if you're just going to use the same resolve function, as you probably would in this case of theming.

Another benefit to using another HOC is that it could expose an API just for its style resolver, for example, if a component had styleName, it wouldn't get resolved, but if it had themeName, it would. Not entirely sure how useful that might be aside from enforcing some base styling, but things like this push me towards using another HOC.

Sorry @sohkai I cleared the notification by mistake and totally forgot about this. Did you get any progress? I've lost a bit of context on the thread and maybe you get some thoughts clear during this time. May you give an update? Check this PR #14 coming for next release. I think you may find it interesting, what do you think??

@javivelasco Unfortunately I got pulled away into doing something else and haven't been able to think about this. I think @gajus and I's conclusion was that the work should be done first on react-css-modules but I haven't had the time to push a PR there. I think if you want, you can close this issue until it becomes apparent that work also needs to be done here.

Ok! If there is something we can do to improve integration just reopen. It looks like the lib is gaining popularity and I'd love to make it as good as possible :)
Thanks and sorry again for my late response @sohkai !!