├── .gitignore
├── client
├── middleware
│ ├── index.js
│ └── logger.js
├── constants
│ └── filters.js
├── reducers
│ ├── index.js
│ └── todos.js
├── actions
│ └── todos.js
├── index.js
├── components
│ ├── Header
│ │ └── index.js
│ ├── TodoTextInput
│ │ ├── style.css
│ │ └── index.js
│ ├── Footer
│ │ ├── index.js
│ │ └── style.css
│ ├── TodoItem
│ │ ├── index.js
│ │ └── style.css
│ └── MainSection
│ │ ├── index.js
│ │ └── style.css
├── store
│ └── index.js
├── containers
│ └── App
│ │ ├── index.js
│ │ └── style.css
└── index.html
├── .babelrc
├── Readme.md
├── package.json
└── webpack.config.js
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_STORE
2 | node_modules
3 | static
4 | .module-cache
5 | *.log*
6 |
7 |
--------------------------------------------------------------------------------
/client/middleware/index.js:
--------------------------------------------------------------------------------
1 |
2 | import logger from './logger'
3 |
4 | export {
5 | logger
6 | }
--------------------------------------------------------------------------------
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["es2015", "stage-0", "react"],
3 | "plugins": ["transform-runtime"]
4 | }
5 |
--------------------------------------------------------------------------------
/client/middleware/logger.js:
--------------------------------------------------------------------------------
1 |
2 | export default store => next => action => {
3 | console.log(action)
4 | return next(action)
5 | }
--------------------------------------------------------------------------------
/client/constants/filters.js:
--------------------------------------------------------------------------------
1 |
2 | export const SHOW_ALL = 'show_all'
3 | export const SHOW_COMPLETED = 'show_completed'
4 | export const SHOW_ACTIVE = 'show_active'
5 |
--------------------------------------------------------------------------------
/client/reducers/index.js:
--------------------------------------------------------------------------------
1 |
2 | import { routeReducer as routing } from 'react-router-redux'
3 | import { combineReducers } from 'redux'
4 | import todos from './todos'
5 |
6 | export default combineReducers({
7 | routing,
8 | todos
9 | })
10 |
--------------------------------------------------------------------------------
/client/actions/todos.js:
--------------------------------------------------------------------------------
1 |
2 | import { createAction } from 'redux-actions'
3 |
4 | export const addTodo = createAction('add todo')
5 | export const deleteTodo = createAction('delete todo')
6 | export const editTodo = createAction('edit todo')
7 | export const completeTodo = createAction('complete todo')
8 | export const completeAll = createAction('complete all')
9 | export const clearCompleted = createAction('clear complete')
10 |
--------------------------------------------------------------------------------
/client/index.js:
--------------------------------------------------------------------------------
1 |
2 | import { Router, Route, browserHistory } from 'react-router'
3 | import { Provider } from 'react-redux'
4 | import ReactDOM from 'react-dom'
5 | import React from 'react'
6 |
7 | import App from './containers/App'
8 | import configure from './store'
9 |
10 | const store = configure()
11 |
12 | ReactDOM.render(
13 |
14 |
15 |
16 |
17 |
18 | ,
19 | document.getElementById('root')
20 | )
21 |
--------------------------------------------------------------------------------
/client/components/Header/index.js:
--------------------------------------------------------------------------------
1 |
2 | import React, { Component } from 'react'
3 | import TodoTextInput from '../TodoTextInput'
4 |
5 | class Header extends Component {
6 | handleSave(text) {
7 | if (text.length) {
8 | this.props.addTodo(text)
9 | }
10 | }
11 |
12 | render() {
13 | return (
14 |
21 | )
22 | }
23 | }
24 |
25 | export default Header
26 |
--------------------------------------------------------------------------------
/client/components/TodoTextInput/style.css:
--------------------------------------------------------------------------------
1 |
2 | .new,
3 | .edit {
4 | position: relative;
5 | margin: 0;
6 | width: 100%;
7 | font-size: 24px;
8 | font-family: inherit;
9 | font-weight: inherit;
10 | line-height: 1.4em;
11 | border: 0;
12 | outline: none;
13 | color: inherit;
14 | padding: 6px;
15 | border: 1px solid #999;
16 | box-shadow: inset 0 -1px 5px 0 rgba(0, 0, 0, 0.2);
17 | box-sizing: border-box;
18 | font-smoothing: antialiased;
19 | }
20 |
21 | .new {
22 | padding: 16px 16px 16px 60px;
23 | border: none;
24 | background: rgba(0, 0, 0, 0.003);
25 | box-shadow: inset 0 -2px 1px rgba(0,0,0,0.03);
26 | }
--------------------------------------------------------------------------------
/client/store/index.js:
--------------------------------------------------------------------------------
1 |
2 | import { createStore, applyMiddleware } from 'redux'
3 | import { syncHistory } from 'react-router-redux'
4 | import { browserHistory } from 'react-router'
5 |
6 | import { logger } from '../middleware'
7 | import rootReducer from '../reducers'
8 |
9 | export default function configure(initialState) {
10 | const create = window.devToolsExtension
11 | ? window.devToolsExtension()(createStore)
12 | : createStore
13 |
14 | const createStoreWithMiddleware = applyMiddleware(
15 | logger,
16 | syncHistory(browserHistory)
17 | )(create)
18 |
19 | const store = createStoreWithMiddleware(rootReducer, initialState)
20 |
21 | if (module.hot) {
22 | module.hot.accept('../reducers', () => {
23 | const nextReducer = require('../reducers')
24 | store.replaceReducer(nextReducer)
25 | })
26 | }
27 |
28 | return store
29 | }
30 |
--------------------------------------------------------------------------------
/client/containers/App/index.js:
--------------------------------------------------------------------------------
1 |
2 | import React, { Component } from 'react'
3 | import { bindActionCreators } from 'redux'
4 | import { connect } from 'react-redux'
5 | import Header from '../../components/Header'
6 | import MainSection from '../../components/MainSection'
7 | import * as TodoActions from '../../actions/todos'
8 | import style from './style.css'
9 |
10 | class App extends Component {
11 | render() {
12 | const { todos, actions, children } = this.props
13 | return (
14 |
15 |
16 |
17 | {children}
18 |
19 | )
20 | }
21 | }
22 |
23 | function mapStateToProps(state) {
24 | return {
25 | todos: state.todos
26 | }
27 | }
28 |
29 | function mapDispatchToProps(dispatch) {
30 | return {
31 | actions: bindActionCreators(TodoActions, dispatch)
32 | }
33 | }
34 |
35 | export default connect(
36 | mapStateToProps,
37 | mapDispatchToProps
38 | )(App)
39 |
--------------------------------------------------------------------------------
/client/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Boilerplate
6 |
7 |
8 |
9 |
10 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/Readme.md:
--------------------------------------------------------------------------------
1 |
2 | # Frontend Boilerplate
3 |
4 | A boilerplate of things that mostly shouldn't exist.
5 |
6 | ## Contains
7 |
8 | - [x] [Webpack](https://webpack.github.io)
9 | - [x] [React](https://facebook.github.io/react/)
10 | - [x] [Redux](https://github.com/rackt/redux)
11 | - [x] [Babel](https://babeljs.io/)
12 | - [x] [Autoprefixer](https://github.com/postcss/autoprefixer)
13 | - [x] [PostCSS](https://github.com/postcss/postcss)
14 | - [x] [CSS modules](https://github.com/outpunk/postcss-modules)
15 | - [x] [Rucksack](http://simplaio.github.io/rucksack/docs)
16 | - [x] [React Router Redux](https://github.com/rackt/react-router-redux)
17 | - [x] [Redux DevTools Extension](https://github.com/zalmoxisus/redux-devtools-extension)
18 | - [ ] Redux effects
19 | - [x] TodoMVC example
20 |
21 | ## Setup
22 |
23 | ```
24 | $ npm install
25 | ```
26 |
27 | ## Running
28 |
29 | ```
30 | $ npm start
31 | ```
32 |
33 | ## Note
34 |
35 | This is just my personal boilerplate, it may or may not be a good fit for your project(s).
36 |
37 | # License
38 |
39 | MIT
40 |
--------------------------------------------------------------------------------
/client/reducers/todos.js:
--------------------------------------------------------------------------------
1 |
2 | import { handleActions } from 'redux-actions'
3 |
4 | const initialState = [{
5 | text: 'Use Redux',
6 | completed: false,
7 | id: 0
8 | }]
9 |
10 | export default handleActions({
11 | 'add todo' (state, action) {
12 | return [{
13 | id: state.reduce((maxId, todo) => Math.max(todo.id, maxId), -1) + 1,
14 | completed: false,
15 | text: action.payload
16 | }, ...state]
17 | },
18 |
19 | 'delete todo' (state, action) {
20 | return state.filter(todo => todo.id !== action.payload )
21 | },
22 |
23 | 'edit todo' (state, action) {
24 | return state.map(todo => {
25 | return todo.id === action.payload.id
26 | ? { ...todo, text: action.payload.text }
27 | : todo
28 | })
29 | },
30 |
31 | 'complete todo' (state, action) {
32 | return state.map(todo => {
33 | return todo.id === action.payload
34 | ? { ...todo, completed: !todo.completed }
35 | : todo
36 | })
37 | },
38 |
39 | 'complete all' (state, action) {
40 | const areAllMarked = state.every(todo => todo.completed)
41 | return state.map(todo => {
42 | return {
43 | ...todo,
44 | completed: !areAllMarked
45 | }
46 | })
47 | },
48 |
49 | 'clear complete' (state, action) {
50 | return state.filter(todo => todo.completed === false)
51 | }
52 | }, initialState)
53 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "frontend-boilerplate",
3 | "version": "1.0.0",
4 | "private": true,
5 | "description": "A boilerplate of things that shouldn't exist",
6 | "main": "index.js",
7 | "scripts": {
8 | "test": "echo \"Error: no test specified\" && exit 1",
9 | "start": "webpack-dev-server --history-api-fallback --hot --inline --progress --colors --port 3000",
10 | "build": "NODE_ENV=production webpack --progress --colors"
11 | },
12 | "license": "MIT",
13 | "devDependencies": {
14 | "babel-core": "^6.3.26",
15 | "babel-loader": "^6.2.0",
16 | "babel-plugin-transform-runtime": "^6.3.13",
17 | "babel-preset-es2015": "^6.3.13",
18 | "babel-preset-react": "^6.3.13",
19 | "babel-preset-stage-0": "^6.3.13",
20 | "css-loader": "^0.23.1",
21 | "file-loader": "^0.8.4",
22 | "postcss-loader": "^0.8.0",
23 | "rucksack-css": "^0.8.5",
24 | "style-loader": "^0.12.4",
25 | "webpack": "^1.12.2",
26 | "webpack-dev-server": "^1.12.0",
27 | "webpack-hot-middleware": "^2.2.0",
28 | "babel-runtime": "^6.3.19",
29 | "classnames": "^2.1.2",
30 | "react": "^0.14.0",
31 | "react-dom": "^0.14.0",
32 | "react-hot-loader": "^1.3.0",
33 | "react-redux": "^4.0.6",
34 | "react-router": "^2.0.0-rc5",
35 | "react-router-redux": "^2.1.0",
36 | "redux": "^3.0.2",
37 | "redux-actions": "^0.9.0"
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/client/components/TodoTextInput/index.js:
--------------------------------------------------------------------------------
1 |
2 | import React, { Component } from 'react'
3 | import classnames from 'classnames'
4 | import style from './style.css'
5 |
6 | class TodoTextInput extends Component {
7 | constructor(props, context) {
8 | super(props, context)
9 | this.state = {
10 | text: this.props.text || ''
11 | }
12 | }
13 |
14 | handleSubmit(e) {
15 | const text = e.target.value.trim()
16 | if (e.which === 13) {
17 | this.props.onSave(text)
18 | if (this.props.newTodo) {
19 | this.setState({ text: '' })
20 | }
21 | }
22 | }
23 |
24 | handleChange(e) {
25 | this.setState({ text: e.target.value })
26 | }
27 |
28 | handleBlur(e) {
29 | const text = e.target.value.trim()
30 | if (!this.props.newTodo) {
31 | this.props.onSave(text)
32 | }
33 | }
34 |
35 | render() {
36 | const classes = classnames({
37 | [style.edit]: this.props.editing,
38 | [style.new]: this.props.newTodo
39 | }, style.normal)
40 |
41 | return (
42 |
50 | )
51 | }
52 | }
53 |
54 | export default TodoTextInput
55 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | var rucksack = require('rucksack-css')
2 | var webpack = require('webpack')
3 | var path = require('path')
4 |
5 | module.exports = {
6 | context: path.join(__dirname, './client'),
7 | entry: {
8 | jsx: './index.js',
9 | html: './index.html',
10 | vendor: ['react']
11 | },
12 | output: {
13 | path: path.join(__dirname, './static'),
14 | filename: 'bundle.js',
15 | },
16 | module: {
17 | loaders: [
18 | {
19 | test: /\.html$/,
20 | loader: 'file?name=[name].[ext]'
21 | },
22 | {
23 | test: /\.css$/,
24 | loaders: [
25 | 'style-loader',
26 | 'css-loader?modules&sourceMap&importLoaders=1&localIdentName=[name]__[local]___[hash:base64:5]',
27 | 'postcss-loader'
28 | ]
29 | },
30 | {
31 | test: /\.(js|jsx)$/,
32 | exclude: /node_modules/,
33 | loaders: [
34 | 'react-hot',
35 | 'babel-loader'
36 | ]
37 | },
38 | ],
39 | },
40 | resolve: {
41 | extensions: ['', '.js', '.jsx']
42 | },
43 | postcss: [
44 | rucksack({
45 | autoprefixer: true
46 | })
47 | ],
48 | plugins: [
49 | new webpack.optimize.CommonsChunkPlugin('vendor', 'vendor.bundle.js'),
50 | new webpack.DefinePlugin({
51 | 'process.env': { NODE_ENV: JSON.stringify(process.env.NODE_ENV || 'development') }
52 | })
53 | ],
54 | devServer: {
55 | contentBase: './client',
56 | hot: true
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/client/containers/App/style.css:
--------------------------------------------------------------------------------
1 |
2 | html,
3 | body {
4 | margin: 0;
5 | padding: 0;
6 | }
7 |
8 | button {
9 | margin: 0;
10 | padding: 0;
11 | border: 0;
12 | background: none;
13 | font-size: 100%;
14 | vertical-align: baseline;
15 | font-family: inherit;
16 | font-weight: inherit;
17 | color: inherit;
18 | appearance: none;
19 | font-smoothing: antialiased;
20 | }
21 |
22 | body {
23 | font: 14px 'Helvetica Neue', Helvetica, Arial, sans-serif;
24 | line-height: 1.4em;
25 | background: #f5f5f5;
26 | color: #4d4d4d;
27 | min-width: 230px;
28 | max-width: 550px;
29 | margin: 0 auto;
30 | -webkit-font-smoothing: antialiased;
31 | -moz-font-smoothing: antialiased;
32 | -ms-font-smoothing: antialiased;
33 | font-smoothing: antialiased;
34 | font-weight: 300;
35 | }
36 |
37 | button,
38 | input[type="checkbox"] {
39 | outline: none;
40 | }
41 |
42 | .normal {
43 | background: #fff;
44 | margin: 200px 0 40px 0;
45 | position: relative;
46 | box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2),
47 | 0 25px 50px 0 rgba(0, 0, 0, 0.1);
48 | }
49 |
50 | .normal input::-webkit-input-placeholder {
51 | font-style: italic;
52 | font-weight: 300;
53 | color: #e6e6e6;
54 | }
55 |
56 | .normal input::-moz-placeholder {
57 | font-style: italic;
58 | font-weight: 300;
59 | color: #e6e6e6;
60 | }
61 |
62 | .normal input::input-placeholder {
63 | font-style: italic;
64 | font-weight: 300;
65 | color: #e6e6e6;
66 | }
67 |
68 | .normal h1 {
69 | position: absolute;
70 | top: -155px;
71 | width: 100%;
72 | font-size: 100px;
73 | font-weight: 100;
74 | text-align: center;
75 | color: rgba(175, 47, 47, 0.15);
76 | -webkit-text-rendering: optimizeLegibility;
77 | -moz-text-rendering: optimizeLegibility;
78 | -ms-text-rendering: optimizeLegibility;
79 | text-rendering: optimizeLegibility;
80 | }
81 |
--------------------------------------------------------------------------------
/client/components/Footer/index.js:
--------------------------------------------------------------------------------
1 |
2 | import React, { Component } from 'react'
3 | import { SHOW_ALL, SHOW_COMPLETED, SHOW_ACTIVE } from '../../constants/filters'
4 | import classnames from 'classnames'
5 | import style from './style.css'
6 |
7 | const FILTER_TITLES = {
8 | [SHOW_ALL]: 'All',
9 | [SHOW_ACTIVE]: 'Active',
10 | [SHOW_COMPLETED]: 'Completed'
11 | }
12 |
13 | class Footer extends Component {
14 | renderTodoCount() {
15 | const { activeCount } = this.props
16 | const itemWord = activeCount === 1 ? 'item' : 'items'
17 |
18 | return (
19 |
20 | {activeCount || 'No'} {itemWord} left
21 |
22 | )
23 | }
24 |
25 | renderFilterLink(filter) {
26 | const title = FILTER_TITLES[filter]
27 | const { filter: selectedFilter, onShow } = this.props
28 |
29 | return (
30 | onShow(filter)}>
33 | {title}
34 |
35 | )
36 | }
37 |
38 | renderClearButton() {
39 | const { completedCount, onClearCompleted } = this.props
40 | if (completedCount > 0) {
41 | return (
42 |
45 | )
46 | }
47 | }
48 |
49 | render() {
50 | return (
51 |
62 | )
63 | }
64 | }
65 |
66 | export default Footer
67 |
--------------------------------------------------------------------------------
/client/components/Footer/style.css:
--------------------------------------------------------------------------------
1 |
2 | .normal {
3 | color: #777;
4 | padding: 10px 15px;
5 | height: 20px;
6 | text-align: center;
7 | border-top: 1px solid #e6e6e6;
8 | }
9 |
10 | .normal:before {
11 | content: '';
12 | position: absolute;
13 | right: 0;
14 | bottom: 0;
15 | left: 0;
16 | height: 50px;
17 | overflow: hidden;
18 | box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2),
19 | 0 8px 0 -3px #f6f6f6,
20 | 0 9px 1px -3px rgba(0, 0, 0, 0.2),
21 | 0 16px 0 -6px #f6f6f6,
22 | 0 17px 2px -6px rgba(0, 0, 0, 0.2);
23 | }
24 |
25 | .filters {
26 | margin: 0;
27 | padding: 0;
28 | list-style: none;
29 | position: absolute;
30 | right: 0;
31 | left: 0;
32 | }
33 |
34 | .filters li {
35 | display: inline;
36 | }
37 |
38 | .filters li a {
39 | color: inherit;
40 | margin: 3px;
41 | padding: 3px 7px;
42 | text-decoration: none;
43 | border: 1px solid transparent;
44 | border-radius: 3px;
45 | }
46 |
47 | .filters li a.selected,
48 | .filters li a:hover {
49 | border-color: rgba(175, 47, 47, 0.1);
50 | }
51 |
52 | .filters li a.selected {
53 | border-color: rgba(175, 47, 47, 0.2);
54 | }
55 |
56 | .count {
57 | float: left;
58 | text-align: left;
59 | }
60 |
61 | .count strong {
62 | font-weight: 300;
63 | }
64 |
65 | .clearCompleted,
66 | html .clearCompleted:active {
67 | float: right;
68 | position: relative;
69 | line-height: 20px;
70 | text-decoration: none;
71 | cursor: pointer;
72 | visibility: hidden;
73 | position: relative;
74 | }
75 |
76 | .clearCompleted::after {
77 | visibility: visible;
78 | content: 'Clear completed';
79 | position: absolute;
80 | right: 0;
81 | white-space: nowrap;
82 | }
83 |
84 | .clearCompleted:hover::after {
85 | text-decoration: underline;
86 | }
87 |
88 | @media (max-width: 430px) {
89 | .normal {
90 | height: 50px;
91 | }
92 |
93 | .filters {
94 | bottom: 10px;
95 | }
96 | }
--------------------------------------------------------------------------------
/client/components/TodoItem/index.js:
--------------------------------------------------------------------------------
1 |
2 | import React, { Component } from 'react'
3 | import TodoTextInput from '../TodoTextInput'
4 | import classnames from 'classnames'
5 | import style from './style.css'
6 |
7 | class TodoItem extends Component {
8 | constructor(props, context) {
9 | super(props, context)
10 | this.state = {
11 | editing: false
12 | }
13 | }
14 |
15 | handleDoubleClick() {
16 | this.setState({ editing: true })
17 | }
18 |
19 | handleSave(id, text) {
20 | if (text.length === 0) {
21 | this.props.deleteTodo(id)
22 | } else {
23 | this.props.editTodo({ id, text })
24 | }
25 | this.setState({ editing: false })
26 | }
27 |
28 | render() {
29 | const {todo, completeTodo, deleteTodo} = this.props
30 |
31 | let element
32 | if (this.state.editing) {
33 | element = (
34 | this.handleSave(todo.id, text)} />
37 | )
38 | } else {
39 | element = (
40 |
41 | completeTodo(todo.id)} />
45 |
46 |
49 |
50 |
52 | )
53 | }
54 |
55 | // TODO: compose
56 | const classes = classnames({
57 | [style.completed]: todo.completed,
58 | [style.editing]: this.state.editing,
59 | [style.normal]: !this.state.editing
60 | })
61 |
62 | return (
63 |
64 | {element}
65 |
66 | )
67 | }
68 | }
69 |
70 | export default TodoItem
71 |
--------------------------------------------------------------------------------
/client/components/MainSection/index.js:
--------------------------------------------------------------------------------
1 |
2 | import React, { Component } from 'react'
3 | import TodoItem from '../TodoItem'
4 | import Footer from '../Footer'
5 | import { SHOW_ALL, SHOW_COMPLETED, SHOW_ACTIVE } from '../../constants/filters'
6 | import style from './style.css'
7 |
8 | const TODO_FILTERS = {
9 | [SHOW_ALL]: () => true,
10 | [SHOW_ACTIVE]: todo => !todo.completed,
11 | [SHOW_COMPLETED]: todo => todo.completed
12 | }
13 |
14 | class MainSection extends Component {
15 | constructor(props, context) {
16 | super(props, context)
17 | this.state = { filter: SHOW_ALL }
18 | }
19 |
20 | handleClearCompleted() {
21 | const atLeastOneCompleted = this.props.todos.some(todo => todo.completed)
22 | if (atLeastOneCompleted) {
23 | this.props.actions.clearCompleted()
24 | }
25 | }
26 |
27 | handleShow(filter) {
28 | this.setState({ filter })
29 | }
30 |
31 | renderToggleAll(completedCount) {
32 | const { todos, actions } = this.props
33 | if (todos.length > 0) {
34 | return
39 | }
40 | }
41 |
42 | renderFooter(completedCount) {
43 | const { todos } = this.props
44 | const { filter } = this.state
45 | const activeCount = todos.length - completedCount
46 |
47 | if (todos.length) {
48 | return (
49 |
54 | )
55 | }
56 | }
57 |
58 | render() {
59 | const { todos, actions } = this.props
60 | const { filter } = this.state
61 |
62 | const filteredTodos = todos.filter(TODO_FILTERS[filter])
63 | const completedCount = todos.reduce((count, todo) => {
64 | return todo.completed ? count + 1 : count
65 | }, 0)
66 |
67 | return (
68 |
69 | {this.renderToggleAll(completedCount)}
70 |
71 | {filteredTodos.map(todo =>
72 |
73 | )}
74 |
75 | {this.renderFooter(completedCount)}
76 |
77 | )
78 | }
79 | }
80 |
81 | export default MainSection
82 |
--------------------------------------------------------------------------------
/client/components/TodoItem/style.css:
--------------------------------------------------------------------------------
1 |
2 | .normal .toggle {
3 | text-align: center;
4 | width: 40px;
5 | /* auto, since non-WebKit browsers doesn't support input styling */
6 | height: auto;
7 | position: absolute;
8 | top: 0;
9 | bottom: 0;
10 | margin: auto 0;
11 | border: none; /* Mobile Safari */
12 | appearance: none;
13 | }
14 |
15 | .normal .toggle:after {
16 | content: url('data:image/svg+xml;utf8,');
17 | }
18 |
19 | .normal .toggle:checked:after {
20 | content: url('data:image/svg+xml;utf8,');
21 | }
22 |
23 | .normal label {
24 | white-space: pre-line;
25 | word-break: break-all;
26 | padding: 15px 60px 15px 15px;
27 | margin-left: 45px;
28 | display: block;
29 | line-height: 1.2;
30 | transition: color 0.4s;
31 | }
32 |
33 | .normal .destroy {
34 | display: none;
35 | position: absolute;
36 | top: 0;
37 | right: 10px;
38 | bottom: 0;
39 | width: 40px;
40 | height: 40px;
41 | margin: auto 0;
42 | font-size: 30px;
43 | color: #cc9a9a;
44 | margin-bottom: 11px;
45 | transition: color 0.2s ease-out;
46 | }
47 |
48 | .normal .destroy:hover {
49 | color: #af5b5e;
50 | }
51 |
52 | .normal .destroy:after {
53 | content: '×';
54 | }
55 |
56 | .normal:hover .destroy {
57 | display: block;
58 | }
59 |
60 | .normal .edit {
61 | display: none;
62 | }
63 |
64 | .editing {
65 | border-bottom: none;
66 | padding: 0;
67 | composes: normal;
68 | }
69 |
70 | .editing:last-child {
71 | margin-bottom: -1px;
72 | }
73 |
74 | .editing .edit {
75 | display: block;
76 | width: 506px;
77 | padding: 13px 17px 12px 17px;
78 | margin: 0 0 0 43px;
79 | }
80 |
81 | .editing .view {
82 | display: none;
83 | }
84 |
85 | .completed label {
86 | color: #d9d9d9;
87 | text-decoration: line-through;
88 | }
89 |
90 | /*
91 | Hack to remove background from Mobile Safari.
92 | Can't use it globally since it destroys checkboxes in Firefox
93 | */
94 | @media screen and (-webkit-min-device-pixel-ratio:0) {
95 | .normal .toggle {
96 | background: none;
97 | }
98 |
99 | .normal .toggle {
100 | height: 40px;
101 | }
102 | }
--------------------------------------------------------------------------------
/client/components/MainSection/style.css:
--------------------------------------------------------------------------------
1 |
2 | .main {
3 | position: relative;
4 | z-index: 2;
5 | border-top: 1px solid #e6e6e6;
6 | }
7 |
8 | .normal {
9 | margin: 0;
10 | padding: 0;
11 | list-style: none;
12 | }
13 |
14 | .normal li {
15 | position: relative;
16 | font-size: 24px;
17 | border-bottom: 1px solid #ededed;
18 | }
19 |
20 | .normal li:last-child {
21 | border-bottom: none;
22 | }
23 |
24 | .normal li.editing {
25 | border-bottom: none;
26 | padding: 0;
27 | }
28 |
29 | .normal li.editing .edit {
30 | display: block;
31 | width: 506px;
32 | padding: 13px 17px 12px 17px;
33 | margin: 0 0 0 43px;
34 | }
35 |
36 | .normal li.editing .view {
37 | display: none;
38 | }
39 |
40 | .normal li .toggle {
41 | text-align: center;
42 | width: 40px;
43 | /* auto, since non-WebKit browsers doesn't support input styling */
44 | height: auto;
45 | position: absolute;
46 | top: 0;
47 | bottom: 0;
48 | margin: auto 0;
49 | border: none; /* Mobile Safari */
50 | appearance: none;
51 | }
52 |
53 | .normal li .toggle:after {
54 | content: url('data:image/svg+xml;utf8,');
55 | }
56 |
57 | .normal li .toggle:checked:after {
58 | content: url('data:image/svg+xml;utf8,');
59 | }
60 |
61 | .normal li label {
62 | white-space: pre-line;
63 | word-break: break-all;
64 | padding: 15px 60px 15px 15px;
65 | margin-left: 45px;
66 | display: block;
67 | line-height: 1.2;
68 | transition: color 0.4s;
69 | }
70 |
71 | .normal li.completed label {
72 | color: #d9d9d9;
73 | text-decoration: line-through;
74 | }
75 |
76 | .normal li .destroy {
77 | display: none;
78 | position: absolute;
79 | top: 0;
80 | right: 10px;
81 | bottom: 0;
82 | width: 40px;
83 | height: 40px;
84 | margin: auto 0;
85 | font-size: 30px;
86 | color: #cc9a9a;
87 | margin-bottom: 11px;
88 | transition: color 0.2s ease-out;
89 | }
90 |
91 | .normal li .destroy:hover {
92 | color: #af5b5e;
93 | }
94 |
95 | .normal li .destroy:after {
96 | content: '×';
97 | }
98 |
99 | .normal li:hover .destroy {
100 | display: block;
101 | }
102 |
103 | .normal li .edit {
104 | display: none;
105 | }
106 |
107 | .normal li.editing:last-child {
108 | margin-bottom: -1px;
109 | }
110 |
111 | .toggleAll {
112 | position: absolute;
113 | top: -55px;
114 | left: -12px;
115 | width: 60px;
116 | height: 34px;
117 | text-align: center;
118 | border: none; /* Mobile Safari */
119 | }
120 |
121 | .toggleAll:before {
122 | content: '❯';
123 | font-size: 22px;
124 | color: #e6e6e6;
125 | padding: 10px 27px 10px 27px;
126 | }
127 |
128 | .toggleAll:checked:before {
129 | color: #737373;
130 | }
131 |
132 | /*
133 | Hack to remove background from Mobile Safari.
134 | Can't use it globally since it destroys checkboxes in Firefox
135 | */
136 | @media screen and (-webkit-min-device-pixel-ratio:0) {
137 | .toggleAll,
138 | .normal li .toggle {
139 | background: none;
140 | }
141 |
142 | .normal li .toggle {
143 | height: 40px;
144 | }
145 |
146 | .toggleAll {
147 | transform: rotate(90deg);
148 | appearance: none;
149 | }
150 | }
--------------------------------------------------------------------------------