0.1.24 • Published 7 years ago

nscss v0.1.24

Weekly downloads
1
License
MIT
Repository
-
Last release
7 years ago

NSCSS - CSS for Components

Styling library for composable components.

How it works?

Static styles by source order, compose unique selectors for each scope that override the scoped subtree Stylesheet has namespace and defines semantic scope parts: Single declaration:

  • class - basic definition to build selectors from
  • typed class - root, scoped & component
  • selector - composed from defined parts

Quick Links

  1. Full Example
  2. Basic Concepts
  3. CSS Syntax proposal

Features

  • Scoped Stylesheet
  • Root definition
  • Typed selectors
  • Custom states
  • Complex selectors
  • Theming
  • Framework Agnostic
  • Extract plain CSS
  • Easy debugging - readable CSS Class Names, fail-fast at evaluation time
  • No Runtime - No Runtime Errors 😜

Native css support

  • @media query
  • @font-face
  • Animations
  • Pseudo Elements / Pseudo Classes / Native States
  • Child selectors
  • Attribute selectors

Plugins & 3rd party integration

  • Hooks to allow style transformations (vendor prefixing, etc.)
  • React integration
  • IDE Plugins

Syntax

  • TypeScript
  • Styles As Object Literals

The whole picture

This examples based on React and uses the nscss react components.

Install:

npm install nscss --save

Usage:

import * as React from "react";
import { render } from "react-dom";
import { NSComponent, NSStateless, Stylesheet } from "nscss/react";


//NSStateless warps a stateless component
const HelloWorld = NSStateless(()=>{
    return <div className="Hello">
        <span className="World">Hello World</span>
    </div>
}, {
    "@define": {
        Hello: NSStateless.root('div', ["floating"])
    },
    ".Hello": {
        background: "green"
    },
    ".World": {
        color: "yellow"
    }
});

// NSComponent wraps react component
const App = NSComponent(class extends React.Component {
    render(){
        return <div className="App">
            {/* classNames and styles are automatically 
               applied on the root element of components */}
            <HelloWorld className="MyHello"/> 
        </div>   
    }
}, {
    "@define": {
        App: NSComponent.root("div"),
        MyHello: NSComponent.scoped(HelloWorld)
    },
    ".App": {
        backgroundColor: "blue"
    },
    ".MyHello": {
        background: "{{lightColor}}"
    },
    ".MyHello:floating": {
        fontSize: "3em"
    },
    ".MyHello::World": {
        color: "{{darkColor}}"
    }
});

// set theme variables and attach all css into the document head
Stylesheet.context.attach({
    lightColor: "red",
    darkColor: "green"
});

render(<App/>, document.querySelector("#app"));

Basic Concepts

Class definition

All Primitive part definitions are class definitions that are used to makeup the selectors. A class may implement another sheet interface and define states.

Scoping

Namespace is prefixed to each class name in the stylesheet

Code:

new Stylesheet({
    "@namespace": "App",
    ".redButton": { color: "red" },
    ".blueButton": { color: "blue" }
});

CSS output:

/* global namespace + separator + local namespace */
.Appâ—¼redButton { color: red; }
.Appâ—¼blueButton { color: blue; }

Root definition

The main class for the component root node.

  • Used as namespace when namespace is not provided
  • Used as default cascade parent for component and scoped classes

Stylesheet with root:

new Stylesheet({
    "@namespace": "Button",
    "@define": {
        "root": Stylesheet.root() // Declare a class as root
    },
    ".root": { color: "red" }
});

CSS output:

/* result CSS class name: namespace + root name */
.Buttonâ—¼root { color: red; }

Typed selectors

Special class definitions that allows you to define your stylesheet structure in order it to be used by other stylesheets.

Component classes

Component class is an alias to a css class name, when used in a selector it will be replaced with the class name of the extended component. it's behavior is like css element (tag) selector it affects all instances of the component

const Label = new Stylesheet({
    "@define": {
        "Label": Stylesheet.root()
    }
});

const Button = new Stylesheet({
    "@define": {
        "Button": Stylesheet.root(),
        "MyLabel": Stylesheet.component(Label)
    },
    ".MyLabel": { color: "red" },
    ".MyLabel:hover": { color: "green" },
    ".Button:hover .MyLabel": { color: "blue" }
});

Possible markup (JSX)

<button className="Button">
    <Label/>
<button>

CSS output:

.Buttonâ—¼Button .Labelâ—¼Label { color: red; }
.Buttonâ—¼Button .Labelâ—¼Label:hover { color: green; }
.Buttonâ—¼Button:hover .Labelâ—¼Label { color: blue; }

Scope classes

