├── .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 | ![](./sourcemaps.gif) 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 ; 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 | 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 | 19 | 20 | 21 | 22 | ``` 23 | 24 | But `drule()` also allows you to add custom styling overrides on the fly. 25 | 26 | ```jsx 27 | 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 | ; 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 ; 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 | --------------------------------------------------------------------------------