Skip to content

Instantly share code, notes, and snippets.

@mattmccray
Last active August 29, 2015 14:09
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mattmccray/3a3c7aaae734a0fc3f38 to your computer and use it in GitHub Desktop.
Save mattmccray/3a3c7aaae734a0fc3f38 to your computer and use it in GitHub Desktop.
StyleBuilder for React components

StyleBuilder

Simple JS-based StyleSheets with no cascading.

That's right. There is intentionally no support for 'externalizing' the CSS, or any cascading support. This is a feature. Be explicit. It's best for everyone.

Also a feature: CSS variables are expanded as soon as the objects are merged. So no, you can't change a variable and have it automatically update any currently rendered nodes.

Example usage:

var S= require('./style-builder.js')

S.vars({
  defaultColor: 'dodgerblue',
  shadowColor: 'rgba(0,0,255, 0.2)'
})

var Button= S({
  border: '1px solid @defaultColor',
  color: '@defaultColor',
  boxShadow: '0 0 0 2px @shadowColor' // Auto-prefixed via react-prefixr
})

// No cascade, but you can 'Subclass' existing styles
var DangerButton= S(Button, {
  borderColor: 'red',
  color: 'red
})


module.exports= React.createClass({

  render() {
    var {styles, ...rest}= this.props
    return (
      <div {...rest} styles={ S(DangerButton, styles || {}) }>
        { this.props.children }
      </div>
    }
  }

})

Todo

  • Support "CSS-style" variables: var(var-name)
  • Mayhaps: Add support for deferring variable merging until usage. It would allow for a more dynamic stylesheet, but it would no longer be a simple object.

Licence

Copyright © 2014 Matt McCray matt@elucidata

This work is free. You can redistribute it and/or modify it under the terms of the Do What The Fuck You Want To Public License, Version 2, as published by Sam Hocevar. See http://www.wtfpl.net/ for more details.

// Meant for use in a Browserify/Webpack environment...
var log= require('debug')('oui:style-builder'),
type= require('elucidata-type'),
prefix= require('react-prefixr'),
var_parser= /(@[\d\w]*)/gim,
VARS= {}
function StyleBuilder( ...rulesets ) {
var merged= {}, ruleset, sel, rule, i, l
for ( i= 0, l= rulesets.length; i < l; i++ ) {
ruleset= rulesets[i]
for ( sel in ruleset ) {
if ( ruleset.hasOwnProperty(sel) ) {
rule= ruleset[ sel ]
merged[ sel ]= expandRule( rule )
}
}
}
merged= prefix(merged)
// log('returning merged style', merged)
return merged
}
function expandRule( rule ) {
if( type.isNotString( rule) ) return rule
return rule.replace( var_parser, function( varName){
var key= varName.substring(1)
if (! VARS.hasOwnProperty( key)) {
throw new Error("Invalid variable name: "+ varName)
}
return VARS[ key ]
})
}
function assignVars( values ) {
var variable
for ( variable in values) {
if ( values.hasOwnProperty( variable)) {
VARS[ variable]= expandRule(values[ variable])
}
}
// log("globalVars", VARS)
return VARS
}
module.exports= StyleBuilder
module.exports.vars= assignVars

Ideas

Pretend for a second that I were to add support for deferred variable merging. What would that look like?

  • Maybe have an apply( extraVariables={} ) method?
var S= require('style-builder')

// These become 'default' vars
S.vars({
    fontFamily: '"Helvetica Neue", Sans-Serif',
    fontSize: '24px',
    color: 'dodgerblue'
})

Typography= S({
    fontFamily: '@fontFamily',
    fontSize: '@fontSize',
    lineHeight: '@fontSize'
})

Button= S( Typography, {
    border: '1px solid @color',
    color: '@color',
    background: 'linear-gradient(to bottom, #FFF 0%, #F0F0F0 100%)'
})

// ... Later in a render() function...

    render() {
        return (
            <div>
                <button styles={ Button.apply() }>Save</button>
                <button styles={ Button.apply({ color:'red' })}>Delete</button>
            </div>
        )
    }

If I were to do that, I'd create my theme (module) such that the index exported merged versions of the styles, but if you wanted to customize them, you'd load them explicitly.

my-theme/index.js

module.exports= {
    Button: require('./button').apply()
}

Then you could use it directly and it'd be zippy since it's just a plain object:

    <a href="#" styles={Button}>My Link Button</a>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment