0.4.11 • Published 8 years ago

cssculpt v0.4.11

Weekly downloads
3
License
MIT
Repository
github
Last release
8 years ago

CSSculpt

npm npm Codacy Badge Build Status Dependencies Dev Dependencies node

We build our components in hierarchical trees, like nested react components. Why not define their styles in the same way?

When I write CSS, I use it to style a component. A component has properties, and states. Simple enough... but what happens when we have, say, a .btn style with 7 sizes, 4 colors, and then multiple states like :hover, disabled, etc. They all start conflicting—things get hard to reason about. That's what's so awesome about react; it makes things very simple.

Lets apply some of the concepts from stateless components && the HOC pattern to building CSS.

Example

disclaimer: all of the following examples are purely hypothetical right now. Although there's already a lot of code written, cssculpt is still in its infancy. This is what it may look like:

import { sculpt, kiln } from 'cssculpt';
import { Color, darken } from 'cssculpt/chroma'; 

const buttonSculpture = sculpt('.button', (props, node) => {{
    color: props.textColor,
    /** optional destructuring for appropriate css rules **/
    font: {
        weight: 500
    },
    borderRadius: '2px',
    border: `1px solid ${darken(props.baseColor, 0.4)}`, // SCSS like utility functions a plenty 
    /**
     * the next line is an example of some shorthand; without a unit (eg: '4px') 
     * it defaults to px  -- and you'll be able configure that default unit
     **/
    padding: [4, 2],
    background: props.baseColor,    
    '&:hover': { // :pseudo selectors, @atrules welcome 
        background: darken(props.baseColor, 0.3)
    }
});
// render the style with a given set of props
const buttonStyle = kiln.fire(buttonSculpture, {
    baseColor: '#ccc',
    textColor: Color.black
});
const styleSheet = StyleSheet();
styleSheet.add(buttonStyle);
console.log(styleSheet.getCSS());

Console Output:

{
    '.button': {
        'color': '#000',
        'font-weight': 500,
        'border-radius': '2px',
        'border': '1px solid #666'
        'padding': '4px 2px',
        'background': '#ccc',
    }, 
    '.button:hover' {
        'background': '#808080'
    }
}

Nothing revolutionary, so far... Here's where things get interesting.

Normally with css. If we wanted to make a small variation on this, like say a button that was blue, we would have to manually go through and create new declarations under rules that have more specific selectors; targeting only a subset eg: .button.button-blue. On the surface that's not that difficult. We've been doing it for years, you're used to it.

But what about the :hover state? You need to specify a background for .button.button-blue:hover. Again not that hard, but say you have an entire pallet of colors, and you decide that :hover should be a little darker. Following the traditional convention, you now have to go back and tweak those values, for every color on the pallet. That's a pain in the ass. Imagine we could do this:

const colors = { red: '#ff0000', green:'#00ff00', blue: '#0000ff' /** orange, indigo, etc.. */};
const coloredButton = buttonStyle.mold((props, parentProps, node) => {
    return node.parent({ ...parentProps, ...props });
});
const colorfulButtons = colors.map((v, k) => {
    return coloredButton.recast(`&.button-${k}`, { baseColor: v });
})

Okay, so this is where it starts to get fun. Let me explain what is happening:

  • buttonStyle.mold() takes one parameter, a callback that looks like:

    mold(props: Object, parentProps: Object, node: TreeNode)

    • props is the object that will be passed when calling coloredButton.recast

    • parentProps is the props object that was used by kiln.fire to create buttonStyle

    • node.parent(props: Object) this calls the "render" method we created for buttonSculpture with sculptNode waaayyyy back at the begining. Its returns a new StyleTree.

    So basically we "re-render" the original sculpture, but we merge our new props from recast props into the original set of props passed by `kiln.fire. This way the rendering logic remains the same, but uses the new set of values. This is the simplest example of whats possible! Just using a different set of props here; later on we'll start merging in custom rules and more.

  • for each color, we compute a new style by calling:

    > `recast(selector: String, props: Object)`
    
    We specify a new root selector for this component for each color. They'll look like:
    `.button.button-red`, `.button.button-green`, etc. But the key part here is: those rendered
    variations are diffed against the original `buttonStyle` and values which are the same are omitted.
    
    And yes, it takes `:hover` and anything else nested in there into consideration too!

    `

So if we spat that out into css, this would be the result

{
    /** ...same output from the first example...  **/
    
    '.button.button-red': {
        'background': '#ff0000',
        'border': '1px solid #99000'
    },
    '.button.button-red:hover': {
        'background': '#b30000'
    },
    '.button.button-green': {
        'background': '#ff0000',
        'border': '1px solid #009900'
    },
    '.button.button-green:hover': {
        'background': '#00b300'
    }
    /** ...etc, etc... **/
}

Sure this could be achieved with some complex, temperamental SASS/LESS mixins, but this is just the begining of what cssculpt can accomplished.

Contributions

Thanks for reading! I'm working hard on this because I think it has the potential to be huge. If you have any feedback at all, please open an issue. This is a pretty ambitious project, so if anyone wanted to join the effort, in any capacity, I would be greatly humbled.

Roadmap

the sky ain't no limit

There's so much that could be added to this, so much that is possible; here's just a few things:

  • crazy nesting with custom syntax
    {
        .Button {
            
            ...buttonStyles
            
            & i.icon {
                ...iconStyles
                
                ^:hover & { // .Button:hover i.icon -- keep dom elements nested in one tree, let them target states on the parent
                    ...iconStylesOnButtonHover
                }
            }
            &:hover {
                ...buttonHoverStyles
            }
        }
    }
  • Composing styles
  • Wide range of helper functions like darken()
    • maybe even node.border('1px', Border.Solid, Color.red) in a NodeSculptor (aka that arrow function given to sculpt() and mold() in the example
  • drop-in postcss support (autoprefixing, and more)
  • react binding via classnames (similar to cssmodules)
  • plugins for webkit, gulp, grunt
  • many more
0.4.11

8 years ago

0.4.10

8 years ago

0.3.1

8 years ago

0.3.0

8 years ago

0.1.10

8 years ago

0.1.9

8 years ago

0.1.8

8 years ago

0.1.7

8 years ago

0.1.6

8 years ago

0.1.5

8 years ago

0.1.4

8 years ago

0.1.3

8 years ago

0.1.2

8 years ago

0.1.1

8 years ago