├── .eslintrc
├── .gitignore
├── LICENSE
├── README.md
├── config
├── styleguidist
│ ├── config.js
│ └── index.html
└── webpack
│ ├── development.js
│ ├── production.js
│ └── server.js
├── package.json
├── scripts
└── create-component.js
├── src
├── client
│ └── index.js
├── server
│ ├── getAssetsPath.js
│ ├── handleRequest.js
│ ├── index.js
│ └── renderFullPage.js
└── shared
│ ├── components
│ ├── About
│ │ ├── About.js
│ │ ├── About.md
│ │ └── index.js
│ ├── Button
│ │ ├── Button.js
│ │ ├── Button.md
│ │ └── index.js
│ ├── Header
│ │ ├── Header.js
│ │ └── index.js
│ ├── Home
│ │ ├── Home.js
│ │ ├── Home.md
│ │ └── index.js
│ ├── NotFound
│ │ ├── NotFound.js
│ │ └── index.js
│ ├── Paper
│ │ ├── Paper.js
│ │ ├── Paper.md
│ │ └── index.js
│ ├── Ripple
│ │ ├── Ripple.js
│ │ └── index.js
│ ├── Status
│ │ ├── Status.js
│ │ └── index.js
│ └── User
│ │ ├── User.js
│ │ ├── User.md
│ │ └── index.js
│ ├── containers
│ └── Home.js
│ ├── index.js
│ ├── reducers
│ └── index.js
│ ├── routes.js
│ └── theme.js
└── yarn.lock
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": ["standard", "standard-react"],
3 | "env": {
4 | "browser": true
5 | }
6 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # App
2 | build
3 | public
4 |
5 | # Logs
6 | logs
7 | *.log
8 | npm-debug.log*
9 |
10 | # Runtime data
11 | pids
12 | *.pid
13 | *.seed
14 |
15 | # Directory for instrumented libs generated by jscoverage/JSCover
16 | lib-cov
17 |
18 | # Coverage directory used by tools like istanbul
19 | coverage
20 |
21 | # nyc test coverage
22 | .nyc_output
23 |
24 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
25 | .grunt
26 |
27 | # node-waf configuration
28 | .lock-wscript
29 |
30 | # Compiled binary addons (http://nodejs.org/api/addons.html)
31 | build/Release
32 |
33 | # Dependency directories
34 | node_modules
35 | jspm_packages
36 |
37 | # Optional npm cache directory
38 | .npm
39 |
40 | # Optional REPL history
41 | .node_repl_history
42 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 Anton Kulakov
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # isomorphic-react
2 |
3 | - React router 4
4 | - Webpack 2
5 | - Express
6 | - Redux
7 | - JSS
8 | - Server side rendering
9 | - React hot loader
10 | - React Styleguidist
11 | - ES Lint
12 | - Standard.js
13 |
14 | ```bash
15 | npm run client
16 | npm run server
17 | npm run build:client
18 | npm run build:server
19 | npm run prod:server
20 | npm run styleguide
21 | npm run styleguide:build
22 | npm run lint
23 | npm run cc
24 | ```
25 |
26 | ### React styleguidist
27 |
28 | 
29 |
--------------------------------------------------------------------------------
/config/styleguidist/config.js:
--------------------------------------------------------------------------------
1 | const { resolve } = require('path')
2 |
3 | module.exports = {
4 | title: 'UI Kit',
5 | // getComponentPathLine (componentPath) {
6 | // const name = basename(componentPath, '.js')
7 | // const dir = dirname(componentPath).replace(/^src\/components/, 'netology-ui-kit/lib')
8 | // return `import ${name} from '${dir}'`
9 | // },
10 | getExampleFilename (componentPath) {
11 | return componentPath.replace(/\.jsx?$/, '.md')
12 | },
13 | webpackConfig: require('../webpack/development.js'),
14 | highlightTheme: 'material',
15 | serverHost: 'localhost',
16 | showSidebar: true,
17 | serverPort: 6060,
18 | showCode: false,
19 | template: resolve(__dirname, 'index.html'),
20 | sections: [
21 | {
22 | name: 'Components',
23 | components: resolve(__dirname, '../../src/shared/components/**/[A-Z]*.js')
24 | }
25 | ],
26 | styleguideDir: resolve(__dirname, '../../build/static/styleguide'),
27 | skipComponentsWithoutExample: true,
28 | styles: {
29 | Markdown: {
30 | blockquote: {
31 | margin: '1em 0',
32 | background: '#F8F8F9',
33 | padding: '1em 1.5em',
34 | lineHeight: '1.4285em',
35 | color: 'rgba(0,0,0,.87)',
36 | transition: 'opacity .1s ease,color .1s ease,background .1s ease,box-shadow .1s ease',
37 | borderRadius: '3px'
38 | },
39 | h5: {
40 | color: '#222',
41 | fontWeight: 700
42 | }
43 | },
44 | Playground: {
45 | root: {
46 | backgroundColor: 'white',
47 | backgroundImage: 'linear-gradient(45deg,#efefef 25%,transparent 0,transparent 75%,#efefef 0,#efefef),linear-gradient(45deg,#efefef 25%,transparent 0,transparent 75%,#efefef 0,#efefef)',
48 | backgroundPosition: '0 0,10px 10px',
49 | backgroundSize: '21px 21px',
50 | fontSize: '1rem'
51 | }
52 | },
53 | StyleGuide: {
54 | root: {
55 | fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif'
56 | }
57 | }
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/config/styleguidist/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | <%=htmlWebpackPlugin.options.title%>
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/config/webpack/development.js:
--------------------------------------------------------------------------------
1 | const { resolve } = require('path')
2 | const webpack = require('webpack')
3 |
4 | module.exports = {
5 | entry: [
6 | 'react-hot-loader/patch',
7 | 'webpack-dev-server/client?http://localhost:3000',
8 | 'webpack/hot/only-dev-server',
9 | './src/client/index.js'
10 | ],
11 | output: {
12 | filename: 'bundle.js',
13 | path: resolve(__dirname, 'public'),
14 | publicPath: '/static/'
15 | },
16 | devtool: 'inline-source-map',
17 | module: {
18 | rules: [
19 | {
20 | test: /\.jsx?$/,
21 | exclude: /node_modules/,
22 | use: [
23 | {
24 | loader: 'babel-loader',
25 | options: {
26 | presets: [
27 | ['env', {
28 | modules: false,
29 | targets: {
30 | browsers: [
31 | 'last 2 versions',
32 | 'safari >= 7'
33 | ]
34 | }
35 | }],
36 | 'react'
37 | ],
38 | 'plugins': [
39 | 'react-hot-loader/babel'
40 | ]
41 | }
42 | },
43 | 'eslint-loader'
44 | ]
45 | }
46 | ]
47 | },
48 | stats: 'errors-only',
49 | bail: true,
50 | plugins: [
51 | new webpack.DefinePlugin({
52 | 'process.env.NODE_ENV': JSON.stringify('development')
53 | }),
54 | new webpack.HotModuleReplacementPlugin(),
55 | new webpack.NamedModulesPlugin(),
56 | new webpack.NoEmitOnErrorsPlugin()
57 | ],
58 | devServer: {
59 | host: 'localhost',
60 | port: 3000,
61 | hot: true,
62 | proxy: {
63 | '*': 'http://localhost:3001'
64 | },
65 | overlay: {
66 | warnings: true,
67 | errors: true
68 | }
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/config/webpack/production.js:
--------------------------------------------------------------------------------
1 | const { resolve } = require('path')
2 | const webpack = require('webpack')
3 | // const ChunkManifestPlugin = require('chunk-manifest-webpack-plugin')
4 | // const WebpackChunkHash = require('webpack-chunk-hash')
5 | const ManifestPlugin = require('webpack-manifest-plugin')
6 |
7 | module.exports = {
8 | // context: resolve(__dirname, 'src'),
9 | entry: {
10 | main: './src/client/index.js'
11 | },
12 | output: {
13 | filename: '[name].[chunkhash].js',
14 | path: resolve(__dirname, 'build/static'),
15 | publicPath: '/static/'
16 | },
17 | devtool: 'cheap-module-source-map',
18 | module: {
19 | rules: [
20 | {
21 | test: /\.jsx?$/,
22 | exclude: /node_modules/,
23 | use: [{
24 | loader: 'babel-loader',
25 | options: {
26 | presets: [
27 | ['env', {
28 | modules: false,
29 | targets: {
30 | browsers: [
31 | 'last 2 versions',
32 | 'safari >= 7'
33 | ]
34 | }
35 | }],
36 | 'react'
37 | ]
38 | }
39 | }]
40 | }
41 | ]
42 | },
43 | // performance: {
44 | // hints: 'error'
45 | // },
46 | // stats: 'errors-only',
47 | plugins: [
48 | new webpack.optimize.CommonsChunkPlugin({
49 | name: 'vendor',
50 | minChunks: function (module) {
51 | // this assumes your vendor imports exist in the node_modules directory
52 | return module.context && module.context.indexOf('node_modules') !== -1
53 | }
54 | // names: ['vendor', 'manifest'] // Specify the common bundle's name.
55 | }),
56 | // CommonChunksPlugin will now extract all the common modules from vendor and main bundles
57 | // new webpack.optimize.CommonsChunkPlugin({
58 | // filename: 'manifest.json',
59 | // name: 'manifest' // But since there are no more common modules between them we end up with just the runtime code included in the manifest file
60 | // }),
61 | new ManifestPlugin(),
62 | new webpack.HashedModuleIdsPlugin(),
63 | // new WebpackChunkHash(),
64 | // new ChunkManifestPlugin(),
65 | new webpack.LoaderOptionsPlugin({
66 | minimize: true,
67 | debug: false
68 | }),
69 | new webpack.optimize.OccurrenceOrderPlugin(),
70 | new webpack.optimize.UglifyJsPlugin({
71 | beautify: false,
72 | mangle: {
73 | screw_ie8: true,
74 | keep_fnames: true
75 | },
76 | compress: {
77 | screw_ie8: true
78 | },
79 | compressor: {
80 | warnings: false
81 | },
82 | comments: false
83 | }),
84 | new webpack.DefinePlugin({
85 | 'process.env.NODE_ENV': JSON.stringify('production')
86 | })
87 | ]
88 | }
89 |
--------------------------------------------------------------------------------
/config/webpack/server.js:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kulakowka/isomorphic-react/9cce31b216169ee4728cd6301b5d748f8d311c05/config/webpack/server.js
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "isomorphic-react",
3 | "version": "1.0.0",
4 | "main": "index.js",
5 | "license": "MIT",
6 | "scripts": {
7 | "start": "",
8 | "client": "webpack-dev-server --config config/webpack/development.js --progress",
9 | "server": "nodemon src/server/index.js --exec babel-node --presets=env,react",
10 | "build:client": "NODE_ENV=production rm -rf public && webpack --config webpack.production.config.js --progress",
11 | "build:server": "NODE_ENV=production babel src -d build --presets=env,react",
12 | "prod:server": "NODE_ENV=production node build/server.js",
13 | "styleguide": "styleguidist server --config config/styleguidist/config.js",
14 | "styleguide:build": "styleguidist build --config config/styleguidist/config.js",
15 | "lint": "standard src/**/*.js",
16 | "cc": "node scripts/create-component"
17 | },
18 | "dependencies": {
19 | "babel-cli": "^6.24.1",
20 | "babel-core": "^6.24.1",
21 | "babel-loader": "^6.4.1",
22 | "babel-preset-env": "^1.3.3",
23 | "babel-preset-es2015": "^6.24.1",
24 | "babel-preset-latest": "^6.24.1",
25 | "babel-preset-react": "^6.24.1",
26 | "chalk": "^1.1.3",
27 | "chunk-manifest-webpack-plugin": "^1.0.0",
28 | "classnames": "^2.2.5",
29 | "eslint": "^3.19.0",
30 | "eslint-config-standard": "^10.2.0",
31 | "eslint-config-standard-react": "^4.3.0",
32 | "eslint-loader": "^1.7.1",
33 | "eslint-plugin-promise": "^3.5.0",
34 | "eslint-plugin-react": "^6.10.3",
35 | "eslint-plugin-standard": "^3.0.1",
36 | "express": "^4.15.2",
37 | "fs-promise": "^2.0.2",
38 | "inquirer": "^3.0.6",
39 | "jss": "^6.5.0",
40 | "jss-preset-default": "^1.3.1",
41 | "nodemon": "^1.11.0",
42 | "prop-types": "^15.5.4",
43 | "react": "^15.5.3",
44 | "react-dom": "^15.5.3",
45 | "react-helmet": "^5.0.2",
46 | "react-hot-loader": "^3.0.0-beta.6",
47 | "react-jss": "^5.4.1",
48 | "react-redux": "^5.0.3",
49 | "react-ripple-effect": "^1.0.4",
50 | "react-router": "^4.0.0",
51 | "react-router-dom": "^4.0.0",
52 | "redux": "^3.6.0",
53 | "redux-devtools-extension": "^2.13.0",
54 | "standard": "^10.0.1",
55 | "webpack": "^2.3.3",
56 | "webpack-chunk-hash": "^0.4.0",
57 | "webpack-dev-server": "^2.4.2",
58 | "webpack-manifest-plugin": "^1.1.0"
59 | },
60 | "devDependencies": {
61 | "react-styleguidist": "^5.0.6"
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/scripts/create-component.js:
--------------------------------------------------------------------------------
1 | const inquirer = require('inquirer')
2 | const fsp = require('fs-promise')
3 | const { resolve } = require('path')
4 | const chalk = require('chalk')
5 |
6 | const COMPONENTS_PATH = resolve(__dirname, `../src/shared/components`)
7 |
8 | inquirer.prompt([
9 | {
10 | type: 'input',
11 | name: 'name',
12 | message: 'What component name?',
13 | filter: val => val[0].toUpperCase() + val.slice(1)
14 | }
15 | ])
16 | .then(createComponentFiles)
17 | .catch(console.log)
18 |
19 | function isComponentExist ({ name }) {
20 | const filePath = resolve(COMPONENTS_PATH, name)
21 | const isExist = fsp.existsSync(filePath)
22 | if (isExist) console.log('Component %s already exist', chalk.red(filePath))
23 | return isExist
24 | }
25 |
26 | function createComponentFiles (answers) {
27 | if (isComponentExist(answers)) return false
28 |
29 | return Promise.all([
30 | createIndexFile,
31 | createComponentFile,
32 | createDocsFile
33 | ].map(f => f(answers)))
34 | }
35 |
36 | function createIndexFile ({ name }) {
37 | const template = `import ${name} from './${name}'\n\nexport default ${name}\n`
38 | const filePath = resolve(COMPONENTS_PATH, name, 'index.js')
39 | console.log('Created index file: ', chalk.blue(filePath))
40 | return fsp.outputFile(filePath, template)
41 | }
42 |
43 | function createComponentFile ({ name }) {
44 | const template = `import React from 'react'
45 | import PropTypes from 'prop-types'
46 | import injectSheet from 'react-jss'
47 |
48 | /**
49 | * A ${name} component.
50 | */
51 | function ${name} (props) {
52 | const {
53 | children,
54 | classes
55 | } = props
56 |
57 | return (
58 |
59 | {children}
60 |
61 | )
62 | }
63 |
64 | ${name}.displayName = '${name}'
65 |
66 | ${name}.propTypes = {
67 | /**
68 | * Primary content.
69 | */
70 | children: PropTypes.node,
71 |
72 | /**
73 | * Classes from JSS
74 | */
75 | classes: PropTypes.object.isRequired
76 | }
77 |
78 | ${name}.defaultProps = {}
79 |
80 | const styles = {
81 | root: {
82 | display: 'flex'
83 | }
84 | }
85 |
86 | export default injectSheet(styles)(${name})
87 | `
88 | const filePath = resolve(COMPONENTS_PATH, name, `${name}.js`)
89 |
90 | console.log('Created component file: ', chalk.blue(filePath))
91 | return fsp.outputFile(filePath, template)
92 | }
93 |
94 | function createDocsFile ({ name }) {
95 | const template = `A standard ${name}.
96 |
97 | ${'```'}example
98 | <${name} />
99 | ${'```'}
100 | `
101 | const filePath = resolve(COMPONENTS_PATH, name, `${name}.md`)
102 |
103 | console.log('Created docs file: ', chalk.blue(filePath))
104 | return fsp.outputFile(filePath, template)
105 | }
106 |
--------------------------------------------------------------------------------
/src/client/index.js:
--------------------------------------------------------------------------------
1 |
2 | import React from 'react'
3 | import ReactDOM from 'react-dom'
4 | import { composeWithDevTools } from 'redux-devtools-extension'
5 | import { createStore, applyMiddleware } from 'redux'
6 | import { Provider } from 'react-redux'
7 | import reducers from '../shared/reducers'
8 | import { BrowserRouter } from 'react-router-dom'
9 | import { AppContainer } from 'react-hot-loader'
10 | import jss from 'jss'
11 | import preset from 'jss-preset-default'
12 | import App from '../shared'
13 |
14 | // One time setup with default plugins and settings.
15 | jss.setup(preset())
16 |
17 | // Grab the state from a global variable injected into the server-generated HTML
18 | const preloadedState = window.__PRELOADED_STATE__
19 |
20 | // Allow the passed state to be garbage-collected
21 | delete window.__PRELOADED_STATE__
22 |
23 | // Create Redux store with initial state
24 | const store = createStore(reducers, preloadedState, composeWithDevTools(
25 | applyMiddleware()
26 | ))
27 |
28 | const render = (Component) => {
29 | ReactDOM.render(
30 |
31 |
32 |
33 |
34 |
35 |
36 | ,
37 | document.getElementById('root'),
38 | () => {
39 | // We don't need the static css any more once we have launched our application.
40 | const ssStyles = document.getElementById('server-side-styles')
41 | ssStyles.parentNode.removeChild(ssStyles)
42 | }
43 | )
44 | }
45 |
46 | render(App)
47 |
48 | if (module.hot) {
49 | module.hot.accept('../shared', () => render(App))
50 | module.hot.accept('../shared/reducers', () => {
51 | const nextRootReducer = require('../shared/reducers').default
52 | store.replaceReducer(nextRootReducer)
53 | })
54 | }
55 |
--------------------------------------------------------------------------------
/src/server/getAssetsPath.js:
--------------------------------------------------------------------------------
1 | export default function getAssetsPath () {
2 | if (process.env.NODE_ENV === 'production') {
3 | const manifest = require('../static/manifest.json')
4 | return `
5 | `
6 | }
7 | return ''
8 | }
9 |
--------------------------------------------------------------------------------
/src/server/handleRequest.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactDOMServer from 'react-dom/server'
3 | import { createStore } from 'redux'
4 | import { Provider } from 'react-redux'
5 | import reducers from '../shared/reducers'
6 | import routes from '../shared/routes'
7 | import { StaticRouter } from 'react-router'
8 | import App from '../shared'
9 | import { matchPath } from 'react-router-dom'
10 | import {SheetsRegistryProvider, SheetsRegistry} from 'react-jss'
11 | import renderFullPage from './renderFullPage'
12 |
13 | export default function handleRequest (req, res, next) {
14 | const context = {}
15 |
16 | const sheets = new SheetsRegistry()
17 |
18 | // inside a request
19 | const promises = []
20 | // use `some` to imitate `` behavior of selecting only
21 | // the first to match
22 | routes.some(route => {
23 | // use `matchPath` here
24 | const match = matchPath(req.url, route)
25 | if (match) promises.push(route.component.loadData(match))
26 | return match
27 | })
28 |
29 | Promise.all(promises).then(data => {
30 | // do something w/ the data so the client
31 | // can access it then render the app
32 | const counter = parseInt(req.query.counter, 10) || 0
33 |
34 | // Compile an initial state
35 | let preloadedState = { counter, data }
36 |
37 | // Create a new Redux store instance
38 | const store = createStore(reducers, preloadedState)
39 |
40 | const html = ReactDOMServer.renderToString(
41 |
42 |
46 |
47 |
48 |
49 |
50 |
51 | )
52 |
53 | if (context.status) {
54 | res.status(context.status)
55 | }
56 |
57 | if (context.url) {
58 | res.redirect(301, context.url)
59 | }
60 |
61 | // Grab the initial state from our Redux store
62 | const finalState = store.getState()
63 |
64 | // Send the rendered page back to the client
65 | res.send(renderFullPage({ sheets, html, finalState }))
66 | })
67 | }
68 |
--------------------------------------------------------------------------------
/src/server/index.js:
--------------------------------------------------------------------------------
1 | import express from 'express'
2 | import { resolve } from 'path'
3 | import handleRequest from './handleRequest'
4 |
5 | const staticPath = express.static(resolve(__dirname, 'static'))
6 |
7 | const app = express()
8 |
9 | app.use('/static', staticPath)
10 | app.use(handleRequest)
11 | app.listen(3001)
12 |
--------------------------------------------------------------------------------
/src/server/renderFullPage.js:
--------------------------------------------------------------------------------
1 | import getAssetsPath from './getAssetsPath'
2 |
3 | export default function renderFullPage ({ sheets, html, finalState }) {
4 | return `
5 |
6 |
7 |
8 | Redux Universal Example
9 |
10 |
13 |
14 |
15 | ${html}
16 |
19 | ${getAssetsPath()}
20 |
21 |
22 | `
23 | }
24 |
--------------------------------------------------------------------------------
/src/shared/components/About/About.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import Paper from '../Paper'
3 |
4 | /**
5 | * About page
6 | */
7 | const About = () => (
8 |
9 | About
10 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
11 | But I must explain to you how all this mistaken idea of denouncing pleasure and praising pain was born and I will give you a complete account of the system, and expound the actual teachings of the great explorer of the truth, the master-builder of human happiness. No one rejects, dislikes, or avoids pleasure itself, because it is pleasure, but because those who do not know how to pursue pleasure rationally encounter consequences that are extremely painful. Nor again is there anyone who loves or pursues or desires to obtain pain of itself, because it is pain, but because occasionally circumstances occur in which toil and pain can procure him some great pleasure. To take a trivial example, which of us ever undertakes laborious physical exercise, except to obtain some advantage from it? But who has any right to find fault with a man who chooses to enjoy a pleasure that has no annoying consequences, or one who avoids a pain that produces no resultant pleasure?
12 |
13 | )
14 |
15 | About.loadData = (match) => Promise.resolve({ data: 2, match })
16 |
17 | export default About
18 |
--------------------------------------------------------------------------------
/src/shared/components/About/About.md:
--------------------------------------------------------------------------------
1 | ```example
2 |
3 | ```
--------------------------------------------------------------------------------
/src/shared/components/About/index.js:
--------------------------------------------------------------------------------
1 | import About from './About'
2 |
3 | export default About
4 |
--------------------------------------------------------------------------------
/src/shared/components/Button/Button.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import PropTypes from 'prop-types'
3 | import injectSheet from 'react-jss'
4 | import cx from 'classnames'
5 | import { Link } from 'react-router-dom'
6 | import { DEFAULT_FONT } from '../../theme'
7 | import Ripple from '../Ripple'
8 |
9 | /**
10 | * A Button component.
11 | */
12 | class Button extends Component {
13 | constructor () {
14 | super()
15 | this.state = {
16 | ripples: []
17 | }
18 | this.handleClick = this.handleClick.bind(this)
19 | }
20 | handleClick (e, b) {
21 | // console.log(e, b)
22 | // console.log('this.button', this.button)
23 | // console.log('event', e)
24 | console.dir(e.currentTarget)
25 | // Get Cursor Position
26 | let cursorPos = {
27 | top: e.clientY - e.currentTarget.offsetTop - 25,
28 | left: e.clientX - e.currentTarget.offsetLeft - 25
29 | }
30 | // console.log('cursorPos', cursorPos, {
31 | // offsetLeft: this.button.offsetLeft,
32 | // offsetTop: this.button.offsetTop
33 | // })
34 |
35 | this.setState({
36 | ripples: this.state.ripples.concat( )
37 | })
38 |
39 | setTimeout(() => {
40 | this.setState({
41 | ripples: this.state.ripples.filter((r, i) => !!i)
42 | })
43 | }, 1000)
44 | }
45 | render () {
46 | const {
47 | children,
48 | href,
49 | content,
50 | classes,
51 | primary,
52 | positive,
53 | negative,
54 | rounded,
55 | size,
56 | onClick
57 | } = this.props
58 |
59 | const classNames = cx(
60 | classes.button,
61 | primary && classes.primary,
62 | positive && classes.positive,
63 | negative && classes.negative,
64 | rounded && classes.rounded,
65 | size && classes[`${size}-size`]
66 | )
67 |
68 | const otherProps = {
69 | onClick
70 | }
71 |
72 | if (href) {
73 | return (
74 | { this.button = button }}
77 | className={classNames}
78 | {...otherProps}
79 | onMouseUp={this.handleClick}
80 | onTouchEnd={this.handleClick}
81 | >
82 | {content || children}
83 | {this.state.ripples}
84 |
85 | )
86 | }
87 |
88 | return (
89 | { this.button = button }}
91 | className={classNames}
92 | {...otherProps}
93 | onMouseUp={this.handleClick}
94 | onTouchEnd={this.handleClick}
95 | >
96 | {content || children}
97 | {this.state.ripples}
98 |
99 | )
100 | }
101 | }
102 |
103 | Button.displayName = 'Button'
104 |
105 | Button.propTypes = {
106 | /** Link href */
107 | href: PropTypes.string,
108 | /** Primary content. */
109 | children: PropTypes.node,
110 | /** Primary content. */
111 | content: PropTypes.node,
112 | /** Classes from JSS */
113 | classes: PropTypes.object.isRequired,
114 | /** Is primary */
115 | primary: PropTypes.bool,
116 | /** Is positive */
117 | positive: PropTypes.bool,
118 | /** Is negative */
119 | negative: PropTypes.bool,
120 | /** Is rounded */
121 | rounded: PropTypes.bool,
122 | /** Size: small|default|big */
123 | size: PropTypes.string.isRequired,
124 | /** On click callback */
125 | onClick: PropTypes.func
126 | }
127 |
128 | Button.defaultProps = {
129 | size: 'default',
130 | rounded: true
131 | }
132 |
133 | const styles = {
134 | button: {
135 | fontFamily: DEFAULT_FONT,
136 | position: 'relative',
137 | overflow: 'hidden',
138 | display: 'inline-flex',
139 | padding: 0,
140 | margin: 0,
141 | border: 0,
142 | cursor: 'pointer',
143 | userSelect: 'none',
144 | background: '#ddd',
145 | color: '#222',
146 | textDecoration: 'none',
147 | textTransform: 'uppercase',
148 | 'box-shadow': '0 9px 3px 0px rgba(0,0,0,0.15)',
149 | transition: '0.1s all',
150 | '&:focus': {
151 | outline: 0
152 | },
153 | '&:hover': {
154 | background: '#eee',
155 | // boxShadow: '0 0 0 3px #ddd',
156 | 'box-shadow': '0 5px 3px 0px rgba(0,0,0,0.1)',
157 | transform: 'scale(1.1)',
158 | position: 'relative',
159 | zIndex: 2
160 | // transform: 'translateY(4px)'
161 | },
162 | '&:active': {
163 | background: '#ddd',
164 | transform: 'scale(1)',
165 | 'box-shadow': '0 9px 3px 0px rgba(0,0,0,0.15)'
166 | }
167 | },
168 | primary: {
169 | background: 'darkblue',
170 | color: 'white',
171 | '&:hover': {
172 | background: 'blue'
173 | },
174 | '&:active': {
175 | background: 'darkblue'
176 | // boxShadow: '0 0 0 3px darkblue'
177 | }
178 | },
179 | positive: {
180 | background: '#3e8e41',
181 | color: 'white',
182 | '&:hover': {
183 | background: '#4CAF50'
184 | },
185 | '&:active': {
186 | background: '#3e8e41'
187 | // boxShadow: '0 0 0 3px darkgreen'
188 | }
189 | },
190 | negative: {
191 | background: 'darkred',
192 | color: 'white',
193 | '&:hover': {
194 | background: 'red'
195 | },
196 | '&:active': {
197 | background: 'darkred'
198 | // boxShadow: '0 0 0 3px darkred'
199 | }
200 | },
201 | rounded: {
202 | borderRadius: '5px'
203 | },
204 | 'small-size': {
205 | padding: '15px 30px',
206 | fontSize: '16px',
207 | lineHeight: '16px'
208 | },
209 | 'default-size': {
210 | padding: '20px 40px',
211 | fontSize: '20px',
212 | lineHeight: '20px'
213 | },
214 | 'big-size': {
215 | padding: '30px 60px',
216 | fontSize: '28px',
217 | lineHeight: '28px'
218 | }
219 | }
220 |
221 | export default injectSheet(styles)(Button)
222 |
--------------------------------------------------------------------------------
/src/shared/components/Button/Button.md:
--------------------------------------------------------------------------------
1 | A standard Button.
2 |
3 | ```example
4 | Click me
5 | ```
6 | ```example
7 |
8 | ```
9 |
10 | A primary button.
11 |
12 | ```example
13 |
14 | ```
15 |
16 | A positive and negative button.
17 |
18 | ```example
19 |
20 |
21 |
22 |
23 | ```
24 |
25 | A no rounded button.
26 |
27 | ```example
28 |
29 | ```
30 |
31 | Sizes.
32 |
33 | ```example
34 |
35 |
36 |
37 |
38 |
39 | ```
--------------------------------------------------------------------------------
/src/shared/components/Button/index.js:
--------------------------------------------------------------------------------
1 | import Button from './Button'
2 |
3 | export default Button
4 |
--------------------------------------------------------------------------------
/src/shared/components/Header/Header.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 | import Button from '../Button'
4 | import injectSheet from 'react-jss'
5 |
6 | const Header = ({ classes }) => (
7 |
8 | home
9 | about
10 | old
11 | 404
12 | user
13 |
14 | )
15 |
16 | Header.propTypes = {
17 | /**
18 | * Classes from JSS
19 | */
20 | classes: PropTypes.object.isRequired
21 | }
22 |
23 | const styles = {
24 | header: {
25 | display: 'flex',
26 | justifyContent: 'space-start'
27 | }
28 | }
29 |
30 | export default injectSheet(styles)(Header)
31 |
--------------------------------------------------------------------------------
/src/shared/components/Header/index.js:
--------------------------------------------------------------------------------
1 | import Header from './Header'
2 |
3 | export default Header
4 |
--------------------------------------------------------------------------------
/src/shared/components/Home/Home.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 | import Paper from '../Paper'
4 | import Button from '../Button'
5 | import injectSheet from 'react-jss'
6 | import { DEFAULT_FONT } from '../../theme'
7 |
8 | /**
9 | * A home page root comoponent
10 | */
11 | const Home = ({ classes, counter, incrementCounter }) => (
12 |
13 | Home
14 | {`Counter: ${counter} 2`}
15 |
16 | increment
17 |
18 |
19 | )
20 |
21 | Home.propTypes = {
22 | /** Counter value */
23 | counter: PropTypes.number.isRequired,
24 | /** Increment counter action creator */
25 | incrementCounter: PropTypes.func.isRequired,
26 | /** Classes from JSS */
27 | classes: PropTypes.object.isRequired
28 | }
29 |
30 | Home.loadData = (match) => Promise.resolve({ data: 1, match })
31 |
32 | const styles = {
33 | title: {
34 | fontFamily: DEFAULT_FONT,
35 | fontSize: '50px',
36 | fontWeight: 'bold',
37 | margin: 0
38 | },
39 | counter: {
40 | fontFamily: DEFAULT_FONT,
41 | fontSize: '20px',
42 | margin: '50px 0'
43 | }
44 | }
45 |
46 | export default injectSheet(styles)(Home)
47 |
--------------------------------------------------------------------------------
/src/shared/components/Home/Home.md:
--------------------------------------------------------------------------------
1 | ```example
2 | console.log('incrementCounter')} />
3 | ```
--------------------------------------------------------------------------------
/src/shared/components/Home/index.js:
--------------------------------------------------------------------------------
1 | import Home from './Home'
2 |
3 | export default Home
4 |
--------------------------------------------------------------------------------
/src/shared/components/NotFound/NotFound.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import Status from '../Status'
3 | import Paper from '../Paper'
4 |
5 | const NotFound = () => (
6 |
7 |
8 | Not Found
9 |
10 |
11 | )
12 |
13 | export default NotFound
14 |
--------------------------------------------------------------------------------
/src/shared/components/NotFound/index.js:
--------------------------------------------------------------------------------
1 | import NotFound from './NotFound'
2 |
3 | export default NotFound
4 |
--------------------------------------------------------------------------------
/src/shared/components/Paper/Paper.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 | import injectSheet from 'react-jss'
4 | import cx from 'classnames'
5 | import { DEFAULT_FONT } from '../../theme'
6 |
7 | /**
8 | * A Paper component.
9 | */
10 | function Paper (props) {
11 | const {
12 | children,
13 | classes,
14 | circle,
15 | rounded,
16 | zDepth
17 | } = props
18 |
19 | const classNames = cx(
20 | classes.paper,
21 | circle && classes.circle,
22 | rounded && classes.rounded,
23 | zDepth && classes[`zDepth${zDepth}`]
24 | )
25 |
26 | return (
27 |
28 | {children}
29 |
30 | )
31 | }
32 |
33 | Paper.displayName = 'Paper'
34 |
35 | Paper.propTypes = {
36 | /** Primary content. */
37 | children: PropTypes.node,
38 | /** Classes from JSS */
39 | classes: PropTypes.object.isRequired,
40 | /** Set to true to generate a circlular paper container. */
41 | circle: PropTypes.bool,
42 | /** By default, the paper container will have a border radius. Set this to false to generate a container with sharp corners. */
43 | rounded: PropTypes.bool,
44 | /** This number represents the zDepth of the paper shadow. */
45 | zDepth: PropTypes.number
46 | }
47 |
48 | Paper.defaultProps = {
49 | circle: false,
50 | rounded: false,
51 | zDepth: 1
52 | }
53 |
54 | const styles = {
55 | paper: {
56 | 'background-color': 'white',
57 | 'transition': '.3s',
58 | '-webkit-tap-highlight-color': 'rgba(0, 0, 0, 0)',
59 | 'padding': '1rem',
60 | 'align-items': 'center',
61 | 'min-width': '30px',
62 | 'min-height': '30px',
63 | 'font-family': DEFAULT_FONT
64 | },
65 | circle: {
66 | 'border-radius': '50%',
67 | 'justify-content': 'center'
68 | },
69 | rounded: {
70 | 'border-radius': '2px'
71 | },
72 | zDepth1: {
73 | 'box-shadow': 'rgba(0, 0, 0, 0.117647) 0px 1px 6px, rgba(0, 0, 0, 0.117647) 0px 1px 4px'
74 | },
75 | zDepth2: {
76 | 'box-shadow': 'rgba(0, 0, 0, 0.156863) 0px 3px 10px, rgba(0, 0, 0, 0.227451) 0px 3px 10px'
77 | },
78 | zDepth3: {
79 | 'box-shadow': 'rgba(0, 0, 0, 0.188235) 0px 10px 30px, rgba(0, 0, 0, 0.227451) 0px 6px 10px'
80 | },
81 | zDepth4: {
82 | 'box-shadow': 'rgba(0, 0, 0, 0.247059) 0px 14px 45px, rgba(0, 0, 0, 0.219608) 0px 10px 18px'
83 | },
84 | zDepth5: {
85 | 'box-shadow': 'rgba(0, 0, 0, 0.298039) 0px 19px 60px, rgba(0, 0, 0, 0.219608) 0px 15px 20px'
86 | }
87 | }
88 |
89 | export default injectSheet(styles)(Paper)
90 |
--------------------------------------------------------------------------------
/src/shared/components/Paper/Paper.md:
--------------------------------------------------------------------------------
1 | A standard Paper.
2 |
3 | ```example
4 |
11 | ```
12 |
13 | A circle Paper.
14 |
15 | ```example
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 | ```
24 |
25 | A rounded Paper.
26 |
27 | ```example
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 | ```
36 |
37 |
38 |
--------------------------------------------------------------------------------
/src/shared/components/Paper/index.js:
--------------------------------------------------------------------------------
1 | import Paper from './Paper'
2 |
3 | export default Paper
4 |
--------------------------------------------------------------------------------
/src/shared/components/Ripple/Ripple.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 | import injectSheet from 'react-jss'
4 |
5 | /**
6 | * A Ripple component.
7 | */
8 | var Ripple = ({ classes, cursorPos, isVisible }) => {
9 | let style = {
10 | left: cursorPos.left,
11 | top: cursorPos.top
12 | }
13 |
14 | return (
15 |
16 | )
17 | }
18 |
19 | const styles = {
20 | ripple: {
21 | position: 'absolute',
22 | 'border-radius': '50%',
23 | width: '50px',
24 | height: '50px',
25 | animation: 'ripple-animation 1s',
26 | 'animation-fill-mode': 'forwards',
27 | background: 'rgba(0, 0, 0, 0.5)'
28 | },
29 | '@keyframes ripple-animation': {
30 | from: {
31 | transform: 'scale(1)',
32 | opacity: 0.4
33 | },
34 | to: {
35 | transform: 'scale(10)',
36 | opacity: 0
37 | }
38 | }
39 | }
40 |
41 | Ripple.propTypes = {
42 | cursorPos: PropTypes.object,
43 | isVisible: PropTypes.bool,
44 | /** Classes from JSS */
45 | classes: PropTypes.object.isRequired
46 | }
47 |
48 | Ripple.displayName = 'Ripple'
49 |
50 | Ripple.defaultProps = {
51 | isVisible: false,
52 | cursorPos: {
53 | left: 0,
54 | top: 0
55 | }
56 | }
57 |
58 | export default injectSheet(styles)(Ripple)
59 |
--------------------------------------------------------------------------------
/src/shared/components/Ripple/index.js:
--------------------------------------------------------------------------------
1 | import Ripple from './Ripple'
2 |
3 | export default Ripple
4 |
--------------------------------------------------------------------------------
/src/shared/components/Status/Status.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 | import { Route } from 'react-router'
4 |
5 | const Status = ({ code, children }) => (
6 | {
7 | if (staticContext) staticContext.status = code
8 | return children
9 | }} />
10 | )
11 |
12 | Status.propTypes = {
13 | children: PropTypes.node.isRequired,
14 | code: PropTypes.number.isRequired
15 | }
16 |
17 | export default Status
18 |
--------------------------------------------------------------------------------
/src/shared/components/Status/index.js:
--------------------------------------------------------------------------------
1 | import Status from './Status'
2 |
3 | export default Status
4 |
--------------------------------------------------------------------------------
/src/shared/components/User/User.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import Paper from '../Paper'
3 |
4 | const User = () => (
5 |
6 | User
7 |
8 | )
9 |
10 | User.loadData = (match) => Promise.resolve({ data: 3, match })
11 |
12 | export default User
13 |
--------------------------------------------------------------------------------
/src/shared/components/User/User.md:
--------------------------------------------------------------------------------
1 | ```example
2 |
3 | ```
--------------------------------------------------------------------------------
/src/shared/components/User/index.js:
--------------------------------------------------------------------------------
1 | import User from './User'
2 |
3 | export default User
4 |
--------------------------------------------------------------------------------
/src/shared/containers/Home.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux'
2 | import Home from '../components/Home'
3 |
4 | export default connect(
5 | (state, props) => ({
6 | counter: state.counter
7 | }),
8 | (dispatch, props) => ({
9 | incrementCounter: () => dispatch({ type: 'INCREMENT' })
10 | })
11 | )(Home)
12 |
--------------------------------------------------------------------------------
/src/shared/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 | import { Switch, Route, Redirect } from 'react-router'
4 | import Header from './components/Header'
5 | import NotFound from './components/NotFound'
6 | import routes from './routes'
7 | import injectSheet from 'react-jss'
8 |
9 | /**
10 | * App component
11 | */
12 | const App = ({ classes }) => (
13 |
14 |
15 |
16 | {routes.map(route => (
17 |
18 | ))}
19 |
20 |
21 |
22 |
23 | )
24 |
25 | App.propTypes = {
26 | /**
27 | * Classes from JSS
28 | */
29 | classes: PropTypes.object.isRequired
30 | }
31 |
32 | const styles = {
33 | root: {
34 | background: 'white'
35 | },
36 | '@global': {
37 | body: {
38 | color: '#333'
39 | }
40 | }
41 | }
42 |
43 | export default injectSheet(styles)(App)
44 |
--------------------------------------------------------------------------------
/src/shared/reducers/index.js:
--------------------------------------------------------------------------------
1 | // Reducers
2 | export default function reducer (state = {}, action) {
3 | switch (action.type) {
4 | case 'INCREMENT':
5 | return Object.assign({}, state, {
6 | counter: state.counter + 1
7 | })
8 | default:
9 | return state
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/src/shared/routes.js:
--------------------------------------------------------------------------------
1 | import Home from './containers/Home'
2 | import About from './components/About'
3 | import User from './components/User'
4 |
5 | const routes = [
6 | { path: '/',
7 | exact: true,
8 | component: Home
9 | },
10 |
11 | { path: '/about',
12 | component: About
13 | },
14 |
15 | { path: '/users/:name',
16 | component: User
17 | }
18 | ]
19 |
20 | export default routes
21 |
--------------------------------------------------------------------------------
/src/shared/theme.js:
--------------------------------------------------------------------------------
1 | export const DEFAULT_FONT = `'Noto Serif', -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif`
2 |
--------------------------------------------------------------------------------