├── .vscode
└── settings.json
├── .storybook
├── .babelrc
├── addons.js
├── vcssom
│ ├── nextName.ts
│ ├── react.ts
│ ├── createUseCssDiff.ts
│ ├── types.ts
│ ├── nano.ts
│ ├── css5-h.ts
│ ├── createUseDataCss.ts
│ ├── css5.ts
│ ├── createUseCssDiffWithNano.ts
│ ├── createUseCss.ts
│ └── hook.tsx
├── webpack.config.js
├── config.js
├── global-css.stories.js
├── rtl.stories.js
├── atoms.stories.js
├── spread.stories.js
├── hydrate.stories.js
├── stable.stories.js
├── stylis.stories.js
├── rule.stories.js
├── prefixer.stories.js
├── putRaw.stories.js
├── global.stories.js
├── array.stories.js
├── important.stories.js
├── put.stories.js
├── googleFont.stories.js
├── CSSOM.stories.js
├── animate.stories.js
├── hyperstyle.stories.js
├── drule.stories.js
├── dsheet.stories.js
├── nesting.stories.js
├── style.stories.js
├── sheet.stories.js
├── useStyles.stories.js
├── withStyles.stories.js
├── ref.stories.js
├── snake.stories.js
├── virtual.stories.js
├── keyframes.stories.js
├── VCSSOM.stories.js
├── decorator.stories.tsx
├── sourcemaps.stories.js
├── jsx.stories.js
├── component.stories.tsx
├── styled
│ └── Button.tsx
└── styled.stories.js
├── docs
├── sourcemaps.gif
├── important.md
├── sourcemaps.md
├── rtl.md
├── stable.md
├── prefixer.md
├── tachyons.md
├── limit.md
├── stylis.md
├── extract.md
├── spread.md
├── resets.md
├── googleFont.md
├── unitless.md
├── units.md
├── reset-font.md
├── array.md
├── animations.md
├── withStyles.md
├── Presets.md
├── SSR.md
├── useStyles.md
├── global.md
├── nesting.md
├── emmet.md
├── keyframes.md
├── component.md
├── cssom.md
├── hydrate.md
├── hyperstyle.md
├── dsheet.md
├── styled.md
├── sheet.md
├── amp.md
├── vcssom.md
├── virtual.md
├── put.md
├── rule.md
├── drule.md
├── atoms.md
└── decorator.md
├── .github
├── FUNDING.yml
├── renovate.json
└── workflows
│ ├── checks.yml
│ ├── release.yml
│ └── gh-pages.yml
├── demo
├── demo1.html
└── demo1.tsx
├── addon
├── __tests__
│ ├── jsx.server.test.js
│ ├── put.server.test.js
│ ├── index.server.test.js
│ ├── units.server.test.js
│ ├── jsx.dev.test.js
│ ├── put.dev.test.js
│ ├── rule.server.test.js
│ ├── virtual.server.test.js
│ ├── atoms.dev.test.js
│ ├── keyframes.server.test.js
│ ├── rule.dev.test.js
│ ├── units.dev.test.js
│ ├── emmet
│ │ ├── emmet.dev.test.js
│ │ ├── emmet.server.test.js
│ │ └── emmet.server.dev.test.js
│ ├── validate.dev.test.js
│ ├── virtual.dev.test.js
│ ├── keyframes.dev.test.js
│ ├── atoms.server.test.js
│ ├── index.server.dev.test.js
│ ├── jsx.server.dev.test.js
│ ├── put.server.dev.test.js
│ ├── units.server.dev.test.js
│ ├── amp.server.dev.test.js
│ ├── rule.server.dev.test.js
│ ├── virtual.server.dev.test.js
│ ├── keyframes.server.dev.test.js
│ ├── atoms.server.dev.test.js
│ ├── env.js
│ ├── setup.js
│ ├── cssom.server.test.js
│ ├── vcssom.server.test.js
│ ├── jsx.test.js
│ ├── reset.test.js
│ ├── __snapshots__
│ │ └── tachyons.test.js.snap
│ ├── units.test.js
│ ├── tachyons.test.js
│ ├── limit.server.test.js
│ ├── atoms.test.js
│ ├── rule.test.js
│ ├── validate.test.js
│ ├── sheet.test.js
│ └── nesting.test.js
├── vcssom
│ ├── removeRule.d.ts
│ ├── cssToTree.d.ts
│ ├── removeRule.js
│ └── cssToTree.js
├── amp.d.ts
├── array.d.ts
├── extract.d.ts
├── decorator.d.ts
├── component.d.ts
├── stable.js
├── hydrate.d.ts
├── global.d.ts
├── cache.d.ts
├── dsheet.d.ts
├── drule.d.ts
├── googleFont.d.ts
├── util
│ ├── cloneElement.js
│ ├── transformComponentStatic.js
│ └── transformComponentDynamic.js
├── cache.js
├── reset
│ ├── Minimalistic.js
│ ├── Minimalistic2.js
│ ├── Minimalistic3.js
│ ├── PoorMan.js
│ ├── Universal.js
│ ├── Tantek.js
│ ├── Siolon.js
│ ├── Yahoo.js
│ ├── ShaunInman.js
│ ├── EricMeyerCondensed.js
│ ├── EricMeyer.js
│ ├── Tripoli.js
│ └── Normalize.js
├── rtl.js
├── cssom.d.ts
├── global.js
├── animate
│ ├── fadeIn.js
│ ├── fadeOutScale.js
│ ├── fadeOut.js
│ ├── fadeInScale.js
│ └── fadeInDown.js
├── units.js
├── sheet.d.ts
├── spread.js
├── __dev__
│ └── warnOnMissingDependencies.js
├── useStyles.js
├── stylis.js
├── drule.js
├── hydrate.js
├── limit.js
├── hyperstyle.js
├── reset-font.js
├── rule.d.ts
├── array.js
├── component.js
├── withStyles.js
├── important.js
├── keyframes.d.ts
├── style.js
├── dsheet.js
├── nesting.js
├── rule.js
├── stylis
│ └── plugin-onRule.js
├── googleFont.js
├── cssom.js
├── sheet.js
├── decorator.js
├── extract.js
├── units.d.ts
├── jsx.js
├── vcssom.d.ts
├── pipe.js
├── keyframes.js
├── unitless.js
└── prefixer.js
├── types
├── demo.ts
└── common.d.ts
├── index.d.ts
├── .gitignore
├── preset
├── sheet.d.ts
├── sheet.js
├── vdom.js
└── react.js
├── .npmignore
├── prettier.config.js
├── SECURITY.md
├── tsconfig.json
├── .eslintrc.yml
└── LICENSE
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "editor.tabSize": 4
3 | }
--------------------------------------------------------------------------------
/.storybook/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [ ["env", {"modules": false} ]]
3 | }
4 |
--------------------------------------------------------------------------------
/docs/sourcemaps.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/streamich/nano-css/HEAD/docs/sourcemaps.gif
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: streamich
4 |
--------------------------------------------------------------------------------
/demo/demo1.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.storybook/addons.js:
--------------------------------------------------------------------------------
1 | import '@storybook/addon-actions/register';
2 | import '@storybook/addon-links/register';
3 |
--------------------------------------------------------------------------------
/addon/__tests__/jsx.server.test.js:
--------------------------------------------------------------------------------
1 | /** @jest-environment node */
2 | 'use strict';
3 |
4 | require('./jsx.test');
5 |
--------------------------------------------------------------------------------
/addon/__tests__/put.server.test.js:
--------------------------------------------------------------------------------
1 | /** @jest-environment node */
2 | 'use strict';
3 |
4 | require('./put.test');
5 |
--------------------------------------------------------------------------------
/addon/__tests__/index.server.test.js:
--------------------------------------------------------------------------------
1 | /** @jest-environment node */
2 | 'use strict';
3 |
4 | require('./index.test');
5 |
--------------------------------------------------------------------------------
/addon/__tests__/units.server.test.js:
--------------------------------------------------------------------------------
1 | /** @jest-environment node */
2 | 'use strict';
3 |
4 | require('./units.test');
5 |
--------------------------------------------------------------------------------
/demo/demo1.tsx:
--------------------------------------------------------------------------------
1 | const {create} = require('../index');
2 |
3 | var renderer = create();
4 |
5 | console.log(renderer);
6 |
--------------------------------------------------------------------------------
/addon/__tests__/jsx.dev.test.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | process.env.NODE_ENV = 'development';
4 |
5 | require('./jsx.test');
6 |
--------------------------------------------------------------------------------
/addon/__tests__/put.dev.test.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | process.env.NODE_ENV = 'development';
4 |
5 | require('./put.test');
6 |
--------------------------------------------------------------------------------
/addon/__tests__/rule.server.test.js:
--------------------------------------------------------------------------------
1 | /** @jest-environment node */
2 |
3 | 'use strict';
4 |
5 | require('./rule.test');
6 |
--------------------------------------------------------------------------------
/addon/__tests__/virtual.server.test.js:
--------------------------------------------------------------------------------
1 | /** @jest-environment node */
2 | 'use strict';
3 |
4 | require('./virtual.test');
5 |
--------------------------------------------------------------------------------
/addon/__tests__/atoms.dev.test.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | process.env.NODE_ENV = 'development';
4 |
5 | require('./atoms.test');
6 |
--------------------------------------------------------------------------------
/addon/__tests__/keyframes.server.test.js:
--------------------------------------------------------------------------------
1 | /** @jest-environment node */
2 | 'use strict';
3 |
4 | require('./keyframes.test');
5 |
--------------------------------------------------------------------------------
/addon/__tests__/rule.dev.test.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | process.env.NODE_ENV = 'development';
4 |
5 | require('./rule.test');
6 |
--------------------------------------------------------------------------------
/addon/__tests__/units.dev.test.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | process.env.NODE_ENV = 'development';
4 |
5 | require('./units.test');
6 |
--------------------------------------------------------------------------------
/types/demo.ts:
--------------------------------------------------------------------------------
1 | import {create} from '..';
2 |
3 | const nano = create();
4 |
5 | nano.rule({
6 | cursor: 'pointer'
7 | });
8 |
--------------------------------------------------------------------------------
/addon/__tests__/emmet/emmet.dev.test.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | process.env.NODE_ENV = 'development';
4 |
5 | require('./emmet.test');
6 |
--------------------------------------------------------------------------------
/addon/__tests__/validate.dev.test.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | process.env.NODE_ENV = 'development';
4 |
5 | require('./validate.test');
6 |
--------------------------------------------------------------------------------
/addon/__tests__/virtual.dev.test.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | process.env.NODE_ENV = 'development';
4 |
5 | require('./virtual.test');
6 |
--------------------------------------------------------------------------------
/addon/__tests__/keyframes.dev.test.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | process.env.NODE_ENV = 'development';
4 |
5 | require('./keyframes.test');
6 |
--------------------------------------------------------------------------------
/addon/vcssom/removeRule.d.ts:
--------------------------------------------------------------------------------
1 | import {CSSOMRule} from '../cssom';
2 |
3 | export function removeRule(sh: CSSStyleSheet, rule: CSSOMRule);
4 |
--------------------------------------------------------------------------------
/addon/__tests__/atoms.server.test.js:
--------------------------------------------------------------------------------
1 | /** @jest-environment node */
2 |
3 | /* eslint-disable */
4 | 'use strict';
5 |
6 | require('./atoms.test');
7 |
--------------------------------------------------------------------------------
/addon/amp.d.ts:
--------------------------------------------------------------------------------
1 | import {NanoRenderer} from '../types/nano';
2 |
3 | export interface AmpAddon {}
4 |
5 | export function addon(nano: NanoRenderer);
6 |
--------------------------------------------------------------------------------
/addon/__tests__/emmet/emmet.server.test.js:
--------------------------------------------------------------------------------
1 | /** @jest-environment node */
2 |
3 | /* eslint-disable */
4 | 'use strict';
5 |
6 | require('./emmet.test');
7 |
--------------------------------------------------------------------------------
/addon/array.d.ts:
--------------------------------------------------------------------------------
1 | import {NanoRenderer} from '../types/nano';
2 |
3 | export interface ArrayAddon {}
4 |
5 | export function addon(nano: NanoRenderer);
6 |
--------------------------------------------------------------------------------
/.storybook/vcssom/nextName.ts:
--------------------------------------------------------------------------------
1 | let counter = 0;
2 | const nextName = (pfx: string = '💄-') => pfx + (counter++).toString(36);
3 |
4 | export default nextName;
5 |
--------------------------------------------------------------------------------
/addon/extract.d.ts:
--------------------------------------------------------------------------------
1 | import {NanoRenderer} from '../types/nano';
2 |
3 | export interface ExtractAddon {}
4 |
5 | export function addon(nano: NanoRenderer);
6 |
--------------------------------------------------------------------------------
/index.d.ts:
--------------------------------------------------------------------------------
1 | import {CreateNano} from './types/nano';
2 | export * from './types/nano';
3 | export * from './types/common';
4 | export const create: CreateNano;
5 |
--------------------------------------------------------------------------------
/addon/__tests__/index.server.dev.test.js:
--------------------------------------------------------------------------------
1 | /** @jest-environment node */
2 | 'use strict';
3 |
4 | process.env.NODE_ENV = 'development';
5 |
6 | require('./index.test');
7 |
--------------------------------------------------------------------------------
/addon/__tests__/jsx.server.dev.test.js:
--------------------------------------------------------------------------------
1 | /** @jest-environment node */
2 | 'use strict';
3 |
4 | process.env.NODE_ENV = 'development';
5 |
6 | require('./jsx.test');
7 |
--------------------------------------------------------------------------------
/addon/__tests__/put.server.dev.test.js:
--------------------------------------------------------------------------------
1 | /** @jest-environment node */
2 | 'use strict';
3 |
4 | process.env.NODE_ENV = 'development';
5 |
6 | require('./put.test');
7 |
--------------------------------------------------------------------------------
/addon/__tests__/units.server.dev.test.js:
--------------------------------------------------------------------------------
1 | /** @jest-environment node */
2 | 'use strict';
3 |
4 | process.env.NODE_ENV = 'development';
5 |
6 | require('./units.test');
7 |
--------------------------------------------------------------------------------
/addon/__tests__/amp.server.dev.test.js:
--------------------------------------------------------------------------------
1 | /** @jest-environment node */
2 | 'use strict';
3 |
4 | process.env.NODE_ENV = 'development';
5 |
6 | require('./amp.server.test');
7 |
--------------------------------------------------------------------------------
/addon/__tests__/rule.server.dev.test.js:
--------------------------------------------------------------------------------
1 | /** @jest-environment node */
2 |
3 | 'use strict';
4 |
5 | process.env.NODE_ENV = 'development';
6 |
7 | require('./rule.test');
8 |
--------------------------------------------------------------------------------
/addon/__tests__/virtual.server.dev.test.js:
--------------------------------------------------------------------------------
1 | /** @jest-environment node */
2 | 'use strict';
3 |
4 | process.env.NODE_ENV = 'development';
5 |
6 | require('./virtual.test');
7 |
--------------------------------------------------------------------------------
/addon/__tests__/keyframes.server.dev.test.js:
--------------------------------------------------------------------------------
1 | /** @jest-environment node */
2 | 'use strict';
3 |
4 | process.env.NODE_ENV = 'development';
5 |
6 | require('./keyframes.test');
7 |
--------------------------------------------------------------------------------
/addon/decorator.d.ts:
--------------------------------------------------------------------------------
1 | import {NanoRenderer} from '../types/nano';
2 |
3 | export interface DecoratorAddon {
4 | css;
5 | }
6 |
7 | export function addon(nano: NanoRenderer);
8 |
--------------------------------------------------------------------------------
/addon/component.d.ts:
--------------------------------------------------------------------------------
1 | import {NanoRenderer} from '../types/nano';
2 |
3 | export interface ComponentAddon {
4 | Component;
5 | }
6 |
7 | export function addon(nano: NanoRenderer);
8 |
--------------------------------------------------------------------------------
/.storybook/vcssom/react.ts:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import createUseCss from './createUseCss';
3 |
4 | export const useCss = createUseCss(React.useMemo, React.useLayoutEffect);
5 |
--------------------------------------------------------------------------------
/addon/stable.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var stringify = require('fastest-stable-stringify');
4 |
5 | exports.addon = function (renderer) {
6 | renderer.stringify = stringify;
7 | };
8 |
--------------------------------------------------------------------------------
/addon/__tests__/atoms.server.dev.test.js:
--------------------------------------------------------------------------------
1 | /** @jest-environment node */
2 |
3 | /* eslint-disable */
4 | 'use strict';
5 |
6 | process.env.NODE_ENV = 'development';
7 |
8 | require('./atoms.test');
9 |
--------------------------------------------------------------------------------
/addon/hydrate.d.ts:
--------------------------------------------------------------------------------
1 | import {NanoRenderer} from '../types/nano';
2 |
3 | export interface HydrateAddon {
4 | hydrate(sh: HTMLStyleElement);
5 | }
6 |
7 | export function addon(nano: NanoRenderer);
8 |
--------------------------------------------------------------------------------
/addon/__tests__/emmet/emmet.server.dev.test.js:
--------------------------------------------------------------------------------
1 | /** @jest-environment node */
2 |
3 | /* eslint-disable */
4 | 'use strict';
5 |
6 | process.env.NODE_ENV = 'development';
7 |
8 | require('./emmet.test');
9 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea
2 | node_modules
3 | /.idea
4 | .nyc_output
5 | coverage
6 | package-lock.json
7 | yarn-error.log
8 | dist/
9 | .DS_Store
10 | lerna-debug.log
11 | /modules/
12 | /dist/
13 | _book/
14 | /demo/
15 |
--------------------------------------------------------------------------------
/docs/important.md:
--------------------------------------------------------------------------------
1 | # `!important` Addon
2 |
3 | Adds `!important` to every style declaration.
4 |
5 |
6 | ## Installation
7 |
8 | Simply install `important` addon. Read more about the [Addon Installation](./Addons.md#addon-installation).
9 |
--------------------------------------------------------------------------------
/.storybook/vcssom/createUseCssDiff.ts:
--------------------------------------------------------------------------------
1 | import {nano} from './nano';
2 | import createUseCssDiffWithNano from './createUseCssDiffWithNano';
3 |
4 | const createUseCssDiff = createUseCssDiffWithNano(nano);
5 |
6 | export default createUseCssDiff;
7 |
--------------------------------------------------------------------------------
/addon/global.d.ts:
--------------------------------------------------------------------------------
1 | import {NanoRenderer} from '../types/nano';
2 | import {CssLikeObject} from '../types/common';
3 |
4 | export interface GlobalAddon {
5 | global(css: CssLikeObject);
6 | }
7 |
8 | export function addon(nano: NanoRenderer);
9 |
--------------------------------------------------------------------------------
/addon/cache.d.ts:
--------------------------------------------------------------------------------
1 | import {NanoRenderer} from '../types/nano';
2 | import {CssLikeObject} from '../types/common';
3 |
4 | export interface CacheAddon {
5 | cache(css: CssLikeObject): string;
6 | }
7 |
8 | export function addon(nano: NanoRenderer);
9 |
--------------------------------------------------------------------------------
/preset/sheet.d.ts:
--------------------------------------------------------------------------------
1 | import {NanoOptions, NanoRenderer} from '../types/nano';
2 | import {RuleAddon} from '../addon/rule';
3 | import {SheetAddon} from '../addon/sheet';
4 |
5 | type SheetPreset = (options: NanoOptions) => NanoRenderer & RuleAddon & SheetAddon;
6 |
--------------------------------------------------------------------------------
/addon/__tests__/env.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | exports.isClient = typeof window === 'object';
4 | exports.isServer = typeof window !== 'object';
5 | exports.isProd = process.env.NODE_ENV === 'production';
6 | exports.isDev = process.env.NODE_ENV !== 'production';
7 |
--------------------------------------------------------------------------------
/addon/__tests__/setup.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | process.env.NODE_ENV = 'production';
4 |
5 | if (typeof window === 'object') {
6 | global.requestAnimationFrame = window.requestAnimationFrame = function (callback) { return setTimeout(callback, 17); };
7 | }
8 |
--------------------------------------------------------------------------------
/addon/dsheet.d.ts:
--------------------------------------------------------------------------------
1 | import {NanoRenderer} from '../types/nano';
2 | import {CssLikeObject} from '../types/common';
3 |
4 | export interface DsheetAddon {
5 | dsheet(map: object, block?: string): object;
6 | }
7 |
8 | export function addon(nano: NanoRenderer);
9 |
--------------------------------------------------------------------------------
/addon/drule.d.ts:
--------------------------------------------------------------------------------
1 | import {CssLikeObject} from '../types/common';
2 | import {NanoRenderer} from '../types/nano';
3 |
4 | export interface DruleAddon {
5 | drule: (css: CssLikeObject, block?: string) => (css?: CssLikeObject) => string;
6 | }
7 |
8 | export function addon(nano: NanoRenderer);
9 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | /test
3 | .idea
4 | .idea/
5 | __tests__/
6 | __mocks__/
7 | __stories__/
8 | .nyc_output
9 | coverage
10 | package-lock.json
11 | yarn.lock
12 | yarn-error.log
13 | tsconfig.json
14 | .vscode/
15 | /docs/
16 | .storybook/
17 | /build/
18 | /dist/
19 | /demo/
20 | /demo/analyzer/
21 |
--------------------------------------------------------------------------------
/.storybook/vcssom/types.ts:
--------------------------------------------------------------------------------
1 | export type EffectCallback = () => void | (() => void | undefined);
2 | export type DependencyList = ReadonlyArray;
3 | export type HookUseEffect = (effect: EffectCallback, deps?: DependencyList) => void;
4 | export type HookUseMemo = (factory: () => T, deps: DependencyList | undefined) => T;
5 |
--------------------------------------------------------------------------------
/addon/googleFont.d.ts:
--------------------------------------------------------------------------------
1 | import {NanoRenderer} from '../types/nano';
2 | import {CssLikeObject} from '../types/common';
3 |
4 | export interface GoogleFontAddon {
5 | googleFont(font: string, weights: number | string | (number | string)[], subsets: string | string[]);
6 | }
7 |
8 | export function addon(nano: NanoRenderer);
9 |
--------------------------------------------------------------------------------
/addon/util/cloneElement.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = function (renderer, element, props) {
4 | var newProps = renderer.assign({}, element.props, props);
5 |
6 | if (element.ref) {
7 | newProps.ref = element.ref;
8 | }
9 |
10 | return renderer.h(element.type, newProps);
11 | };
12 |
--------------------------------------------------------------------------------
/prettier.config.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = {
4 | arrowParens: 'always',
5 | printWidth: 120,
6 | tabWidth: 2,
7 | useTabs: false,
8 | semi: true,
9 | singleQuote: true,
10 | trailingComma: 'none',
11 | bracketSpacing: false,
12 | jsxBracketSameLine: false
13 | };
14 |
--------------------------------------------------------------------------------
/.storybook/webpack.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | module: {
3 | rules: [
4 | {
5 | test: /\.tsx?$/,
6 | loader: 'ts-loader'
7 | }
8 | ]
9 | },
10 |
11 | resolve: {
12 | extensions: ['.ts', '.tsx', '.js', '.jsx', '.json'],
13 | enforceExtension: false
14 | }
15 | };
16 |
--------------------------------------------------------------------------------
/.storybook/config.js:
--------------------------------------------------------------------------------
1 | import { configure } from '@storybook/react';
2 |
3 | // automatically import all files ending in *.stories.js
4 | const req = require.context('../.storybook', true, /.stories.(j|t)sx?$/);
5 | function loadStories() {
6 | req.keys().forEach((filename) => req(filename));
7 | }
8 |
9 | configure(loadStories, module);
10 |
--------------------------------------------------------------------------------
/addon/__tests__/cssom.server.test.js:
--------------------------------------------------------------------------------
1 | /** @jest-environment node */
2 | 'use strict';
3 |
4 | process.env.NODE_ENV = 'development';
5 |
6 | var create = require('../../index').create;
7 | var addon = require('../../addon/cssom').addon;
8 |
9 | test('should load without crashing', () => {
10 | var nano = create();
11 | addon(nano);
12 | });
13 |
--------------------------------------------------------------------------------
/addon/cache.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | exports.addon = function (renderer) {
4 | var cache = {};
5 |
6 | renderer.cache = function (css) {
7 | if (!css) return '';
8 |
9 | var key = renderer.hash(css);
10 |
11 | if (!cache[key]) {
12 | cache[key] = renderer.rule(css, key);
13 | }
14 |
15 | return cache[key];
16 | };
17 | };
18 |
--------------------------------------------------------------------------------
/addon/reset/Minimalistic.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | exports.addon = function (renderer) {
4 | if (process.env.NODE_ENV !== 'production') {
5 | require('../__dev__/warnOnMissingDependencies')('reset', renderer, ['put']);
6 | }
7 |
8 | var css = {
9 | '*': {
10 | pad: 0,
11 | mar: 0,
12 | },
13 | };
14 |
15 | renderer.put('', css);
16 | };
17 |
--------------------------------------------------------------------------------
/.storybook/vcssom/nano.ts:
--------------------------------------------------------------------------------
1 | import {create} from '../../index';
2 | import {addon as addonCSSOM, CSSOMAddon} from '../../addon/cssom';
3 | import {addon as addonVCSSOM, VCSSOMAddon} from '../../addon/vcssom';
4 | import {NanoRenderer} from '../../types/nano';
5 |
6 | type Nano = NanoRenderer & CSSOMAddon & VCSSOMAddon;
7 |
8 | const nano = create() as Nano;
9 | addonCSSOM(nano);
10 | addonVCSSOM(nano);
11 |
12 | export {nano};
13 |
--------------------------------------------------------------------------------
/addon/reset/Minimalistic2.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | exports.addon = function (renderer) {
4 | if (process.env.NODE_ENV !== 'production') {
5 | require('../__dev__/warnOnMissingDependencies')('reset', renderer, ['put']);
6 | }
7 |
8 | var css = {
9 | '*': {
10 | pad: 0,
11 | mar: 0,
12 | bd: 0,
13 | },
14 | };
15 |
16 | renderer.put('', css);
17 | };
18 |
--------------------------------------------------------------------------------
/addon/rtl.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var rtl = require('rtl-css-js').default;
4 |
5 | exports.addon = function (renderer) {
6 | if (process.env.NODE_ENV !== 'production') {
7 | require('./__dev__/warnOnMissingDependencies')('rtl', renderer, ['put']);
8 | }
9 |
10 | var put = renderer.put;
11 |
12 | renderer.put = function (selector, css) {
13 | return put(selector, rtl(css));
14 | };
15 | };
16 |
--------------------------------------------------------------------------------
/addon/__tests__/vcssom.server.test.js:
--------------------------------------------------------------------------------
1 | /** @jest-environment node */
2 | 'use strict';
3 |
4 | process.env.NODE_ENV = 'development';
5 |
6 | var create = require('../../index').create;
7 | var addonCSSOM = require('../../addon/cssom').addon;
8 | var addonVCSSOM = require('../../addon/vcssom').addon;
9 |
10 | test('should load without crashing', () => {
11 | var nano = create();
12 | addonCSSOM(nano);
13 | addonVCSSOM(nano);
14 | });
15 |
--------------------------------------------------------------------------------
/addon/reset/Minimalistic3.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | exports.addon = function (renderer) {
4 | if (process.env.NODE_ENV !== 'production') {
5 | require('../__dev__/warnOnMissingDependencies')('reset', renderer, ['put']);
6 | }
7 |
8 | var css = {
9 | '*': {
10 | pad: 0,
11 | mar: 0,
12 | bd: 0,
13 | out: 0,
14 | },
15 | };
16 |
17 | renderer.put('', css);
18 | };
19 |
--------------------------------------------------------------------------------
/addon/vcssom/cssToTree.d.ts:
--------------------------------------------------------------------------------
1 | import {CssLikeObject} from '../../types/common';
2 |
3 | export interface Css {
4 | [key: string]: CssLikeObject[keyof CssLikeObject] | Css;
5 | }
6 |
7 | export interface Tree {
8 | [atRulePrelude: string]: {
9 | [selector: string]: {
10 | [property: string]: CssLikeObject;
11 | };
12 | };
13 | }
14 |
15 | export function cssToTree(tree: {}, css: Css, selector: string, prelude: string): Tree;
16 |
--------------------------------------------------------------------------------
/addon/vcssom/removeRule.js:
--------------------------------------------------------------------------------
1 | function removeRule (rule) {
2 | var maxIndex = rule.index;
3 | var sh = rule.parentStyleSheet;
4 | var rules = sh.cssRules || sh.rules;
5 | maxIndex = Math.max(maxIndex, rules.length - 1);
6 | while (maxIndex >= 0) {
7 | if (rules[maxIndex] === rule) {
8 | sh.deleteRule(maxIndex);
9 | break;
10 | }
11 | maxIndex--;
12 | }
13 | }
14 |
15 | exports.removeRule = removeRule;
16 |
--------------------------------------------------------------------------------
/.storybook/vcssom/css5-h.ts:
--------------------------------------------------------------------------------
1 | import {styled} from './css5';
2 |
3 | const cache = {};
4 | const cachedBlock = (useCss, h, tag) => cache[tag] || (cache[tag] = styled(useCss)(h)(tag));
5 |
6 | export const H =
7 | (h, useCss) =>
8 | (tag, props, ...rest) => {
9 | if (!props || !props.css || typeof tag !== 'string') return h(tag, props, ...rest);
10 | const Block = cachedBlock(useCss, h, tag);
11 | return h(Block, props, ...rest);
12 | };
13 |
--------------------------------------------------------------------------------
/.github/renovate.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json",
3 | "extends": ["config:base"],
4 | "lockFileMaintenance": {
5 | "enabled": true,
6 | "automerge": true
7 | },
8 | "rangeStrategy": "replace",
9 | "postUpdateOptions": ["yarnDedupeHighest"],
10 | "packageRules": [
11 | {
12 | "matchUpdateTypes": ["minor", "patch"],
13 | "matchCurrentVersion": "!/^0/",
14 | "automerge": true
15 | }
16 | ]
17 | }
--------------------------------------------------------------------------------
/docs/sourcemaps.md:
--------------------------------------------------------------------------------
1 | # `sourcemaps` Addon
2 |
3 | Adds sourcemap support in development mode.
4 |
5 | 
6 |
7 | Do not use this addon in production. Check environment to exclude it from production:
8 |
9 | ```js
10 | if (process.env.NODE_ENV !== 'production') {
11 | addonSourcemaps(nano);
12 | }
13 | ```
14 |
15 |
16 | ## Installation
17 |
18 | Simply install `sourcemaps` addon. Read more about the [Addon Installation](./Addons.md#addon-installation).
19 |
--------------------------------------------------------------------------------
/SECURITY.md:
--------------------------------------------------------------------------------
1 | # Security Policy
2 |
3 | ## Supported Versions
4 |
5 | We release patches for security vulnerabilities. The latest major version
6 | will support security patches.
7 |
8 | ## Reporting a Vulnerability
9 |
10 | Please report (suspected) security vulnerabilities to
11 | **[streamich@gmail.com](mailto:streamich@gmail.com)**. We will try to respond
12 | within 48 hours. If the issue is confirmed, we will release a patch as soon
13 | as possible depending on complexity.
14 |
--------------------------------------------------------------------------------
/docs/rtl.md:
--------------------------------------------------------------------------------
1 | # `rtl` Addon
2 |
3 | Flips all your styles to support right-to-left styling.
4 |
5 | Example:
6 |
7 | ```js
8 | nano.put('.foo', {
9 | textAlign: 'left',
10 | marginLeft: '100px'
11 | });
12 | ```
13 |
14 | Result:
15 |
16 | ```css
17 | .foo {
18 | text-align: right;
19 | margin-right: 100px;
20 | }
21 | ```
22 |
23 |
24 | ## Installation
25 |
26 | Simply install `rtl` addon.
27 |
28 | Read more about the [Addon Installation](./Addons.md#addon-installation).
29 |
--------------------------------------------------------------------------------
/addon/cssom.d.ts:
--------------------------------------------------------------------------------
1 | import {NanoRenderer} from '../types/nano';
2 |
3 | export interface CSSOMRule extends CSSRule {
4 | index: number;
5 | style: CSSStyleDeclaration;
6 | styleMap: any;
7 | }
8 |
9 | export interface CSSOMAddon {
10 | /**
11 | * @param selector CSS rule selector string.
12 | * @param prelude Media query at-rule prelude string.
13 | */
14 | createRule(selector: string, prelude?: string): CSSOMRule;
15 | }
16 |
17 | export function addon(nano: NanoRenderer);
18 |
--------------------------------------------------------------------------------
/.storybook/vcssom/createUseDataCss.ts:
--------------------------------------------------------------------------------
1 | let counter = 0;
2 | const nextName = (pfx: string = 'use-css-') => pfx + (counter++).toString(36);
3 |
4 | // By default generate class name selectors.
5 | const getSelector = (name: string) => `[data-${name}]`;
6 |
7 | const createUseDataCss = (useCss) => {
8 | const useDataCss = (css: object) => {
9 | const name = useCss(css, nextName, getSelector);
10 | return {[`data-${name}`]: ''};
11 | };
12 | return useDataCss;
13 | };
14 |
15 | export default createUseDataCss;
16 |
--------------------------------------------------------------------------------
/docs/stable.md:
--------------------------------------------------------------------------------
1 | # `stable` Addon
2 |
3 | When generating class names, `nano-css` stringifies CSS-like objects to compute a hash and generate a unique class name. By default it uses `JSON.stringify`, which is not stable. This
4 | addon makes `nano-css` use [`fastest-stable-stringify`](https://github.com/streamich/fastest-stable-stringify)
5 | to generate fast predictable hashes across JavaScript runtimes.
6 |
7 | ## Installation
8 |
9 | Simply install `stable` addon. Read more about the [Addon Installation](./Addons.md#addon-installation).
10 |
--------------------------------------------------------------------------------
/.storybook/vcssom/css5.ts:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import {useCss} from './react';
3 |
4 | export const styled =
5 | (useCss) =>
6 | (h) =>
7 | (tag) =>
8 | ({as = tag, css, className, ...rest}) => {
9 | const extraClass = useCss(css);
10 | rest.className = className ? className + ' ' + extraClass : extraClass;
11 | return h(as, rest);
12 | };
13 |
14 | export const Box = styled(useCss)(React.createElement)('div') as any;
15 | export const Text = styled(useCss)(React.createElement)('span') as any;
16 |
--------------------------------------------------------------------------------
/docs/prefixer.md:
--------------------------------------------------------------------------------
1 | # `prefixer` Addon
2 |
3 | Uses [`inline-style-prefixer`](https://github.com/rofrischmann/inline-style-prefixer) library
4 | to auto-prefix your styles on the server and browser.
5 |
6 | Example:
7 |
8 | ```js
9 | nano.put('.foo', {
10 | flex: 1
11 | });
12 | ```
13 |
14 | Result:
15 |
16 | ```css
17 | .foo {
18 | -webkit-flex: 1;
19 | flex: 1;
20 | }
21 | ```
22 |
23 |
24 | ## Installation
25 |
26 | Simply install `prefixer` addon.
27 |
28 | Read more about the [Addon Installation](./Addons.md#addon-installation).
29 |
--------------------------------------------------------------------------------
/.storybook/global-css.stories.js:
--------------------------------------------------------------------------------
1 | import {createElement as h} from 'react';
2 | import {storiesOf} from '@storybook/react';
3 | const {action} = require('@storybook/addon-actions');
4 | const {linkTo} = require('@storybook/addon-links');
5 | const {create} = require('../index');
6 | const {addon: resetEricMeyerCondensed} = require('../addon/reset/EricMeyerCondensed');
7 |
8 | const nano = create();
9 | resetEricMeyerCondensed(nano);
10 |
11 | storiesOf('CSS resets', module)
12 | .add('Eric Meyer - condensed', () =>
13 | h('div', null, 'Hello world')
14 | )
15 |
--------------------------------------------------------------------------------
/addon/global.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | exports.addon = function (renderer) {
4 | if (process.env.NODE_ENV !== 'production') {
5 | require('./__dev__/warnOnMissingDependencies')('global', renderer, ['put']);
6 | }
7 |
8 | var selector = renderer.selector;
9 |
10 | renderer.selector = function (parent, current) {
11 | if (parent.indexOf(':global') > -1) parent = '';
12 |
13 | return selector(parent, current);
14 | };
15 |
16 | renderer.global = function (css) {
17 | return renderer.put('', css);
18 | };
19 | };
20 |
--------------------------------------------------------------------------------
/types/common.d.ts:
--------------------------------------------------------------------------------
1 | import * as CSS from 'csstype';
2 | import {Atoms} from '../addon/atoms';
3 |
4 | export interface CssProps extends CSS.Properties, CSS.PropertiesHyphen, Atoms {}
5 |
6 | export interface CssLikeObject extends CssProps {
7 | [selector: string]: any | CssLikeObject;
8 | }
9 |
10 | export type TDynamicCss = (css: CssLikeObject) => string;
11 | export type THyperstyleElement = object;
12 | export type THyperstyle = (...args) => THyperstyleElement;
13 | export type THyperscriptType = string | Function;
14 | export type THyperscriptComponent = Function;
15 |
--------------------------------------------------------------------------------
/.github/workflows/checks.yml:
--------------------------------------------------------------------------------
1 | name: Node CI
2 |
3 | on:
4 | push:
5 | branches: [ master ]
6 | pull_request:
7 | branches: [ master ]
8 |
9 | jobs:
10 | checks:
11 | runs-on: ubuntu-latest
12 | strategy:
13 | matrix:
14 | node-version: [20.x]
15 | steps:
16 | - uses: actions/checkout@v4
17 | - name: Use Node.js ${{ matrix.node-version }}
18 | uses: actions/setup-node@v4
19 | with:
20 | node-version: ${{ matrix.node-version }}
21 | cache: yarn
22 | - run: yarn
23 | - run: yarn test
24 |
--------------------------------------------------------------------------------
/docs/tachyons.md:
--------------------------------------------------------------------------------
1 | # `tachyons` Addon
2 |
3 | `tachyons` addon adds [Tachyons](https://tachyons.io/) to [`snake`](./snake.md) addon rules. It installs `snake` addon automatically for you.
4 |
5 | Example:
6 |
7 | ```js
8 | Hello world
9 | ```
10 |
11 | ## Installation
12 |
13 | Simply install `tachyons` addon. If you, will evaluate styles using `.valueOf()` or `.toString()`, you also
14 | need to install `cache` addon.
15 |
16 | Read more about the [Addon Installation](./Addons.md#addon-installation).
17 |
--------------------------------------------------------------------------------
/addon/animate/fadeIn.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | exports.addon = function (renderer) {
4 | if (process.env.NODE_ENV !== 'production') {
5 | require('../__dev__/warnOnMissingDependencies')('animate', renderer, ['keyframes']);
6 | }
7 |
8 | renderer.put('', {
9 | '@keyframes fadeIn': {
10 | from: {
11 | opacity: 0,
12 | },
13 | to: {
14 | opacity: 1,
15 | }
16 | },
17 |
18 | '.fadeIn': {
19 | animation: 'fadeIn .4s linear',
20 | }
21 | });
22 | };
23 |
--------------------------------------------------------------------------------
/docs/limit.md:
--------------------------------------------------------------------------------
1 | # `limit` Addon
2 |
3 | Limits the size of server-side style sheet, in bytes.
4 |
5 |
6 | ## Usage
7 |
8 | Install `limit` addon, specifying the maximum size of server-size style sheet in bytes as
9 | the second argument. If not specified, defaults to `50000`, or 50Kb, which is the maximum
10 | style sheet size for Google AMP (See [`amp` addon](./amp.md) for more).
11 |
12 | ```js
13 | import {addon as addonLimit} from 'nano-css/addon/limit';
14 |
15 | addonLimit(nano, 100000);
16 | ```
17 |
18 |
19 | ## Installation
20 |
21 | Read more about the [Addon Installation](./Addons.md#addon-installation).
22 |
--------------------------------------------------------------------------------
/addon/reset/PoorMan.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | exports.addon = function (renderer) {
4 | if (process.env.NODE_ENV !== 'production') {
5 | require('../__dev__/warnOnMissingDependencies')('reset', renderer, ['put']);
6 | }
7 |
8 | var css = {
9 | 'html, body': {
10 | pad: 0,
11 | mar: 0,
12 | },
13 | html: {
14 | fz: '1em',
15 | },
16 | body: {
17 | fz: '100%',
18 | },
19 | 'a img, :link img, :visited img': {
20 | bd: 0,
21 | },
22 | };
23 |
24 | renderer.put('', css);
25 | };
26 |
--------------------------------------------------------------------------------
/addon/reset/Universal.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | exports.addon = function (renderer) {
4 | if (process.env.NODE_ENV !== 'production') {
5 | require('../__dev__/warnOnMissingDependencies')('reset', renderer, ['put']);
6 | }
7 |
8 | var css = {
9 | '*': {
10 | 'vertical-align': 'baseline',
11 | fw: 'inherit',
12 | ff: 'inherit',
13 | fs: 'inherit',
14 | fz: '100%',
15 | bd: '0 none',
16 | out: 0,
17 | pad: 0,
18 | mar: 0,
19 | },
20 | };
21 |
22 | renderer.put('', css);
23 | };
24 |
--------------------------------------------------------------------------------
/addon/units.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var units = {};
4 | var unitList = 'px,cm,mm,in,pt,pc,em,ex,ch,rem,vw,vh,deg,vmin,vmax'.split(',');
5 |
6 | function f (unit, val) {
7 | return val + unit;
8 | }
9 |
10 | for (var i = 0; i < unitList.length; i++) {
11 | var unit = unitList[i];
12 |
13 | units[unit] = f.bind(null, unit);
14 | }
15 |
16 | units.inch = function (val) {
17 | return val + 'in';
18 | };
19 |
20 | units.pct = function (val) {
21 | return val + '%';
22 | };
23 |
24 | exports.addon = function (renderer) {
25 | renderer.assign(renderer, units);
26 | renderer.units = units;
27 | };
28 |
--------------------------------------------------------------------------------
/addon/animate/fadeOutScale.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | exports.addon = function (renderer) {
4 | if (process.env.NODE_ENV !== 'production') {
5 | require('../__dev__/warnOnMissingDependencies')('animate', renderer, ['keyframes']);
6 | }
7 |
8 | renderer.put('', {
9 | '@keyframes fadeOutScale': {
10 | to: {
11 | opacity: 0,
12 | transform: 'scale(.95)',
13 | }
14 | },
15 |
16 | '.fadeOutScale': {
17 | animation: 'fadeOutScale .3s linear',
18 | 'animation-fill-mode': 'forwards',
19 | }
20 | });
21 | };
22 |
--------------------------------------------------------------------------------
/addon/sheet.d.ts:
--------------------------------------------------------------------------------
1 | import {CssLikeObject} from '../types/common';
2 | import {NanoRenderer} from '../types/nano';
3 |
4 | export interface SheetAddon {
5 | /**
6 | * Creates a collection of CSS rules.
7 | *
8 | * ```js
9 | * const classes = sheet({
10 | * wrapper: {
11 | * border: '1px solid red',
12 | * },
13 | * button: {
14 | * color: 'red',
15 | * },
16 | * });
17 | * ```
18 | */
19 | sheet: (cssMap: {[s: string]: CssLikeObject}, block?: string) => {[s: string]: string};
20 | }
21 |
22 | export function addon(nano: NanoRenderer);
23 |
--------------------------------------------------------------------------------
/addon/animate/fadeOut.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | exports.addon = function (renderer) {
4 | if (process.env.NODE_ENV !== 'production') {
5 | require('../__dev__/warnOnMissingDependencies')('animate', renderer, ['keyframes']);
6 | }
7 |
8 | renderer.put('', {
9 | '@keyframes fadeOut': {
10 | from: {
11 | opacity: 1,
12 | },
13 | to: {
14 | opacity: 0,
15 | }
16 | },
17 |
18 | '.fadeOut': {
19 | animation: 'fadeOut .3s linear',
20 | 'animation-fill-mode': 'forwards',
21 | }
22 | });
23 | };
24 |
--------------------------------------------------------------------------------
/docs/stylis.md:
--------------------------------------------------------------------------------
1 | # `stylis` Addon
2 |
3 | Adds [`stylis`](https://github.com/thysultan/stylis.js) pre-processor. This allows you
4 | to write CSS as a string using `stylis` syntax everywhere where CSS-like object is expected.
5 |
6 | ```js
7 | const className = nano.rule(`
8 | color: red;
9 | &:hover {
10 | color: blue
11 | }
12 | `);
13 | ```
14 |
15 | ---
16 |
17 | > __Nota Bene__
18 | >
19 | > If you use `stylis` pre-processor some other addons may not work with it.
20 |
21 | ---
22 |
23 |
24 | ## Installation
25 |
26 | Simply install `stylis` addon. Read more about the [Addon Installation](./Addons.md#addon-installation).
27 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "module": "commonjs",
5 | "removeComments": false,
6 | "noImplicitAny": false,
7 | "sourceMap": false,
8 | "experimentalDecorators": true,
9 | "allowJs": true,
10 | "allowSyntheticDefaultImports": true,
11 | "jsx": "react",
12 | "jsxFactory": "h",
13 | "importHelpers": true,
14 | "lib": [
15 | "dom",
16 | "es5",
17 | "es6",
18 | "es7",
19 | "es2015",
20 | "scripthost",
21 | "dom.iterable"
22 | ]
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/.storybook/rtl.stories.js:
--------------------------------------------------------------------------------
1 | import {createElement as h} from 'react';
2 | import {storiesOf} from '@storybook/react';
3 | const {action} = require('@storybook/addon-actions');
4 | const {linkTo} = require('@storybook/addon-links');
5 | const {create} = require('../index');
6 |
7 | const renderer = create();
8 | require('../addon/rtl').addon(renderer);
9 | require('../addon/rule').addon(renderer);
10 | const {rule} = renderer;
11 |
12 | const className = rule({
13 | paddingLeft: '100px',
14 | textAlign: 'left',
15 | });
16 |
17 | storiesOf('Addons/rtl', module)
18 | .add('Default', () =>
19 | h('div', {className: className}, 'Hello world')
20 | )
21 |
--------------------------------------------------------------------------------
/.eslintrc.yml:
--------------------------------------------------------------------------------
1 | extends: eslint:recommended
2 | env:
3 | node: true
4 | browser: true
5 | rules:
6 | block-scoped-var: 2
7 | callback-return: 2
8 | dot-notation: 2
9 | indent: 2
10 | linebreak-style: [2, unix]
11 | new-cap: 2
12 | no-console: [2, allow: [warn, error]]
13 | no-else-return: 2
14 | no-eq-null: 2
15 | no-fallthrough: 2
16 | no-invalid-this: 2
17 | no-return-assign: 2
18 | no-shadow: 1
19 | no-trailing-spaces: 2
20 | no-use-before-define: [2, nofunc]
21 | quotes: [2, single, avoid-escape]
22 | semi: [2, always]
23 | strict: [2, global]
24 | valid-jsdoc: [2, requireReturn: false]
25 | no-control-regex: 0
26 | no-useless-escape: 2
27 |
28 |
--------------------------------------------------------------------------------
/docs/extract.md:
--------------------------------------------------------------------------------
1 | # `extract` Addon
2 |
3 | This addon allows you to extract CSS styles into an external style sheet at build time. This
4 | addon is not all-in-one solution, but rather the lowest level that enables style extraction
5 | at build time, here is what it does:
6 |
7 | - injects styles from [`sheet()`](./sheet.md), which are normally injected lazily
8 | - renders at least once [`jsx()`](./jsx.md) and [`style()`](./style.md) components with default props
9 |
10 | See demo [here](https://github.com/streamich/nano-css-extract-demo).
11 |
12 |
13 | ## Installation
14 |
15 | Simply install `extract` addon. Read more about the [Addon Installation](./Addons.md#addon-installation).
16 |
--------------------------------------------------------------------------------
/docs/spread.md:
--------------------------------------------------------------------------------
1 | # `spread()` Addon
2 |
3 | Works the same as [`rule()`](./rule.md) interface, but instead of returning a string of
4 | class names, it returns an object of data attributes that can be "spread".
5 |
6 | ```js
7 | const rule = nano.spread({
8 | color: 'red'
9 | });
10 |
11 | Hello world!
12 | ```
13 |
14 | Or, it can be stringified to get a class name.
15 |
16 | ```js
17 | const rule = nano.spread({
18 | color: 'red'
19 | });
20 |
21 | Hello world!
22 | ```
23 |
24 |
25 | ## Installation
26 |
27 | Simply install `spread` addon.
28 |
29 | Read more about the [Addon Installation](./Addons.md#addon-installation).
30 |
--------------------------------------------------------------------------------
/addon/spread.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | exports.addon = function (renderer) {
4 | if (process.env.NODE_ENV !== 'production') {
5 | require('./__dev__/warnOnMissingDependencies')('spread', renderer, ['put']);
6 | }
7 |
8 | renderer.spread = function (css, block) {
9 | block = block || renderer.hash(css);
10 | block = renderer.pfx + block;
11 | renderer.put('.' + block + ',[data-' + block + ']', css);
12 |
13 | var spread = {
14 | toString: function () {
15 | return ' ' + block;
16 | }
17 | };
18 |
19 | spread['data-' + block] = '';
20 |
21 | return spread;
22 | };
23 | };
24 |
--------------------------------------------------------------------------------
/addon/animate/fadeInScale.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | exports.addon = function (renderer) {
4 | if (process.env.NODE_ENV !== 'production') {
5 | require('../__dev__/warnOnMissingDependencies')('animate', renderer, ['keyframes']);
6 | }
7 |
8 | renderer.put('', {
9 | '@keyframes fadeInScale': {
10 | from: {
11 | opacity: 0,
12 | transform: 'scale(.95)'
13 | },
14 |
15 | to: {
16 | opacity: 1,
17 | transform: 'scale(1)',
18 | }
19 | },
20 |
21 | '.fadeInScale': {
22 | animation: 'fadeInScale .3s',
23 | }
24 | });
25 | };
26 |
--------------------------------------------------------------------------------
/docs/resets.md:
--------------------------------------------------------------------------------
1 | # CSS Resets
2 |
3 | `nano-css` has a number of CSS reset addons. Simply, install one of the following
4 | addons and that CSS reset will be injected on your page.
5 |
6 | - `reset/EricMeyer`
7 | - `reset/EricMeyerCondensed`
8 | - `reset/Minimalistic`
9 | - `reset/Minimalistic2`
10 | - `reset/Minimalistic3`
11 | - `reset/PoorMan`
12 | - `reset/ShaunInman`
13 | - `reset/Siolon`
14 | - `reset/Tantek`
15 | - `reset/Tripoli`
16 | - `reset/Universal`
17 |
18 | Read more about the [Addon Installation](./Addons.md#addon-installation).
19 |
20 |
21 | ## Example
22 |
23 | ```js
24 | import {addon as addonReset} from 'nano-css/addon/reset/EricMeyerCondensed';
25 |
26 | adddonReset(nano);
27 | ```
28 |
--------------------------------------------------------------------------------
/.storybook/atoms.stories.js:
--------------------------------------------------------------------------------
1 | import {createElement as h} from 'react';
2 | import {storiesOf} from '@storybook/react';
3 | const {action} = require('@storybook/addon-actions');
4 | const {linkTo} = require('@storybook/addon-links');
5 | const {create} = require('../index');
6 | const {addon: addonRule} = require('../addon/rule');
7 | const {addon: addonAtoms} = require('../addon/atoms');
8 |
9 | const renderer = create({h});
10 | addonRule(renderer);
11 | addonAtoms(renderer);
12 | const {rule} = renderer;
13 |
14 | const className = rule({
15 | bd: '1px solid red'
16 | }, 'atoms');
17 |
18 | storiesOf('Addons/Atoms', module)
19 | .add('Default', () =>
20 | h('div', {className}, 'Red')
21 | )
22 |
--------------------------------------------------------------------------------
/.storybook/spread.stories.js:
--------------------------------------------------------------------------------
1 | import {createElement as h} from 'react';
2 | import {storiesOf} from '@storybook/react';
3 | const {action} = require('@storybook/addon-actions');
4 | const {linkTo} = require('@storybook/addon-links');
5 | const {create} = require('../index');
6 | const {addon} = require('../addon/spread');
7 |
8 | const renderer = create();
9 | addon(renderer);
10 | const {spread} = renderer;
11 |
12 | const rule = spread({
13 | border: '1px solid red'
14 | }, 'RedBorder');
15 |
16 | storiesOf('Addons/spread()', module)
17 | .add('As class name', () =>
18 | h('div', {className: rule}, 'Hello world')
19 | )
20 | .add('As data attribute', () =>
21 | h('div', rule, 'Hello world')
22 | )
23 |
--------------------------------------------------------------------------------
/addon/animate/fadeInDown.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | exports.addon = function (renderer) {
4 | if (process.env.NODE_ENV !== 'production') {
5 | require('../__dev__/warnOnMissingDependencies')('animate', renderer, ['keyframes']);
6 | }
7 |
8 | renderer.put('', {
9 | '@keyframes fadeInDown': {
10 | from: {
11 | opacity: 0,
12 | transform: 'translate3d(0, -10%, 0)'
13 | },
14 |
15 | to: {
16 | opacity: 1,
17 | transform: 'translate3d(0, 0, 0)',
18 | }
19 | },
20 |
21 | '.fadeInDown': {
22 | animation: 'fadeInDown .3s',
23 | }
24 | });
25 | };
26 |
--------------------------------------------------------------------------------
/.storybook/hydrate.stories.js:
--------------------------------------------------------------------------------
1 | import {createElement as h} from 'react';
2 | import {storiesOf} from '@storybook/react';
3 | const {action} = require('@storybook/addon-actions');
4 | const {linkTo} = require('@storybook/addon-links');
5 | const {create} = require('../index');
6 | const {addon: addonHydrate} = require('../addon/hydrate');
7 |
8 | const nano1 = create();
9 | nano1.put('.hydrate-test', {color: 'red'});
10 |
11 | const nano2 = create({
12 | sh: nano1.sh
13 | });
14 | addonHydrate(nano2);
15 | // nano2.hydrate(nano1.sh);
16 | nano2.put('.hydrate-test', {color: 'red'});
17 |
18 | storiesOf('Addons/hydrate()', module)
19 | .add('Default', () =>
20 | h('div', {}, 'Should log hydration info')
21 | )
22 |
--------------------------------------------------------------------------------
/.storybook/stable.stories.js:
--------------------------------------------------------------------------------
1 | import {createElement as h} from 'react';
2 | import {storiesOf} from '@storybook/react';
3 | const {action} = require('@storybook/addon-actions');
4 | const {linkTo} = require('@storybook/addon-links');
5 | const {create} = require('../index');
6 | const {addon: addonRule} = require('../addon/rule');
7 | const {addon: addonStable} = require('../addon/stable');
8 |
9 | const renderer = create({h});
10 | addonRule(renderer);
11 | addonStable(renderer);
12 | const {rule} = renderer;
13 |
14 | const className = rule({
15 | bd: '1px solid red'
16 | }, 'atoms');
17 |
18 | storiesOf('Addons/Stable hash', module)
19 | .add('Default', () =>
20 | h('div', {className}, 'Red')
21 | )
22 |
--------------------------------------------------------------------------------
/.storybook/vcssom/createUseCssDiffWithNano.ts:
--------------------------------------------------------------------------------
1 | import {cssToTree} from '../../addon/vcssom/cssToTree';
2 | import {HookUseMemo} from './types';
3 |
4 | const createUseCssDiffWithNano = (nano: any) => (useMemo: HookUseMemo) => {
5 | const {VSheet} = nano;
6 | const useCssDiff = () => {
7 | const sheet = useMemo(() => new VSheet(), []);
8 | const diff = useMemo(
9 | () => (css, selector) => {
10 | const tree = {};
11 | cssToTree(tree, css, selector, '');
12 | sheet.diff(tree);
13 | },
14 | []
15 | );
16 | return diff;
17 | };
18 | return useCssDiff;
19 | };
20 |
21 | export default createUseCssDiffWithNano;
22 |
--------------------------------------------------------------------------------
/addon/__dev__/warnOnMissingDependencies.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var pkgName = 'nano-css';
4 |
5 | module.exports = function warnOnMissingDependencies (addon, renderer, deps) {
6 | var missing = [];
7 |
8 | for (var i = 0; i < deps.length; i++) {
9 | var name = deps[i];
10 |
11 | if (!renderer[name]) {
12 | missing.push(name);
13 | }
14 | }
15 |
16 | if (missing.length) {
17 | var str = 'Addon "' + addon + '" is missing the following dependencies:';
18 |
19 | for (var j = 0; j < missing.length; j++) {
20 | str += '\n require("' + pkgName + '/addon/' + missing[j] + '").addon(nano);';
21 | }
22 |
23 | throw new Error(str);
24 | }
25 | };
26 |
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: Node.js CI
2 |
3 | on:
4 | push:
5 | branches: [ master ]
6 |
7 | jobs:
8 | release:
9 | runs-on: ubuntu-latest
10 | strategy:
11 | matrix:
12 | node-version: [20.x]
13 | steps:
14 | - uses: actions/checkout@v4
15 | - name: Use Node.js ${{ matrix.node-version }}
16 | uses: actions/setup-node@v4
17 | with:
18 | node-version: ${{ matrix.node-version }}
19 | cache: yarn
20 | - run: yarn
21 | - run: yarn test
22 | - name: Semantic Release
23 | uses: cycjimmy/semantic-release-action@v4
24 | env:
25 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
26 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
27 |
--------------------------------------------------------------------------------
/docs/googleFont.md:
--------------------------------------------------------------------------------
1 | # `googleFont()` Addon
2 |
3 | Creates `googleFont()` method that loads fonts from *Google Fonts*. Signature:
4 |
5 | ```ts
6 | googleFont(name: string, widths?: number | number[], subsets?: string | string[])
7 | ```
8 |
9 |
10 | ## Example
11 |
12 | Below example loads `Roboto Slab` font at `400` and `700` widths, including `cyrillic` characters.
13 |
14 | ```js
15 | nano.googleFont('Roboto Slab', [400, 700], 'cyrillic');
16 | ```
17 |
18 | Now you can use this font.
19 |
20 | ```js
21 | const className = nano.rule({
22 | fontFamily: '"Roboto Slab", sans'
23 | });
24 | ```
25 |
26 |
27 | ## Installation
28 |
29 | Simply install `googleFont` addon. Read more about the [Addon Installation](./Addons.md#addon-installation).
30 |
--------------------------------------------------------------------------------
/.storybook/stylis.stories.js:
--------------------------------------------------------------------------------
1 | import {createElement as h} from 'react';
2 | import {storiesOf} from '@storybook/react';
3 | const {action} = require('@storybook/addon-actions');
4 | const {linkTo} = require('@storybook/addon-links');
5 | const {create} = require('../index');
6 | const {addon: addonStylis} = require('../addon/stylis');
7 | const {addon: addonRule} = require('../addon/rule');
8 |
9 | const renderer = create({h});
10 | addonStylis(renderer);
11 | addonRule(renderer);
12 | const {rule} = renderer;
13 |
14 | const className = rule(`
15 | color: red;
16 | &:hover {
17 | color: blue;
18 | }
19 | `);
20 |
21 | storiesOf('Addons/stylis', module)
22 | .add('Default', () =>
23 | h('div', {className}, 'Hello world')
24 | )
25 |
--------------------------------------------------------------------------------
/addon/useStyles.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | exports.addon = function (renderer) {
4 | if (process.env.NODE_ENV !== 'production') {
5 | require('./__dev__/warnOnMissingDependencies')('useStyles', renderer, ['sheet']);
6 | }
7 |
8 | renderer.useStyles = function (map, fn, block) {
9 | block = block || fn.displayName || fn.name;
10 |
11 | var styles = renderer.sheet(map, block);
12 | var Component = function (props) {
13 | return fn(props, styles);
14 | };
15 |
16 | if (process.env.NODE_ENV !== 'production') {
17 | if (block) {
18 | Component.displayName = 'useStyles(' + block + ')';
19 | }
20 | }
21 |
22 | return Component;
23 | };
24 | };
25 |
--------------------------------------------------------------------------------
/.github/workflows/gh-pages.yml:
--------------------------------------------------------------------------------
1 | name: Node.js CI
2 |
3 | on:
4 | push:
5 | branches: [ master ]
6 |
7 | jobs:
8 | gh-pages:
9 | runs-on: ubuntu-latest
10 | strategy:
11 | matrix:
12 | node-version: [20.x]
13 | steps:
14 | - uses: actions/checkout@v4
15 | - name: Use Node.js ${{ matrix.node-version }}
16 | uses: actions/setup-node@v4
17 | with:
18 | node-version: ${{ matrix.node-version }}
19 | cache: yarn
20 | - run: yarn install --frozen-lockfile
21 | - run: yarn test --coverage
22 | - name: Publish to gh-pages
23 | uses: peaceiris/actions-gh-pages@v3
24 | with:
25 | github_token: ${{ secrets.GITHUB_TOKEN }}
26 | publish_dir: ./coverage/lcov-report
27 |
--------------------------------------------------------------------------------
/docs/unitless.md:
--------------------------------------------------------------------------------
1 | # `unitless` Addon
2 |
3 | When a style declarations accepts a single numeric value with a unit, in `nano-css`
4 | you have to specify that as a string.
5 |
6 | ```js
7 | nano.rule('.foo', {
8 | width: '50px'
9 | });
10 | ```
11 |
12 | This addon allows you to use numbers instead, it will postfix every numeric property that
13 | requires a unit with `px`. Properties that don't need a unit will remain unitless.
14 |
15 | ```js
16 | nano.rule('.foo', {
17 | width: 50,
18 | zIndex: 10
19 | });
20 | ```
21 |
22 | Result:
23 |
24 | ```css
25 | .foo {
26 | width: 50px;
27 | z-index: 10;
28 | }
29 | ```
30 |
31 | ## Installation
32 |
33 | Simply install `unitless` addon. Read more about the [Addon Installation](./Addons.md#addon-installation).
34 |
--------------------------------------------------------------------------------
/addon/stylis.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var Stylis = require('stylis');
4 | var onRulePlugin = require('./stylis/plugin-onRule');
5 |
6 | exports.addon = function (renderer) {
7 | if (process.env.NODE_ENV !== 'production') {
8 | require('./__dev__/warnOnMissingDependencies')('stylis', renderer, ['put']);
9 | }
10 |
11 | renderer.stylis = new Stylis();
12 |
13 | var plugin = onRulePlugin(function (rawCssRule) {
14 | renderer.putRaw(rawCssRule);
15 | });
16 |
17 | renderer.stylis.use(plugin);
18 |
19 | var put = renderer.put;
20 |
21 | renderer.put = function (selector, css) {
22 | if (typeof css !== 'string') {
23 | return put(selector, css);
24 | }
25 |
26 | renderer.stylis(selector, css);
27 | };
28 | };
29 |
--------------------------------------------------------------------------------
/addon/drule.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | exports.addon = function (renderer) {
4 | if (process.env.NODE_ENV !== 'production') {
5 | require('./__dev__/warnOnMissingDependencies')('drule', renderer, ['rule', 'cache']);
6 | }
7 |
8 | renderer.drule = function (styles, block) {
9 | var className = renderer.rule(styles, block);
10 |
11 | var closure = function (dynamicStyles) {
12 | if (!dynamicStyles) {
13 | return className;
14 | }
15 |
16 | var dynamicClassName = renderer.cache(dynamicStyles);
17 |
18 | return className + dynamicClassName;
19 | };
20 |
21 | closure.toString = function () {
22 | return className;
23 | };
24 |
25 | return closure;
26 | };
27 | };
28 |
--------------------------------------------------------------------------------
/docs/units.md:
--------------------------------------------------------------------------------
1 | # `.units` Addon
2 |
3 | > Inspired by [`electron-css`](https://github.com/azukaar/electron-css#units).
4 |
5 | Functions for adding units to your numbers.
6 |
7 | ```js
8 | nano.px(36); // 36px
9 | nano.em(1); // 1em
10 | nano.pct(25); // 25%
11 | nano.inch(3); // 3in
12 | // ...
13 | ```
14 |
15 | Supports all CSS units and has to special functions `.pct()` and `.inch()` for `%` and `in`, respectively.
16 |
17 | All functions are also available as a separate `.units` property.
18 |
19 | ```js
20 | nano.units.px(36); // 36px
21 | nano.units.em(1); // 1em
22 | nano.units.pct(25); // 25%
23 | nano.units.inch(3); // 3in
24 | // ...
25 | ```
26 |
27 | ## Installation
28 |
29 | Simply install `units` addon. Read more about the [Addon Installation](./Addons.md#addon-installation).
30 |
--------------------------------------------------------------------------------
/.storybook/rule.stories.js:
--------------------------------------------------------------------------------
1 | import {createElement as h} from 'react';
2 | import {storiesOf} from '@storybook/react';
3 | const {action} = require('@storybook/addon-actions');
4 | const {linkTo} = require('@storybook/addon-links');
5 | const {create} = require('../index');
6 | const {addon} = require('../addon/rule');
7 |
8 | const nano = create();
9 | addon(nano);
10 | const {rule} = nano;
11 |
12 | const className1 = rule({
13 | border: '1px solid red'
14 | }, 'RedBorder');
15 |
16 | // Chech if generating multiple times same styles result in clash of CSS selector.
17 | rule({
18 | border: '1px solid red'
19 | }, 'Rule');
20 | rule({
21 | border: '1px solid red'
22 | }, 'Rule');
23 |
24 | storiesOf('Addons/rule()', module)
25 | .add('Default', () =>
26 | h('div', {className: className1}, 'Hello world')
27 | )
28 |
--------------------------------------------------------------------------------
/addon/hydrate.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | exports.addon = function (renderer) {
4 | if (process.env.NODE_ENV !== 'production') {
5 | require('./__dev__/warnOnMissingDependencies')('hydrate', renderer, ['put']);
6 | }
7 |
8 | var hydrated = {};
9 |
10 | renderer.hydrate = function (sh) {
11 | var cssRules = sh.cssRules || sh.sheet.cssRules;
12 |
13 | for (var i = 0; i < cssRules.length; i++)
14 | hydrated[cssRules[i].selectorText] = 1;
15 | };
16 |
17 | if (renderer.client) {
18 | if (renderer.sh) renderer.hydrate(renderer.sh);
19 |
20 | var put = renderer.put;
21 |
22 | renderer.put = function (selector, css) {
23 | if (selector in hydrated) return;
24 |
25 | put(selector, css);
26 | };
27 | }
28 | };
29 |
--------------------------------------------------------------------------------
/docs/reset-font.md:
--------------------------------------------------------------------------------
1 | # `reset-font` Addon
2 |
3 | Injects the below global styles to make fonts look better.
4 |
5 | ```js
6 | {
7 | 'html, body': {
8 | fontFamily: '"Trebuchet MS","Lucida Grande","Lucida Sans Unicode","Lucida Sans",sans-serif',
9 | fontWeight: 400,
10 | fontSize: '16px',
11 |
12 | '-moz-text-size-adjust': '100%',
13 | '-ms-text-size-adjust': '100%',
14 | '-webkit-text-size-adjust': '100%',
15 | 'text-size-adjust': '100%',
16 |
17 | // Makes fonts more smooth/prettier.
18 | '-webkit-font-smoothing': 'antialiased',
19 | '-moz-osx-font-smoothing': 'grayscale',
20 | }
21 | }
22 | ```
23 |
24 |
25 | ## Installation
26 |
27 | Simply install `reset-font` addon. Read more about the [Addon Installation](./Addons.md#addon-installation).
28 |
--------------------------------------------------------------------------------
/docs/array.md:
--------------------------------------------------------------------------------
1 | # `array` Addon
2 |
3 | This addon allows to specify multiple values for the same declaration property.
4 |
5 | ```js
6 | nano.put('.foo', {
7 | display: ['flex', '-webkit-flex']
8 | });
9 | ```
10 |
11 | Result:
12 |
13 | ```css
14 | .foo {
15 | display: flex;
16 | display: -webkit-flex;
17 | }
18 | ```
19 |
20 | It also allows to specify multiple CSS-like objects as an array, which will be merged.
21 |
22 | ```js
23 | nano.put('.bar', [
24 | {
25 | color: 'red'
26 | },
27 | {
28 | border: '1px solid red'
29 | }
30 | ]);
31 | ```
32 |
33 | Result:
34 |
35 | ```css
36 | .bar {
37 | color: red;
38 | border: 1px solid red;
39 | }
40 | ```
41 |
42 |
43 | ## Installation
44 |
45 | Simply install `array` addon. Read more about the [Addon Installation](./Addons.md#addon-installation).
46 |
--------------------------------------------------------------------------------
/addon/limit.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | exports.addon = function (renderer, limit) {
4 | limit = limit || 50000;
5 |
6 | if (process.env.NODE_ENV !== 'production') {
7 | require('./__dev__/warnOnMissingDependencies')('limit', renderer, ['putRaw']);
8 | }
9 |
10 | if (!renderer.client) {
11 | var putRaw = renderer.putRaw;
12 |
13 | renderer.putRaw = function (rawCssRule) {
14 | if (renderer.raw.length + rawCssRule.length > limit) {
15 | /* eslint-disable */
16 | console.info('CSS was not injected, because total CSS would go over ' + limit + ' byte limit.');
17 | console.log(rawCssRule);
18 | /* eslint-enable */
19 |
20 | return;
21 | }
22 |
23 | putRaw(rawCssRule);
24 | };
25 | }
26 | };
27 |
--------------------------------------------------------------------------------
/.storybook/prefixer.stories.js:
--------------------------------------------------------------------------------
1 | import {createElement as h} from 'react';
2 | import {storiesOf} from '@storybook/react';
3 | const {action} = require('@storybook/addon-actions');
4 | const {linkTo} = require('@storybook/addon-links');
5 | const {create} = require('../index');
6 | const {addon: addonRule} = require('../addon/rule');
7 | const {addon: addonPrefixer} = require('../addon/prefixer');
8 |
9 | const renderer = create();
10 | addonRule(renderer);
11 | addonPrefixer(renderer);
12 | const {rule} = renderer;
13 |
14 | const className1 = rule({
15 | flex: 1,
16 | display: 'flex',
17 | alignItems: 'center',
18 | boxShadow: '0 0 5px red',
19 | 'text-shadow': '2px 2px #ff0000',
20 | });
21 |
22 | storiesOf('Addons/prefixer', module)
23 | .add('Default', () =>
24 | h('div', {className: className1}, 'Hello world')
25 | )
26 |
--------------------------------------------------------------------------------
/preset/sheet.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var create = require('../index').create;
4 | var addonStable = require('../addon/stable').addon;
5 | var addonNesting = require('../addon/nesting').addon;
6 | var addonAtoms = require('../addon/atoms').addon;
7 | var addonKeyframes = require('../addon/keyframes').addon;
8 | var addonRule = require('../addon/rule').addon;
9 | var addonSheet = require('../addon/sheet').addon;
10 | var addonSourcemaps = require('../addon/sourcemaps').addon;
11 |
12 | exports.preset = function (config) {
13 | var nano = create(config);
14 |
15 | addonStable(nano);
16 | addonNesting(nano);
17 | addonAtoms(nano);
18 | addonKeyframes(nano);
19 | addonRule(nano);
20 | addonSheet(nano);
21 |
22 | if (process.env.NODE_ENV !== 'production') {
23 | addonSourcemaps(nano);
24 | }
25 |
26 | return nano;
27 | };
28 |
--------------------------------------------------------------------------------
/addon/util/transformComponentStatic.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = function (renderer, prototype, styles, block) {
4 | var render_ = prototype.render;
5 | var className = '';
6 |
7 | prototype.render = function () {
8 | var element = render_.call(this);
9 |
10 | if (element) {
11 | if (!className) {
12 | className = renderer.rule(styles, block);
13 | }
14 |
15 | if (process.env.NODE_ENV === 'production') {
16 | element.props.className = (element.props.className || '') + className;
17 | } else {
18 | element = require('react').cloneElement(element, {
19 | className: (element.props.className || '') + className,
20 | });
21 | }
22 | }
23 |
24 | return element;
25 | };
26 | };
27 |
--------------------------------------------------------------------------------
/addon/hyperstyle.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | exports.addon = function (renderer) {
4 | if (process.env.NODE_ENV !== 'production') {
5 | require('./__dev__/warnOnMissingDependencies')('hyperstyle', renderer, ['sheet']);
6 | }
7 |
8 | renderer.hyperstyle = function (map, block) {
9 | var styles = renderer.sheet(map, block);
10 |
11 | return function (type, props) {
12 | if (props) {
13 | var styleName = props.styleName;
14 |
15 | if (styleName) {
16 | var className = styles[styleName];
17 |
18 | if (className) {
19 | props.className = (props.className || '') + className;
20 | }
21 | }
22 | }
23 |
24 | return renderer.h.apply(null, arguments);
25 | };
26 | };
27 | };
28 |
--------------------------------------------------------------------------------
/addon/reset-font.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | exports.addon = function (renderer) {
4 | if (process.env.NODE_ENV !== 'production') {
5 | require('./__dev__/warnOnMissingDependencies')('reset-font', renderer, ['rule']);
6 | }
7 |
8 | renderer.put('', {
9 | 'html, body': {
10 | fontFamily: '"Trebuchet MS","Lucida Grande","Lucida Sans Unicode","Lucida Sans",sans-serif',
11 | fontWeight: 400,
12 | fontSize: '16px',
13 |
14 | '-moz-text-size-adjust': '100%',
15 | '-ms-text-size-adjust': '100%',
16 | '-webkit-text-size-adjust': '100%',
17 | 'text-size-adjust': '100%',
18 |
19 | // Makes fonts more smooth/prettier.
20 | '-webkit-font-smoothing': 'antialiased',
21 | '-moz-osx-font-smoothing': 'grayscale',
22 | },
23 | });
24 | };
25 |
--------------------------------------------------------------------------------
/addon/reset/Tantek.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | exports.addon = function (renderer) {
4 | if (process.env.NODE_ENV !== 'production') {
5 | require('../__dev__/warnOnMissingDependencies')('reset', renderer, ['put']);
6 | }
7 |
8 | var css = {
9 | ':link,:visited': {
10 | td: 'none',
11 | },
12 | 'ul,ol': {
13 | 'list-style': 'none',
14 | },
15 | 'h1,h2,h3,h4,h5,h6,pre,code,p': {
16 | fz: '1em',
17 | },
18 | 'ul,ol,dl,li,dt,dd,h1,h2,h3,h4,h5,h6,pre,form,body,html,p,blockquote,fieldset,input': {
19 | pad: 0,
20 | mar: 0,
21 | },
22 | 'a img,:link img,:visited img': {
23 | bd: 'none',
24 | },
25 | address: {
26 | fs: 'normal',
27 | },
28 | };
29 |
30 | renderer.put('', css);
31 | };
32 |
--------------------------------------------------------------------------------
/.storybook/putRaw.stories.js:
--------------------------------------------------------------------------------
1 | import {createElement as h} from 'react';
2 | import {storiesOf} from '@storybook/react';
3 | const {action} = require('@storybook/addon-actions');
4 | const {linkTo} = require('@storybook/addon-links');
5 | const {create} = require('../index');
6 |
7 | const renderer = create();
8 | const {putRaw} = renderer;
9 |
10 | putRaw(`
11 | .red-border-raw {
12 | border: 1px solid red;
13 | }
14 | `);
15 | putRaw(`
16 | .red-border-raw:hover {
17 | font-weight: bold;
18 | }
19 | `);
20 | putRaw(`
21 | .red-border-raw span {
22 | color: red;
23 | }
24 | `);
25 |
26 | storiesOf('Addons/putRaw()', module)
27 | .add('Default', () =>
28 | h('div', {className: 'red-border-raw'}, 'Hello world')
29 | )
30 | .add('Nesting', () =>
31 | h('div', {className: 'red-border-raw'}, 'Hello ', h('span', null, 'world'))
32 | )
33 |
--------------------------------------------------------------------------------
/addon/rule.d.ts:
--------------------------------------------------------------------------------
1 | import {CssLikeObject} from '../types/common';
2 | import {NanoRenderer} from '../types/nano';
3 |
4 | export interface RuleAddon {
5 | /**
6 | * You need to install `rule` addon to add this method.
7 | *
8 | * ```js
9 | * import {create} from 'nano-css';
10 | * import {addon as addonRule} from 'nano-css/addon/rule';
11 | *
12 | * const nano = create();
13 | * addonRule(nano);
14 | *
15 | * const className = nano.rule({
16 | * color: 'red',
17 | * });
18 | * ```
19 | *
20 | * @param css [CSS-like object](https://github.com/streamich/nano-css/blob/master/docs/put.md#css-like-object).
21 | * @param block Optional semantic name of this rule, must be unique.
22 | */
23 | rule: (css: CssLikeObject, block?: string) => string;
24 | }
25 |
26 | export function addon(nano: NanoRenderer);
27 |
--------------------------------------------------------------------------------
/docs/animations.md:
--------------------------------------------------------------------------------
1 | # Animations
2 |
3 | `nano-css` has a number of CSS animation addons. Simply install any of the
4 | below animation addons and you will have available class and animation with that same
5 | name.
6 |
7 | - `animate/fadeIn`
8 | - `animate/fadeInDown`
9 | - `animate/fadeInScale`
10 | - `animate/fadeOut`
11 | - `animate/fadeOutScale`
12 |
13 | Read more about the [Addon Installation](./Addons.md#addon-installation).
14 |
15 |
16 | ## Example
17 |
18 | ```js
19 | import {addon as addonAnimateFadeIn} from 'nano-css/addon/animate/fadeIn';
20 |
21 | addonAnimateFadeIn(nano);
22 | ```
23 |
24 | Now you can use the `fadeIn` class name to *"fade in"* your elements.
25 |
26 | ```html
27 | Hello world!
28 | ```
29 |
30 | Or use the `fadeIn` animation directly to apply custom tween settings.
31 |
32 | ```html
33 | Hello world!
34 | ```
35 |
--------------------------------------------------------------------------------
/addon/__tests__/jsx.test.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | 'use strict';
3 |
4 | var env = require('./env');
5 | var create = require('../../index').create;
6 | var addonRule = require('../../addon/rule').addon;
7 | var addonCache = require('../../addon/cache').addon;
8 | var addonJsx = require('../../addon/jsx').addon;
9 |
10 | function createNano (config) {
11 | var nano = create(config);
12 |
13 | addonRule(nano);
14 | addonCache(nano);
15 | addonJsx(nano);
16 |
17 | return nano;
18 | };
19 |
20 | describe('jsx()', function () {
21 | it('installs interface', function () {
22 | var nano = createNano();
23 |
24 | expect(typeof nano.jsx).toBe('function');
25 | });
26 |
27 | it('creates a styling block', function () {
28 | var nano = createNano();
29 | var Comp = nano.jsx('button', {color: 'red'});
30 |
31 | expect(typeof Comp).toBe('function');
32 | });
33 | });
34 |
--------------------------------------------------------------------------------
/addon/array.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | exports.addon = function (renderer) {
4 | if (process.env.NODE_ENV !== 'production') {
5 | require('./__dev__/warnOnMissingDependencies')('rule', renderer, ['put', 'decl']);
6 | }
7 |
8 | var decl = renderer.decl;
9 |
10 | renderer.decl = function (prop, value) {
11 | var result = decl(prop, value);
12 |
13 | if (value instanceof Array) {
14 | var pos = result.indexOf(':');
15 |
16 | prop = result.substr(0, pos + 1);
17 |
18 | result = prop + value.join(';' + prop) + ';';
19 | }
20 |
21 | return result;
22 | };
23 |
24 | var put = renderer.put;
25 |
26 | renderer.put = function (selector, decls, atrule) {
27 | if (decls instanceof Array) {
28 | decls = renderer.assign.apply(null, decls);
29 | }
30 |
31 | return put(selector, decls, atrule);
32 | };
33 | };
34 |
--------------------------------------------------------------------------------
/addon/reset/Siolon.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | exports.addon = function (renderer) {
4 | if (process.env.NODE_ENV !== 'production') {
5 | require('../__dev__/warnOnMissingDependencies')('reset', renderer, ['put']);
6 | }
7 |
8 | var css = {
9 | '*': {
10 | 'vertical-align': 'baseline',
11 | ff: 'inherit',
12 | fs: 'inherit',
13 | fz: '100%',
14 | bd: 'none',
15 | pad: 0,
16 | mar: 0,
17 | },
18 | body: {
19 | pad: '5px',
20 | },
21 | 'h1, h2, h3, h4, h5, h6, p, pre, blockquote, form, ul, ol, dl': {
22 | mar: '20px 0',
23 | },
24 | 'li, dd, blockquote': {
25 | marl: '40px',
26 | },
27 | table: {
28 | 'border-collapse': 'collapse',
29 | 'border-spacing': 0,
30 | },
31 | };
32 |
33 | renderer.put('', css);
34 | };
35 |
--------------------------------------------------------------------------------
/.storybook/global.stories.js:
--------------------------------------------------------------------------------
1 | import {createElement as h} from 'react';
2 | import {storiesOf} from '@storybook/react';
3 | const {action} = require('@storybook/addon-actions');
4 | const {linkTo} = require('@storybook/addon-links');
5 | const {create} = require('../index');
6 | const {addon} = require('../addon/global');
7 | const {addon: addonRule} = require('../addon/rule');
8 |
9 | const nano = create();
10 | addon(nano);
11 | addonRule(nano);
12 |
13 | nano.global({
14 | '.foo': {
15 | color: 'red'
16 | }
17 | });
18 |
19 | nano.rule({
20 | color: 'blue',
21 | '.bar': {
22 | color: 'green'
23 | },
24 | ':global': {
25 | '.baz': {
26 | fontWeight: 'bold'
27 | }
28 | }
29 | });
30 |
31 | storiesOf('Addons/:global', module)
32 | .add('global()', () =>
33 | h('div', {className: 'foo'}, 'Hello world')
34 | )
35 | .add(':global', () =>
36 | h('div', {className: 'bar baz'}, 'Hello world')
37 | )
38 |
--------------------------------------------------------------------------------
/.storybook/vcssom/createUseCss.ts:
--------------------------------------------------------------------------------
1 | import {HookUseMemo, HookUseEffect} from './types';
2 | import createUseCssDiff from './createUseCssDiff';
3 | import nextNameDefault from './nextName';
4 |
5 | // By default generate class name selectors.
6 | const getSelectorDefault = (name: string) => '.' + name;
7 |
8 | const createUseCss = (useMemo: HookUseMemo, useLayoutEffect: HookUseEffect) => {
9 | const useCssDiff = createUseCssDiff(useMemo);
10 | const useCss = (css: object, nextName = nextNameDefault, getSelector = getSelectorDefault) => {
11 | const name = useMemo(nextName, []);
12 | const diff = useCssDiff();
13 |
14 | useLayoutEffect(() => {
15 | const selector = getSelector(name);
16 | diff(css, selector);
17 | return () => {
18 | diff({}, '');
19 | };
20 | });
21 |
22 | return name;
23 | };
24 | return useCss;
25 | };
26 |
27 | export default createUseCss;
28 |
--------------------------------------------------------------------------------
/addon/__tests__/reset.test.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | 'use strict';
3 |
4 | var create = require('../../index').create;
5 |
6 | var resets = [
7 | 'EricMeyer',
8 | 'EricMeyerCondensed',
9 | 'Minimalistic',
10 | 'Minimalistic2',
11 | 'Minimalistic3',
12 | 'PoorMan',
13 | 'ShaunInman',
14 | 'Siolon',
15 | 'Tantek',
16 | 'Tripoli',
17 | 'Universal',
18 | 'Yahoo',
19 | 'Normalize',
20 | ];
21 |
22 | describe('reset', function () {
23 | resets.forEach(function (name) {
24 | var addon = require('../../addon/reset/' + name).addon;
25 |
26 | it(name, function () {
27 | var nano = create();
28 |
29 | nano.put = jest.fn();
30 |
31 | addon(nano);
32 |
33 | expect(nano.put).toHaveBeenCalledTimes(1);
34 | expect(nano.put.mock.calls[0][0]).toBe('');
35 | expect(nano.put.mock.calls[0][1]).toMatchSnapshot();
36 | });
37 | });
38 | });
39 |
--------------------------------------------------------------------------------
/.storybook/array.stories.js:
--------------------------------------------------------------------------------
1 | import {createElement as h} from 'react';
2 | import {storiesOf} from '@storybook/react';
3 | const {action} = require('@storybook/addon-actions');
4 | const {linkTo} = require('@storybook/addon-links');
5 | const {create} = require('../index');
6 | const {addon: addonRule} = require('../addon/rule');
7 | const {addon: addonArray} = require('../addon/array');
8 |
9 | const renderer = create({h});
10 | addonRule(renderer);
11 | addonArray(renderer);
12 | const {rule} = renderer;
13 |
14 | const cn1 = rule({
15 | display: ['flex', '-webkit-flex'],
16 | });
17 |
18 | const cn2 = rule([
19 | {
20 | color: 'red'
21 | },
22 | {
23 | border: '1px solid green'
24 | }
25 | ]);
26 |
27 | storiesOf('Addons/array', module)
28 | .add('Value array', () =>
29 | h('div', {className: cn1}, 'Value as array')
30 | )
31 | .add('CSS-like object array', () =>
32 | h('div', {className: cn2}, 'CSS-like object array')
33 | )
34 |
--------------------------------------------------------------------------------
/.storybook/important.stories.js:
--------------------------------------------------------------------------------
1 | import {createElement as h} from 'react';
2 | import {storiesOf} from '@storybook/react';
3 | const {action} = require('@storybook/addon-actions');
4 | const {linkTo} = require('@storybook/addon-links');
5 | const {create} = require('../index');
6 | const {addon: addonRule} = require('../addon/rule');
7 | const {addon: addonImportant} = require('../addon/important');
8 |
9 | const renderer = create();
10 | addonRule(renderer);
11 | addonImportant(renderer);
12 | const {rule} = renderer;
13 |
14 | const className1 = rule({
15 | border: '1px solid red'
16 | }, 'RedBorderImportant');
17 |
18 | const className2 = rule({
19 | border: '1px solid red !important'
20 | }, 'RedBorderImportantImportant');
21 |
22 | storiesOf('Addons/!important', module)
23 | .add('Default', () =>
24 | h('div', {className: className1}, 'Hello world')
25 | )
26 | .add('Double !important', () =>
27 | h('div', {className: className2}, 'Hello world')
28 | )
29 |
--------------------------------------------------------------------------------
/.storybook/put.stories.js:
--------------------------------------------------------------------------------
1 | import {createElement as h} from 'react';
2 | import {storiesOf} from '@storybook/react';
3 | const {action} = require('@storybook/addon-actions');
4 | const {linkTo} = require('@storybook/addon-links');
5 | const {create} = require('../index');
6 |
7 | const renderer = create({
8 | verbose: true
9 | });
10 | const {put} = renderer;
11 |
12 | put('.red-border', {
13 | border: '1px solid red',
14 | ':hover': {
15 | fontWeight: 'bold'
16 | },
17 | '::trololo': {
18 | color: 'blue',
19 | },
20 | span: {
21 | color: 'red'
22 | }
23 | });
24 |
25 | put('.check-same-name-warning', {color: 'red'});
26 | put('.check-same-name-warning', {color: 'red'});
27 |
28 | storiesOf('Addons/put()', module)
29 | .add('Default', () =>
30 | h('div', {className: 'red-border'}, 'Hello world')
31 | )
32 | .add('Nesting', () =>
33 | h('div', {className: 'red-border'}, 'Hello ', h('span', null, 'world'))
34 | )
35 |
--------------------------------------------------------------------------------
/docs/withStyles.md:
--------------------------------------------------------------------------------
1 | # `withStyles()` Addon
2 |
3 | `withStyles()` is a higher order component, which injects `styles` props.
4 |
5 | ```jsx
6 | const cssMap = {
7 | main: {
8 | border: '1px solid red'
9 | }
10 | };
11 |
12 | const MyComp = withStyles(cssMap, ({styles}) => {
13 | return
14 | });
15 | ```
16 |
17 | You can specify the name our your component as a third argument.
18 |
19 | ```js
20 | withStyles(cssMap, MyComp, 'MyComponent');
21 | ```
22 |
23 | Or you can name your function, in that case the function name will
24 | be automatically picked up.
25 |
26 | ```jsx
27 | const MyComp = withStyles(cssMap, function MyComponent ({styles}) {
28 | return
29 | });
30 | ```
31 |
32 |
33 | ## Installation
34 |
35 | Install `withStyles` addon and its dependencies:
36 |
37 | - [`rule()`](./rule.md)
38 | - [`sheet()`](./sheet.md)
39 |
40 | Read more about the [Addon Installation](./Addons.md#addon-installation).
41 |
--------------------------------------------------------------------------------
/.storybook/googleFont.stories.js:
--------------------------------------------------------------------------------
1 | import {createElement as h} from 'react';
2 | import {storiesOf} from '@storybook/react';
3 | const {action} = require('@storybook/addon-actions');
4 | const {linkTo} = require('@storybook/addon-links');
5 | const {create} = require('../index');
6 | const {addon: addonRule} = require('../addon/rule');
7 | const {addon: addonGoogleFont} = require('../addon/googleFont');
8 |
9 | const nano = create();
10 | addonRule(nano);
11 | addonGoogleFont(nano);
12 |
13 | nano.googleFont('Roboto');
14 | nano.googleFont('Roboto Slab', 700);
15 |
16 | const className1 = nano.rule({
17 | fontFamily: '"Roboto"'
18 | }, 'Roboto');
19 |
20 | const className2 = nano.rule({
21 | fontFamily: '"Roboto Slab"'
22 | }, 'Roboto_Slab');
23 |
24 | storiesOf('Addons/googleFont', module)
25 | .add('Roboto', () =>
26 | h('div', {className: className1}, 'Hello world')
27 | )
28 | .add('Roboto Slab, bold', () =>
29 | h('div', {className: className2}, 'Hello world')
30 | )
31 |
--------------------------------------------------------------------------------
/addon/component.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var React = require('react');
4 | var Component = React.Component;
5 | var transformComponentStatic = require('./util/transformComponentStatic');
6 | var transformComponentDynamic = require('./util/transformComponentDynamic');
7 |
8 | exports.addon = function (renderer) {
9 | if (process.env.NODE_ENV !== 'production') {
10 | require('./__dev__/warnOnMissingDependencies')('component', renderer, ['rule', 'cache']);
11 | }
12 |
13 | function CssComponent (props, context) {
14 | Component.call(this, props, context);
15 |
16 | var Comp = this.constructor;
17 |
18 | if (Comp.css) transformComponentStatic(renderer, Comp.prototype, Comp.css);
19 | if (this.css) transformComponentDynamic(renderer, Comp, this.css.bind(this));
20 | }
21 |
22 | CssComponent.prototype = Object.create(Component.prototype);
23 | CssComponent.prototype.constructor = CssComponent;
24 |
25 | renderer.Component = CssComponent;
26 | };
27 |
--------------------------------------------------------------------------------
/.storybook/CSSOM.stories.js:
--------------------------------------------------------------------------------
1 | import {createElement as h} from 'react';
2 | import {storiesOf} from '@storybook/react';
3 | const {create} = require('../index');
4 | const {addon: addonCSSOM} = require('../addon/cssom');
5 |
6 | const nano = create();
7 | addonCSSOM(nano);
8 |
9 | const rule = nano.createRule('.test_cssom_rule');
10 | rule.style.color = 'red';
11 | rule.style.borderColor = 'red';
12 | rule.selectorText = '.test_cssom_rule2';
13 | console.log('rule', rule);
14 | for (let i = 0; i < rule.style.length; i++) {
15 | console.log(rule.style[i]);
16 | }
17 |
18 | const atrule = nano.createRule('.test_cssom_atrule', '@media only screen and (max-width: 600px)');
19 | atrule.style.color = 'blue';
20 | console.log('atrule', atrule);
21 |
22 | storiesOf('Addons/CSSOM', module)
23 | .add('rule', () =>
24 | h('div', {className: 'test_cssom_rule2'}, 'Hello world')
25 | )
26 | .add('with @media at rule', () =>
27 | h('div', {className: 'test_cssom_atrule'}, 'Hello world')
28 | )
29 |
--------------------------------------------------------------------------------
/addon/withStyles.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | exports.addon = function (renderer) {
4 | if (process.env.NODE_ENV !== 'production') {
5 | require('./__dev__/warnOnMissingDependencies')('withStyles', renderer, ['sheet']);
6 | }
7 |
8 | renderer.withStyles = function (map, fn, block) {
9 | block = block || fn.displayName || fn.name;
10 |
11 | var styles = renderer.sheet(map, block);
12 | var Component = function (props) {
13 | if (process.env.NODE_ENV !== 'production') {
14 | return fn(Object.assign({}, props, {
15 | styles: styles
16 | }));
17 | }
18 |
19 | props.styles = styles;
20 |
21 | return fn(props);
22 | };
23 |
24 | if (process.env.NODE_ENV !== 'production') {
25 | if (block) {
26 | Component.displayName = 'withStyles(' + block + ')';
27 | }
28 | }
29 |
30 | return Component;
31 | };
32 | };
33 |
--------------------------------------------------------------------------------
/docs/Presets.md:
--------------------------------------------------------------------------------
1 | # Presets
2 |
3 | `nano-css` comes with a range of addons that sometimes may be overwhelming to pick through and
4 | cumbersome to install. To ease this we have created *Presets* — presets are simply functions
5 | that create a `nano-css` instance and install addons automatically for you.
6 |
7 |
8 | ## Example
9 |
10 | ```js
11 | import {preset} from 'nano-css/preset/sheet';
12 |
13 | const {rule, sheet} = preset();
14 |
15 | export {
16 | rule,
17 | sheet
18 | };
19 | ```
20 |
21 |
22 | ## List
23 |
24 | Below is a list of available presets.
25 |
26 | - `sheet` — installs [`sheet()`](./sheet.md) addon, as well as `stable`, `nesting`, `atoms`, and `keyframes` addons
27 | - `vdom` — similar to `sheet` preset, but also installs [`jsx()`](./jsx.md) addon, you need to provide `h` function in configuration
28 | - `react` — similar to `vdom` preset, but specifies hyperscript function `h` automatically and also installs `snake`, `style`, `styled`, and `decorator` addons
29 |
--------------------------------------------------------------------------------
/docs/SSR.md:
--------------------------------------------------------------------------------
1 | # Server-side Rendering
2 |
3 | When used in a non-browser environment `nano-css` will not attempt to inject CSS into the DOM, but
4 | will instead accumulate it as a raw CSS string in a `raw` property. Use that to generate your templates
5 | on the server:
6 |
7 | ```js
8 | html += ``;
9 | ```
10 |
11 |
12 | ## Re-hydrating
13 |
14 | `nano-css` can re-hydrate server-side generated CSS. To do that, you need to install [`hydrate` addon](hydrate.md);
15 | and provide style sheet you want to hydrate to the `nano-css` instance when creating it.
16 |
17 | ```js
18 | const nano = create({
19 | sh: typeof document === 'object' ? document.getElementById('nano-css') : null
20 | });
21 |
22 | html += ``;
23 | ```
24 |
25 |
26 | ## External Style Sheet Extraction
27 |
28 | You can also extract styles into an external style sheet. Make sure you need to do it, because it might
29 | be an anti-pattern. To do that use [`extract`](./extract.md) addon.
30 |
--------------------------------------------------------------------------------
/addon/important.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | function hasImportant (rawDecl) {
4 | var parts = rawDecl.split(' ');
5 |
6 | for (var i = 0; i < parts.length; i++) {
7 | var part = parts[i].trim();
8 |
9 | if (part === '!important') return true;
10 | }
11 |
12 | return false;
13 | }
14 |
15 | exports.addon = function (renderer) {
16 | var decl = renderer.decl;
17 |
18 | renderer.decl = function (prop, value) {
19 | var rawDecl = decl(prop, value);
20 | var decls = rawDecl.split(';');
21 | var css = '';
22 |
23 | for (var i = 0; i < decls.length; i++) {
24 | rawDecl = decls[i].trim();
25 |
26 | if (!rawDecl) continue;
27 |
28 | // Don't add "!important" if it is already added.
29 | if (!hasImportant(rawDecl)) {
30 | css += rawDecl + ' !important;';
31 | } else {
32 | css += rawDecl + ';';
33 | }
34 | }
35 |
36 | return css;
37 | };
38 | };
39 |
--------------------------------------------------------------------------------
/docs/useStyles.md:
--------------------------------------------------------------------------------
1 | # `useStyles()` Addon
2 |
3 | `useStyles()` interface provides a style map as a second argument to your component,
4 | next to props.
5 |
6 | ```jsx
7 | const cssMap = {
8 | main: {
9 | border: '1px solid red'
10 | }
11 | };
12 |
13 | const MyComp = useStyles(cssMap, (props, styles) => {
14 | return
15 | });
16 | ```
17 |
18 | You can specify the name our your component as a third argument.
19 |
20 | ```js
21 | useStyles(cssMap, MyComp, 'MyComponent');
22 | ```
23 |
24 | Or you can name your function, in that case the function name will
25 | be automatically picked up.
26 |
27 | ```jsx
28 | const MyComp = useStyles(cssMap, function MyComponent (props, styles) {
29 | return
30 | });
31 | ```
32 |
33 |
34 | ## Installation
35 |
36 | Install `useStyles` addon and its dependencies:
37 |
38 | - [`rule()`](./rule.md)
39 | - [`sheet()`](./sheet.md)
40 |
41 | Read more about the [Addon Installation](./Addons.md#addon-installation).
42 |
--------------------------------------------------------------------------------
/.storybook/animate.stories.js:
--------------------------------------------------------------------------------
1 | import {createElement as h} from 'react';
2 | import {storiesOf} from '@storybook/react';
3 | const {action} = require('@storybook/addon-actions');
4 | const {linkTo} = require('@storybook/addon-links');
5 | const {create} = require('../index');
6 | const {addon: addonRule} = require('../addon/rule');
7 | const {addon: addonKeyframes} = require('../addon/keyframes');
8 |
9 | const animations = [
10 | 'fadeIn',
11 | 'fadeInDown',
12 | 'fadeInScale',
13 | 'fadeOut',
14 | 'fadeOutScale',
15 | ];
16 |
17 | const nano = create();
18 | addonRule(nano);
19 | addonKeyframes(nano);
20 | const {rule} = nano;
21 |
22 | var className = rule({
23 | width: '200px',
24 | height: '200px',
25 | background: 'red',
26 | });
27 |
28 | let stories = storiesOf('Addons/Animate', module);
29 |
30 | animations.forEach(name => {
31 | stories = stories.add(name, () => {
32 | require('../addon/animate/' + name).addon(nano);
33 | return h('div', {className: name + className});
34 | });
35 | });
36 |
--------------------------------------------------------------------------------
/addon/keyframes.d.ts:
--------------------------------------------------------------------------------
1 | import {NanoRenderer} from '../types/nano';
2 |
3 | export interface KeyframesAddon {
4 | /**
5 | * @param frames Map of keyframes.
6 | * @param block Optional semantic name.
7 | *
8 | * Returns a generated animation name.
9 | *
10 | * ```js
11 | * const animationName = nano.keyframes({
12 | * from: {
13 | * left: '0%',
14 | * },
15 | * to: {
16 | * left: '100%',
17 | * },
18 | * });
19 | * ```
20 | *
21 | * You need to install [`keyframes()` addon]()https://github.com/streamich/nano-css/blob/master/docs/keyframes.md) to use this method.
22 | *
23 | * ```js
24 | * import {create} from 'nano-css';
25 | * import {addon as addonKeyframes} from 'nano-css/addon/keyframes';
26 | *
27 | * const nano = create();
28 | * addonKeyframes(nano);
29 | * ```
30 | */
31 | keyframes: (frames: object, block?: string) => string;
32 | }
33 |
34 | export function addon(nano: NanoRenderer);
35 |
--------------------------------------------------------------------------------
/docs/global.md:
--------------------------------------------------------------------------------
1 | # `:global` Addon
2 |
3 | `global` addon allows to inject global CSS. It introduces a `:global` selector that can be
4 | used in any CSS-like object and exposes `global()` function for convenience.
5 |
6 | Use `:global` selector.
7 |
8 | ```js
9 | const className = nano.put('.foo', {
10 | color: 'red',
11 | '.nested': {
12 | fontWeight: 'bold'
13 | },
14 | ':global': {
15 | '.global_class': {
16 | border: '1px solid red'
17 | }
18 | }
19 | });
20 | ```
21 |
22 | This results in:
23 |
24 | ```css
25 | .foo {
26 | color: red;
27 | }
28 | .foo .nested {
29 | font-weight: bold;
30 | }
31 | .global_class {
32 | border: 1px solid red;
33 | }
34 | ```
35 |
36 | Use `global()` function to emit global CSS.
37 |
38 | ```js
39 | nano.global({
40 | '.global_class': {
41 | border: '1px solid red'
42 | }
43 | });
44 | ```
45 |
46 |
47 | ## Installation
48 |
49 | Simply install `global` addon. Read more about the [Addon Installation](./Addons.md#addon-installation).
50 |
--------------------------------------------------------------------------------
/.storybook/hyperstyle.stories.js:
--------------------------------------------------------------------------------
1 | import {createElement} from 'react';
2 | import {storiesOf} from '@storybook/react';
3 | const {action} = require('@storybook/addon-actions');
4 | const {linkTo} = require('@storybook/addon-links');
5 | const {create} = require('../index');
6 | const {addon: addonRule} = require('../addon/rule');
7 | const {addon: addonSheet} = require('../addon/sheet');
8 | const {addon: addonHyperstyle} = require('../addon/hyperstyle');
9 |
10 | const renderer = create({h: createElement});
11 | addonRule(renderer);
12 | addonSheet(renderer);
13 | addonHyperstyle(renderer);
14 | const {hyperstyle} = renderer;
15 |
16 | const h = hyperstyle({
17 | tomato: {
18 | border: '1px solid tomato',
19 | },
20 | yellow: {
21 | border: '1px solid yellow',
22 | },
23 | }, 'hello');
24 |
25 | storiesOf('Addons/hyperstyle()', module)
26 | .add('Default', () =>
27 | h('div', null,
28 | h('div', {styleName: 'tomato'}, 'Red'),
29 | h('div', {styleName: 'yellow'}, 'Yellow'),
30 | )
31 | )
32 |
--------------------------------------------------------------------------------
/addon/style.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | exports.addon = function (renderer) {
4 | if (process.env.NODE_ENV !== 'production') {
5 | require('./__dev__/warnOnMissingDependencies')('style', renderer, ['jsx']);
6 | }
7 |
8 | renderer.style = function (fn, styles, dynamicTemplate, block) {
9 | var jsxComponent = renderer.jsx(fn, styles, block);
10 |
11 | var Component = function(props) {
12 | var copy = props;
13 |
14 | if (process.env.NODE_ENV !== 'production') {
15 | copy = Object.assign({}, props);
16 | }
17 |
18 | if (dynamicTemplate) {
19 | copy.css = dynamicTemplate(props);
20 | }
21 |
22 | return jsxComponent(copy);
23 | };
24 |
25 | if (process.env.NODE_ENV !== 'production') {
26 | if (block || (typeof fn === 'function')) {
27 | Component.displayName = 'style(' + (block || fn.displayName || fn.name) + ')';
28 | }
29 | }
30 |
31 | return Component;
32 | };
33 | };
34 |
--------------------------------------------------------------------------------
/docs/nesting.md:
--------------------------------------------------------------------------------
1 | # `nesting` Addon
2 |
3 | `nano-css` allows to do basic class name nesting out-of-the-box. This addon
4 | extends that functionality in the following ways.
5 |
6 | Interpolates nested selectors separated by comma.
7 |
8 | ```js
9 | nano.put('.foo', {
10 | '.bar, .baz': {
11 | color: 'red'
12 | }
13 | });
14 | ```
15 |
16 | This results in:
17 |
18 | ```css
19 | .foo .bar, .foo .baz {
20 | color: red;
21 | }
22 | ```
23 |
24 | Introduces `&` meta character, which refers to the parent selector.
25 |
26 | ```js
27 | nano.put('.foo', {
28 | color: 'red',
29 | '&:hover': {
30 | color: 'blue'
31 | },
32 | '.global_class &': {
33 | color: 'green'
34 | }
35 | });
36 | ```
37 |
38 | This results in:
39 |
40 | ```css
41 | .foo {
42 | color: red;
43 | }
44 | .foo:hover {
45 | color: blue;
46 | }
47 | .global_class .foo {
48 | color: green;
49 | }
50 | ```
51 |
52 |
53 | ## Installation
54 |
55 | Simply install `nesting` addon. Read more about the [Addon Installation](./Addons.md#addon-installation).
56 |
--------------------------------------------------------------------------------
/.storybook/drule.stories.js:
--------------------------------------------------------------------------------
1 | import {createElement as h} from 'react';
2 | import {storiesOf} from '@storybook/react';
3 | const {action} = require('@storybook/addon-actions');
4 | const {linkTo} = require('@storybook/addon-links');
5 | const {create} = require('../index');
6 | const {addon: addonRule} = require('../addon/rule');
7 | const {addon: addonDrule} = require('../addon/drule');
8 | const {addon: addonCache} = require('../addon/cache');
9 |
10 | const renderer = create();
11 | addonCache(renderer);
12 | addonRule(renderer);
13 | addonDrule(renderer);
14 | const {drule} = renderer;
15 |
16 | const className1 = drule({
17 | border: '1px solid red'
18 | }, 'RedBorder');
19 |
20 | storiesOf('Addons/drule()', module)
21 | .add('Default', () =>
22 | h('div', {className: '' + className1}, 'Hello world')
23 | )
24 | .add('Dynamic styles', () =>
25 | h('div', null,
26 | h('div', {className: className1({fontWeight: 'bold'})}, 'Hello world'),
27 | h('div', {className: className1({color: 'blue'})}, 'Hello world')
28 | )
29 | )
30 |
--------------------------------------------------------------------------------
/addon/__tests__/__snapshots__/tachyons.test.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`tachyons .dim 1`] = `
4 | Object {
5 | ":focus": Object {
6 | "opacity": ".5",
7 | },
8 | ":hover": Object {
9 | "opacity": ".5",
10 | },
11 | "opacity": 1,
12 | "transition": "opacity .15s ease-in",
13 | }
14 | `;
15 |
16 | exports[`tachyons .grow 1`] = `
17 | Object {
18 | "-moz-osx-font-smoothing": "grayscale",
19 | ":focus": Object {
20 | "transform": "scale(1.05)",
21 | },
22 | ":hover": Object {
23 | "transform": "scale(1.05)",
24 | },
25 | "backfaceVisibility": "hidden",
26 | "transform": "translateZ(0)",
27 | "transition": "transform 0.25s ease-out",
28 | }
29 | `;
30 |
31 | exports[`tachyons multiple rules 1`] = `
32 | Object {
33 | "float": "left",
34 | "fontFamily": "georgia,times,serif",
35 | "fontSize": "1.25rem",
36 | "fontStyle": "italic",
37 | "fontWeight": "bold",
38 | "maxWidth": "30em",
39 | "textDecoration": "line-through",
40 | "textTransform": "uppercase",
41 | "width": "36.66667%",
42 | }
43 | `;
44 |
--------------------------------------------------------------------------------
/docs/emmet.md:
--------------------------------------------------------------------------------
1 | # `emmet` Addon
2 |
3 | This addon is a super-set for [`atoms`](./atoms.md) addon, but uses [Emmet](https://emmet.io/)
4 | specific abbreviations.
5 |
6 | > :warning: Since this is a shorthand library, similar to `atoms`, do not use both these
7 | > libraries at the same time, as few of the abbreviations might conflict.
8 |
9 | ### Usage
10 |
11 | ```js
12 | const className = rule({
13 | c: '#fafafa',
14 | bgc: '#424242',
15 | bd: '1px solid #424242',
16 | });
17 | ```
18 |
19 | Those are good for **DX** and they will decrease your overall bundle size after
20 | sufficient use. You can find the full up-to-date emmet list [here](../addon/emmet.js) or refer
21 | [Emmet Cheatsheet](https://docs.emmet.io/cheat-sheet/) (CSS Section) for all possible abbreviations.
22 |
23 | > Currently `@media` queries, `@keyframes` and related abbreviations are not supported. If a specific Emmet
24 | > shorthand is **required** and is not present in this addon, please raise an issue requesting for
25 | > the support.
26 |
27 | ## Installation
28 |
29 | Simply install `emmet` addon. Read more about the [Addon Installation](./Addons.md#addon-installation).
30 |
--------------------------------------------------------------------------------
/addon/dsheet.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | exports.addon = function (renderer) {
4 | if (process.env.NODE_ENV !== 'production') {
5 | require('./__dev__/warnOnMissingDependencies')('dsheet', renderer, ['sheet', 'cache']);
6 | }
7 |
8 | renderer.dsheet = function (map, block) {
9 | var styles = renderer.sheet(map, block);
10 | var closures = {};
11 |
12 | var createClosure = function (elementModifier) {
13 | var closure = function (dynamicStyles) {
14 | if (!dynamicStyles) {
15 | return styles[elementModifier];
16 | }
17 |
18 | var dynamicClassName = renderer.cache(dynamicStyles);
19 |
20 | return styles[elementModifier] + dynamicClassName;
21 | };
22 |
23 | closure.toString = function () {
24 | return styles[elementModifier];
25 | };
26 |
27 | return closure;
28 | };
29 |
30 | for (var elementModifier in map) {
31 | closures[elementModifier] = createClosure(elementModifier);
32 | }
33 |
34 | return closures;
35 | };
36 | };
37 |
--------------------------------------------------------------------------------
/.storybook/dsheet.stories.js:
--------------------------------------------------------------------------------
1 | import {createElement as h} from 'react';
2 | import {storiesOf} from '@storybook/react';
3 | const {action} = require('@storybook/addon-actions');
4 | const {linkTo} = require('@storybook/addon-links');
5 | const {create} = require('../index');
6 | const {addon: addonRule} = require('../addon/rule');
7 | const {addon: addonSheet} = require('../addon/sheet');
8 | const {addon: addonDsheet} = require('../addon/dsheet');
9 | const {addon: addonCache} = require('../addon/cache');
10 |
11 | const renderer = create();
12 | addonRule(renderer);
13 | addonCache(renderer);
14 | addonSheet(renderer);
15 | addonDsheet(renderer);
16 | const {dsheet} = renderer;
17 |
18 | const styles = dsheet({
19 | block: {
20 | border: '1px solid red'
21 | }
22 | });
23 |
24 | storiesOf('Addons/dsheet()', module)
25 | .add('Default', () =>
26 | h('div', {className: '' + styles.block}, 'Hello world')
27 | )
28 | .add('Dynamic', () =>
29 | h('div', null,
30 | h('div', {className: styles.block({color: 'red'})}, 'Hello world'),
31 | h('div', {className: styles.block({color: 'green'})}, 'Hello world'),
32 | )
33 | )
34 |
--------------------------------------------------------------------------------
/addon/__tests__/units.test.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | 'use strict';
3 |
4 | var create = require('../../index').create;
5 | var addonUnits = require('../../addon/units').addon;
6 |
7 | function createNano (config) {
8 | var nano = create(config);
9 |
10 | addonUnits(nano);
11 |
12 | return nano;
13 | };
14 |
15 | describe('units', function () {
16 | it('creates public methods', function () {
17 | var nano = createNano();
18 |
19 | expect(typeof nano.px).toBe('function');
20 | expect(typeof nano.units).toBe('object');
21 | expect(typeof nano.units.px).toBe('function');
22 | });
23 |
24 | it('works', function () {
25 | var nano = createNano();
26 |
27 | expect(nano.px(25)).toBe('25px');
28 | expect(nano.units.px(25)).toBe('25px');
29 | expect(nano.units.pt(1)).toBe('1pt');
30 | expect(nano.pt(1)).toBe('1pt');
31 | expect(nano.vmax(100)).toBe('100vmax');
32 | expect(nano.vw(3)).toBe('3vw');
33 | });
34 |
35 | it('special cases', function () {
36 | var nano = createNano();
37 |
38 | expect(nano.pct(20)).toBe('20%');
39 | expect(nano.inch(3)).toBe('3in');
40 | });
41 | });
42 |
--------------------------------------------------------------------------------
/addon/reset/Yahoo.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | exports.addon = function (renderer) {
4 | if (process.env.NODE_ENV !== 'production') {
5 | require('../__dev__/warnOnMissingDependencies')('reset', renderer, ['put']);
6 | }
7 |
8 | var css = {
9 | 'body,div,dl,dt,dd,ul,ol,li,h1,h2,h3,h4,h5,h6,pre,form,fieldset,input,textarea,p,blockquote,th,td': {
10 | pad: 0,
11 | mar: 0,
12 | },
13 | table: {
14 | 'border-collapse': 'collapse',
15 | 'border-spacing': 0,
16 | },
17 | 'fieldset,img': {
18 | bd: 0,
19 | },
20 | 'address,caption,cite,code,dfn,em,strong,th,var': {
21 | fw: 'normal',
22 | fs: 'normal',
23 | },
24 | 'ol,ul': {
25 | 'list-style': 'none',
26 | },
27 | 'caption,th': {
28 | ta: 'left',
29 | },
30 | 'h1,h2,h3,h4,h5,h6': {
31 | fw: 'normal',
32 | fz: '100%',
33 | },
34 | 'q:before,q:after': {
35 | con: '""',
36 | },
37 | 'abbr,acronym': {
38 | bd: 0,
39 | },
40 | };
41 |
42 | renderer.put('', css);
43 | };
44 |
--------------------------------------------------------------------------------
/docs/keyframes.md:
--------------------------------------------------------------------------------
1 | # `keyframes()` Addon
2 |
3 | This addon allows one to define CSS `@keyframes` in any CSS-like object.
4 |
5 | ```js
6 | const className = nano.rule({
7 | animation: 'my-animation 2s',
8 | '@keyframes my-animation': {
9 | '0%': {
10 | transform: 'rotate(0deg)'
11 | },
12 | '100%': {
13 | transform: 'rotate(359deg)'
14 | }
15 | }
16 | });
17 | ```
18 |
19 | It also exposes a `keyframes()` function, which generates a unique animation name automatically.
20 |
21 | ```js
22 | const animation = nano.keyframes({
23 | '0%': {
24 | transform: 'rotate(0deg)'
25 | },
26 | '100%': {
27 | transform: 'rotate(359deg)'
28 | }
29 | });
30 |
31 | const className = rule({
32 | animation: `${animation} 5s`
33 | });
34 | ```
35 |
36 |
37 | ## Configuration
38 |
39 | As a second argument, `keyframes` addon can accept a configuration object with the following keys:
40 |
41 | - `prefixes` — optional, array of prefixes, defaults to `['-webkit-', '-moz-', '-o-', '']`.
42 |
43 |
44 | ## Installation
45 |
46 | Simply install `keyframes` addon. Read more about the [Addon Installation](./Addons.md#addon-installation).
47 |
--------------------------------------------------------------------------------
/addon/__tests__/tachyons.test.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | 'use strict';
3 |
4 | var create = require('../../index').create;
5 | var addonRule = require('../../addon/rule').addon;
6 | var addonCache = require('../../addon/cache').addon;
7 | var addonTachyons = require('../../addon/tachyons').addon;
8 |
9 | function createNano (config) {
10 | var nano = create(config);
11 |
12 | addonRule(nano);
13 | addonCache(nano);
14 | addonTachyons(nano);
15 |
16 | return nano;
17 | };
18 |
19 | describe('tachyons', function () {
20 | it('works', function () {
21 | var nano = createNano();
22 |
23 | expect(nano.s.f1.obj).toEqual({
24 | fontSize: '3rem'
25 | });
26 | });
27 |
28 | it('multiple rules', function () {
29 | var nano = createNano();
30 |
31 | expect(nano.s.f4.i.b.strike.ttu.serif.measure.fl.wTwoThirds.obj).toMatchSnapshot();
32 | });
33 |
34 | it('.grow', function () {
35 | var nano = createNano();
36 |
37 | expect(nano.s.grow.obj).toMatchSnapshot();
38 | });
39 |
40 | it('.dim', function () {
41 | var nano = createNano();
42 |
43 | expect(nano.s.dim.obj).toMatchSnapshot();
44 | });
45 | });
46 |
--------------------------------------------------------------------------------
/addon/reset/ShaunInman.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | exports.addon = function (renderer) {
4 | if (process.env.NODE_ENV !== 'production') {
5 | require('../__dev__/warnOnMissingDependencies')('reset', renderer, ['put']);
6 | }
7 |
8 | var css = {
9 | 'body,div,dl,dt,dd,ul,ol,li,h1,h2,h3,h4,h5,h6,pre,form,fieldset,input,p,blockquote,table,th,td,embed,object': {
10 | pad: 0,
11 | mar: 0,
12 | },
13 | table: {
14 | 'border-collapse': 'collapse',
15 | 'border-spacing': 0,
16 | },
17 | 'fieldset,img,abbr': {
18 | bd: 0,
19 | },
20 | 'address,caption,cite,code,dfn,em,h1,h2,h3,h4,h5,h6,strong,th,var': {
21 | fw: 'normal',
22 | fs: 'normal',
23 | },
24 | ul: {
25 | 'list-style': 'none',
26 | },
27 | 'caption,th': {
28 | ta: 'left',
29 | },
30 | 'h1,h2,h3,h4,h5,h6': {
31 | fz: '1.0em',
32 | },
33 | 'q:before,q:after': {
34 | con: '""',
35 | },
36 | 'a,ins': {
37 | td: 'none',
38 | },
39 | };
40 |
41 | renderer.put('', css);
42 | };
43 |
--------------------------------------------------------------------------------
/.storybook/nesting.stories.js:
--------------------------------------------------------------------------------
1 | import {createElement as h} from 'react';
2 | import {storiesOf} from '@storybook/react';
3 | const {action} = require('@storybook/addon-actions');
4 | const {linkTo} = require('@storybook/addon-links');
5 | const {create} = require('../index');
6 | const {addon: addonRule} = require('../addon/rule');
7 | const {addon: addonNesting} = require('../addon/nesting');
8 |
9 | const renderer = create({h});
10 | addonRule(renderer);
11 | addonNesting(renderer);
12 | const {rule} = renderer;
13 |
14 | const className = rule({
15 | border: '1px solid red',
16 | '&:hover': {
17 | fontWeight: 'bold'
18 | },
19 | '&>span': {
20 | color: 'red',
21 | },
22 | }, 'nesting');
23 |
24 | const classNamePlus = rule({
25 | border: '1px solid red',
26 | '& + &': {
27 | border: '1px solid blue',
28 | },
29 | }, 'plus');
30 |
31 | storiesOf('Addons/Nesting', module)
32 | .add('Default', () =>
33 | h('div', {className}, 'Hover ', h('span', null, 'me!'))
34 | )
35 | .add('& + &', () =>
36 | h('div', {},
37 | h('div', {className: classNamePlus}, 'a'),
38 | h('div', {className: classNamePlus}, 'b'),
39 | )
40 | )
41 |
--------------------------------------------------------------------------------
/docs/component.md:
--------------------------------------------------------------------------------
1 | # `component` Addon
2 |
3 | `component` addon creates a new `Component`class, which you inherit from to create
4 | stateful styled React components.
5 |
6 | ```jsx
7 | const {Component} = nano;
8 |
9 | class Button extends Component {
10 | static css = {
11 | display: 'inline-block',
12 | borderRadius: '3px',
13 | padding: '0.5rem 0',
14 | margin: '0.5rem 1rem',
15 | width: '11rem',
16 | background: 'transparent',
17 | color: 'white',
18 | border: '2px solid white',
19 | };
20 |
21 | render () {
22 | return Click me! ;
23 | }
24 | }
25 | ```
26 |
27 | You can also have 4th generation dynamic styles that change with
28 | props.
29 |
30 | ```js
31 | class Button extends Component {
32 | css () {
33 | return {
34 | background: this.props.primary ? 'blue' : 'grey'
35 | };
36 | }
37 |
38 | render () {
39 | // ...
40 | }
41 | }
42 | ```
43 |
44 |
45 | ## Installation
46 |
47 | Simply install `component` addon and its dependencies:
48 |
49 | - `cache`
50 | - [`rule()`](./rule.md)
51 |
52 | Read more about the [Addon Installation](./Addons.md#addon-installation).
53 |
--------------------------------------------------------------------------------
/addon/nesting.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | exports.addon = function (renderer) {
4 | renderer.selector = function (parentSelectors, selector) {
5 | var parents = parentSelectors.split(',');
6 | var result = [];
7 | var selectors = selector.split(',');
8 | var len1 = parents.length;
9 | var len2 = selectors.length;
10 | var i, j, sel, pos, parent, replacedSelector;
11 |
12 | for (i = 0; i < len2; i++) {
13 | sel = selectors[i];
14 | pos = sel.indexOf('&');
15 |
16 | if (pos > -1) {
17 | for (j = 0; j < len1; j++) {
18 | parent = parents[j];
19 | replacedSelector = sel.replace(/&/g, parent);
20 | result.push(replacedSelector);
21 | }
22 | } else {
23 | for (j = 0; j < len1; j++) {
24 | parent = parents[j];
25 |
26 | if (parent) {
27 | result.push(parent + ' ' + sel);
28 | } else {
29 | result.push(sel);
30 | }
31 | }
32 | }
33 | }
34 |
35 | return result.join(',');
36 | };
37 | };
38 |
--------------------------------------------------------------------------------
/addon/reset/EricMeyerCondensed.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | exports.addon = function (renderer) {
4 | if (process.env.NODE_ENV !== 'production') {
5 | require('../__dev__/warnOnMissingDependencies')('reset', renderer, ['put']);
6 | }
7 |
8 | var css = {
9 | 'body,div,dl,dt,dd,ul,ol,li,h1,h2,h3,h4,h5,h6,pre,form,fieldset,input,textarea,p,blockquote,th,td': {
10 | pad: 0,
11 | mar: 0,
12 | },
13 | 'fieldset, img': {
14 | bd: 0,
15 | },
16 | table: {
17 | 'border-collapse': 'collapse',
18 | 'border-spacing': 0,
19 | },
20 | 'ol, ul': {
21 | 'list-style': 'none',
22 | },
23 | 'address, caption, cite, code, dfn, em, strong, th, var': {
24 | fw: 'normal',
25 | fs: 'normal',
26 | },
27 | 'caption, th': {
28 | ta: 'left',
29 | },
30 | 'h1, h2, h3, h4, h5, h6': {
31 | fw: 'normal',
32 | fs: '100%',
33 | },
34 | 'q:before, q:after': {
35 | con: "''",
36 | },
37 | 'abbr, acronym': {
38 | bd: 0,
39 | },
40 | };
41 |
42 | renderer.put('', css);
43 | };
44 |
45 |
--------------------------------------------------------------------------------
/addon/rule.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | exports.addon = function (renderer) {
4 | if (process.env.NODE_ENV !== 'production') {
5 | require('./__dev__/warnOnMissingDependencies')('rule', renderer, ['put']);
6 | }
7 |
8 | var blocks;
9 |
10 | if (process.env.NODE_ENV !== 'production') {
11 | blocks = {};
12 | }
13 |
14 | renderer.rule = function (css, block) {
15 | // Warn user if CSS selectors clash.
16 | if (process.env.NODE_ENV !== 'production') {
17 | if (block) {
18 | if (typeof block !== 'string') {
19 | throw new TypeError(
20 | 'nano-css block name must be a string. ' +
21 | 'For example, use nano.rule({color: "red", "RedText").'
22 | );
23 | }
24 |
25 | if (blocks[block]) {
26 | console.error('Block name "' + block + '" used more than once.');
27 | }
28 |
29 | blocks[block] = 1;
30 | }
31 | }
32 |
33 | block = block || renderer.hash(css);
34 | block = renderer.pfx + block;
35 | renderer.put('.' + block, css);
36 |
37 | return ' ' + block;
38 | };
39 | };
40 |
--------------------------------------------------------------------------------
/docs/cssom.md:
--------------------------------------------------------------------------------
1 | # `cssom` Addon
2 |
3 | Adds a utility `createRule` function that creates `CSSRule` objects. The `.createRule`
4 | function offers the following features:
5 |
6 | 1. It creates plain `CSSRule`s and ones that with media query.
7 | 2. It places `CSSRules`s with media query into a different style sheet, because placing
8 | them in the same style sheet throws.
9 | 3. It adds to the rule object index property `rule.index`, which is that rule's
10 | insertion index in the style sheet.
11 | 4. In case rule has media query, the returned media query rule will still have top
12 | level `.style` and `.styleMap` properties, which can be used to set the styling.
13 |
14 | ```js
15 | nano.createRule(selector, mediaQueryAtRulePrelude?);
16 | ```
17 |
18 |
19 | ## Usage
20 |
21 | Create `CSSRule` object.
22 |
23 | ```js
24 | const rule = nano.createRule('.my-component');
25 | const rule = nano.createRule('.my-component', '@media only screen and (max-width: 600px)');
26 | ```
27 |
28 | Use `rule` object to set CSS.
29 |
30 | ```js
31 | rule.style.color = 'red';
32 | rule.style.setProperty('color', 'green');
33 | ```
34 |
35 |
36 | ## Installation
37 |
38 | Simply install `cssom` addon.
39 |
40 | Read more about the [Addon Installation](./Addons.md#addon-installation).
41 |
--------------------------------------------------------------------------------
/docs/hydrate.md:
--------------------------------------------------------------------------------
1 | # `hydrate` Addon
2 |
3 | Re-hydrates CSS styles generated on the server.
4 |
5 | First, install the `hydrate` addon, then add `nano-css` id to your style sheet.
6 |
7 | ```js
8 | html += ``;
9 | ```
10 |
11 | And when creating `nano-css` instance provide that style sheet in configuration.
12 |
13 | ```js
14 | const isClient = typeof document === 'object';
15 |
16 | const nano = create({
17 | sh: isClient ? document.getElementById('nano-css') : null
18 | });
19 | ```
20 |
21 | That's it! `nano-css` will not inject CSS rules are already present in the style sheet.
22 |
23 | You can also manually hydrate any stylesheet or external stylesheet you might have created using [`extract`](./extract.md) addon.
24 |
25 | Let's say you have and external style sheet:
26 |
27 | ```html
28 |
29 | ```
30 |
31 | You can hydrate it like so:
32 |
33 | ```js
34 | nano.hydrate(document.getElementById('extracted-css'));
35 | ```
36 |
37 | *P.S. Currently, does not hydrate media queries or animation keyframes.*
38 |
39 | ## Installation
40 |
41 | Simply install `hydrate` addon.
42 |
43 | Read more about the [Addon Installation](./Addons.md#addon-installation).
44 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | This is free and unencumbered software released into the public domain.
2 |
3 | Anyone is free to copy, modify, publish, use, compile, sell, or
4 | distribute this software, either in source code form or as a compiled
5 | binary, for any purpose, commercial or non-commercial, and by any
6 | means.
7 |
8 | In jurisdictions that recognize copyright laws, the author or authors
9 | of this software dedicate any and all copyright interest in the
10 | software to the public domain. We make this dedication for the benefit
11 | of the public at large and to the detriment of our heirs and
12 | successors. We intend this dedication to be an overt act of
13 | relinquishment in perpetuity of all present and future rights to this
14 | software under copyright law.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22 | OTHER DEALINGS IN THE SOFTWARE.
23 |
24 | For more information, please refer to
25 |
--------------------------------------------------------------------------------
/docs/hyperstyle.md:
--------------------------------------------------------------------------------
1 | # `hyperstyle()` Addon
2 |
3 | The `hyperstyle()` creates a new hyperscript function `h` that you use to create your
4 | virtual DOM elements.
5 |
6 | ```jsx
7 | const h = nano.hyperstyle({
8 | foo: {
9 | color: 'red'
10 | }
11 | });
12 |
13 | const App = () =>
14 | h('div', {styleName: 'foo'}, 'My app...');
15 | ```
16 |
17 | Then in your elements you use `styleName` prop to pick one of the defined styles.
18 |
19 |
20 | For TypeScript you can set in `tsconfig.json` to use `h` as hyperscript function.
21 |
22 | ```json
23 | {
24 | "compilerOptions": {
25 | "jsxFactory": "h"
26 | }
27 | }
28 | ```
29 |
30 | For Babel you can use `jsx` pragma to set `h` as hyperscript function.
31 |
32 | ```js
33 | /** @jsx h */
34 | ```
35 |
36 | This way you can use JSX in your code:
37 |
38 | ```jsx
39 | /** @jsx h */
40 |
41 | const h = nano.hyperstyle({
42 | foo: {
43 | color: 'red'
44 | }
45 | });
46 |
47 | const App = () =>
48 | My app..
;
49 | ```
50 |
51 |
52 | ## Installation
53 |
54 | Simply install `hyperstyle` addon and its dependencies:
55 |
56 | - `cache`
57 | - [`rule()`](./rule.md)
58 | - [`sheet()`](./sheet.md)
59 |
60 | Read more about the [Addon Installation](./Addons.md#addon-installation).
61 |
--------------------------------------------------------------------------------
/.storybook/style.stories.js:
--------------------------------------------------------------------------------
1 | import {createElement as h} from 'react';
2 | import {storiesOf} from '@storybook/react';
3 | const {action} = require('@storybook/addon-actions');
4 | const {linkTo} = require('@storybook/addon-links');
5 | const {create} = require('../index');
6 | const {addon: addonRule} = require('../addon/rule');
7 | const {addon: addonJsx} = require('../addon/jsx');
8 | const {addon: addonStyle} = require('../addon/style');
9 |
10 | const renderer = create({h});
11 | addonRule(renderer);
12 | addonJsx(renderer);
13 | addonStyle(renderer);
14 | const {style} = renderer;
15 |
16 | const RedBorder = style('div', {
17 | border: '1px solid red'
18 | }, (props) => {
19 | if (props.primary) {
20 | return {
21 | color: 'blue'
22 | };
23 | }
24 | });
25 |
26 | const RedBorderItalic = style(RedBorder, {
27 | fontStyle: 'italic'
28 | });
29 |
30 | storiesOf('Addons/style()', module)
31 | .add('Default', () =>
32 | h(RedBorder, null, 'Hello world')
33 | )
34 | .add('Custom CSS', () =>
35 | h(RedBorder, {primary: true}, 'Hello world')
36 | )
37 | .add('Inline styles', () =>
38 | h(RedBorder, {style: {color: 'red'}, primary: true}, 'Hello world')
39 | )
40 | .add('Composability', () =>
41 | h(RedBorderItalic, null, 'Hello world')
42 | )
43 |
--------------------------------------------------------------------------------
/.storybook/sheet.stories.js:
--------------------------------------------------------------------------------
1 | import {createElement as h} from 'react';
2 | import {storiesOf} from '@storybook/react';
3 | const {action} = require('@storybook/addon-actions');
4 | const {linkTo} = require('@storybook/addon-links');
5 | const {create} = require('../index');
6 | const {addon: addonRule} = require('../addon/rule');
7 | const {addon} = require('../addon/sheet');
8 |
9 | const renderer = create({h});
10 | addonRule(renderer);
11 | addon(renderer);
12 | const {sheet} = renderer;
13 |
14 | const styles = sheet({
15 | tomato: {
16 | border: '1px solid tomato',
17 | },
18 | yellow: {
19 | border: '1px solid yellow',
20 | },
21 | }, 'hello');
22 |
23 |
24 | const styles2 = renderer.sheet({
25 | tomato: {
26 | border: '1px solid tomato',
27 | },
28 | yellow: {
29 | border: '1px solid yellow',
30 | },
31 | });
32 |
33 | storiesOf('Addons/sheet()', module)
34 | .add('Default', () =>
35 | h('div', null,
36 | h('div', {className: styles.tomato}, 'Red'),
37 | h('div', {className: styles.yellow}, 'Yellow'),
38 | )
39 | )
40 | .add('No block name', () =>
41 | h('div', null,
42 | h('div', {className: styles2.tomato}, 'Red'),
43 | h('div', {className: styles2.yellow}, 'Yellow'),
44 | )
45 | )
46 |
--------------------------------------------------------------------------------
/docs/dsheet.md:
--------------------------------------------------------------------------------
1 | # `dsheet()` Addon
2 |
3 | `dsheet()` (or *Dynamic Sheet*) interface is similar to [`sheet()`](./sheet.md) interface, but allows you to add
4 | CSS overrides inside render functions, making it a [5th generation](https://github.com/streamich/freestyler/blob/master/docs/en/generations.md#5th-generation)
5 | interface.
6 |
7 | ```js
8 | const cssMap = {
9 | input: {
10 | border: '1px solid grey',
11 | },
12 | button: {
13 | border: '1px solid red',
14 | color: 'red',
15 | }
16 | };
17 | const styles = dsheet(cssMap);
18 | ```
19 |
20 | Usage:
21 |
22 | ```jsx
23 |
24 |
25 |
26 |
27 |
34 | ```
35 |
36 | Optionally, you can name your dynamic style sheets.
37 |
38 | ```js
39 | const styles = sheet(cssMap, 'ContactForm');
40 | ```
41 |
42 |
43 | ## Installation
44 |
45 | Install `dsheet` addon and its dependencies:
46 |
47 | - `cache`
48 | - [`rule()`](./rule.md)
49 | - [`sheet()`](./sheet.md)
50 |
51 | Read more about the [Addon Installation](./Addons.md#addon-installation).
52 |
--------------------------------------------------------------------------------
/addon/stylis/plugin-onRule.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = function (insertRule) {
4 | var delimiter = '/*|*/';
5 | var needle = delimiter + '}';
6 |
7 | function toSheet (block) {
8 | if (block)
9 | try {
10 | insertRule(block + '}');
11 | } catch (e) {
12 | // eslint-disable-next-line
13 | };
14 | }
15 |
16 | return function ruleSheet (context, content, selectors, parents, line, column, length, ns, depth, at) {
17 | switch (context) {
18 | // property
19 | case 1:
20 | // @import
21 | if (depth === 0 && content.charCodeAt(0) === 64)
22 | return insertRule(content+';'), '';
23 | break;
24 | // selector
25 | case 2:
26 | if (ns === 0)
27 | return content + delimiter;
28 | break;
29 | // at-rule
30 | case 3:
31 | switch (ns) {
32 | // @font-face, @page
33 | case 102:
34 | case 112:
35 | return insertRule(selectors[0]+content), '';
36 | default:
37 | return content + (at === 0 ? delimiter : '');
38 | }
39 | case -2:
40 | content.split(needle).forEach(toSheet);
41 | }
42 | };
43 | };
44 |
--------------------------------------------------------------------------------
/addon/googleFont.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | function createUrl (font, weights, subsets) {
4 | var params = '?family=' + encodeURIComponent(font);
5 |
6 | if (weights) {
7 | if (!(weights instanceof Array))
8 | weights = [weights];
9 |
10 | params += ':' + weights.join(',');
11 | }
12 |
13 | if (subsets) {
14 | if (!(subsets instanceof Array))
15 | subsets = [subsets];
16 |
17 | params += '&subset=' + subsets.join(',');
18 | }
19 |
20 | return 'https://fonts.googleapis.com/css' + params;
21 | }
22 |
23 | exports.addon = function (renderer) {
24 | if (process.env.NODE_ENV !== 'production') {
25 | require('./__dev__/warnOnMissingDependencies')('hydrate', renderer, ['put']);
26 | }
27 |
28 | if (renderer.client) {
29 | renderer.googleFont = function (font, weights, subsets) {
30 | var el = document.createElement('link');
31 |
32 | el.href = createUrl(font, weights, subsets);
33 | el.rel = 'stylesheet';
34 | el.type = 'text/css';
35 |
36 | document.head.appendChild(el);
37 | };
38 | } else {
39 | renderer.googleFont = function (font, weights, subsets) {
40 | renderer.putRaw("@import url('" + createUrl(font, weights, subsets) + "');");
41 | };
42 | }
43 | };
44 |
--------------------------------------------------------------------------------
/docs/styled.md:
--------------------------------------------------------------------------------
1 | # `styled()()` Addon
2 |
3 | The `styled()()` allows you to create *"styled components"* just like
4 | [`style()`](./style.md), but has a different syntax and comes with a list of
5 | pre-generated HTML tags.
6 |
7 | ```jsx
8 | const Button = styled('button')({
9 | display: 'inline-block',
10 | borderRadius: '3px',
11 | }, (props) => ({
12 | background: props.primary ? 'blue' : 'grey',
13 | fontSize: props.small ? '12px' : '16px'
14 | }));
15 | ```
16 |
17 | `styled()()` comes with a list of [pre-generated HTML tag names](../addon/styled.js).
18 |
19 | ```jsx
20 | const Button = styled.button({
21 | display: 'inline-block',
22 | borderRadius: '3px',
23 | }, (props) => ({
24 | background: props.primary ? 'blue' : 'grey',
25 | fontSize: props.small ? '12px' : '16px'
26 | }));
27 | ```
28 |
29 | Optionally, you can specify semantic component name.
30 |
31 | ```js
32 | const Button = styled.button(css, dynamicCss, 'MyButton');
33 | ```
34 |
35 | or
36 |
37 | ```js
38 | const Button = styled('button')(css, dynamicCss, 'MyButton');
39 | ```
40 |
41 |
42 | ## Installation
43 |
44 | Simply install `styled` addon and its dependencies:
45 |
46 | - `cache`
47 | - [`rule()`](./rule.md)
48 | - [`jsx()`](./jsx.md)
49 | - [`style()`](./style.md)
50 |
51 | Read more about the [Addon Installation](./Addons.md#addon-installation).
52 |
--------------------------------------------------------------------------------
/.storybook/useStyles.stories.js:
--------------------------------------------------------------------------------
1 | import {createElement as h} from 'react';
2 | import {storiesOf} from '@storybook/react';
3 | const {action} = require('@storybook/addon-actions');
4 | const {linkTo} = require('@storybook/addon-links');
5 | const {create} = require('../index');
6 |
7 | const renderer = create({h});
8 | require('../addon/rule').addon(renderer);
9 | require('../addon/sheet').addon(renderer);
10 | require('../addon/useStyles').addon(renderer);
11 | const {useStyles} = renderer;
12 |
13 | const styles = {
14 | tomato: {
15 | border: '1px solid tomato',
16 | },
17 | yellow: {
18 | border: '1px solid yellow',
19 | },
20 | };
21 |
22 | const Example1 = useStyles(styles, function Example1 (props, styles) {
23 | return h('div', null,
24 | h('div', {className: styles.tomato}, 'Red'),
25 | h('div', {className: styles.yellow}, 'Yellow'),
26 | )
27 | });
28 |
29 | const styles2 = {
30 | main: {
31 | border: '1px solid red',
32 | }
33 | };
34 |
35 | function HelloWorld (props, styles) {
36 | return h('div', {className: styles.main}, 'Hello world!');
37 | }
38 |
39 | const Bordered = useStyles(styles2, HelloWorld);
40 |
41 | storiesOf('Addons/useStyles()', module)
42 | .add('Default', () =>
43 | h(Example1)
44 | )
45 | .add('Hello world', () =>
46 | h(Bordered)
47 | )
48 |
--------------------------------------------------------------------------------
/addon/__tests__/limit.server.test.js:
--------------------------------------------------------------------------------
1 | /** @jest-environment node */
2 | /* eslint-disable */
3 | 'use strict';
4 |
5 | var create = require('../../index').create;
6 | var addonLimit = require('../../addon/limit').addon;
7 |
8 | function createNano (config) {
9 | var nano = create(config);
10 |
11 | addonLimit(nano);
12 |
13 | return nano;
14 | };
15 |
16 | describe('limit', function () {
17 | it('allows inserting rules', function () {
18 | var nano = create();
19 |
20 | nano.put('.foo', {
21 | color: 'red'
22 | });
23 |
24 | nano.put('.bar', {
25 | color: 'red'
26 | });
27 |
28 | nano.put('.baz', {
29 | color: 'red'
30 | });
31 |
32 | expect(nano.raw.replace(/[\s\n]+/g, '')).toBe('.foo{color:red;}.bar{color:red;}.baz{color:red;}');
33 | });
34 |
35 | it('caps at limit', function () {
36 | var nano = create();
37 |
38 | addonLimit(nano, 40);
39 |
40 | nano.put('.foo', {
41 | color: 'red'
42 | });
43 |
44 | nano.put('.bar', {
45 | color: 'red'
46 | });
47 |
48 | nano.put('.baz', {
49 | color: 'red'
50 | });
51 | console.log(nano.raw);
52 | expect(nano.raw.replace(/[\s\n]+/g, '')).toBe('.foo{color:red;}.bar{color:red;}');
53 | });
54 | });
55 |
--------------------------------------------------------------------------------
/.storybook/withStyles.stories.js:
--------------------------------------------------------------------------------
1 | import {createElement as h} from 'react';
2 | import {storiesOf} from '@storybook/react';
3 | const {action} = require('@storybook/addon-actions');
4 | const {linkTo} = require('@storybook/addon-links');
5 | const {create} = require('../index');
6 |
7 | const renderer = create({h});
8 | require('../addon/rule').addon(renderer);
9 | require('../addon/sheet').addon(renderer);
10 | require('../addon/withStyles').addon(renderer);
11 | const {withStyles} = renderer;
12 |
13 | const styles = {
14 | tomato: {
15 | border: '1px solid tomato',
16 | },
17 | yellow: {
18 | border: '1px solid yellow',
19 | },
20 | };
21 |
22 | const Example1 = withStyles(styles, function Example1 (props) {
23 | var styles = props.styles;
24 | return h('div', null,
25 | h('div', {className: styles.tomato}, 'Red'),
26 | h('div', {className: styles.yellow}, 'Yellow'),
27 | )
28 | });
29 |
30 | const styles2 = {
31 | main: {
32 | border: '1px solid red',
33 | }
34 | };
35 |
36 | function HelloWorld ({styles}) {
37 | return h('div', {className: styles.main}, 'Hello world!');
38 | }
39 |
40 | const Bordered = withStyles(styles2, HelloWorld);
41 |
42 | storiesOf('Addons/withStyles()', module)
43 | .add('Default', () =>
44 | h(Example1)
45 | )
46 | .add('Hello world', () =>
47 | h(Bordered)
48 | )
49 |
--------------------------------------------------------------------------------
/preset/vdom.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var create = require('../index').create;
4 | var addonCache = require('../addon/cache').addon;
5 | var addonStable = require('../addon/stable').addon;
6 | var addonNesting = require('../addon/nesting').addon;
7 | var addonAtoms = require('../addon/atoms').addon;
8 | var addonKeyframes = require('../addon/keyframes').addon;
9 | var addonRule = require('../addon/rule').addon;
10 | var addonSheet = require('../addon/sheet').addon;
11 | var addonJsx = require('../addon/jsx').addon;
12 | var addonSourcemaps = require('../addon/sourcemaps').addon;
13 |
14 | exports.preset = function (config) {
15 | if (process.env.NODE_ENV !== 'production') {
16 | if (!config || !(config instanceof Object) || !config.h) {
17 | console.error(
18 | 'For "vdom" nano-css preset you have to provide virtual DOM ' +
19 | 'hyperscript function h. Such as: preset({h: require("react").createElement})'
20 | );
21 | }
22 | }
23 |
24 | var nano = create(config);
25 |
26 | addonCache(nano);
27 | addonStable(nano);
28 | addonNesting(nano);
29 | addonAtoms(nano);
30 | addonKeyframes(nano);
31 | addonRule(nano);
32 | addonSheet(nano);
33 | addonJsx(nano);
34 |
35 | if (process.env.NODE_ENV !== 'production') {
36 | addonSourcemaps(nano);
37 | }
38 |
39 | return nano;
40 | };
41 |
--------------------------------------------------------------------------------
/.storybook/ref.stories.js:
--------------------------------------------------------------------------------
1 | import {createElement as h, Component} from 'react';
2 | import {storiesOf} from '@storybook/react';
3 | const {action} = require('@storybook/addon-actions');
4 | const {linkTo} = require('@storybook/addon-links');
5 | const {create} = require('../index');
6 | const {addon: addonRef} = require('../addon/ref');
7 |
8 | const nano = create();
9 |
10 | addonRef(nano);
11 |
12 | const css = nano.createRef();
13 |
14 | class RefExample extends Component {
15 | constructor(props) {
16 | super(props);
17 | this.state = {
18 | color: 'red',
19 | show: true
20 | };
21 | }
22 |
23 | render () {
24 | var data = nano.ref({'&': {color: this.state.color}, '&:hover': {color: 'blue'}});
25 |
26 | return h('div', {},
27 | this.state.show && h('div', data, 'Hello world'),
28 |
29 | h('br'),
30 |
31 | h('button', {onClick: () => this.setState({color: 'red'})}, 'red'),
32 | h('button', {onClick: () => this.setState({color: 'blue'})}, 'blue'),
33 | h('button', {onClick: () => this.setState({show: !this.state.show})}, 'show/hide'),
34 | );
35 | }
36 | }
37 |
38 | storiesOf('Addons/ref', module)
39 | .add('Default', () =>
40 | h('div', css({'&': {color: 'red'}, '&:hover': {color: 'blue'}}), 'Hello world')
41 | )
42 | .add('ref()', () =>
43 | h(RefExample)
44 | )
45 |
--------------------------------------------------------------------------------
/.storybook/snake.stories.js:
--------------------------------------------------------------------------------
1 | import {createElement as h} from 'react';
2 | import {storiesOf} from '@storybook/react';
3 | const {action} = require('@storybook/addon-actions');
4 | const {linkTo} = require('@storybook/addon-links');
5 | const {create} = require('../index');
6 | const {addon: addonRule} = require('../addon/rule');
7 | const {addon: addonCache} = require('../addon/cache');
8 | const {addon: addonSnake} = require('../addon/snake');
9 |
10 | const nano = create({h});
11 | addonRule(nano);
12 | addonCache(nano);
13 | addonSnake(nano, {
14 | inline: function () {
15 | this.display = 'inline';
16 | },
17 |
18 | cornerRadius: function (value) {
19 | this.borderRadius = value;
20 | },
21 |
22 | style: 'fontStyle'
23 | });
24 |
25 | const {s} = nano;
26 |
27 | const redBorderClass = nano.rule(
28 | s.bd('1px solid red').obj
29 | );
30 |
31 | const lazy = s.ta('center');
32 |
33 | storiesOf('Addons/snake', module)
34 | .add('Inside rule()', () =>
35 | h('div', {className: redBorderClass}, 'Hello world')
36 | )
37 | .add('Evaluate inline', () =>
38 | h('div', {className: s.col('blue')}, 'Hello world')
39 | )
40 | .add('Lazy evaluate', () =>
41 | h('div', {className: lazy}, 'Hello world')
42 | )
43 | .add('Getters', () =>
44 | h('div', {className: s.bgBlack.col('pink').inline.pointer.cornerRadius('4px').style('italic').bold}, 'Hello world')
45 | )
46 |
--------------------------------------------------------------------------------
/preset/react.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var h = require('react').createElement;
4 | var create = require('../index').create;
5 | var addonCache = require('../addon/cache').addon;
6 | var addonStable = require('../addon/stable').addon;
7 | var addonNesting = require('../addon/nesting').addon;
8 | var addonAtoms = require('../addon/atoms').addon;
9 | var addonSnake = require('../addon/snake').addon;
10 | var addonKeyframes = require('../addon/keyframes').addon;
11 | var addonRule = require('../addon/rule').addon;
12 | var addonSheet = require('../addon/sheet').addon;
13 | var addonJsx = require('../addon/jsx').addon;
14 | var addonStyle = require('../addon/style').addon;
15 | var addonStyled = require('../addon/styled').addon;
16 | var addonDecorator = require('../addon/decorator').addon;
17 | var addonSourcemaps = require('../addon/sourcemaps').addon;
18 |
19 | exports.preset = function (config) {
20 | config = config || {};
21 | config.h = config.h || h;
22 |
23 | var nano = create(config);
24 |
25 | addonCache(nano);
26 | addonStable(nano);
27 | addonNesting(nano);
28 | addonAtoms(nano);
29 | addonSnake(nano);
30 | addonKeyframes(nano);
31 | addonRule(nano);
32 | addonSheet(nano);
33 | addonJsx(nano);
34 | addonStyle(nano);
35 | addonStyled(nano);
36 | addonDecorator(nano);
37 |
38 | if (process.env.NODE_ENV !== 'production') {
39 | addonSourcemaps(nano);
40 | }
41 |
42 | return nano;
43 | };
44 |
--------------------------------------------------------------------------------
/addon/__tests__/atoms.test.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | 'use strict';
3 |
4 | var create = require('../../index').create;
5 | var addonAtoms = require('../../addon/atoms').addon;
6 |
7 | function createNano (config) {
8 | var nano = create(config);
9 |
10 | addonAtoms(nano);
11 |
12 | return nano;
13 | };
14 |
15 | describe('atoms', function () {
16 | it('installs without crashing', function () {
17 | var nano = createNano();
18 | });
19 |
20 | it('passes through standard properties', function () {
21 | var nano = createNano();
22 |
23 | nano.putRaw = jest.fn();
24 |
25 | nano.put('.foo', {
26 | color: 'red'
27 | });
28 |
29 | expect(nano.putRaw.mock.calls[0][0].includes('color:red')).toBe(true);
30 | });
31 |
32 | it('expands atoms', function () {
33 | var nano = createNano();
34 |
35 | nano.putRaw = jest.fn();
36 |
37 | nano.put('.bar', {
38 | col: 'blue',
39 | ta: 'center',
40 | minH: '100px',
41 | maxH: '200px'
42 | });
43 |
44 | expect(nano.putRaw.mock.calls[0][0].includes('color:blue')).toBe(true);
45 | expect(nano.putRaw.mock.calls[0][0].includes('text-align:center')).toBe(true);
46 | expect(nano.putRaw.mock.calls[0][0].includes('min-height:100px')).toBe(true);
47 | expect(nano.putRaw.mock.calls[0][0].includes('max-height:200px')).toBe(true);
48 | });
49 | });
50 |
--------------------------------------------------------------------------------
/docs/sheet.md:
--------------------------------------------------------------------------------
1 | # `sheet()` Addon
2 |
3 | `sheet()` interface is similar to [`rule()`](./rule.md) interface, but allows you
4 | to specify multiple rules at once.
5 |
6 | ```jsx
7 | const cssMap = {
8 | input: {
9 | border: '1px solid grey',
10 | },
11 | button: {
12 | border: '1px solid red',
13 | color: 'red',
14 | }
15 | };
16 | const styles = sheet(cssMap);
17 |
18 |
19 | Click me!
20 | ```
21 |
22 | ---
23 |
24 | > __Nota Bene__
25 | >
26 | > Unlike styles created using `rule()` interface, the styles created with `sheet()` interface are
27 | > not injected into the DOM when they are created. The injection is postponed until the style is accessed for the first time.
28 | >
29 | > ```js
30 | > // CSS in not injected yet.
31 | > const inputClassName = styles.input; // Now it is injected.
32 | > ```
33 | >
34 | > This allows to insert styles *just-in-time* and only if they are actually used.
35 |
36 | ---
37 |
38 | For semantic and performance reasons you can optionally specify a name for your sheet.
39 |
40 | ```js
41 | const styles = sheet(cssMap, 'ContactForm');
42 | ```
43 |
44 | This way your class names will look semantic:
45 |
46 | ```js
47 | console.log(styles.input); // pfx-ContactForm-input
48 | ```
49 |
50 | ## Installation
51 |
52 | Simply install `rule` and `sheet` addons.
53 |
54 | Read more about the [Addon Installation](./Addons.md#addon-installation).
55 |
--------------------------------------------------------------------------------
/docs/amp.md:
--------------------------------------------------------------------------------
1 | # `amp` Addon
2 |
3 | Sets [restrictions](https://www.ampproject.org/docs/design/responsive/style_pages) and displays AMP style sheet
4 | warnings for AMP apps.
5 |
6 | - limits style sheet size to 50Kb
7 | - displays error messages in development, if `!important` is used
8 | - removes `!important` modifiers
9 | - displays error messages in development, if `behavior` or `-moz-binding` banned declarations are used
10 | - removes `behavior` and `-moz-binding` banned declarations
11 | - displays error messages in development, if `.-amp-*` or `i-admp-*` selectors are used
12 | - removes CSS rules that use `.-amp-*` or `i-admp-*` selectors
13 |
14 |
15 | ## Example
16 |
17 | Install and configure `amp` addon:
18 |
19 | ```js
20 | import {addon as addonAmp} from 'nano-css/addon/amp';
21 |
22 | addonAmp(nano);
23 | ```
24 |
25 | or
26 |
27 | ```js
28 | addonAmp(nano, {
29 | limit: 50000,
30 | removeImportant: true,
31 | removeReserved: true,
32 | removeBanned: true,
33 | });
34 | ```
35 |
36 | , where
37 |
38 | - `limit` — maximum size of style sheet on server, defaults to `50000`
39 | - `removeImportant` — whether to remove `!important` modfiers, defaults to `false`
40 | - `removeReserved` — whether to remove rules with reserved selectors, defaults to `false`
41 | - `removeBanned` — whether to remove banned declarations, defaults to `false`
42 |
43 |
44 | ## Installation
45 |
46 | Read more about the [Addon Installation](./Addons.md#addon-installation).
47 |
--------------------------------------------------------------------------------
/addon/cssom.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | exports.addon = function (renderer) {
4 | // CSSOM support only browser environment.
5 | if (!renderer.client) return;
6 |
7 | if (process.env.NODE_ENV !== 'production') {
8 | require('./__dev__/warnOnMissingDependencies')('cssom', renderer, ['sh']);
9 | }
10 |
11 | // Style sheet for media queries.
12 | document.head.appendChild(renderer.msh = document.createElement('style'));
13 |
14 | renderer.createRule = function (selector, prelude) {
15 | var rawCss = selector + '{}';
16 | if (prelude) rawCss = prelude + '{' + rawCss + '}';
17 | var sheet = prelude ? renderer.msh.sheet : renderer.sh.sheet;
18 | var index = sheet.insertRule(rawCss, sheet.cssRules.length);
19 | var rule = (sheet.cssRules || sheet.rules)[index];
20 |
21 | // Keep track of `index` where rule was inserted in the sheet. This is
22 | // needed for rule deletion.
23 | rule.index = index;
24 |
25 | if (prelude) {
26 | // If rule has media query (it has prelude), move style (CSSStyleDeclaration)
27 | // object to the "top" to normalize it with a rule without the media
28 | // query, so that both rules have `.style` property available.
29 | var selectorRule = (rule.cssRules || rule.rules)[0];
30 | rule.style = selectorRule.style;
31 | rule.styleMap = selectorRule.styleMap;
32 | }
33 |
34 | return rule;
35 | };
36 | };
37 |
--------------------------------------------------------------------------------
/addon/vcssom/cssToTree.js:
--------------------------------------------------------------------------------
1 | function cssToTree (tree, css, selector, prelude) {
2 | var declarations = {};
3 | var hasDeclarations = false;
4 | var key, value;
5 |
6 | for (key in css) {
7 | value = css[key];
8 | if (typeof value !== 'object') {
9 | hasDeclarations = true;
10 | declarations[key] = value;
11 | }
12 | }
13 |
14 | if (hasDeclarations) {
15 | if (!tree[prelude]) tree[prelude] = {};
16 | tree[prelude][selector] = declarations;
17 | }
18 |
19 | for (key in css) {
20 | value = css[key];
21 | if (typeof value === 'object') {
22 | if (key[0] === '@') {
23 | cssToTree(tree, value, selector, key);
24 | } else {
25 | var hasCurrentSymbol = key.indexOf('&') > -1;
26 | var selectorParts = selector.split(',');
27 | if (hasCurrentSymbol) {
28 | for (var i = 0; i < selectorParts.length; i++) {
29 | selectorParts[i] = key.replace(/&/g, selectorParts[i]);
30 | }
31 | } else {
32 | for (var i = 0; i < selectorParts.length; i++) {
33 | selectorParts[i] = selectorParts[i] + ' ' + key;
34 | }
35 | }
36 | cssToTree(tree, value, selectorParts.join(','), prelude);
37 | }
38 | }
39 | }
40 | };
41 |
42 | exports.cssToTree = cssToTree;
43 |
--------------------------------------------------------------------------------
/docs/vcssom.md:
--------------------------------------------------------------------------------
1 | # `vcssom` Addon
2 |
3 | Adds `VRule` and `VSheet` classes, which can be used for rendering and diffing
4 | virtual CSS. Both classes have `.diff()` methods. The `.diff()` method will compute
5 | differences with the previous render and add or remove only necessary CSS rules/declarations.
6 |
7 |
8 | ## Usage
9 |
10 | Create a virtual CSS rule.
11 |
12 | ```js
13 | const rule = new nano.VRule('.my-class');
14 | ```
15 |
16 | Apply styles to it.
17 |
18 | ```js
19 | rule.diff({
20 | color: 'red',
21 | 'font-weight': 'bold',
22 | });
23 |
24 | rule.diff({
25 | color: 'blue',
26 | });
27 | ```
28 |
29 | Remove the rule from CSSOM (you cannot call `.diff` after this anymore).
30 |
31 | ```js
32 | rule.del();
33 | ```
34 |
35 | Create virtual CSS style sheet.
36 |
37 | ```js
38 | const sheet = new nano.VSheet();
39 | ```
40 |
41 | Render CSS.
42 |
43 | ```js
44 | sheet.diff({
45 | '': {
46 | '.my-class': {
47 | color: 'red',
48 | },
49 | '.my-class:hover': {
50 | color: 'blue',
51 | },
52 | },
53 | '@media only screen and (max-width: 600px)': {
54 | '.my-class': {
55 | fontWeight: 'bold',
56 | },
57 | },
58 | });
59 | ```
60 |
61 | Remove all rendered CSS.
62 |
63 | ```js
64 | sheet.diff({});
65 | ```
66 |
67 |
68 | ## Installation
69 |
70 | Simply install `vcssom` addon and its dependencies:
71 |
72 | - [`cssom`](./cssom.md)
73 |
74 | Read more about the [Addon Installation](./Addons.md#addon-installation).
75 |
--------------------------------------------------------------------------------
/addon/reset/EricMeyer.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | exports.addon = function (renderer) {
4 | if (process.env.NODE_ENV !== 'production') {
5 | require('../__dev__/warnOnMissingDependencies')('reset', renderer, ['put']);
6 | }
7 |
8 | var css = {
9 | 'html,body,div,span,applet,object,iframe,table,caption,tbody,tfoot,thead,tr,th,td,del,dfn,em,font,img,ins,kbd,q,s,samp,small,strike,strong,sub,sup,tt,var,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,big,cite,code,dl,dt,dd,ol,ul,li,fieldset,form,label,legend': {
10 | 'vertical-align': 'baseline',
11 | ff: 'inherit',
12 | fw: 'inherit',
13 | fs: 'inherit',
14 | fz: '100%',
15 | out: 0,
16 | pad: 0,
17 | mar: 0,
18 | bd: 0,
19 | },
20 | ':focus': {
21 | out: 0,
22 | },
23 | body: {
24 | bg: 'white',
25 | lh: 1,
26 | col: 'black',
27 | },
28 | 'ol, ul': {
29 | 'list-style': 'none',
30 | },
31 | table: {
32 | 'border-collapse': 'separate',
33 | 'border-spacing': 0,
34 | },
35 | 'caption, th, td': {
36 | fw: 'normal',
37 | ta: 'left',
38 | },
39 | 'blockquote:before, blockquote:after, q:before, q:after': {
40 | content: '""',
41 | },
42 | 'blockquote, q': {
43 | quotes: '"" ""',
44 | },
45 | };
46 |
47 | renderer.put('', css);
48 | };
49 |
--------------------------------------------------------------------------------
/.storybook/virtual.stories.js:
--------------------------------------------------------------------------------
1 | import {createElement as h} from 'react';
2 | import {storiesOf} from '@storybook/react';
3 | const {action} = require('@storybook/addon-actions');
4 | const {linkTo} = require('@storybook/addon-links');
5 | const {create} = require('../index');
6 |
7 | const nano = create({
8 | pfx: 'test-',
9 | h: require('react').createElement
10 | });
11 | require('../addon/rule').addon(nano);
12 | require('../addon/virtual').addon(nano);
13 | require('../addon/sheet').addon(nano);
14 | require('../addon/jsx').addon(nano);
15 |
16 | const className1 = nano.rule({
17 | border: '1px solid red',
18 | color: 'blue',
19 | });
20 |
21 | const className2 = nano.rule({
22 | border: '1px solid red',
23 | color: 'pink',
24 | ':hover': {
25 | color: 'green'
26 | }
27 | });
28 |
29 | const styles1 = nano.sheet({
30 | button: {
31 | border: '1px solid blue',
32 | color: 'blue'
33 | }
34 | });
35 |
36 | const Button = nano.jsx('button', {
37 | color: 'red',
38 | border: '1px solid blue',
39 | ':hover': {
40 | background: 'blue',
41 | color: 'white',
42 | }
43 | });
44 |
45 | storiesOf('Addons/virtual', module)
46 | .add('Default', () =>
47 | h('div', {className: className1}, 'Hello world')
48 | )
49 | .add('Default 2', () =>
50 | h('div', {className: className2}, 'Hello world 2')
51 | )
52 | .add('sheet()', () =>
53 | h('div', {className: styles1.button}, 'sheet()')
54 | )
55 | .add('jsx()', () =>
56 | h(Button, {}, 'Click me!')
57 | )
58 |
--------------------------------------------------------------------------------
/addon/util/transformComponentDynamic.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = function (renderer, Comp, dynamicTemplate) {
4 | if (process.env.NODE_ENV !== 'production') {
5 | if (typeof dynamicTemplate !== 'function') {
6 | var what;
7 |
8 | try {
9 | what = JSON.stringify(dynamicTemplate);
10 | } catch (error) {
11 | what = String(dynamicTemplate);
12 | }
13 |
14 | throw new TypeError('Dynamic CSS template must always be a function, ' + 'received: ' + what);
15 | }
16 | }
17 |
18 | var prototype = Comp.prototype;
19 | var render_ = prototype.render;
20 |
21 | prototype.render = function () {
22 | var element = render_.apply(this, arguments);
23 | var props = element.props;
24 | var dynamicClassName = '';
25 |
26 | if (dynamicTemplate) {
27 | var dynamicStyles = dynamicTemplate(this.props);
28 |
29 | if (dynamicStyles) {
30 | dynamicClassName = renderer.cache(dynamicStyles);
31 | }
32 | }
33 |
34 | if (!dynamicClassName) {
35 | return element;
36 | }
37 |
38 | var className = (props.className || '') + dynamicClassName;
39 |
40 | if (process.env.NODE_ENV !== 'production') {
41 | return require('react').cloneElement(element, Object.assign({}, props, {
42 | className: className
43 | }), props.children);
44 | }
45 |
46 | props.className = className;
47 |
48 | return element;
49 | };
50 | };
51 |
--------------------------------------------------------------------------------
/addon/sheet.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | exports.addon = function (renderer) {
4 | if (process.env.NODE_ENV !== 'production') {
5 | require('./__dev__/warnOnMissingDependencies')('sheet', renderer, ['rule']);
6 | }
7 |
8 | renderer.sheet = function (map, block) {
9 | var result = {};
10 |
11 | if (!block) {
12 | block = renderer.hash(map);
13 | }
14 |
15 | var onElementModifier = function (elementModifier) {
16 | var styles = map[elementModifier];
17 |
18 | if ((process.env.NODE_ENV !== 'production') && renderer.sourcemaps) {
19 | // In dev mode emit CSS immediately to generate sourcemaps.
20 | result[elementModifier] = renderer.rule(styles, block + '-' + elementModifier);
21 | } else {
22 | Object.defineProperty(result, elementModifier, {
23 | configurable: true,
24 | enumerable: true,
25 | get: function () {
26 | var classNames = renderer.rule(styles, block + '-' + elementModifier);
27 |
28 | Object.defineProperty(result, elementModifier, {
29 | value: classNames,
30 | enumerable: true
31 | });
32 |
33 | return classNames;
34 | },
35 | });
36 | }
37 | };
38 |
39 | for (var elementModifier in map) {
40 | onElementModifier(elementModifier);
41 | }
42 |
43 | return result;
44 | };
45 | };
46 |
--------------------------------------------------------------------------------
/addon/decorator.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var transformComponentStatic = require('./util/transformComponentStatic');
4 | var transformComponentDynamic = require('./util/transformComponentDynamic');
5 |
6 | exports.addon = function (renderer) {
7 | if (process.env.NODE_ENV !== 'production') {
8 | require('./__dev__/warnOnMissingDependencies')('css', renderer, ['rule', 'cache']);
9 | }
10 |
11 | renderer.css = function (a, b) {
12 | var isComponent = a && a.prototype && a.prototype.render;
13 |
14 | // Static class decorator.
15 | if (isComponent) {
16 | if (a.css) transformComponentStatic(renderer, a.prototype, a.css);
17 |
18 | var componentWillMount_ = a.prototype.componentWillMount;
19 |
20 | a.prototype.componentWillMount = function () {
21 | if (this.css) transformComponentDynamic(renderer, a, this.css.bind(this));
22 | if (componentWillMount_) componentWillMount_.apply(this);
23 | };
24 |
25 | return a;
26 | }
27 |
28 | return function (instanceOrComp, key, descriptor) {
29 | if (typeof key === 'string') {
30 | // .render() method decorator
31 | var Comp = instanceOrComp.constructor;
32 |
33 | transformComponentDynamic(renderer, Comp, a);
34 | descriptor.value = Comp.prototype.render;
35 |
36 | return descriptor;
37 | }
38 |
39 | // Class decorator
40 | transformComponentStatic(renderer, instanceOrComp.prototype, a, b);
41 | };
42 | };
43 | };
44 |
--------------------------------------------------------------------------------
/addon/__tests__/rule.test.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | 'use strict';
3 |
4 | var create = require('../../index').create;
5 | var addonRule = require('../../addon/rule').addon;
6 |
7 | function createNano (config) {
8 | var nano = create(config);
9 |
10 | addonRule(nano);
11 |
12 | return nano;
13 | };
14 |
15 | describe('rule()', function () {
16 | it('installs rule() method', function () {
17 | var nano = create();
18 |
19 | expect(typeof nano.rule).toBe('undefined');
20 |
21 | addonRule(nano);
22 |
23 | expect(typeof nano.rule).toBe('function');
24 | });
25 |
26 | it('puts CSS styles', function () {
27 | var nano = createNano({
28 | pfx: 'test-'
29 | });
30 |
31 | nano.put = jest.fn();
32 |
33 | var classNames = nano.rule({
34 | color: 'red'
35 | }, 'foobar');
36 |
37 | expect(nano.put).toHaveBeenCalledTimes(1);
38 | expect(nano.put).toHaveBeenCalledWith('.test-foobar', {color: 'red'});
39 | expect(classNames).toBe(' test-foobar');
40 | });
41 |
42 | it('generates class name automatically if not specified', function () {
43 | var nano = createNano({
44 | pfx: 'test-'
45 | });
46 |
47 | nano.put = jest.fn();
48 |
49 | var css = {color: 'red'};
50 | var classNames = nano.rule(css);
51 | var computed = 'test-' + nano.hash(css);
52 |
53 | expect(nano.put).toHaveBeenCalledTimes(1);
54 | expect(nano.put).toHaveBeenCalledWith('.' + computed, css);
55 | expect(classNames).toBe(' ' + computed);
56 | });
57 | });
58 |
--------------------------------------------------------------------------------
/.storybook/keyframes.stories.js:
--------------------------------------------------------------------------------
1 | import {createElement as h} from 'react';
2 | import {storiesOf} from '@storybook/react';
3 | const {action} = require('@storybook/addon-actions');
4 | const {linkTo} = require('@storybook/addon-links');
5 | const {create} = require('../index');
6 | const {addon: addonAtoms} = require('../addon/atoms');
7 | const {addon: addonRule} = require('../addon/rule');
8 | const {addon: addonKeyframes} = require('../addon/keyframes');
9 |
10 | const renderer = create({
11 | pfx: 'animations-'
12 | });
13 | addonAtoms(renderer);
14 | addonRule(renderer);
15 | addonKeyframes(renderer);
16 | const {rule, keyframes} = renderer;
17 |
18 | var styles = {
19 | w: '10px',
20 | h: '30px',
21 | bg: 'black',
22 | mar: '20px',
23 | animation: 'spinner-rotate-bar 1.2s infinite linear',
24 | '@keyframes spinner-rotate-bar': {
25 | to: {
26 | transform: 'rotate(359.9deg)'
27 | }
28 | },
29 | };
30 |
31 | const className = rule(styles);
32 |
33 | var animation1 = keyframes({
34 | to: {
35 | transform: 'rotate(359deg)'
36 | }
37 | });
38 |
39 | var animation2 = keyframes({
40 | to: {
41 | transform: 'rotate(358deg)'
42 | }
43 | }, 'rotator');
44 |
45 | storiesOf('Addons/keyframes()', module)
46 | .add('Bar spinner', () =>
47 | h('div', {className}, '')
48 | )
49 | .add('keyframes() hash', () =>
50 | h('div', {style: {background: 'red', width: 10, height: 10, animation: animation1 + ' 1s infinite linear'}}, '')
51 | )
52 | .add('keyframes() custom name', () =>
53 | h('div', {style: {background: 'red', width: 10, height: 10, animation: animation2 + ' 1s infinite linear'}}, '')
54 | )
55 |
--------------------------------------------------------------------------------
/addon/extract.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | exports.addon = function (renderer) {
4 | if (renderer.client) {
5 | console.error(
6 | 'You are running nano-css "extract" addon in browser. ' +
7 | 'You should use it ONLY on server and ONLY at build time.'
8 | );
9 |
10 | return;
11 | }
12 |
13 | var sheet = renderer.sheet;
14 |
15 | // eslint-disable-next-line no-unused-vars
16 | var dummy;
17 |
18 | // Evaluate all lazy-evaluated sheet() styles.
19 | if (sheet) {
20 | renderer.sheet = function (map) {
21 | var styles = sheet.apply(this, arguments);
22 |
23 | for (var name in map) dummy = styles[name];
24 |
25 | return styles;
26 | };
27 | }
28 |
29 | var jsx = renderer.jsx;
30 |
31 | // Render jsx component once to extract its static CSS.
32 | if (jsx) {
33 | renderer.jsx = function () {
34 | var jsxComponent = jsx.apply(this, arguments);
35 |
36 | process.nextTick(function () {
37 | jsxComponent(jsxComponent.defaultProps || {});
38 | });
39 |
40 | return jsxComponent;
41 | };
42 | }
43 |
44 | var style = renderer.style;
45 |
46 | // Render styled component once with default props
47 | // to extract its static CSS and "default" dynamic CSS.
48 | if (style) {
49 | renderer.style = function () {
50 | var styledComponent = style.apply(this, arguments);
51 |
52 | process.nextTick(function () {
53 | styledComponent(styledComponent.defaultProps || {});
54 | });
55 |
56 | return styledComponent;
57 | };
58 | }
59 | };
60 |
--------------------------------------------------------------------------------
/.storybook/vcssom/hook.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import {useCss} from './react';
3 | import createUseDataCss from './createUseDataCss';
4 | import {Box} from './css5';
5 | import {H} from './css5-h';
6 | import Svg from 'iconista';
7 |
8 | const useDataCss = createUseDataCss(useCss);
9 |
10 | const h = H(React.createElement, useCss);
11 |
12 | declare module 'react' {
13 | interface DOMAttributes {
14 | css?: any;
15 | }
16 | }
17 |
18 | export const Demo = () => {
19 | const className = useCss({
20 | color: 'red',
21 | border: '1px solid red',
22 | fontWeight: 'bold',
23 | '&:hover': {
24 | color: 'blue',
25 | },
26 | });
27 | const italic = useDataCss({
28 | 'font-style': 'italic',
29 | '&:hover': {
30 | color: 'red',
31 | },
32 | });
33 |
34 | console.log('italic', italic)
35 |
36 | const css = {
37 | color: 'blue',
38 | textDecoration: 'underline',
39 | };
40 |
41 | const svgClass = useCss({
42 | fill: 'green',
43 | '&:hover': {
44 | fill: 'red',
45 | },
46 | '&:active': {
47 | fill: 'black',
48 | },
49 | });
50 |
51 | return (
52 |
53 |
asdf++
54 |
hello...
55 |
Italic text..
56 |
57 |
58 |
59 |
60 |
61 | );
62 | };
63 |
--------------------------------------------------------------------------------
/docs/virtual.md:
--------------------------------------------------------------------------------
1 | # `virtual` Addon
2 |
3 | Uses [*Virtual CSS*](https://ryantsao.com/blog/virtual-css-with-styletron) when injecting rules — splits all CSS
4 | rules into atomic single declarations, where each is assigned a class name and reused.
5 |
6 | Example:
7 |
8 | ```js
9 | const classNames1 = nano.rule({
10 | color: 'red',
11 | border: '1px solid red',
12 | textAlign: 'center'
13 | });
14 | // _a _b _c
15 |
16 | const classNames2 = nano.rule({
17 | border: '1px solid red',
18 | });
19 | // _b
20 |
21 |
//
22 |
//
23 | ```
24 |
25 |
26 | ## `.atomic()`
27 |
28 | Creates an `.atomic(selectorTemplate, rawDecl, atrule?)` method which returns a class name given a *selector template* and
29 | a raw declaration string.
30 |
31 | > __Selector template__
32 | >
33 | > *Selector template* is a string that contains an `&` character, which represents the current component. Examples:
34 | > `"&"`, `"&:hover"`, `".parent &:hover svg"`.
35 |
36 | Usage:
37 |
38 | ```js
39 | nano.atomic('&', 'color:red'); // _a
40 | nano.atomic('&:hover', 'color:blue'); // _b
41 | nano.atomic('&', 'color:green', '@media (min-width: 400px)'); // _c
42 | nano.atomic('&', 'color:red'); // _a
43 | ```
44 |
45 |
46 | ## `.virtual()`
47 |
48 | Returns a list of class names given a *selector template* and a CSS-like object.
49 |
50 | ```js
51 | const classNames1 = nano.virtual('&', {
52 | color: 'red',
53 | border: '1px solid red',
54 | textAlign: 'center'
55 | });
56 | // _a _b _c
57 | ```
58 |
59 |
60 | ## Installation
61 |
62 | Simply install `virtual` addon. Read more about the [Addon Installation](./Addons.md#addon-installation).
63 |
--------------------------------------------------------------------------------
/.storybook/VCSSOM.stories.js:
--------------------------------------------------------------------------------
1 | import {createElement as h} from 'react';
2 | import {storiesOf} from '@storybook/react';
3 | import {Demo} from './vcssom/hook';
4 | const {create} = require('../index');
5 | const {addon: addonCSSOM} = require('../addon/cssom');
6 | const {addon: addonVCSSOM} = require('../addon/vcssom');
7 |
8 | const nano = create();
9 | addonCSSOM(nano);
10 | addonVCSSOM(nano);
11 |
12 | const sheet = new nano.VSheet();
13 | sheet.diff({
14 | '': {
15 | '.test_vcssom': {
16 | color: 'green',
17 | }
18 | }
19 | });
20 | sheet.diff({
21 | '': {
22 | '.test_vcssom': {
23 | border: '1px solid tomato',
24 | }
25 | },
26 | '@media only screen and (max-width: 600px)': {
27 | '.test_vcssom': {
28 | 'text-decoration': 'underline',
29 | color: 'green',
30 | },
31 | }
32 | });
33 | sheet.diff({
34 | '': {
35 | '.test_vcssom': {
36 | color: 'orange',
37 | 'text-transform': 'uppercase',
38 | 'font-style': 'italic',
39 | 'text-align': 'center',
40 | border: '1px solid black',
41 | },
42 | '.test_vcssom:hover': {
43 | color: 'red',
44 | }
45 | },
46 | '@media only screen and (max-width: 600px)': {
47 | '.test_vcssom': {
48 | 'text-decoration': 'underline',
49 | color: 'pink',
50 | },
51 | '.test_vcssom:hover': {
52 | color: 'yellow',
53 | },
54 | }
55 | });
56 |
57 | console.log('sheet', sheet);
58 |
59 | storiesOf('Addons/VCSSOM', module)
60 | .add('rule', () =>
61 | h('div', {className: 'test_vcssom'}, 'addonVCSSOM')
62 | )
63 | .add('hook demo', () => h(Demo))
64 |
--------------------------------------------------------------------------------
/addon/units.d.ts:
--------------------------------------------------------------------------------
1 | import {NanoRenderer} from '../types/nano';
2 |
3 | export interface Units {
4 | /**
5 | * Adds `px` postfix.
6 | */
7 | px: (val: number) => string;
8 |
9 | /**
10 | * Adds `cm` postfix.
11 | */
12 | cm: (val: number) => string;
13 |
14 | /**
15 | * Adds `pt` postfix.
16 | */
17 | pt: (val: number) => string;
18 |
19 | /**
20 | * Adds `pc` postfix.
21 | */
22 | pc: (val: number) => string;
23 |
24 | /**
25 | * Adds `em` postfix.
26 | */
27 | em: (val: number) => string;
28 |
29 | /**
30 | * Adds `ex` postfix.
31 | */
32 | ex: (val: number) => string;
33 |
34 | /**
35 | * Adds `ch` postfix.
36 | */
37 | ch: (val: number) => string;
38 |
39 | /**
40 | * Adds `rem` postfix.
41 | */
42 | rem: (val: number) => string;
43 |
44 | /**
45 | * Adds `vw` postfix.
46 | */
47 | vw: (val: number) => string;
48 |
49 | /**
50 | * Adds `vh` postfix.
51 | */
52 | vh: (val: number) => string;
53 |
54 | /**
55 | * Adds `deg` postfix.
56 | */
57 | deg: (val: number) => string;
58 |
59 | /**
60 | * Adds `vmin` postfix.
61 | */
62 | vmin: (val: number) => string;
63 |
64 | /**
65 | * Adds `vmax` postfix.
66 | */
67 | vmax: (val: number) => string;
68 |
69 | /**
70 | * Adds `in` postfix.
71 | */
72 | inch: (val: number) => string;
73 |
74 | /**
75 | * Adds `in` postfix.
76 | */
77 | in: (val: number) => string;
78 |
79 | /**
80 | * Adds `%` postfix.
81 | */
82 | pct: (val: number) => string;
83 | }
84 |
85 | export interface UnitsAddon extends Units {
86 | units: Units;
87 | }
88 |
89 | export function addon(nano: NanoRenderer);
90 |
--------------------------------------------------------------------------------
/.storybook/decorator.stories.tsx:
--------------------------------------------------------------------------------
1 | import {createElement as h, Component} from 'react';
2 | import {storiesOf} from '@storybook/react';
3 | const {action} = require('@storybook/addon-actions');
4 | const {linkTo} = require('@storybook/addon-links');
5 |
6 | import {create} from '../index';
7 | import {addon as addonRule} from '../addon/rule';
8 | import {addon as addonCache} from '../addon/cache';
9 | import {addon as addonDecorator} from '../addon/decorator';
10 | const renderer = create({h});
11 | addonRule(renderer);
12 | addonCache(renderer);
13 | addonDecorator(renderer);
14 |
15 | const {css} = renderer;
16 |
17 | @css({
18 | border: '1px solid red'
19 | })
20 | class CssTest extends Component {
21 | render () {
22 | return Hello there
;
23 | }
24 | }
25 |
26 | @css({
27 | border: '1px solid red'
28 | })
29 | class Dynamic extends Component {
30 | @css(props => ({
31 | color: 'green'
32 | }))
33 | render () {
34 | return Hello there
;
35 | }
36 | }
37 |
38 | @css
39 | class Static extends Component {
40 | static css = {
41 | border: '1px dashed blue',
42 | };
43 |
44 | render () {
45 | return Hello there
;
46 | }
47 | }
48 |
49 | @css
50 | class Static2 extends Component {
51 | static css = {
52 | border: '1px dashed blue',
53 | };
54 |
55 | css () {
56 | return {
57 | color: this.props.color
58 | };
59 | }
60 |
61 | render () {
62 | return Hello there
;
63 | }
64 | }
65 |
66 | storiesOf('Addons/@css decorator', module)
67 | .add('Default', () =>
68 |
69 | )
70 | .add('Dynamic styles', () =>
71 |
72 | )
73 | .add('Static decorator', () =>
74 |
75 | )
76 | .add('Static dynamic styles', () =>
77 |
78 | )
79 |
--------------------------------------------------------------------------------
/.storybook/sourcemaps.stories.js:
--------------------------------------------------------------------------------
1 | import {createElement as h} from 'react';
2 | import {storiesOf} from '@storybook/react';
3 | const {action} = require('@storybook/addon-actions');
4 | const {linkTo} = require('@storybook/addon-links');
5 | const {create} = require('../index');
6 | const {addon} = require('../addon/rule');
7 | const {addon: addonSourcemaps} = require('../addon/sourcemaps');
8 | const {addon: addonSheet} = require('../addon/sheet');
9 | const {addon: addonCache} = require('../addon/cache');
10 | const {addon: addonJsx} = require('../addon/jsx');
11 | const {addon: addonStyle} = require('../addon/style');
12 | const {addon: addonStyled} = require('../addon/styled');
13 |
14 | const nano = create({
15 | h
16 | });
17 | addon(nano);
18 | addonSheet(nano);
19 | addonCache(nano);
20 | addonJsx(nano);
21 | addonStyle(nano);
22 | addonStyled(nano);
23 | addonSourcemaps(nano);
24 | const {rule, sheet, jsx, styled} = nano;
25 |
26 | const className1 = rule({
27 | border: '1px solid red'
28 | }, 'RedBorder');
29 |
30 | const styles1 = sheet({
31 | wrapper: {
32 | border: '1px solid red',
33 | },
34 | button: {
35 | color: 'green',
36 | background: 'black',
37 | }
38 | });
39 |
40 | const Button = jsx('button', {
41 | border: '1px solid black',
42 | background: 'black',
43 | color: 'white',
44 | });
45 |
46 | const Button2 = styled.button({
47 | border: '1px solid black',
48 | background: 'black',
49 | color: 'red',
50 | });
51 |
52 | storiesOf('Addons/sourcemaps', module)
53 | .add('Default', () =>
54 | h('div', {className: className1}, 'Hello world')
55 | )
56 | .add('sheet', () =>
57 | h('div', {className: styles1.wrapper},
58 | h('button', {className: styles1.button}, 'Click me!')
59 | )
60 | )
61 | .add('jsx', () => h(Button, {}, 'Click me!'))
62 | .add('styled', () => h(Button2, {}, 'Click me!'))
63 |
--------------------------------------------------------------------------------
/addon/__tests__/validate.test.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | 'use strict';
3 |
4 | var env = require('./env');
5 | var create = require('../../index').create;
6 | var addonValidate = require('../../addon/validate').addon;
7 |
8 | function createNano (config) {
9 | var nano = create(config);
10 |
11 | addonValidate(nano);
12 |
13 | return nano;
14 | };
15 |
16 | describe('validate', function () {
17 |
18 | // TODO: Disable validate addon tests, because it is not stable.
19 | // It shows:
20 | // TypeError: this.fn is not a function
21 | // Need to reinstall /node_modules folder, to remove this error.
22 | it.only('installs without crashing', function () {
23 | var nano = createNano();
24 | });
25 |
26 | it('shows warning in production', function () {
27 | var console$warn = console.warn;
28 |
29 | console.warn = jest.fn();
30 |
31 | var nano = createNano();
32 |
33 | expect(console.warn).toHaveBeenCalledTimes(env.isProd ? 1 : 0);
34 |
35 | console.warn = console$warn;
36 | });
37 |
38 | it('does nothing on valid styles', function () {
39 | var nano = createNano();
40 |
41 | var console$error = console.error;
42 |
43 | console.error = jest.fn();
44 |
45 | nano.put('.foo', {
46 | color: 'red'
47 | });
48 |
49 | expect(console.error).toHaveBeenCalledTimes(0);
50 |
51 | console.error = console$error;
52 | });
53 |
54 | it('shows error on invalid styles', function () {
55 | var nano = createNano();
56 |
57 | var console$error = console.error;
58 |
59 | console.error = jest.fn();
60 |
61 | nano.put('.foo', {
62 | color: 'rrrred'
63 | });
64 |
65 | expect(console.error.mock.calls.length > 0).toBe(true);
66 |
67 | console.error = console$error;
68 | });
69 | });
70 |
--------------------------------------------------------------------------------
/addon/jsx.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var addonCache = require('./cache').addon;
4 |
5 | exports.addon = function (renderer) {
6 | if (!renderer.cache) {
7 | addonCache(renderer);
8 | }
9 |
10 | if (process.env.NODE_ENV !== 'production') {
11 | require('./__dev__/warnOnMissingDependencies')('jsx', renderer, ['rule', 'cache']);
12 | }
13 |
14 | renderer.jsx = function (fn, styles, block) {
15 | var className;
16 | var isElement = typeof fn === 'string';
17 |
18 | // In dev mode emit CSS immediately so correct sourcemaps can be generated.
19 | if (process.env.NODE_ENV !== 'production') {
20 | className = renderer.rule(styles, block);
21 | }
22 |
23 | var Component = function (props) {
24 | if (!className) {
25 | className = renderer.rule(styles, block);
26 | }
27 |
28 | var copy = props;
29 | var $as = copy.$as;
30 | var $ref = copy.$ref;
31 |
32 | if (process.env.NODE_ENV !== 'production') {
33 | copy = renderer.assign({}, props);
34 | }
35 |
36 | var dynamicClassName = renderer.cache(props.css);
37 | delete copy.css;
38 | delete copy.$as;
39 |
40 | if (isElement || $as) {
41 | delete copy.$ref;
42 | copy.ref = $ref;
43 | }
44 |
45 | copy.className = (props.className || '') + className + dynamicClassName;
46 |
47 | return (isElement || $as)
48 | ? renderer.h($as || fn, copy)
49 | : fn(copy);
50 | };
51 |
52 | if (process.env.NODE_ENV !== 'production') {
53 | if (block) {
54 | Component.displayName = 'jsx(' + block + ')';
55 | }
56 | }
57 |
58 | return Component;
59 | };
60 | };
61 |
--------------------------------------------------------------------------------
/docs/put.md:
--------------------------------------------------------------------------------
1 | # `put()`
2 |
3 | `put()` is the only addon that comes pre-installed with `nano-css`.
4 |
5 | It simply injects CSS given a selector and a CSS-like object.
6 |
7 | ```js
8 | nano.put('.foo', {
9 | color: 'red',
10 | ':hover': {
11 | color: 'blue'
12 | }
13 | });
14 | ```
15 |
16 | Now you can use the `foo` class name.
17 |
18 | ```html
19 | Hover me!
20 | ```
21 |
22 |
23 | ## CSS-like object
24 |
25 | The second argument of the `put()` function is a *CSS-like object*.
26 |
27 | ```js
28 | {
29 | color: 'red',
30 | ':hover': {
31 | color: 'blue'
32 | }
33 | }
34 | ```
35 |
36 | It maps 1-to-1 to analogous CSS.
37 |
38 | ```css
39 | .selector {
40 | color: red;
41 | }
42 |
43 | .selector:hover {
44 | color: blue;
45 | }
46 | ```
47 |
48 |
49 | ## Nesting
50 |
51 | `put()` supports basic arbitrarily deep nesting out-of-the-box.
52 |
53 | ```js
54 | {
55 | div: {
56 | span: {
57 | ':hover': {
58 | color: 'blue'
59 | }
60 | }
61 | }
62 | }
63 | ```
64 |
65 | The above will result in:
66 |
67 | ```css
68 | .selector div span:hover {
69 | color: blue;
70 | }
71 | ```
72 |
73 | For more advanced nesting feature install [`nesting` addon](./nesting.md).
74 |
75 |
76 | ## Kebab-case and Camel-case Properties
77 |
78 | Out-of-the-box `nano-css` supports kebab-case
79 |
80 | ```js
81 | {
82 | 'text-decoration': 'none'
83 | }
84 | ```
85 |
86 | and camel-case property syntax.
87 |
88 | ```js
89 | {
90 | textDecoration: 'none'
91 | }
92 | ```
93 |
94 | It also supports *Atoms* via the [`atoms`](./atoms.md) addon and "chaining" using the [`snake`](./snake.md) addon.
95 | You might also want to take a look at [`stylis`](./stylis.md) addon that supports writing CSS as a string and
96 | [`tachyons`](./tachyons.md) addon that allows to chain *tachyons*.
97 |
--------------------------------------------------------------------------------
/.storybook/jsx.stories.js:
--------------------------------------------------------------------------------
1 | import {createElement as h} from 'react';
2 | import {storiesOf} from '@storybook/react';
3 | const {action} = require('@storybook/addon-actions');
4 | const {linkTo} = require('@storybook/addon-links');
5 | const {create} = require('../index');
6 | const {addon: addonKeyframes} = require('../addon/keyframes');
7 | const {addon: addonRule} = require('../addon/rule');
8 | const {addon: addonJsx} = require('../addon/jsx');
9 |
10 | const renderer = create({h});
11 | addonKeyframes(renderer);
12 | addonRule(renderer);
13 | addonJsx(renderer);
14 | const {jsx} = renderer;
15 |
16 | const RedBorder = jsx('div', {
17 | border: '1px solid red'
18 | });
19 |
20 | const Block = jsx('div', {
21 | 'text-align': 'center'
22 | });
23 |
24 | const Button = jsx('button', {
25 | background: 'red',
26 | width: '320px',
27 | padding: '20px',
28 | borderRadius: '5px',
29 | textAlign: 'center',
30 | cursor: 'pointer',
31 | outline: 'none',
32 | ':hover': {
33 | color: '#fff',
34 | },
35 | ':active': {
36 | position: 'relative',
37 | top: '2px',
38 | },
39 | '@media (max-width: 480px)': {
40 | width: '160px',
41 | }
42 | });
43 |
44 | const ButtonYellow = jsx(Button, {
45 | color: 'yellow'
46 | });
47 |
48 | storiesOf('Addons/jsx()', module)
49 | .add('Default', () =>
50 | h(RedBorder, null, 'Hello world')
51 | )
52 | .add('Custom CSS', () =>
53 | h(RedBorder, {css: {fontWeight: 'bold'}}, 'Hello world')
54 | )
55 | .add('Inline styles', () =>
56 | h(RedBorder, {style: {color: 'red'}, css: {fontWeight: 'bold'}}, 'Hello world')
57 | )
58 | .add('Button', () =>
59 | h(Block, null,
60 | h(Button, null, 'Click me!')
61 | )
62 | )
63 | .add('Composition', () =>
64 | h(Block, null,
65 | h(ButtonYellow, null, 'Click me!')
66 | )
67 | )
68 |
--------------------------------------------------------------------------------
/.storybook/component.stories.tsx:
--------------------------------------------------------------------------------
1 | import {createElement as h} from 'react';
2 | import {storiesOf} from '@storybook/react';
3 | const {action} = require('@storybook/addon-actions');
4 | const {linkTo} = require('@storybook/addon-links');
5 | import {create} from '../index';
6 | import {addon as addonRule} from '../addon/rule';
7 | import {addon as addonCache} from '../addon/cache';
8 | import {addon as addonComponent} from '../addon/component';
9 | const renderer = create({h});
10 | addonRule(renderer);
11 | addonCache(renderer);
12 | addonComponent(renderer);
13 |
14 | const {Component} = renderer;
15 |
16 | class CssTest extends Component {
17 | static css = {
18 | border: '1px solid red'
19 | };
20 |
21 | render () {
22 | return Hello there
;
23 | }
24 | }
25 |
26 | class Dynamic extends Component {
27 | static css = {
28 | border: '1px solid red'
29 | };
30 |
31 | css() {
32 | return {
33 | color: 'green'
34 | };
35 | }
36 |
37 | render () {
38 | return Hello there
;
39 | }
40 | }
41 |
42 | class Static extends Component {
43 | static css = {
44 | border: '1px dashed blue',
45 | };
46 |
47 | render () {
48 | return Hello there
;
49 | }
50 | }
51 |
52 | class Static2 extends Component {
53 | static css = {
54 | border: '1px dashed blue',
55 | };
56 |
57 | css () {
58 | return {
59 | color: this.props.color
60 | };
61 | }
62 |
63 | render () {
64 | return Hello there
;
65 | }
66 | }
67 |
68 | storiesOf('Addons/Component', module)
69 | .add('Default', () =>
70 | h(CssTest as any)
71 | )
72 | .add('Dynamic styles', () =>
73 | h(Dynamic as any)
74 | )
75 | .add('Static decorator', () =>
76 | h(Static as any)
77 | )
78 | .add('Static dynamic styles', () =>
79 | h(Static2 as any, {color: 'blue'})
80 | )
81 |
--------------------------------------------------------------------------------
/.storybook/styled/Button.tsx:
--------------------------------------------------------------------------------
1 | import {createElement as h} from 'react';
2 | const {create} = require('../../index');
3 | const {addon: addonRule} = require('../../addon/rule');
4 | const {addon: addonJsx} = require('../../addon/jsx');
5 | const {addon: addonStyle} = require('../../addon/style');
6 | const {addon: addonStyled} = require('../../addon/styled');
7 | const {addon: addonNesting} = require('../../addon/nesting');
8 | const {addon: addonAtoms} = require('../../addon/atoms');
9 |
10 | const renderer = create({h, pfx: 'lol-'});
11 | addonNesting(renderer);
12 | addonAtoms(renderer);
13 | addonRule(renderer);
14 | addonJsx(renderer);
15 | addonStyle(renderer);
16 | addonStyled(renderer);
17 | const {styled} = renderer;
18 |
19 | export interface IButtonProps extends React.ButtonHTMLAttributes {
20 | disabled?: boolean,
21 | primary?: boolean,
22 | lite?: boolean,
23 | small?: boolean,
24 | simple?: boolean;
25 | outline?: boolean,
26 | }
27 |
28 | const staticTemplate = {
29 | fz: '14px',
30 | textTransform: 'uppercase',
31 | letterSpacing: '0.14px',
32 | lh: '24px',
33 | trs: 'background 0.2s',
34 | mar: '10px 0 0',
35 | bdrad: '3px',
36 | bd: '0',
37 | '&:active': {
38 | op: 1,
39 | bg: '#777',
40 | col: '#fff',
41 | boxShadow: 'none',
42 | },
43 | };
44 |
45 | const dynamicTemplate = (props) => {
46 | const {disabled, outline, lite, primary, simple, small} = props;
47 | const style: any = {
48 | cur: disabled ? 'normal' : 'pointer',
49 | bg: primary ? 'red' : (lite ? 'transparent' : 'rgba(127,127,127,0.2)'),
50 | pad: (small ? 6 : 11) + 'px 15px',
51 | col: primary ? '#fff' : '#777',
52 | boxShadow: outline ? 'inset 0 0 1px rgba(0,0,0,0.35)' : 'none',
53 | };
54 |
55 | return style;
56 | };
57 |
58 | const Button: any = styled('button')(staticTemplate, dynamicTemplate);
59 |
60 | export default Button;
--------------------------------------------------------------------------------
/addon/vcssom.d.ts:
--------------------------------------------------------------------------------
1 | import {CSSOMAddon} from './cssom';
2 | import {Css} from './vcssom/cssToTree';
3 | import {NanoRenderer} from '../types/nano';
4 | import {CSSOMRule} from './cssom';
5 | import {CssProps} from '../types/common';
6 |
7 | export interface VRule {
8 | /**
9 | * CSS declarations, like `{color: 'red'}`
10 | */
11 | decl: CssProps;
12 | rule: CSSOMRule;
13 |
14 | /**
15 | * Re-render css rule according to new CSS declarations.
16 | * @param decl CSS declarations, like `{color: 'red'}`
17 | */
18 | diff(decl: CssProps);
19 |
20 | /**
21 | * Removes this `VRule` from CSSOM. After calling this method, you
22 | * cannot call `diff` anymore as this rule will be removed from style sheet.
23 | */
24 | del();
25 | }
26 |
27 | export interface VSheet {
28 | /**
29 | * Re-renders style sheet according to specified CSS-like object. The `css`
30 | * object is a 3-level tree structure:
31 | *
32 | * ```
33 | * {
34 | * media-query-prelude: {
35 | * selector: {
36 | * declarations
37 | * }
38 | * }
39 | * }
40 | * ```
41 | *
42 | * Example:
43 | *
44 | * ```js
45 | * sheet.diff({
46 | * '': {
47 | * '.my-class': {
48 | * color: 'red',
49 | * },
50 | * '.my-class:hover': {
51 | * color: 'blue',
52 | * },
53 | * },
54 | * '@media only screen and (max-width: 600px)': {
55 | * '.my-class': {
56 | * color: 'green',
57 | * },
58 | * },
59 | * });
60 | * ```
61 | *
62 | * @param css CSS-like object with media queries as top level.
63 | */
64 | diff(css: Css);
65 | }
66 |
67 | export interface VCSSOMAddon {
68 | VRule: new (selector: string, mediaQuery?: string) => VRule;
69 | VSheet: new () => VSheet;
70 | }
71 |
72 | export function addon(nano: NanoRenderer);
73 |
--------------------------------------------------------------------------------
/addon/pipe.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var addonCssom = require('./cssom').addon;
4 |
5 | exports.addon = function (renderer) {
6 | if (!renderer.putRule) {
7 | addonCssom(renderer);
8 | }
9 |
10 | if (process.env.NODE_ENV !== 'production') {
11 | require('./__dev__/warnOnMissingDependencies')('pipe', renderer, ['createRule']);
12 | }
13 |
14 | var counter = 0;
15 |
16 | renderer.pipe = function () {
17 | var rules = {};
18 | var className = renderer.pfx + 'pipe-' + (counter++).toString(36);
19 | var attr = 'data-' + className;
20 | var scope1 = '.' + className;
21 | var scope2 = '[' + attr + ']';
22 |
23 | var obj = {
24 | attr: attr,
25 | className: className,
26 | css: function (css) {
27 | for (var selectorTemplate in css) {
28 | var declarations = css[selectorTemplate];
29 | var rule = rules[selectorTemplate];
30 |
31 | if (!rule) {
32 | var selector = selectorTemplate.replace('&', scope1) + ',' + selectorTemplate.replace('&', scope2);
33 |
34 | rules[selectorTemplate] = rule = renderer.putRule(selector);
35 | }
36 |
37 | for (var prop in declarations)
38 | rule.style.setProperty(prop, declarations[prop]);
39 | }
40 |
41 | // GC
42 | for (var selectorTemplate2 in rules) {
43 | if (!(selectorTemplate2 in css)) {
44 | rules[selectorTemplate2].remove();
45 | delete rules[selectorTemplate2];
46 | }
47 | }
48 | },
49 | remove: function () {
50 | for (var selectorTemplate in rules)
51 | renderer.sh.deleteRule(rule.index);
52 | }
53 | };
54 |
55 | return obj;
56 | };
57 | };
58 |
--------------------------------------------------------------------------------
/addon/keyframes.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | exports.addon = function (renderer, config) {
4 | if (process.env.NODE_ENV !== 'production') {
5 | require('./__dev__/warnOnMissingDependencies')('keyframes', renderer, ['putRaw', 'put']);
6 | }
7 |
8 | config = renderer.assign({
9 | prefixes: ['-webkit-', '-moz-', '-o-', ''],
10 | }, config || {});
11 |
12 | var prefixes = config.prefixes;
13 |
14 | if (renderer.client) {
15 | // Craete @keyframe Stylesheet `ksh`.
16 | document.head.appendChild(renderer.ksh = document.createElement('style'));
17 | }
18 |
19 | var putAt = renderer.putAt;
20 |
21 | renderer.putAt = function (__, keyframes, prelude) {
22 | // @keyframes
23 | if (prelude[1] === 'k') {
24 | var str = '';
25 |
26 | for (var keyframe in keyframes) {
27 | var decls = keyframes[keyframe];
28 | var strDecls = '';
29 |
30 | for (var prop in decls)
31 | strDecls += renderer.decl(prop, decls[prop]);
32 |
33 | str += keyframe + '{' + strDecls + '}';
34 | }
35 |
36 | for (var i = 0; i < prefixes.length; i++) {
37 | var prefix = prefixes[i];
38 | var rawKeyframes = prelude.replace('@keyframes', '@' + prefix + 'keyframes') + '{' + str + '}';
39 |
40 | if (renderer.client) {
41 | renderer.ksh.appendChild(document.createTextNode(rawKeyframes));
42 | } else {
43 | renderer.putRaw(rawKeyframes);
44 | }
45 | }
46 |
47 | return;
48 | }
49 |
50 | putAt(__, keyframes, prelude);
51 | };
52 |
53 | renderer.keyframes = function (keyframes, block) {
54 | if (!block) block = renderer.hash(keyframes);
55 | block = renderer.pfx + block;
56 |
57 | renderer.putAt('', keyframes, '@keyframes ' + block);
58 |
59 | return block;
60 | };
61 | };
62 |
--------------------------------------------------------------------------------
/addon/unitless.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var UNITLESS_NUMBER_PROPS = [
4 | 'animation-iteration-count',
5 | 'border-image-outset',
6 | 'border-image-slice',
7 | 'border-image-width',
8 | 'box-flex',
9 | 'box-flex-group',
10 | 'box-ordinal-group',
11 | 'column-count',
12 | 'columns',
13 | 'flex',
14 | 'flex-grow',
15 | 'flex-positive',
16 | 'flex-shrink',
17 | 'flex-negative',
18 | 'flex-order',
19 | 'grid-row',
20 | 'grid-row-end',
21 | 'grid-row-span',
22 | 'grid-row-start',
23 | 'grid-column',
24 | 'grid-column-end',
25 | 'grid-column-span',
26 | 'grid-column-start',
27 | 'font-weight',
28 | 'line-clamp',
29 | 'line-height',
30 | 'opacity',
31 | 'order',
32 | 'orphans',
33 | 'tabSize',
34 | 'widows',
35 | 'z-index',
36 | 'zoom',
37 |
38 | // SVG-related properties
39 | 'fill-opacity',
40 | 'flood-opacity',
41 | 'stop-opacity',
42 | 'stroke-dasharray',
43 | 'stroke-dashoffset',
44 | 'stroke-miterlimit',
45 | 'stroke-opacity',
46 | 'stroke-width',
47 | ];
48 |
49 | var unitlessCssProperties = {};
50 |
51 | for (var i = 0; i < UNITLESS_NUMBER_PROPS.length; i++) {
52 | var prop = UNITLESS_NUMBER_PROPS[i];
53 |
54 | unitlessCssProperties[prop] = 1;
55 | unitlessCssProperties['-webkit-' + prop] = 1;
56 | unitlessCssProperties['-ms-' + prop] = 1;
57 | unitlessCssProperties['-moz-' + prop] = 1;
58 | unitlessCssProperties['-o-' + prop] = 1;
59 | }
60 |
61 | exports.addon = function (renderer) {
62 | var decl = renderer.decl;
63 |
64 | renderer.decl = function (prop, value) {
65 | var str = decl(prop, value);
66 |
67 | if (typeof value === 'number') {
68 | var pos = str.indexOf(':');
69 | var propKebab = str.substr(0, pos);
70 |
71 | if (!unitlessCssProperties[propKebab]) {
72 | return decl(prop, value + 'px');
73 | }
74 | }
75 |
76 | return str;
77 | };
78 | };
79 |
--------------------------------------------------------------------------------
/addon/__tests__/sheet.test.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | 'use strict';
3 |
4 | var create = require('../../index').create;
5 | var addonRule = require('../../addon/rule').addon;
6 | var addonSheet = require('../../addon/sheet').addon;
7 |
8 | function createNano (config) {
9 | var nano = create(config);
10 |
11 | addonRule(nano);
12 | addonSheet(nano);
13 |
14 | return nano;
15 | };
16 |
17 | describe('sheet()', function () {
18 | it('installs sheet() method', function () {
19 | var nano = create();
20 |
21 | expect(typeof nano.sheet).toBe('undefined');
22 |
23 | addonRule(nano);
24 | addonSheet(nano);
25 |
26 | expect(typeof nano.sheet).toBe('function');
27 | });
28 |
29 | it('returns a styles object', function () {
30 | var nano = createNano();
31 | var styles = nano.sheet({
32 | input: {
33 | color: 'red',
34 | },
35 | button: {
36 | color: 'blue'
37 | }
38 | });
39 |
40 | expect(typeof styles.input).toBe('string');
41 | expect(typeof styles.button).toBe('string');
42 | });
43 |
44 | it('inserts a rule only when first accessed', function () {
45 | var nano = createNano();
46 |
47 | nano.rule = jest.fn();
48 |
49 | var styles = nano.sheet({
50 | input: {
51 | color: 'red',
52 | },
53 | button: {
54 | color: 'blue'
55 | }
56 | });
57 |
58 | expect(nano.rule).toHaveBeenCalledTimes(0);
59 |
60 | styles.input;
61 |
62 | expect(nano.rule).toHaveBeenCalledTimes(1);
63 |
64 | styles.button;
65 |
66 | expect(nano.rule).toHaveBeenCalledTimes(2);
67 |
68 | expect(nano.rule.mock.calls[0][0]).toEqual({color: 'red'});
69 | expect(nano.rule.mock.calls[1][0]).toEqual({color: 'blue'});
70 |
71 | expect(typeof nano.rule.mock.calls[0][1]).toBe('string');
72 | expect(typeof nano.rule.mock.calls[1][1]).toBe('string');
73 | });
74 | });
75 |
--------------------------------------------------------------------------------
/addon/reset/Tripoli.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | exports.addon = function (renderer) {
4 | if (process.env.NODE_ENV !== 'production') {
5 | require('../__dev__/warnOnMissingDependencies')('reset', renderer, ['put']);
6 | }
7 |
8 | var css = {
9 | '*': {
10 | td: 'none',
11 | fz: '1em',
12 | out: 'none',
13 | pad: 0,
14 | mar: 0,
15 | },
16 | 'code,kbd,samp,pre,tt,var,textarea,input,select,isindex,listing,xmp,plaintext': {
17 | 'white-space': 'normal',
18 | fz: '1em',
19 | font: 'inherit',
20 | },
21 | 'dfn,i,cite,var,address,em': {
22 | fs: 'normal',
23 | },
24 | 'th,b,strong,h1,h2,h3,h4,h5,h6': {
25 | fw: 'normal',
26 | },
27 | 'a,img,a img,iframe,form,fieldset,abbr,acronym,object,applet,table': {
28 | bd: 'none',
29 | },
30 | table: {
31 | 'border-collapse': 'collapse',
32 | 'border-spacing': 0,
33 | },
34 | 'caption,th,td,center': {
35 | 'vertical-align': 'top',
36 | ta: 'left',
37 | },
38 | body: {
39 | bg: 'white',
40 | lh: 1,
41 | col: 'black',
42 | },
43 | q: {
44 | quotes: '"" ""',
45 | },
46 | 'ul,ol,dir,menu': {
47 | 'list-style': 'none',
48 | },
49 | 'sub,sup': {
50 | 'vertical-align': 'baseline',
51 | },
52 | a: {
53 | col: 'inherit',
54 | },
55 | hr: {
56 | d: 'none',
57 | },
58 | font: {
59 | col: 'inherit !important',
60 | font: 'inherit !important',
61 | },
62 | marquee: {
63 | ov: 'inherit !important',
64 | '-moz-binding': 'none',
65 | },
66 | blink: {
67 | td: 'none',
68 | },
69 | nobr: {
70 | 'white-space': 'normal',
71 | },
72 | };
73 |
74 | renderer.put('', css);
75 | };
76 |
--------------------------------------------------------------------------------
/docs/rule.md:
--------------------------------------------------------------------------------
1 | # `rule()` Addon
2 |
3 | `rule()` is a wrapper around [`put()`](./put.md) interface; it is a [3rd generation](https://github.com/streamich/freestyler/blob/master/docs/en/generations.md#3rd-generation)
4 | interface. You can find this interface in many other CSS-in-JS libraries, it simply
5 | returns a list of class names given a CSS-like object:
6 |
7 | ```js
8 | const css = {
9 | color: 'tomato',
10 | ':hover': {
11 | color: 'blue',
12 | },
13 | };
14 | const className = nano.rule(css);
15 |
16 | Hello world!
17 | ```
18 |
19 | ---
20 |
21 | > __Nota Bene__
22 | >
23 | > The code above will automatically generate predictable class names on the server and browser.
24 | > However, by default it uses unstable JSON stringify, which is fine in most cases if your
25 | > app runs only in a browser, however, if you render on the server side and want to re-hydrate
26 | > your CSS, you should use [`stable` addon](./stable.md), which makes sure that class names
27 | > are the same between different JavaScript environments.
28 |
29 | ---
30 |
31 | Optionally, using the second argument, you can specify a name or your style explicitly for performance
32 | and semantic reasons.
33 |
34 | ```js
35 | const className = rule(css, 'RedText');
36 | ```
37 |
38 | > __P.S.__
39 | >
40 | > If you specify all style names explicitly, you don't need to install `stable` addon.
41 |
42 |
43 | ## Leading Space
44 |
45 | `nano-css` always returns class names with a leading space, so you can concatenate those with other classes.
46 |
47 | ```jsx
48 | const otherClass = 'foo';
49 | const className = rule(css);
50 |
51 | Hello world!
52 | ```
53 |
54 | This results in:
55 |
56 | ```html
57 | Hello world!
58 | ```
59 |
60 |
61 | ## Installation
62 |
63 | Simply add the the `rule` addon.
64 |
65 | ```js
66 | import {create} from 'nano-css';
67 | import {addon} from 'nano-css/addon/rule';
68 |
69 | const nano = create();
70 | addon(nano);
71 | const {rule} = nano;
72 |
73 | export {
74 | rule
75 | }
76 | ```
77 |
78 | Read more about the [Addon Installation](./Addons.md#addon-installation).
79 |
--------------------------------------------------------------------------------
/docs/drule.md:
--------------------------------------------------------------------------------
1 | # `drule()` Addon
2 |
3 | `drule()` (or *Dynamic Rule*) interface is similar to [`rule()`](./rule.md) interface, but also allows you
4 | to create CSS dynamically inside your render function, making it a [5th generation](https://github.com/streamich/freestyler/blob/master/docs/en/generations.md#5th-generation)
5 | interface.
6 |
7 | ```jsx
8 | const css = {
9 | border: '1px solid #888',
10 | color: '#888',
11 | };
12 | const classNames = nano.drule(css);
13 | ```
14 |
15 | Static use case is similar to [`rule()`](./rule.md) interface:
16 |
17 | ```jsx
18 | Click me!
19 | Click me!
20 | Click me!
21 | Click me!
22 | ```
23 |
24 | But `drule()` also allows you to add custom styling overrides on the fly.
25 |
26 | ```jsx
27 | Click me!
28 | ```
29 |
30 | Just like with `rule()` interface you can specify a semantic name.
31 |
32 | ```js
33 | const classNames = drule(css, 'MyButton');
34 | ```
35 |
36 |
37 | ## Example
38 |
39 | Below is an example of a React button component that changes its color based on `primary` prop.
40 |
41 | ```jsx
42 | const classNames = drule({
43 | border: '1px solid #888',
44 | color: '#fff',
45 | });
46 |
47 | const Button = ({children, primary}) =>
48 |
51 | {children}
52 | ;
53 | ```
54 |
55 |
56 | ## Installation
57 |
58 | To use, install `drule` addon, which depends on `rule` and `cache` addons.
59 |
60 | ```js
61 | import {create} from 'nano-css';
62 | import {addon as addonCache} from 'nano-css/addon/cache';
63 | import {addon as addonRule} from 'nano-css/addon/rule';
64 | import {addon as addonDrule} from 'nano-css/addon/drule';
65 |
66 | const nano = create();
67 | addonCache(nano);
68 | addonRule(nano);
69 | addonDrule(nano);
70 | const {rule, drule} = nano;
71 |
72 | export {
73 | rule,
74 | drule
75 | }
76 | ```
77 |
78 | Read more about the [Addon Installation](./Addons.md#addon-installation).
79 |
--------------------------------------------------------------------------------
/docs/atoms.md:
--------------------------------------------------------------------------------
1 | # `atoms` Addon
2 |
3 | When composing CSS-like objects in `nano-css`, you can use either kebab-case
4 |
5 | ```js
6 | const className = rule({
7 | 'border-top': '1px solid red'
8 | });
9 | ```
10 |
11 | or camel-case syntax
12 |
13 | ```js
14 | const className = rule({
15 | borderTop: '1px solid red'
16 | });
17 | ```
18 |
19 | `atoms` addon provides a list of *atoms* that can be used as shorthands for CSS rule declarations properties.
20 |
21 | ```js
22 | const className = rule({
23 | bdt: '1px solid red'
24 | });
25 | ```
26 |
27 | Those are good for __DX__ and they will decrease your overall bundle size after
28 | sufficient use. You can find the full up-to-date atom list [here](../addon/atoms.js). Below
29 | is the atom list at the moment of this writing:
30 |
31 | ```js
32 | var atoms = {
33 | d: 'display',
34 |
35 | mar: 'margin',
36 | mart: 'margin-top',
37 | marr: 'margin-right',
38 | marb: 'margin-bottom',
39 | marl: 'margin-left',
40 | pad: 'padding',
41 | padt: 'padding-top',
42 | padr: 'padding-right',
43 | padb: 'padding-bottom',
44 | padl: 'padding-left',
45 |
46 | bd: 'border',
47 | bdt: 'border-top',
48 | bdr: 'border-right',
49 | bdb: 'border-bottom',
50 | bdl: 'border-left',
51 | bdrad: 'border-radius',
52 |
53 | col: 'color',
54 | op: 'opacity',
55 | bg: 'background',
56 | bgc: 'background-color',
57 |
58 | fz: 'font-size',
59 | fs: 'font-style',
60 | fw: 'font-weight',
61 | ff: 'font-family',
62 |
63 | lh: 'line-height',
64 | bxz: 'box-sizing',
65 | cur: 'cursor',
66 | ov: 'overflow',
67 | pos: 'position',
68 | ls: 'list-style',
69 | ta: 'text-align',
70 | td: 'text-decoration',
71 | fl: 'float',
72 | w: 'width',
73 | h: 'height',
74 | trs: 'transition',
75 | out: 'outline',
76 | vis: 'visibility',
77 | ww: 'word-wrap',
78 | con: 'content',
79 | z: 'z-index',
80 | };
81 | ```
82 |
83 | For even better DX see [`snake`](./snake.md) addon.
84 |
85 |
86 | ## Installation
87 |
88 | Simply install `atoms` addon. Read more about the [Addon Installation](./Addons.md#addon-installation).
89 |
--------------------------------------------------------------------------------
/docs/decorator.md:
--------------------------------------------------------------------------------
1 | # `decorator` Addon
2 |
3 | The `decorator` addon exposes `@css` decorator that you can use to style your stateful
4 | React class components.
5 |
6 | ```jsx
7 | const {css} = nano;
8 |
9 | @css({
10 | display: 'inline-block',
11 | borderRadius: '3px',
12 | padding: '0.5rem 0',
13 | margin: '0.5rem 1rem',
14 | width: '11rem',
15 | background: 'transparent',
16 | color: 'white',
17 | border: '2px solid white',
18 | })
19 | class Button extends Component {
20 | render () {
21 | return Click me! ;
22 | }
23 | }
24 | ```
25 |
26 |
27 | ## Usage
28 |
29 | You can use `@css` decorator in a number of ways. You can specify statics styles, as well as,
30 | dynamic styles that depend on `props`.
31 |
32 |
33 | ### Simple Decorator
34 |
35 | You can set the decorator as a simple class `@css` decorator and use static `css` property for
36 | static styles.
37 |
38 | ```js
39 | @css
40 | class Button extends Component {
41 | static css = {
42 | color: 'red'
43 | };
44 |
45 | render () {
46 | // ...
47 | }
48 | }
49 | ```
50 |
51 | You can also specify a dynamic CSS template in the `css` method.
52 |
53 | ```js
54 | @css
55 | class Button extends Component {
56 | css () {
57 | return {
58 | background: this.props.primary ? 'blue' : 'grey'
59 | };
60 | }
61 |
62 | render () {
63 | // ...
64 | }
65 | }
66 | ```
67 |
68 |
69 | ### Customizable Decorator
70 |
71 | You can specify static styles directly in the class decorator.
72 |
73 | ```js
74 | @css({
75 | color: 'red'
76 | })
77 | class Button extends Component {
78 | render () {
79 | // ...
80 | }
81 | }
82 | ```
83 |
84 | Then you can specify dynamic CSS template as a `render()` method decorator.
85 |
86 | ```js
87 | class Button extends Component {
88 | @css((props) => ({
89 | background: props.primary ? 'blue' : 'grey'
90 | }))
91 | render () {
92 | // ...
93 | }
94 | }
95 | ```
96 |
97 |
98 | ## Installation
99 |
100 | Simply install `decorator` addon and its dependencies:
101 |
102 | - `cache`
103 | - [`rule()`](./rule.md)
104 |
105 | Read more about the [Addon Installation](./Addons.md#addon-installation).
106 |
--------------------------------------------------------------------------------
/addon/reset/Normalize.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | exports.addon = function (renderer) {
4 | if (process.env.NODE_ENV !== 'production') {
5 | require('../__dev__/warnOnMissingDependencies')('reset', renderer, ['put']);
6 | }
7 |
8 | // Adopted from https://raw.githubusercontent.com/necolas/normalize.css/master/normalize.css
9 | var css = {
10 | html: {
11 | lineHeight: 1.15,
12 | '-webkit-text-size-adjust': '100%',
13 | },
14 | body: {
15 | margin: 0,
16 | },
17 | h1: {
18 | fontSize: '2em',
19 | margin: '0.67em 0',
20 | },
21 | hr: {
22 | boxSizing: 'content-box',
23 | height: 0,
24 | overflow: 'visible',
25 | },
26 | pre: {
27 | fontFamily: 'monospace, monospace',
28 | fontSize: '1em',
29 | },
30 | 'b,strong': {
31 | fontWeight: 'bolder',
32 | },
33 | 'code,kbd,samp': {
34 | fontFamily: 'monospace, monospace',
35 | fontSize: '1em',
36 | },
37 | 'small': {
38 | fontSize: '80%',
39 | },
40 | 'sub,sup': {
41 | fontSize: '75%',
42 | lineHeight: 0,
43 | position: 'relative',
44 | verticalAlign: 'baseline',
45 | },
46 | sub: {
47 | bottom: '-0.25em',
48 | },
49 | sup: {
50 | top: '-0.5em',
51 | },
52 | 'button,input,optgroup,select,textarea': {
53 | fontFamily: 'inherit',
54 | fontSize: '100%',
55 | lineHeight: 1.15,
56 | margin: 0,
57 | },
58 | 'button,input': {
59 | overflow: 'visible',
60 | },
61 | 'button,select': {
62 | textTransform: 'none',
63 | },
64 | fieldset: {
65 | padding: '0.35em 0.75em 0.625em',
66 | },
67 | legend: {
68 | boxSizing: 'border-box',
69 | display: 'table',
70 | maxWidth: '100%',
71 | padding: 0,
72 | whiteSpace: 'normal',
73 | },
74 | progress: {
75 | verticalAlign: 'baseline',
76 | },
77 | summary: {
78 | display: 'list-item',
79 | },
80 | };
81 |
82 | renderer.put('', css);
83 | };
84 |
--------------------------------------------------------------------------------
/addon/__tests__/nesting.test.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | 'use strict';
3 |
4 | var env = require('./env');
5 | var create = require('../../index').create;
6 | var addonNesting = require('../../addon/nesting').addon;
7 |
8 | function createNano (config) {
9 | var nano = create(config);
10 |
11 | addonNesting(nano);
12 |
13 | return nano;
14 | };
15 |
16 | describe('nesting', function () {
17 | it('installs without crashing', function () {
18 | var nano = createNano();
19 | });
20 |
21 | it('prepends selectors if no & operand', function () {
22 | var nano = createNano();
23 |
24 | nano.putRaw = jest.fn();
25 |
26 | nano.put('.foo', {
27 | '.one,.two': {
28 | color: 'tomato'
29 | }
30 | });
31 |
32 | expect(nano.putRaw.mock.calls[0][0].includes('.foo .one,.foo .two')).toBe(true);
33 | });
34 |
35 | it('expands & operand after', function () {
36 | var nano = createNano();
37 |
38 | nano.putRaw = jest.fn();
39 |
40 | nano.put('.one, #two', {
41 | '.foo &': {
42 | color: 'tomato'
43 | }
44 | });
45 |
46 | var result = nano.putRaw.mock.calls[0][0].replace(/ +(?= )/g,'');
47 |
48 | expect(result.includes('.foo .one,.foo #two')).toBe(true);
49 | });
50 |
51 | it('expands & operand before', function () {
52 | var nano = createNano();
53 |
54 | nano.putRaw = jest.fn();
55 | nano.put('.foo', {
56 | '&:hover': {
57 | color: 'tomato'
58 | },
59 | '& .bar': {
60 | color: 'tomato'
61 | },
62 | });
63 |
64 | var css1 = nano.putRaw.mock.calls[0][0].replace(/ +(?= )/g,'');
65 | var css2 = nano.putRaw.mock.calls[1][0].replace(/ +(?= )/g,'');
66 |
67 | expect(css1.includes('.foo:hover')).toBe(true);
68 | expect(css2.includes('.foo .bar')).toBe(true);
69 | });
70 |
71 | it('expands multiple & operands', function () {
72 | var nano = createNano();
73 |
74 | nano.putRaw = jest.fn();
75 | nano.put('.foo', {
76 | '& + &': {
77 | color: 'tomato'
78 | },
79 | });
80 |
81 | var css1 = nano.putRaw.mock.calls[0][0].replace(/ +(?= )/g,'');
82 |
83 | expect(css1.includes('.foo + .foo')).toBe(true);
84 | });
85 | });
86 |
--------------------------------------------------------------------------------
/.storybook/styled.stories.js:
--------------------------------------------------------------------------------
1 | import {createElement as h} from 'react';
2 | import {storiesOf} from '@storybook/react';
3 | import Button2 from './styled/Button';
4 | const {action} = require('@storybook/addon-actions');
5 | const {linkTo} = require('@storybook/addon-links');
6 | const {create} = require('../index');
7 | const {addon: addonRule} = require('../addon/rule');
8 | const {addon: addonJsx} = require('../addon/jsx');
9 | const {addon: addonStyle} = require('../addon/style');
10 | const {addon: addonStyled} = require('../addon/styled');
11 | const {addon: addonNesting} = require('../addon/nesting');
12 | const {addon: addonAtoms} = require('../addon/atoms');
13 |
14 | const renderer = create({h});
15 | addonNesting(renderer);
16 | addonAtoms(renderer);
17 | addonRule(renderer);
18 | addonJsx(renderer);
19 | addonStyle(renderer);
20 | addonStyled(renderer);
21 | const {styled} = renderer;
22 |
23 | const RedBorder = styled.div({
24 | border: '1px solid red'
25 | }, (props) => {
26 | if (props.primary) {
27 | return {
28 | color: 'blue'
29 | };
30 | }
31 | });
32 |
33 | const RedBorderItalic = styled(RedBorder)({
34 | fontStyle: 'italic'
35 | });
36 |
37 | const Button = styled('button')({
38 | fontFamily: 'inherit',
39 | fontSize: '14px',
40 | fontWeight: 'bold',
41 | lineHeight: 16/14,
42 | display: 'inline-block',
43 | margin: 0,
44 | verticalAlign: 'middle',
45 | textAlign: 'center',
46 | textDecoration: 'none',
47 | borderRadius: '3px',
48 | border: 0,
49 | appearance: 'none',
50 | color: 'white',
51 | backgroundColor: 'blue',
52 | '&:hover': {
53 | boxShadow: `inset 0 0 0 999px #888`
54 | },
55 | '&:active': {
56 | boxShadow: `inset 0 0 8px #999`
57 | },
58 | '&:disabled': {
59 | opacity: 1/4
60 | }
61 | })
62 |
63 | storiesOf('Addons/styled()', module)
64 | .add('Default', () =>
65 | h(RedBorder, null, 'Hello world')
66 | )
67 | .add('Custom CSS', () =>
68 | h(RedBorder, {primary: true}, 'Hello world')
69 | )
70 | .add('Inline styles', () =>
71 | h(RedBorder, {style: {color: 'red'}, primary: true}, 'Hello world')
72 | )
73 | .add('Composability', () =>
74 | h(RedBorderItalic, null, 'Hello world')
75 | )
76 | .add('Button', () =>
77 | h(Button, null, 'Click me!')
78 | )
79 | .add('Button 2', () =>
80 | h(Button2, null, 'Click me!')
81 | )
82 |
--------------------------------------------------------------------------------
/addon/prefixer.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var prefix = require('inline-style-prefixer').prefix;
4 |
5 | var CAMEL_REGEX = /-[a-z\u00E0-\u00F6\u00F8-\u00FE]/g;
6 |
7 | var matchCallback = function (match) {
8 | return match.slice(1).toUpperCase();
9 | };
10 |
11 | exports.addon = function (renderer) {
12 | var decl = renderer.decl;
13 | var origPut = renderer.put;
14 |
15 | renderer.camel = function (prop) {
16 | return prop.replace(CAMEL_REGEX, matchCallback);
17 | };
18 |
19 | renderer.prefix = function (prop, value) {
20 | var obj = {};
21 | obj[renderer.camel(prop)] = value;
22 | obj = prefix(obj);
23 |
24 | var result = {};
25 |
26 | for (var propPrefixed in obj) {
27 | value = obj[propPrefixed];
28 | if (propPrefixed.slice(0, 2) === 'ms') {
29 | propPrefixed = 'M' + propPrefixed.slice(1);
30 | }
31 | propPrefixed = renderer.kebab(propPrefixed);
32 |
33 | if (value instanceof Array) {
34 | result[propPrefixed] = value.join(';' + propPrefixed + ':');
35 | } else {
36 | result[propPrefixed] = value;
37 | }
38 | }
39 |
40 | return result;
41 | };
42 |
43 | renderer.decl = function (prop, value) {
44 | var result = renderer.prefix(prop, value);
45 |
46 | var returned = '';
47 | Object.keys(result).forEach(function (key) {
48 | var str = decl(key, value);
49 | returned += str;
50 | });
51 |
52 | return returned;
53 | };
54 |
55 | function newPut(selector, decls, atrule) {
56 | var indexed = selector.indexOf('::placeholder');
57 | if (indexed > -1) {
58 | var split = selector.split(',');
59 | if (split.length > 1) {
60 | split.forEach(function(sp) {
61 | newPut(sp.trim(), decls, atrule);
62 | });
63 | return;
64 | }
65 | var bareSelect = selector.substring(0, indexed);
66 | [
67 | '::-webkit-input-placeholder',
68 | '::-moz-placeholder',
69 | ':-ms-input-placeholder',
70 | ':-moz-placeholder'
71 | ].forEach(function(ph) {
72 | origPut(bareSelect + ph, decls, atrule);
73 | });
74 | }
75 | origPut(selector, decls, atrule);
76 | }
77 |
78 | renderer.put = newPut;
79 | };
80 |
--------------------------------------------------------------------------------