derma

0.0.4 • Public • Published

Derma

Derma - dynamic style system for use with React

It is inspired by JSS and Radium

NOTE: Work in progress...

I'm starting with this readme to declare my intended usage... this may change as implementation details are worked out... comments welcome.

You should view the current readme on github.

Install

npm i derma

exports: {DermaComponent, DermaDirective}

Usage

TODO

DermaComponent

Wrap your application in a derma/component.

import {DermaComponent as Derma} from 'derma';
...
    // agent string is to support properties for server-side rendering
    <DermaComponent  {...dermaProperties}>
      <YourApp {...props} />
    </DermaComponent>

Properties:

  • id (String, optional, default: "": used for context tracking and style element generation.
    • Should not typically be needed at the application level, mostly for reusable control/component libraries.
    • Any references to id will have said id trimmed, lowercased and anything other than letters and numbers replaced with a hyphen
    • ADVANCED-NOTE: used as part of the context property. An id of "MyLibrary" means this.context.dermaMyLibrary
      • TODO: confirm that setting this.contextTypes in the constructor method will work.
    • characters other than letters and numbers is replaced with hyphens.
  • disableStyleOutput (Boolean, optional, default: false): when set to true will disable output and target rendering.
  • staticStyleElementId (String, optional, default: "dermaID"):
    • By default will render a <style> component with the default id specified.
    • When set to null will disable output rendering altogether
    • when set to an id, will target that id directly (client-side only), such as a <style> element in the head
  • instanceStyleElementId (String, optional, default: "dermaID-dynamic"):
    • By default will render a <style> component with the default id specified.
    • when set to null will disable output rendering altogether
    • when set will target an existing <style> element matching the id instead of rendering an element based on the id property. This can be used to target a pre-set element in the head for example.
    • the target in question will be used for dynamic updates.
  • theme (Object, optional): Will be made available in decorated components as this.props.theme. This can be used for configuration values as well as helper methods.
    • NOTE: if a theme propery is already set, it will not be overridden
  • styles (Object, optional): style object used for top-level style definitions (will be processed)
  • prefixCss (String, optional, context.dermaID.css.prefix): CSS to prefix to all static styles, including styles property.
  • context (object, optional, ADVANCED): base for contextual reference. Used as context object base, will be expanded.
    • Use for server-side rendering, and client-side mounting of server render.
    • WARNING/ADVANCED: This object will mutate.

DermaDirective

@Derma(className, options)

Properties:

  • className (String optional): the class to use for static style definition prefix, be default ClassName is used (constructor function's .name)
  • options (Object optional): options
    • id (string optional, default: "") - if set, should match the DermaComponent. This is so that component libraries can use this library in isolation from application-level usage.
...
import classnames from 'classnames';
import {DermaDirective as Derma} from 'derma';
 
// pass the decorator the classname to use, if unspecified
// then the constructor function's name will be used, or
// a unique name will be generated, and a static darma.className 
// will be added 
//
// all rules will use ".my-component" as their class, overriding
// the default.
@Derma('my-component') 
export class MyComponent extends React.Component {
  /*********************************************************
  NOTE: The convention for component rendering will require 
  that data-derma-id and className be injected
  *********************************************************/
  render() {
    return <div 
      
      //REQUIRED: instance rules based on this
      //  Derma will ensure an "id" prop exists on components if not assigned from outside
      //  Note: if an id is already assigned, it will be used and should be unique in the app
      id={this.props.id}
      
      className={classnames(
        //REQUIRED: class rules based on this className (".my-component" set with decorator)
        this.props.dermaClassName,
        
        //additional dynamic classes included in static style rules
        {
          'active':this.props.active,
          'noprint':this.props.noprint
        }
      )}
    >
      {this.props.children}
    </div>
  }
 
 
  /*********************************************************
  static style declarations
  NOTE: Any context properties needed should be set before derma/component
  *********************************************************/
  static styles(context) {
    return {
      'display': 'block',
    
      //extend component-level class with & in declaration
      '&.noprint': {
        //de-nest media queries
        '@media print': {
          display:none;
        }
      },
    
      '&.active': {
        color: '#88c'
      },
    
      //same as '& .child-component'
      '.child-component': {
        color: #800
      }
    
      // if you need to break out of the class for global rules,
      // you can start your rule with /
      '/body': {
        //output multiple statements for fallback
        background: ['...gradient...', '#fff'],
      
        color: '#333'
      }
    }
  };
 
 
  /*********************************************************
  dynamic instance style calculation
  
  NOTE: most components should use additional classes to 
  define alternative state.
  *********************************************************/
  styles() {
    return {
      //calculated styles on instance
      'font-weight': this.props.active ? 'bold' : 'normal',
      
      //media queries on instance
      '@media (max-width:500px)': {
        'font-weight': normal
      }
    }
  }
 
}

Derma Context

The context property for the DermaControl, or a generated object will be passed via this.context.dermaID (with no id set: this.context.derma) and will be referred to as simply context.derma below.

context.derma.output.staticCss

The CSS to use for the static style element, generally this will not mutate beyond initial render, in development is subject to mutation in support of hot loading.

context.derma.output.instanceCss

The CSS to use for the dynamic instance style element. This will mutate on each render/change from components upward, compared in decorator via componentWillMount/Update

context.derma.classes.{className}

Details for internal tracking of decorated classes by className and static styles that have been loaded/generated. Set with decorator via componentWillMount.

{
  style: Object
  ,output: String //CSS
  ,updated: Date
}

context.derma.instances.{id}

Details for internal tracking of instances by id and styles that have been loaded/generated. Set with decorator via componentWillMount/Update/Unmount

{
  style: Object,
  output: String, //CSS
  updated: Date
}

context.derma.lastStatic

When the static css was last rendered (for comparison)

context.derma.lastInstance

When the instance css was last rendered (for comparison)

Static Stylesheet Creation

NOTE: thinking may be able to tie this into content rendering, for server-side use instead of process below, need to test

Example: subject to change

var dermaContext = {};
var output = React.renderToString(
  <Derma 
    context={dermaContext}
    disableStyleOutput={true}
    dynamicStyleElementId="dynamic-styles"
  >
    <MyApp />
  </Derma>
);
 
//See dermaContext.style.staticCss and dermaContext.style.instanceCss
//assume htmlEncode function
var html = (`<!DOCTYPE html><html>
    <head>
      <!-- additional headers here -->
      <style type="text/css">${htmlEncode(dermaContext.style.staticCss)}</style>
      <style id="dynamic-styles" type="text/css">$(htmlEncode(dermaContext.style.instanceCss))</style>
    </head>
    <body>
      <div id="appWrapper">${output}</div>
      <!-- binding scripts here -->
    </body>
</html>`);

Intent

The Derma Component is required to setup the appropriate wrapper. There should only be one instance of the Derma component in the application, and it should wrap any children that are decorated.

Any static style declarations will be part of a single <style /> element in the final render. As part of the render process, instance style() calls will be made, and an instance <style /> element per class will be rendered.

I'm thinking that as part of the component mount/unmount that dynamic styles can be injected/removed on-demand... and that a default render will be as a direct component output... but as part of a document/ready event when loaded in the browser, it can be used in order to move/adjust styles within the head as an on-demand.

My hope is to be able to support both server and client rendering scenarios, as well as client-side dynamic updates.

  • Selectors with a value that is an object will use the following rules
    • Selectors beginning with '/' will be top-level not enclosed
    • Selectors containing '&' will have the class/instance identifier injected rather than prefixed
    • Selectors beginning with '@media' will be de-nested
  • Values that contain an array will output multiple results

Reasoning

I want to support the following features...

  • stateful representation
  • raw javascript declaration
  • inheritance support
  • dynamic styling
  • media queries

While avoiding the following pitfalls...

  • javascript eventing for style execution
  • avoid repeatative inline style declarations

Package Sidebar

Install

npm i derma

Weekly Downloads

0

Version

0.0.4

License

ISC

Last publish

Collaborators

  • tracker1