New Research: Supply Chain Attack on Axios Pulls Malicious Dependency from npm.Details →
Socket
Book a DemoSign in
Socket

cssculpt

Package Overview
Dependencies
Maintainers
1
Versions
14
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

cssculpt

Imagine React's Stateless Components... but for css. Create a style and then use the HOC pattern to generate variations on those styles

latest
Source
npmnpm
Version
0.4.11
Version published
Maintainers
1
Created
Source

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

FAQs

Package last updated on 22 Jun 2016

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts