├── .gitignore ├── .npmignore ├── LICENSE.md ├── README.md ├── package.json ├── src ├── example │ ├── FacebookProfile.js │ ├── GoogleProfile.js │ ├── Profile.js │ ├── TwitterProfile.js │ └── index.js └── react-inline-css.js ├── static ├── index.html ├── mao.jpg ├── mao2.png ├── mao3.png └── mao4.png ├── webpack.client-watch.js └── webpack.client.js /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | node_modules/ 3 | dist/ 4 | *.log 5 | *.map 6 | .DS_Store 7 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | node_modules/ 3 | dist/ 4 | *.log 5 | *.map 6 | .DS_Store 7 | 8 | static/ 9 | src/example/ 10 | src/example.* 11 | tmp/ 12 | webpack.*.js 13 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # BSD 3-Clause License 2 | 3 | Copyright © 2015, Rick Wong 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright 10 | notice, this list of conditions and the following disclaimer. 11 | 2. Redistributions in binary form must reproduce the above copyright 12 | notice, this list of conditions and the following disclaimer in the 13 | documentation and/or other materials provided with the distribution. 14 | 3. Neither the name of the copyright holder nor the 15 | names of its contributors may be used to endorse or promote products 16 | derived from this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 19 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 20 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY 22 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 23 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 24 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 25 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 27 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![screenshot](https://i.imgur.com/7Pop4SZ.png?1) 2 | 3 | # React Inline CSS 4 | 5 | Make your React components visually predictable. React Inline CSS allows you to write traditional CSS stylesheets in your components, automatically namespacing them for you. 6 | 7 | Inspired by the [SUIT CSS](https://suitcss.github.io/) methodology. 8 | 9 | ## Demo 10 | 11 | [Mao-mao-mao!](https://edealer.nl/mao) 12 | 13 | ## Example 14 | 15 | You write: 16 | 17 | ```javascript 18 | var Profile = React.createClass({ 19 | render: function () { 20 | return ( 21 | 37 |
38 | 39 |

Mao

40 |
41 |
42 | ); 43 | } 44 | }); 45 | ``` 46 | 47 | You get namespaced CSS that works on sub-components (comparable to HTML5 ` 71 | 72 | ``` 73 | 74 | For a cascaded effect, see the `index.html` demo. 75 | 76 | ## Installation 77 | 78 | npm install --save react-inline-css react 79 | 80 | ## Usage 81 | 82 | Run `npm run watch` in your terminal and play with `example/` to get a feel of react-inline-css. 83 | 84 | ### SASS / LESS 85 | 86 | You can override the `&` as the selector to the current component. This is useful if you want to require precompiled stylesheets from an external file. Here's an example with [SASS loader for Webpack](https://www.npmjs.com/package/sass-loader): 87 | 88 | **UserComponent.js** 89 | ```javascript 90 | import React from "react"; 91 | import InlineCss from "react-inline-css"; 92 | const stylesheet = require("!raw!sass!./UserComponent.scss"); 93 | 94 | class UserComponent extends React.Component { 95 | render () { 96 | return ( 97 | 98 |
Mao is no longer red!
99 |
Mao is no longer red!
100 |
Mao is no longer red!
101 |
102 | ); 103 | } 104 | }; 105 | ``` 106 | 107 | **UserComponent.scss** 108 | ```scss 109 | UserComponent { 110 | color: red; 111 | .facebook { 112 | color: blue; 113 | } 114 | .google { 115 | color: blue; 116 | } 117 | .twitter { 118 | color: green; 119 | } 120 | } 121 | ``` 122 | 123 | **result** 124 | 125 | ![screenshot](https://i.imgur.com/e3ErqTz.png?1) 126 | 127 | ## Community 128 | 129 | Let's start one together! After you ★Star this project, follow me [@Rygu](https://twitter.com/rygu) 130 | on Twitter. 131 | 132 | ### Contributors 133 | 134 | - [Danilo Moret](https://github.com/moret) 135 | - [Will Butler](https://github.com/willbutler) 136 | 137 | ## License 138 | 139 | BSD 3-Clause license. Copyright © 2015, Rick Wong. All rights reserved. 140 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-inline-css", 3 | "description": "Inline CSS in your React components, namespaced automatically.", 4 | "version": "2.3.1", 5 | "license": "BSD-3-Clause", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/RickWong/react-inline-css.git" 9 | }, 10 | "homepage": "https://github.com/RickWong/react-inline-css", 11 | "keywords": [ 12 | "react", 13 | "inline", 14 | "css", 15 | "react-component", 16 | "style" 17 | ], 18 | "main": "src/react-inline-css", 19 | "scripts": { 20 | "localhost": "sleep 2; which open && open http://localhost:8080", 21 | "build": "webpack --verbose --colors --display-error-details --config webpack.client.js", 22 | "watch-client": "webpack --verbose --colors --display-error-details --config webpack.client-watch.js && webpack-dev-server --config webpack.client-watch.js", 23 | "watch": "concurrently 'npm run watch-client' 'npm run localhost'" 24 | }, 25 | "devDependencies": { 26 | "babel-core": "6.11.4", 27 | "babel-loader": "6.2.4", 28 | "babel-preset-es2015": "6.9.0", 29 | "babel-preset-react": "6.11.1", 30 | "concurrently": "2.2.0", 31 | "json-loader": "0.5.4", 32 | "react": "15.3.0", 33 | "react-create-class": "1.0.0", 34 | "react-dom": "15.3.0", 35 | "react-hot-loader": "1.3.0", 36 | "webpack": "1.13.1", 37 | "webpack-dev-server": "1.14.1" 38 | }, 39 | "dependencies": { 40 | "prop-types": "15.5.10" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/example/FacebookProfile.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Profile from "./Profile"; 3 | import InlineCss from "../react-inline-css"; 4 | 5 | /** 6 | * @module Profile 7 | */ 8 | const FacebookProfile = React.createClass({ 9 | statics: { 10 | css: () => ` 11 | & .card { 12 | padding-top: 30px; 13 | border-radius: 3px; 14 | border: 1px solid rgb(208, 209, 213); 15 | border-left-color: rgb(223, 224, 228); 16 | border-right-color: rgb(223, 224, 228); 17 | border-top-color: rgb(229, 230, 233); 18 | background: linear-gradient(to bottom, rgba(255,255,255,1) 56%,rgba(204,204,204,1) 100%); 19 | } 20 | & .card > img { 21 | vertical-align: middle; 22 | padding: 4px; 23 | background: #fff; 24 | border: 1px solid #ccc; 25 | border-radius: 3px; 26 | } 27 | & .card > p { 28 | font-family: Helvetica, Arial, 'lucida grande', tahoma, verdana, arial, sans-serif; 29 | font-weight: bold; 30 | color: rgb(59, 89, 152); 31 | display: inline; 32 | } 33 | ` 34 | }, 35 | render: function () { 36 | return ( 37 | 38 | 39 | 40 | ); 41 | } 42 | }); 43 | 44 | export default FacebookProfile; 45 | -------------------------------------------------------------------------------- /src/example/GoogleProfile.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Profile from "./Profile"; 3 | import InlineCss from "../react-inline-css"; 4 | 5 | /** 6 | * @module GoogleProfile 7 | */ 8 | const GoogleProfile = React.createClass({ 9 | statics: { 10 | css: () => ` 11 | @import url(https://fonts.googleapis.com/css?family=Roboto); 12 | 13 | & .card { 14 | border-radius: 3px; 15 | background-color: #fff; 16 | box-shadow: inset 1px 1px 0 rgba(0,0,0,.1),inset 0 -1px 0 rgba(0,0,0,.07); 17 | border: 1px solid #d8d8d8; 18 | border-bottom-width: 2px; 19 | border-top-width: 0; 20 | vertical-align: top; 21 | } 22 | & .card > img { 23 | vertical-align: middle; 24 | border-radius: 9999px; 25 | border: 0; 26 | padding: 0; 27 | margin-right: 3px; 28 | } 29 | & .card > p { 30 | font-family: Roboto, arial, sans-serif; 31 | color: rgb(38, 38, 38); 32 | display: block; 33 | } 34 | ` 35 | }, 36 | render: function () { 37 | return ( 38 | 39 | 40 | 41 | ); 42 | } 43 | }); 44 | 45 | export default GoogleProfile; 46 | -------------------------------------------------------------------------------- /src/example/Profile.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import InlineCss from "../react-inline-css"; 3 | 4 | /** 5 | * @module Profile 6 | */ 7 | const Profile = React.createClass({ 8 | statics: { 9 | css: (avatarSize) => ` 10 | & .card { 11 | margin: 15px; 12 | padding: 15px; 13 | text-align: center; 14 | height: 200px; 15 | } 16 | & .card > img { 17 | width: ${avatarSize}px; 18 | height: ${avatarSize}px; 19 | } 20 | & .card > p { 21 | margin: 10px; 22 | } 23 | ` 24 | }, 25 | render: function () { 26 | return ( 27 | 28 |
29 | 30 |

{this.props.name || 'Default Mao'}

31 |
32 |
33 | ); 34 | } 35 | }); 36 | 37 | export default Profile; 38 | -------------------------------------------------------------------------------- /src/example/TwitterProfile.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Profile from "./Profile"; 3 | import InlineCss from "../react-inline-css"; 4 | 5 | /** 6 | * @module TwitterProfile 7 | */ 8 | const TwitterProfile = React.createClass({ 9 | statics: { 10 | css: () => ` 11 | & .card { 12 | border-radius: 5px; 13 | background-color: rgb(178, 223, 218); 14 | border: 1px solid #e1e8ed; 15 | padding: 0; 16 | } 17 | & .card > img { 18 | background: #fff; 19 | border: 6px solid #fff; 20 | border-radius: 6px; 21 | margin: 15px; 22 | box-shadow: rgba(136, 153, 166, 0.14902) 0px 1px 1px 0px; 23 | } 24 | & .card > p { 25 | font-family: 'Helvetica Neue', Helvetica, arial, sans-serif; 26 | color: #292f33; 27 | font-size: 18px; 28 | display: block; 29 | background: #f5f8fa; 30 | border-radius: 0 0 5px 5px; 31 | margin: 0; 32 | padding: 10px; 33 | bottom: 0; 34 | font-weight: bold; 35 | } 36 | ` 37 | }, 38 | render: function () { 39 | return ( 40 | 41 | 42 | 43 | ); 44 | } 45 | }); 46 | 47 | export default TwitterProfile; 48 | -------------------------------------------------------------------------------- /src/example/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom"; 3 | import InlineCss from "./../react-inline-css" 4 | import Profile from "./Profile"; 5 | import FacebookProfile from "./FacebookProfile"; 6 | import GoogleProfile from "./GoogleProfile"; 7 | import TwitterProfile from "./TwitterProfile"; 8 | 9 | /** 10 | * @module Main 11 | */ 12 | const Main = React.createClass({ 13 | statics: { 14 | css: (avatarSize) => ` 15 | * { 16 | box-sizing: border-box; 17 | } 18 | & { 19 | font-family: monospace; 20 | padding: 10px 30px 30px; 21 | width: 450px; 22 | margin: 10px auto; 23 | } 24 | & #github { 25 | position: absolute; 26 | top: 0; 27 | right: 0; 28 | border: 0; 29 | } 30 | ` 31 | }, 32 | render () { 33 | return ( 34 | 35 | 36 | Fork me on GitHub 39 | 40 |

