adamterlson / cairn

Hierarchical shared and component-based style definitions with selector-based style application, for React Native

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Support component-specific styles

adamterlson opened this issue · comments

Cairn was designed first to provide a hierarchy of types and subtypes. This works great for totally reusable global styles, for example creating a shared suite of button styles, input types, typography, etc.

In addition, it should have an answer for appending in component-specific styles within the component file itself (a la CSS Modules). So given a login page, being able to group all styles related to it that are specific and non-reusable and use them in conjunction with the global styles would greatly add to the organization of things.

One possible way to do this is to allow for passing multiple objects to the cairn() factory, or if the global styles are "compiled" already, providing an "extend" function.

Feedback is welcome on this!

After a bit of pondering on it, the extend method would be a good way to do what you're looking for if the overall plan is to keep component-specific styles inside the components themselves. What are your thoughts on the the extend method's actual use? Would it only return a modification of the existing style object, or would it be capable of adding to it as well? If opting to do both, then there'd be no reason to pass multiple objects to the factory. It seems a bit overkill to need to mass-produce entire stylesheets for specific components, when the existing global style object could be inherited and modified as needed.

Would it only return a modification of the existing style object, or would it be capable of adding to it as well?

I wouldn't modify anything. How Cairn works over all is that no object truly ever overwrites another and I never actually collapse styles either. Instead, styling in React Native is done via arrays of style references styles={[styles.foo, styles.bar]} where the last style in the array has precedence. All Cairn does is construct this array in the appropriate order.

When it comes to extend, it would return a new selector that would "have access" to the extended styles, it would not modify the global styles in any way. So:

base = cairn({ ... });
stylesA = base.extend({ bar: { height: 200 } });
stylesB = base.extend({ baz: { height: 200 } });
stylesC = stylesB.extend({ foo: { height: 200 }, baz: { height: 300 } });

// This will have no visual style as base doesn't know about bar and baz
// styles={[]}
<View {...base('bar baz')} />

// This will have the styling of bar, but not baz
// styles={[ stylesA.bar ]}
<View {...stylesA('bar baz')} />

// This will have the styling of baz, but not bar
// styles={[ stylesB.baz ]}
<View {...stylesB('bar baz')} />

// This will have the styling of both baz and foo, but not bar
// Also styles from extensions have precedence
// styles={[ stylesB.baz, stylesC.baz, stylesC.foo ]}
<View {...stylesC('bar baz foo')} />

Alright, so let's say there's this global stylesheet with nested styles:

export const style = cairn({ 
  foo: {
    backgroundColor: 'green',
    fontSize: 24,
    bar: {
      width: '50%'
      baz: {
        fontSize: 14
      }
    }
  }
}, styles => StyleSheet.create(styles));

If I were to import that and run the theoretical extend on it, like so:

const stylesB = globalStyles.extend({
  foo: { bar: { baz: { fontSize: 100 } } }
});

...this would add a new style for baz, and thus:

// styles={[ {...globalStyles.foo/bar/baz}, stylesB.foo, stylesB.foo.bar, stylesB.foo.bar.baz ]}
<View {...stylesB('foo.bar.baz')} />

React Native would take stylesB.foo.bar.baz and "override" globalStyles.foo.bar.baz, giving the larger font size to that element, correct? Just making sure I'm understanding this all correctly. As long as I'm on the same page with you, I know I would personally prefer using extend in this way on the global styles as opposed to pushing multiple objects at the beginning. It keeps the component specific style with the component so it's easily visible, it would be trivial for the writer to implement without any real learning/time curves. Bonus points if it were set up to correctly extend without writing an empty object nest down to baz?

React Native would take stylesB.foo.bar.baz and "override" globalStyles.foo.bar.baz, giving the larger font size to that element, correct?

Correct!

I would personally prefer using extend in this way on the global styles as opposed to pushing multiple objects at the beginning

Totally agreed, I think the best way to do that is to define it right in the module itself (or in a paired style file such as in react-universal-hot-example) via extend.

Bonus points if it were set up to correctly extend without writing an empty object nest down to baz?

There be dragons. Consider a global style stylesheet of:

{
    foo: {
        bar: { ... }
    },
    baz: {
        bar: { ... }
    }
}

Now in your component styles with which global is extended you write this:

{
    bar: { ... }
}

Which of the above should it override? This would cause unintended side effects and be pretty much impossible given that nested keys need not be globally unique. Consider however, that this works just fine today if you don't want to nest out the object explicitly:

{
    'foo.bar.baz': { ... }
}

Thanks for the discussion!

No worries! My initial thought on the no-nesting was to do it explicitly in a name string like you just did. An eventual extend method should handle either the nested object (for changing multiple styles down the tree) or an explicitly named object, though.

Pushed commits for the next Cairn version which includes extend. Check out the readme, let me know what you think!