Intended to be passed to a component of the scoped type. when used in a selector it's output will have stronger specificity then a component class.

const Label = new Stylesheet(...);

new Stylesheet({
    "@define": {
        "Button": Stylesheet.root(),
        "Label": Stylesheet.scoped(Label)
    },
    ".Label": { color: "red" },
    ".Label:hover": { color: "green" },
    ".Button:hover > .Label": { color: "blue" }
});

Possible markup (JSX)

<button className="Button">
    <Label/>
    <Label className="Label"/>
<button>

CSS output:

.Buttonâ—¼Button .Buttonâ—¼Label { color: red; }
.Buttonâ—¼Button .Buttonâ—¼Label:hover { color: green; }
.Buttonâ—¼Button:hover > .Buttonâ—¼Label { color: blue; }

Customization of inner parts

When using typed classes, it is possible to style the inner parts with the :: operator. This ability can be chained for as many level as needed.

const Label = new Stylesheet({
    "@define": {
        "Label": Stylesheet.root()
    },
    ".Text": { color: "red" } // label inner text class
});

const Button = new Stylesheet({
    "@define": {
        "Button": Stylesheet.root(),
        "Label": Stylesheet.scoped(Label),
    }
});

new Stylesheet({
    "@define": {
        "MyForm": Stylesheet.root(),
        "Button": Stylesheet.component(Button),
        "SubmitButton": Stylesheet.scoped(Button)
    },
    ".Button::Label::Text": { color: "green" },
    ".SubmitButton::Label::Text": { color: "blue" }
});

CSS output:

.Labelâ—¼Text { color: red; }
.MyFormâ—¼MyForm .Buttonâ—¼Button .Buttonâ—¼Label .Labelâ—¼Text { color: green; }
.MyFormâ—¼MyForm .MyFormâ—¼SubmitButton.Buttonâ—¼Button .Buttonâ—¼Label .Labelâ—¼Text { color: blue; }

Custom states

Custom states are mapping between an attribute selector to a name. Any class definition can accept states that can be used to build selectors.

Root with custom states:

new Stylesheet({
    "@define": {
        "Button": Stylesheet.root('div', {
            mouseHover: "[data-state-hover]",
            disabled: "[data-state-disabled]"
        })
    },
    ".Button": { color: "red" },
    ".Button:mouseHover": { color: "green" }
});

CSS output:

.Buttonâ—¼Button { color: red; }
.Buttonâ—¼Button[data-state-hover] { color: "green" }

Custom states auto mapping

Requires integration with stylesheet instance to generate state attributes in the default format of data-${namespace}-${stateName}.

Root with auto states:

const sheet = new Stylesheet({
    "@define": {
        "Button": Stylesheet.root('div', ["mouseHover", "disabled"])
    },
    ".Button": { color: "red" },
    ".Button:mouseHover": { color: "green" }
});


// possible react use case
function Button(){
    return <button {...sheet.cssStates({mouseHover: true, disabled: false})}></button>
}

CSS output:

.Buttonâ—¼Button { color: red; }
.Buttonâ—¼Button[data-Button-mouseHover] { color: "green" }

Extend stylesheet

When a stylesheet definition root extends another stylesheet*, it automatically extends states.

  • view should have a root component matching the extended sheet.
const Button = new Stylesheet({
    "@define": {
        "Button": Stylesheet.root('button', ["hover", "focus"])
    }
});
new Stylesheet({
    "@define": {
        "ToggleButton": Stylesheet.root(Button, ["toggled", "focus"])
    },
    ".ToggleButton:active": { color: "black" },
    ".ToggleButton:hover": { color: "red" },
    ".ToggleButton:toggled": { color: "green" },
    ".ToggleButton:focus": { color: "blue" }
});

CSS output:

/* active state is not overrided and default to native  */
.ToggleButtonâ—¼ToggleButton:active { color: black; }
/* hover state is not overrided and namespaced by the Button  */
.ToggleButtonâ—¼ToggleButton[data-Button-hover] { color: red; }
/* toggled state is new and namespaced by the ToggleButton  */
.ToggleButtonâ—¼ToggleButton[data-ToggleButton-toggled] { color: green; }
/* focus state is overriden and namespaced by the ToggleButton  */
.ToggleButtonâ—¼ToggleButton[data-ToggleButton-focus] { color: blue; }

Complex Selectors

Selector definition must start with a defined part and then it can target internal parts, states or other defined parts.

new Stylesheet({
    "@define": {
        "App": Stylesheet.root()
    },
    ".Button": {
        color: "red"
    },
    ".Container .Button": {
        color: "blue"
    },
    ".Container[data-active] > .Button:hover": {
        color: "green"
    }
});

Possible markup (JSX)

