Noodle Printing Machine

    sass-deployables

    1.0.2 • Public • Published

    sass-deployables

    A library for creating object-oriented sass components.

    Proudly sponsored by Marketdial

    MarketDial logo


    npm install --save sass-deployables
    @import 'path/to/node_modules/sass-deployables'
     
    // for example, if using webpack node-sass
    @import '~sass-deployables'
     
    .component
      +dy-deployable
     
      // ...

    Deployables allow you to define a component with internal state properties, which you can then reference from the content of the component. Then you can define different versions and transformations of the component, with different values of those state properties, and the changes you make will cascade to all the references.

    Here's a basic example.

    .deployable-component
      // activates this selector as a deployable. only valid on a single simple selector
      +dy-deployable
      // creates a new internal property, and sets the default to red
      +dy-define-state(color, red)
     
      // normal css attributes are of course fine
      width: 100%
     
      // uses the color internal property, outputting its value to border-color 
      +dy-output(border-color, color)
     
      &.primary
        // creates a new version, with blue as its state
        +dy-define-version(color, blue)
     
      &:hover
        // creates a new transform,
        // saying that every version should have a lightened hover state
        +dy-define-transform(lighten, 'this.color', 10%)
     
      // builds the deployable
      +dy-build

    This will output something equivalent to this:

    .deployable-component
      border-color: red
     
      width: 100%
     
      &:hover
        border-color: lighten(red, 20%)
     
      &.primary
        border-color: blue
     
        &:hover
          border-color: lighten(blue, 20%)

    See how the Deployable remembered the reference (dy-output), and output an adjusted version of it for .primary? And notice how the hover transformation was only defined once but was output for both versions?

    This gets much more powerful when you have more references, versions, and transforms.

    .deployable-component
      +dy-deployable
      +dy-define-state(color, red)
     
      width: 100%
     
      +dy-output(color)
      .nested-block
        +dy-output(border-color, color)
        +dy-output-function(background-color, adjust-hue, 'this.color', 180)
     
      &.primary
        +dy-define-version(color, blue)
        
      &.success
        +dy-define-version(color, green)
     
      &:hover
        +dy-define-transform(lighten, 'this.color', 10%)
     
      +dy-build

    Compiles to something like:

    .deployable-component
      color: red
      width: 100%
     
      .nested-block
        border-color: red
        background-color: adjust-hue(red, 180)
     
      &:hover
        color: lighten(red, 20%)
     
        .nested-block
          border-color: lighten(red, 20%)
          background-color: adjust-hue(lighten(red, 20%), 180)
     
      &.primary
        color: blue
     
        .nested-block
          border-color: blue
          background-color: adjust-hue(blue, 180)
     
        &:hover
          color: lighten(blue, 20%)
     
          .nested-block
            border-color: lighten(blue, 20%)
            background-color: adjust-hue(lighten(blue, 20%), 180)
     
      &.success
        color: green
     
        .nested-block
          border-color: green
          background-color: adjust-hue(green, 180)
     
        &:hover
          color: lighten(green, 20%)
     
          .nested-block
            border-color: lighten(green, 20%)
            background-color: adjust-hue(lighten(green, 20%), 180)

    Using Deployables will help you cut down on a huge amount of redundancy.

    API

    There are six mixins to create Deployables, and one to build them.

    • @mixin dy-deployable($parent-selector: null)
    • @mixin dy-define-state($name, $value)
    • @mixin dy-define-version($name, $value)
    • @mixin dy-define-transform($name, $func, $func-args...)
    • @mixin dy-output($css-prop, $var-name: null)
    • @mixin dy-output-function($css-prop, $func, $func-args...)
    • @mixin dy-build

    Thorough explanations are given below.

    Concepts

    Deployables are made of a few core concepts:

    • States. Internal variables the component has.
    • Versions. Distinct types of the component. Each version should have at least one state variable that is different than all the others.
    • Transforms. Changes that should be applied to the state variables no matter what they are.
    • References, or the Content. The uses of the state variables, made in the content of the component.

    States

    Defining a new state variable is simple. Just call dy-define-state in the base of the component, in the "default" version.

    .deployable
      // initialize
      +dy-deployable
      // everything at the base of the component is the "default" version
      +dy-define-state(color, gray)

    Now that variable can be referred to in the content, given different values in different versions, and changed in transforms.

    Note: you can't call dy-define-state in versions, transforms, or nested blocks.

    .deployable-component
      +dy-deployable
      +dy-define-state(color, red)
     
      .nested-block
        // -> ERROR, invalid location to set deployable state
        +dy-define-state(color, green)
        +dy-output(color)
     
      &.primary
        // -> ERROR, can't call dy-define-state in version
        // use dy-define-version instead
        +dy-define-state(color, red)

    Content References

    In order to output the state values in a way that will be remembered and used for every Version, you can use the dy-output and dy-output-function functions. You can even call these functions in nested blocks, and they'll still work exactly as expected.

    .deployable-component
      +dy-deployable
      +dy-define-state(color, red)
     
      // has two forms
      // one that outputs to the css property with the same name
      +dy-output(color)
      // -> color: the value of color
     
      // and one that outputs to a named css property
      +dy-output(border-color, color)
      // -> border-color: the value of color
     
     
      // supply the css property to be output to,
      // the function name,
      // and the list of arguments,
      // using the format 'this.statevar' to refer to state properties
      +dy-output-function(background-color, adjust-hue, 'this.color', 180)
     
     
      .nested-block
        // every version will have a .nested-block with the appropriate background-color
        +dy-output(background-color, color)
     
     
      // ... versions and transforms ...
     
      +dy-build

    Content Overrides

    In different Versions and Transforms, you can override the content outputs, or add new ones.

    .deployable-component
      +dy-deployable
      +dy-define-state(color, red)
     
      +dy-output(color)
      +dy-output(border-color, color)
     
      &.primary
        +dy-define-state(color, green)
     
        // this will override the normal "border-color: color" output
        +dy-output-function(border-color, lighten, 'this.color', 10%)
     
        // this will only output for this version
        +dy-output(background-color, color)
        
     
      +dy-build

    Compiles to something like:

    .deployable-component
      color: red
      border-color: red
     
      &.primary
        color: green
        border-color: lighten(green, 10%)
        background-color: green

    Versions

    A Version is any different kind of the component that is distinct from all the others, and doesn't overlap with them.

    A common example is different kinds of buttons:

    .button
      +dy-deployable
     
      // the default value
      +dy-define-state(color, black)
     
      +dy-output(color)
      +dy-output(border-color, color)
      +dy-output-function(background-color, lighten, 'this.color', 90%)
     
      &.primary
        +dy-define-version(color, blue)
        
      &.success
        +dy-define-version(color, green)
     
      &.danger
        +dy-define-version(color, red)
     
      +dy-build

    Each of those types (.primary, .success, .danger) can't coexist with the others. A button can't be both .success and .danger. That's what makes them Versions, their separateness.

    Interleaving Versions

    Even though Versions shouldn't overlap, you can have different kinds of Versions changing different qualities. As long as they don't conflict with each other everything will work. A simple example is a button with different Versions for colors, and another set of Versions for sizes.

    .button
      +dy-deployable
     
      // states, outputs, and versions for coloring
      +dy-define-state(color, black)
      +dy-output(color)
      +dy-output(border-color, color)
     
      &.primary
        +dy-define-version(color, blue)
        
      &.success
        +dy-define-version(color, green)
     
     
      // states, outputs, and versions for sizing
      +dy-define-state(font-size, 16px)
      +dy-output(font-size)
      +dy-output(padding, font-size)
     
      &.big
        +dy-define-version(font-size, 20px)
     
      &.small
        +dy-define-version(font-size, 12px)
     
      +dy-build

    Now when you make buttons in your templates, you can use Versions for colors and for sizes together, like .button.primary.big. As long as these interleaved Versions don't operate on the same States, they'll work great.

    Version Selector Limitations

    Right now, the rules for versions are pretty specific. A valid version is any selector that starts with &. Here are some examples of valid versions.

    • Compounding selectors: &#id, &.big
    • Pseudo-elements: &::before
    • Attributes or psueudo-classes: &:active, &[target=_blank]
    • Suffixes: &-suffix

    Transforms

    You can define Transforms on the component. These work very similarly to Versions, but instead of setting the state directly, you define a change to be made to it.

    .deployable-component
      +dy-deployable
      +dy-define-state(color, red)
     
      +dy-output(color)
     
      &.success
        +dy-define-version(color, green)
     
      &:hover
        +dy-define-transform(color, fade-out, 'this.color', 0.2)
     
      +dy-build
    .deployable-component
      color: red
     
      &:hover
        color: fade-out(red, 0.2)
     
      &.success
        color: green
     
        &:hover
          color: fade-out(green, 0.2)

    Transforms will be applied to all versions, so you only have to declare them once.

    Compounding Transforms

    All transforms you define will be compounded on top of each other, in every combination. This means that when something has multiple transforms applied to it at the same time, all the transform functions will be run in order of their definition.

    Here's an example.

    .deployable-component
      +dy-deployable
      +dy-define-state(color, red)
     
      +dy-output(color)
     
      &:hover
        +dy-define-transform(color, fade-out, 'this.color', 0.2)
     
      &.open
        +dy-define-transform(color, adjust-hue, 'this.color', 180deg)
     
      +dy-build
    .deployable-component
      color: red
     
      // first both of them by themselves
      &:hover
        color: fade-out(red, 0.2)
     
      &.open
        color: adjust-hue(red, 180deg)
     
      // then both together
      &.open:hover
        // first the fade-out is applied,
        // since :hover was specified first,
        // and then the adjust-hue
        color: adjust-hue(fade-out(red, 0.2), 180deg)

    This compounding also occurs for content references. When two transforms both specify an output for an attribute, the one that was defined last will win.

    .deployable-component
      +dy-deployable
      +dy-define-state(color, red)
     
      +dy-output(color)
     
      &:hover
        +dy-define-transform(color, fade-out, 'this.color', 0.2)
        +dy-output-function(color, desaturate, 'this.color', 10%))
     
      &.open
        +dy-define-transform(color, adjust-hue, 'this.color', 180deg)
        +dy-output-function(color, desaturate, 'this.color', 20%))
     
      +dy-build
    .deployable-component
      color: red
     
      // for all of these, the transform function(s) happen before the output function
      &:hover
        color: desaturate(fade-out(red, 0.2), 10%)
     
      &.open
        color: desaturate(adjust-hue(red, 180deg), 20%)
     
      &.open:hover
        // here 20% desaturate happens, because open was defined last
        color: desaturate(adjust-hue(fade-out(red, 0.2), 180deg), 20%)

    If you like, you can provide your own compound Transforms. These will override all transform functions or content references you defined in the constituent Transforms, and only use the things you explicitly define.

    .deployable-component
      +dy-deployable
      +dy-define-state(color, red)
     
      +dy-output(color)
     
      &:hover
        +dy-define-transform(color, fade-out, 'this.color', 0.2)
        +dy-output-function(color, desaturate, 'this.color', 10%))
     
      &.open
        +dy-define-transform(color, adjust-hue, 'this.color', 180deg)
        +dy-output-function(color, desaturate, 'this.color', 20%))
     
      &.open:hover
        +dy-define-transform(color, adjust-hue, 'this.color', 180deg)
     
      +dy-build
    .deployable-component
      color: red
     
      &:hover
        color: desaturate(fade-out(red, 0.2), 10%)
     
      &.open
        color: desaturate(adjust-hue(red, 180deg), 20%)
     
      &.open:hover
        // notice how this doesn't use anything from :hover and .open?
        // neither the fade-out from :hover, nor the desaturate from either
        // this transform stands on it's own
        color: adjust-hue(red, 180deg)

    Obviously, the more Transforms you have the crazier the number of combinations can get ((2 ^ n) - 1 to be exact). For the next version of this functionality, there will be an ability to control which Transforms compound and which don't, but for now just don't go too crazy.

    Mingling Versions and Transforms

    It isn't allowed to mix versions and transforms in the same block.

    .deployable-component
      +dy-deployable
      +dy-define-state(color, red)
     
      +dy-output(color)
     
      &.success
        +dy-define-version(color, green)
     
        // -> ERROR, can't mingle versions and transforms
        +dy-define-transform(color, saturate, 'this.color', 10%)
     
      &:hover
        +dy-define-transform(color, fade-out, 'this.color', 0.2)
        
        // -> ERROR, can't mingle versions and transforms
        +dy-define-version(color, gray)
     
      +dy-build

    Inheritance

    You can create secondary Deployables that inherit the states, versions, and transforms of existing Deployables. Just make the component object available to other scopes, and pass it to dy-deployable. @extend is called for you when you do this.

    .parent-component
      +dy-deployable
      +dy-define-state(color, black)
     
      // here's a normal css attribute
      // which will be extended when you inherit
      width: 100%
     
      +dy-output(color)
     
      &:hover
        +dy-define-transform(color, fade-out, 'this.color', 0.2)
     
      +dy-build
     
     
    .child-component
      // calls @extend for you under the hood
      +dy-deployable('.parent-component')
     
      &.primary
        +dy-define-version(color, blue)
     
      &.success
        +dy-define-version(color, green)
     
      +dy-build

    This is just as useful with extend-only selectors. Just skip the dy-build call.

    %hoverable
      +dy-deployable
     
      &:hover
        +dy-define-transform(color, fade-out, 'this.color', 0.2)
     
     
    .deployable-component
      +dy-deployable('%hoverable')
     
      // ... etc ...
     
      +dy-build

    Because they can be inherited, Deployables can be shared in libraries.

    // in a package called 'cool-buttons'
    // 'main.sass'
    %cool-btn
      +dy-deployable
     
      +dy-define-state(color, black)
      +dy-output(color)
      +dy-output(border-color, color)
     
      &.primary
        +dy-define-version(color, blue)
        
      &.success
        +dy-define-version(color, green)
     
      &:hover
        +dy-define-transform(color, fade-out, 'this.color', 10%)
     
     
    // in user code
    @import 'path/to/packages/cool-buttons/main'
    .extended-btn
      +dy-deployable('%cool-btn')
     
      // overriding an old Version
      &.primary
        +dy-define-version(color, cyan)
     
      // creating a new Version
      &.danger
        +dy-define-version(color, red)
     
      // creating a new Transform
      &:active
        +dy-define-transform(color, saturate, 'this.color', 10%)
     
      // ... etc ...
     
      +dy-build

    Roadmap

    Features

    • transform compounding
    • hidden $c object saving, so the calls aren't so cluttered
    • getting rid of dy-extend and calling @extend in dy-deployable
    • control over compounding
    • ability to strip versions and transforms out of extend deployables
    • more flexible version definitions, allowing the & to be in different places
    • "related" versions, ones that are derived from a base value

    Administrivia

    • robust testing with sassaby
    • thorough errors and warnings

    Contributing

    If you'd like to contribute, just fork the project and then make a pull request!

    In the dev directory, there is a _dev.sass file that is set up for you to play around with the library. Running npm run dev in the terminal will run that file, and compile to dev/output.css.

    Install

    npm i sass-deployables

    DownloadsWeekly Downloads

    3

    Version

    1.0.2

    License

    MIT

    Last publish

    Collaborators

    • blainehansen