React Inline CSS

41 |

React Inline CSS allows you to cascade CSS stylesheets on your components, automatically namespacing them.

42 | 43 | 44 | 45 | 46 |
47 | ); 48 | } 49 | }); 50 | 51 | ReactDOM.render(
, document.getElementById("react-root")); 52 | -------------------------------------------------------------------------------- /src/react-inline-css.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @copyright © 2015, Rick Wong. All rights reserved. 3 | */ 4 | var React = require("react"); 5 | var PropTypes = require("prop-types"); 6 | var assign = Object.assign ? Object.assign : React.__spread; 7 | var refCounter = 0; 8 | var createReactClass = require('create-react-class'); 9 | 10 | 11 | /** 12 | * @module InlineCss 13 | */ 14 | var InlineCss = createReactClass({ 15 | displayName: "InlineCss", 16 | propTypes: { 17 | namespace: PropTypes.string, 18 | componentName: PropTypes.string, 19 | stylesheet: PropTypes.string.isRequired, 20 | className: PropTypes.string, 21 | wrapper: PropTypes.string 22 | }, 23 | _transformSheet: function (stylesheet, componentName, namespace) { 24 | return stylesheet. 25 | // Prettier output. 26 | replace(/}\s*/ig, '\n}\n'). 27 | // Regular rules are namespaced. 28 | replace( 29 | /(^|{|}|;|,)\s*([&a-z0-9\-~_=\.:#^\|\(\)\[\]\$'",>*\s]+)\s*(\{)/ig, 30 | function (matched) { 31 | return matched.replace(new RegExp(componentName, "g"), "#" + namespace); 32 | } 33 | ); 34 | }, 35 | render: function () { 36 | var namespace = this.props.namespace || "InlineCss-" + refCounter++; 37 | var componentName = this.props.componentName || "&"; 38 | var stylesheet = this._transformSheet(this.props.stylesheet, componentName, namespace); 39 | var Wrapper = this.props.wrapper || "div"; 40 | 41 | var wrapperProps = assign({}, this.props, { 42 | id: namespace 43 | }); 44 | 45 | delete wrapperProps.namespace; 46 | delete wrapperProps.componentName; 47 | delete wrapperProps.stylesheet; 48 | delete wrapperProps.wrapper; 49 | 50 | return React.createElement( 51 | Wrapper, 52 | wrapperProps, 53 | this.props.children, 54 | React.createElement("style", { 55 | scoped: true, 56 | dangerouslySetInnerHTML: {__html: stylesheet} 57 | }) 58 | ); 59 | } 60 | }); 61 | 62 | module.exports = InlineCss; 63 | -------------------------------------------------------------------------------- /static/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | react-inline-style 6 | 7 | 8 |
9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /static/mao.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RickWong/react-inline-css/897ece053eb90f5a15ebf0ce9f053e03a05118e0/static/mao.jpg -------------------------------------------------------------------------------- /static/mao2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RickWong/react-inline-css/897ece053eb90f5a15ebf0ce9f053e03a05118e0/static/mao2.png -------------------------------------------------------------------------------- /static/mao3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RickWong/react-inline-css/897ece053eb90f5a15ebf0ce9f053e03a05118e0/static/mao3.png -------------------------------------------------------------------------------- /static/mao4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RickWong/react-inline-css/897ece053eb90f5a15ebf0ce9f053e03a05118e0/static/mao4.png -------------------------------------------------------------------------------- /webpack.client-watch.js: -------------------------------------------------------------------------------- 1 | var webpack = require("webpack"); 2 | var config = require("./webpack.client.js"); 3 | 4 | config.cache = true; 5 | config.debug = true; 6 | config.devtool = "eval"; 7 | 8 | config.entry.WDS = "webpack-dev-server/client?http://localhost:8080"; 9 | config.entry.hot = "webpack/hot/only-dev-server"; 10 | 11 | config.module.postLoaders = [ 12 | {test: /\.js$/, loaders: ["react-hot"], exclude: /node_modules/} 13 | ]; 14 | 15 | config.output.publicPath = "http://localhost:8080/dist/"; 16 | config.output.hotUpdateMainFilename = "update/[hash]/update.json"; 17 | config.output.hotUpdateChunkFilename = "update/[hash]/[id].update.js"; 18 | 19 | config.plugins = [ 20 | new webpack.HotModuleReplacementPlugin() 21 | ]; 22 | 23 | config.devServer = { 24 | publicPath: "http://localhost:8080/dist/", 25 | contentBase: "./static", 26 | hot: true, 27 | inline: true, 28 | quiet: true, 29 | noInfo: true, 30 | headers: {"Access-Control-Allow-Origin": "*"}, 31 | stats: {colors: true} 32 | }; 33 | 34 | module.exports = config; 35 | -------------------------------------------------------------------------------- /webpack.client.js: -------------------------------------------------------------------------------- 1 | var webpack = require("webpack"); 2 | var path = require("path"); 3 | 4 | module.exports = { 5 | target: "web", 6 | cache: false, 7 | context: __dirname, 8 | devtool: false, 9 | entry: {example:"./src/example"}, 10 | output: { 11 | path: path.join(__dirname, "static/dist"), 12 | filename: "[name].js", 13 | chunkFilename: "[name].[id].js", 14 | publicPath: "dist/" 15 | }, 16 | plugins: [ 17 | new webpack.DefinePlugin({"process.env": {NODE_ENV: '"production"'}}), 18 | new webpack.optimize.DedupePlugin(), 19 | new webpack.optimize.OccurenceOrderPlugin(), 20 | new webpack.optimize.UglifyJsPlugin() 21 | ], 22 | module: { 23 | loaders: [ 24 | {test: /\.json$/, loaders: ["json-loader"]}, 25 | {test: /\.js$/, loaders: ["babel-loader?presets[]=es2015&presets[]=react"], exclude: /node_modules/}, 26 | {test: /\.scss$/, loaders: ["raw-loader", "sass-loader"], exclude: /node_modules/} 27 | ] 28 | }, 29 | resolve: { 30 | extensions: ["", ".json", ".jsx", ".js"] 31 | } 32 | }; 33 | --------------------------------------------------------------------------------