<div className="App">
    <div data-active className="Container">
        <button className="Button"></button>
    </div>
    <div className="Container">
        <button className="Button"></button>
    </div>
    <button className="Button"></button>
<div>

CSS output:

...
.Appâ—¼Button { color: red; }
.Appâ—¼Container .Appâ—¼Button { color: blue; }
.Appâ—¼Container[data-active] > .Appâ—¼Button:hover { color: green; }

Global Theme

All css values in nscss are actually part of a template engine the data for the variables comes when you attach the stylesheet to the dom

new Stylesheet({
    "@define": {
        "Button": Stylesheet.root()
    },
    ".Button": {
        color: "{{primaryColor}}" // this will be injected
    }
});

//at the app entry
Stylesheet.attach({
    primaryColor: "red"
});

CSS output:

.Buttonâ—¼Button { color: red; }

Mixins

Mixins are recipes to apply complex rule sets and selectors with simplified interface.

using mixins is very straight forward:

new Stylesheet({
    "@define": {
        "Container": Stylesheet.root()
    },
    ".Container": {
        ...GridMixin({ itemsPerRow: 3, gutter: '50px' })
    }
})

CSS output:

.Containerâ—¼Container { /* rules for grid container */ }
.Containerâ—¼Container > * { /* rules for grid item */ }

How to write a mixin

This is the definition of GridMixin it shows all the available apis

interface Options {
    itemsPerRow: number;
    gutterX: string;
    gutterY: string;
}

function mixinGridCell(options: Options){...}

const GridMixin = defineMixin('GridMixin', (ctx, options: Options) => {

    ctx.insertRules({
        'display': 'flex',
        'flexWrap': 'wrap',
        'justifyContent': 'space-around',
        'alignContent': 'space-around',
    });

    ctx.insertSelector(' > *', mixinGridCell(options));

});

React integration

  • Component integration
  • SSR

CSS Syntax proposal

/*************************************/
           /*button.ns.css*/

.Button {
    /* define root class*/
    -ns-root: true;
    /* auto state and mapped state*/
    -ns-states: floating, loading("[data-Button-loading]"); 
    /* grab color from global theme */
     color: theme(BGColor); 
}

.Icon {}

/*************************************/





/*************************************/
        /*toggle-button.ns.css*/

.ToggleButton {
    -ns-root: true;
    /* imports the root type of button */
    -ns-type: './button'; 
    /* extend states */
    -ns-states: toggled, focus;
    /*mixin multiple mixins*/
    -ns-mixin: mixinA("Yoo!!"), mixinB("Yoo!!");  
    /* mixin short hand */
    -ns-mixin-mixinC: 1, 2, 3; 
    /* mixin long hand */
    -ns-mixin-mixinC-param: 1; 
    color: green;
}

/* target inner part*/
.ToggleButton::Icon { 
    color: gold;
}

/* native states */
.ToggleButton:hover { color: black }
/* state from button */
.ToggleButton:loading { color: black }
.ToggleButton:floating { color: red }
/* state from toggle button */
.ToggleButton:toggled { color: green }
.ToggleButton:focus { color: blue }

/*************************************/




/*************************************/
          /*gallery.ns.css*/

.Gallery {
    -ns-root: true;
}

.GalleryToggleButton {
    /* used on inner class */
    -ns-type: './toggle-button';
}
/*************************************/

TBD

  • target stylesheet selector part
  • multiple different app context -write example
  • Per Stylesheet Theme
  • change component class to subtree class?
  • change scope class to class class?
  • auto internal part ::content to reference root if not used
  • separate stylesheet schema/interface from selector definition for shared interface between sheets
  • define stylesheet from css
  • order of scoped override in css and dom for readability (.Aâ—¼A.Bâ—¼B vs .Bâ—¼B.Aâ—¼A)
0.1.24

7 years ago

0.1.23

7 years ago

0.1.22

7 years ago

0.1.21

7 years ago

0.1.20

7 years ago

0.1.19

7 years ago

0.1.17

7 years ago

0.1.16

7 years ago

0.1.15

7 years ago

0.1.14

7 years ago

0.1.13

7 years ago

0.1.12

7 years ago

0.1.11

7 years ago

0.1.10

7 years ago

0.1.9

7 years ago

0.1.8

7 years ago

0.1.7

7 years ago

0.1.6

7 years ago

0.1.5

7 years ago

0.1.4

7 years ago

0.1.3

7 years ago

0.1.2

7 years ago

0.1.1

7 years ago

0.1.0

7 years ago

0.0.6

7 years ago

0.0.5

7 years ago

0.0.4

7 years ago

0.0.3

7 years ago

0.0.2

7 years ago

0.0.1

7 years ago