derma
v0.0.4
Published
Dynamic style system for use with React
Downloads
7
Maintainers
Readme
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"
meansthis.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 thehead
- By default will render a
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 theid
property. This can be used to target a pre-set element in thehead
for example. - the target in question will be used for dynamic updates.
- By default will render a
theme
(Object, optional): Will be made available in decorated components asthis.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
- NOTE: if a
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, includingstyles
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 theDermaComponent
. 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