(or how I learned to stop using inline styles)
CSS is globally scoped. Styles are accessible anywhere. That means that changing a style could have unintended consequences. Or you have become increasingly more specific with your styles.
Which leads to...
Image courtesy of Smashing Magazine
We've all seen this before. This case is a minor infraction, but in the aggregate it can be harmful.
var Radium = require('radium');
var React = require('react');
var color = require('color');
@Radium
class Button extends React.Component {
static propTypes = {
kind: React.PropTypes.oneOf(['primary', 'warning']).isRequired
};
render() {
// Radium extends the style attribute to accept an array. It will merge
// the styles in order. We use this feature here to apply the primary
// or warning styles depending on the value of the `kind` prop. Since its
// all just JavaScript, you can use whatever logic you want to decide which
// styles are applied (props, state, context, etc).
return (
<button
style={[
styles.base,
styles[this.props.kind]
]}>
{this.props.children}
</button>
);
}
}
// You can create your style objects dynamically or share them for
// every instance of the component.
var styles = {
base: {
color: '#fff',
// Adding interactive state couldn't be easier! Add a special key to your
// style object (:hover, :focus, :active, or @media) with the additional rules.
':hover': {
background: color('#0074d9').lighten(0.2).hexString()
}
},
primary: {
background: '#0074D9'
},
warning: {
background: '#FF4136'
}
};
var Radium = require('radium');
var React = require('react');
var color = require('color');
@Radium
class View extends React.Component {
render() {
const { buttons } = this.props;
return (
{buttons.map( (button, index) => {
const style = index === 0 ? styles.first : styles.base;
return (
<button
style={style}>
{this.props.children}
</button>
)
})}
);
}
}
var styles = {
base: {
color: '#fff',
':hover': {
background: color('#0074d9').lighten(0.2).hexString()
}
},
first{
background: 'green',
':hover': {
background: color('green').lighten(0.2).hexString()
}
}
};
var React = require('react');
import CSSModules from 'react-css-modules';
import styles from './button.scss';
@CSSModules(styles, {allowMultiple: true})
class View extends React.Component {
render() {
const { buttons } = this.props;
return (
{buttons.map( button => {
return (
<button styleName="base">{button}</button>
)
})}
)
}
}
//buttons.scss
.base{
color: white
&:hover{
background-color: lighten(green, .2);
}
&:first-of-type{
background-color: lighten(red, .2);
}
}
'use strict';
var path = require('path');
var webpack = require('webpack');
var HtmlWebpackPlugin = require('html-webpack-plugin');
const devServer = {
contentBase: path.resolve(__dirname, './app'),
outputPath: path.join(__dirname, './dist'),
colors: true,
quiet: false,
noInfo: false,
publicPath: '/',
historyApiFallback: false,
host: '127.0.0.1',
port: 3000,
hot: true
};
module.exports = {
devtool: 'eval-source-map',
debug: true,
devServer: devServer,
entry: [
'webpack/hot/dev-server',
'webpack-hot-middleware/client?reload=true',
path.join(__dirname, 'app/main.js')
],
output: {
path: path.join(__dirname, '/dist/'),
filename: '[name].js',
publicPath: devServer.publicPath
},
module: {
loaders: [
{
test: /\.js?$/,
loader: 'babel',
exclude: /node_modules|lib/,
},
{
test: /\.json?$/,
loader: 'json'
},
{
test: /\.css$/,
loader: 'style!css?modules&localIdentName=[name]---[local]---[hash:base64:5]'
},
{
test: /\.scss$/,
loaders: [
'style?sourceMap',
'css?modules&importLoaders=1&localIdentName=[path]___[name]__[local]',
'sass?sourceMap'
],
exclude: /node_modules|lib/
},
],
},
plugins: [
new HtmlWebpackPlugin({
template: 'app/index.tpl.html',
inject: 'body',
filename: 'index.html'
}),
new webpack.optimize.OccurenceOrderPlugin(),
new webpack.HotModuleReplacementPlugin(),
new webpack.NoErrorsPlugin(),
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify('dev')
})
],
node: {
fs: 'empty'
}
};
webpack.config.js
'use strict';
var path = require('path');
var webpack = require('webpack');
var HtmlWebpackPlugin = require('html-webpack-plugin');
var ExtractTextPlugin = require('extract-text-webpack-plugin');
var StatsPlugin = require('stats-webpack-plugin');
module.exports = {
entry: [
path.join(__dirname, 'app/main.js')
],
output: {
path: path.join(__dirname, '/dist/'),
filename: '[name]-[hash].min.js'
},
plugins: [
new ExtractTextPlugin('/app.min.css', {
allChunks: true
}),
new HtmlWebpackPlugin({
template: 'app/index.tpl.html',
inject: 'body',
filename: 'index.html'
}),
new webpack.optimize.OccurenceOrderPlugin(),
new webpack.optimize.UglifyJsPlugin({
compressor: {
warnings: false,
screw_ie8: true
}
}),
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV)
})
],
module: {
loaders: [
{
test: /\.js?$/,
loader: 'babel',
exclude: /node_modules|lib/,
},
{
test: /\.json?$/,
loader: 'json'
},
{
test: /\.css$/,
loader: 'style!css?modules&localIdentName=[name]---[local]---[hash:base64:5]'
},
{
test: /\.scss$/,
loader: ExtractTextPlugin.extract('style', 'css?modules&importLoaders=1&localIdentName=[name]__[local]!sass'),
exclude: /node_modules|lib/,
},
],
}
};
webpack.production.config.js
/* eslint no-console: 0 */
import path from 'path';
import express from 'express';
import webpack from 'webpack';
import WebpackDevServer from 'webpack-dev-server';
import webpackMiddleware from 'webpack-dev-middleware';
import webpackHotMiddleware from 'webpack-hot-middleware';
import config from './webpack.config.js';
const isDeveloping = process.env.NODE_ENV !== 'production';
const APP_PORT = isDeveloping ? 3000 : process.env.PORT || 3000;
let app = express();
if (isDeveloping) {
const compiler = webpack(config);
app = new WebpackDevServer(compiler, {
hot: true,
historyApiFallback: true,
contentBase: 'src',
publicPath: config.output.publicPath,
stats: {
colors: true,
hash: false,
timings: true,
chunks: false,
chunkModules: false,
modules: false
}
});
app.use(webpackHotMiddleware(compiler));
} else {
app.use(express.static('./dist'));
app.get('*', function response(req, res, next) {
res.sendFile(path.join(__dirname, '/index.html'));
});
}
app.listen(APP_PORT, () => {
console.log(`App is now running on http://localhost:${APP_PORT}`);
});
server.js
import React from 'react';
import ReactDOM from 'react-dom';
import styles from './App.scss';
class App extends React.Component {
render() {
const { children } = this.props;
return (
<div className="application">
{children}
</div>
)
}
}
export default App;
@import "./styles/reset";
@import "./styles/fonts";
@import "./styles/vars";
@import "./styles/typeplate/typeplate";
@import "./styles/z-index";
*{
box-sizing: border-box;
}
body{
font-family: $sansSerif;
font-weight: 400;
background-color: darken(white, 10%);
}
a{
text-decoration: none;
}
import React from 'react';
import Page from './page.js';
import Section from '../sections/section.js';
class HomePage extends React.Component{
render(){
return (
<Page>
<Section title="Ramsay" type="primary"></Section>
<Section title="Work" type="secondary"></Section>
<Section title="Play" type="dark"></Section>
<Section title="Hire" type="tertiary" backgroundImage="http://red-badger.com/blog/wp-content/uploads/2015/04/react-logo-1000-transparent.png"></Section>
</Page>
)
}
}
export default HomePage;
import React from 'react';
import CSSModules from 'react-css-modules';
import styles from './section.scss';
@CSSModules(styles, {allowMultiple: true})
class Section extends React.Component{
_renderPostTitle(){
const title = this.props.title.match(/.{1,2}/g);
return title.map( (fragment, index) => {
return <span key={index}>{fragment}<br></br></span>
})
}
render(){
const { type, backgroundImage } = this.props;
const bg = {
backgroundImage: "url('" + backgroundImage + "')",
};
const bgClass = backgroundImage ? 'with_background' : '';
return(
<div styleName={type + ' ' + bgClass} style={bg}>
<h2 styleName="title">{this._renderPostTitle()}</h2>
{this.props.children}
</div>
)
}
}
export default Section;
.base{
composes: child_1 container justify_center from '../../styles/flexbox.scss';
position: relative;
}
.primary{
composes: base;
composes: priamry primary_bg from '../../styles/colors.scss';
}
.secondary{
composes: base;
composes: secondary_bg from '../../styles/colors.scss';
}
.dark{
composes: base;
composes: dark_bg from '../../styles/colors.scss';
}
.tertiary{
composes: base;
composes: tertiary_bg from '../../styles/colors.scss';
}
.with_background{
composes: cover center from '../../styles/background.scss';
position: relative;
&:after{
background-color: fade-out(black, .65);
}
}
.title{
composes: strong uppercase center bold from '../../styles/typography.scss';
composes: self_center from '../../styles/flexbox.scss';
// background-color: fade-out(white, .2);
border: 10px solid fade-out(black, .8);
color: white;
font-size: 6vw;
line-height: 5vw;
padding: 1rem 2rem;
}