├── readme ├── cover.png ├── demo.gif └── demo-api.gif ├── public ├── favicon.ico ├── manifest.json └── index.html ├── src ├── styles │ ├── pages │ │ ├── _todo.sass │ │ └── _home.sass │ ├── config │ │ └── _variable.sass │ ├── vendors │ │ └── _fonts.scss │ ├── components │ │ ├── _header.sass │ │ ├── _button.sass │ │ ├── _weather.sass │ │ ├── _demo.sass │ │ └── _todo.sass │ ├── main.scss │ └── base │ │ ├── _typography.sass │ │ └── _normalize.scss ├── constants │ └── ActionTypes.js ├── components │ ├── App.js │ ├── TodoList.js │ ├── Todo.js │ ├── TodoFooter.js │ ├── Link.js │ ├── Header.js │ └── Navigation.js ├── reducers │ ├── visibilityFilter.js │ ├── todos.js │ └── weatherReducer.js ├── actions │ ├── todoActions.js │ └── weatherActions.js ├── pages │ ├── Weather.js │ ├── Home.js │ └── Todo.js ├── routes.js ├── containers │ ├── FilterLink.js │ ├── VisibleTodoList.js │ ├── AddTodo.js │ └── Weather.js ├── index.js ├── services │ └── weatherApi.js ├── store.js ├── images │ └── logo.svg └── registerServiceWorker.js ├── renovate.json ├── .editorconfig ├── tests ├── components │ └── App.spec.js └── reducers │ └── weatherReducer.spec.js ├── .gitignore ├── .travis.yml ├── .github └── dependabot.yml ├── LICENSE ├── package.json └── README.md /readme/cover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yingray/create-react-redux-app/HEAD/readme/cover.png -------------------------------------------------------------------------------- /readme/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yingray/create-react-redux-app/HEAD/readme/demo.gif -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yingray/create-react-redux-app/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /src/styles/pages/_todo.sass: -------------------------------------------------------------------------------- 1 | .p-todo 2 | text-align: left 3 | &__title 4 | text-align: left 5 | -------------------------------------------------------------------------------- /readme/demo-api.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yingray/create-react-redux-app/HEAD/readme/demo-api.gif -------------------------------------------------------------------------------- /src/styles/pages/_home.sass: -------------------------------------------------------------------------------- 1 | .p-home 2 | &__title 3 | text-align: left 4 | &__article 5 | text-align: left 6 | -------------------------------------------------------------------------------- /src/styles/config/_variable.sass: -------------------------------------------------------------------------------- 1 | // $base_font_color: #764abc 2 | $base_font_color: #888 3 | $base_border_color: #A082D1 4 | // $base_border_color: #888 5 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "labels": ["dependencies"], 4 | "extends": ["config:recommended"] 5 | } 6 | -------------------------------------------------------------------------------- /src/constants/ActionTypes.js: -------------------------------------------------------------------------------- 1 | export const ADD_TODO = 'ADD_TODO' 2 | export const SET_VISIBILITY_FILTER = 'SET_VISIBILITY_FILTER' 3 | export const TOGGLE_TODO = 'TOGGLE_TODO' -------------------------------------------------------------------------------- /src/styles/vendors/_fonts.scss: -------------------------------------------------------------------------------- 1 | @import url("https://fonts.googleapis.com/css?family=Source+Sans+Pro"); 2 | @import url("https://fonts.googleapis.com/icon?family=Material+Icons"); 3 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | [*] 3 | indent_style = space 4 | indent_size = 2 5 | end_of_line = lf 6 | charset = utf-8 7 | trim_trailing_whitespace = true 8 | insert_final_newline = true 9 | [*.md] 10 | trim_trailing_whitespace = false 11 | -------------------------------------------------------------------------------- /src/components/App.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Header from './Header' 3 | 4 | const App = ({ children }) => 5 |
6 |
7 |
8 | {children} 9 |
10 |
11 | 12 | export default App 13 | -------------------------------------------------------------------------------- /tests/components/App.spec.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom' 3 | import App from '../../src/components/App' 4 | 5 | it('renders without crashing', () => { 6 | const div = document.createElement('div') 7 | ReactDOM.render(, div) 8 | }) 9 | -------------------------------------------------------------------------------- /src/reducers/visibilityFilter.js: -------------------------------------------------------------------------------- 1 | const visibilityFilter = (state = 'SHOW_ALL', action) => { 2 | switch (action.type) { 3 | case 'SET_VISIBILITY_FILTER': 4 | return action.filter 5 | default: 6 | return state 7 | } 8 | } 9 | 10 | export default visibilityFilter 11 | -------------------------------------------------------------------------------- /src/styles/components/_header.sass: -------------------------------------------------------------------------------- 1 | .c-header 2 | text-align: center 3 | background-color: #fff 4 | width: 100% 5 | box-sizing: border-box 6 | padding: 20px 7 | // color: #764abc 8 | color: #444 9 | 10 | &__logo 11 | animation: App-logo-spin infinite 20s linear 12 | height: 80px 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ee http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | node_modules 5 | 6 | # testing 7 | coverage 8 | 9 | # production 10 | build 11 | main.css 12 | 13 | # misc 14 | .DS_Store 15 | .env 16 | .vscode 17 | 18 | npm-debug.log 19 | .idea 20 | 21 | *.log 22 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 6 4 | - 8 5 | cache: 6 | directories: 7 | - node_modules 8 | branches: 9 | only: 10 | - master 11 | - gh-pages 12 | before_install: 13 | - npm install -g coveralls 14 | script: 15 | # - npm test -- --coverage && cat ./coverage/lcov.info | coveralls 16 | - npm test -- --coverage -------------------------------------------------------------------------------- /src/components/TodoList.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Todo from './Todo' 3 | 4 | const TodoList = ({ todos, onTodoClick }) => 5 | 6 | 7 | 8 | {todos.map(todo => onTodoClick(todo.id)} />)} 9 | 10 |
11 | 12 | export default TodoList 13 | -------------------------------------------------------------------------------- /src/styles/main.scss: -------------------------------------------------------------------------------- 1 | @import "vendors/fonts"; 2 | 3 | @import "config/variable"; 4 | 5 | @import "base/normalize", 6 | "base/typography"; 7 | 8 | @import "pages/home", 9 | "pages/todo"; 10 | 11 | @import "components/header", 12 | "components/button", 13 | "components/todo", 14 | "components/weather", 15 | "components/demo"; 16 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "192x192", 8 | "type": "image/png" 9 | } 10 | ], 11 | "start_url": "./index.html", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /src/components/Todo.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const Todo = ({ onClick, completed, text }) => 4 | 5 | 11 | 12 | {text} 13 | 14 | 15 | 16 | 17 | export default Todo 18 | -------------------------------------------------------------------------------- /src/components/TodoFooter.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import FilterLink from '../containers/FilterLink' 3 | 4 | const TodoFooter = () => 5 |

6 | Show: All 7 | {', '} 8 | Active 9 | {', '} 10 | Completed 11 |

12 | 13 | export default TodoFooter 14 | -------------------------------------------------------------------------------- /src/styles/components/_button.sass: -------------------------------------------------------------------------------- 1 | /* BEM naming: component - button */ 2 | .c-button 3 | display: inline-block 4 | color: #A082D1 5 | width: 150px 6 | height: 35px 7 | line-height: 35px 8 | border: solid 1px #A082D1 9 | border-radius: 2em 10 | text-decoration: none 11 | text-transform: uppercase 12 | margin: 0 10px 13 | background-color: #fff 14 | &:hover 15 | background-color: #A082D1 16 | color: #fff 17 | -------------------------------------------------------------------------------- /src/styles/components/_weather.sass: -------------------------------------------------------------------------------- 1 | .c-weather 2 | border: solid 1px #ddd 3 | padding: 20px 4 | background-color: whitesmoke 5 | &__spinner 6 | border: 16px solid #f3f3f3 7 | border-top: 16px solid #A082D1 8 | border-radius: 50% 9 | width: 50px 10 | height: 50px 11 | animation: spin .3s linear infinite 12 | 13 | @keyframes spin 14 | 0% 15 | transform: rotate(0deg) 16 | 100% 17 | transform: rotate(360deg) 18 | -------------------------------------------------------------------------------- /src/components/Link.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const Link = ({ active, children, onClick }) => { 4 | if (active) { 5 | return ( 6 | 7 | {children} 8 | 9 | ) 10 | } 11 | 12 | return ( 13 | { 16 | e.preventDefault() 17 | onClick() 18 | }} 19 | > 20 | {children} 21 | 22 | ) 23 | } 24 | 25 | export default Link 26 | -------------------------------------------------------------------------------- /src/actions/todoActions.js: -------------------------------------------------------------------------------- 1 | import * as types from '../constants/ActionTypes' 2 | 3 | let nextTodoId = 0 4 | export const addTodo = text => { 5 | return { 6 | type: types.ADD_TODO, 7 | id: nextTodoId++, 8 | text 9 | } 10 | } 11 | 12 | export const setVisibilityFilter = filter => { 13 | return { 14 | type: types.SET_VISIBILITY_FILTER, 15 | filter 16 | } 17 | } 18 | 19 | export const toggleTodo = id => { 20 | return { 21 | type: types.TOGGLE_TODO, 22 | id 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/pages/Weather.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Helmet } from 'react-helmet' 3 | import Weather from '../containers/Weather' 4 | 5 | const WeatherPage = () => [ 6 | 7 | 8 | , 9 |
10 |

Get Weather - Practice Async Event!

11 |

Click the button below to fetch RESTful API and get weather.

12 | 13 |
14 | ] 15 | 16 | export default WeatherPage 17 | -------------------------------------------------------------------------------- /src/routes.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Route, IndexRoute } from 'react-router' 3 | import App from './components/App' 4 | import Home from './pages/Home' 5 | import Todo from './pages/Todo' 6 | import Weather from './pages/Weather' 7 | 8 | const routes = ( 9 | 10 | 11 | 12 | 13 | 14 | 15 | ) 16 | 17 | export default routes 18 | -------------------------------------------------------------------------------- /src/components/Header.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Navigation from './Navigation' 3 | import logo from '../images/logo.svg' 4 | 5 | const Header = props => 6 |
7 | create-react-redux-app-logo 8 |

Create React Redux App

9 |

10 | 11 | https://github.com/yingray/create-react-redux-app 12 | 13 |

14 | 15 |
16 | 17 | export default Header 18 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "npm" # See documentation for possible values 9 | directory: "/" # Location of package manifests 10 | schedule: 11 | interval: "weekly" 12 | -------------------------------------------------------------------------------- /src/components/Navigation.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Link } from 'react-router' 3 | 4 | const Navigation = ({ className, buttonClassName }) => 5 | 16 | 17 | Navigation.defaultProps = { 18 | className: '', 19 | buttonClassName: 'c-button' 20 | } 21 | 22 | export default Navigation 23 | -------------------------------------------------------------------------------- /src/containers/FilterLink.js: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux' 2 | import { setVisibilityFilter } from '../actions/todoActions' 3 | import Link from '../components/Link' 4 | 5 | const mapStateToProps = (state, ownProps) => { 6 | return { 7 | active: ownProps.filter === state.visibilityFilter 8 | } 9 | } 10 | 11 | const mapDispatchToProps = (dispatch, ownProps) => { 12 | return { 13 | onClick: () => { 14 | dispatch(setVisibilityFilter(ownProps.filter)) 15 | } 16 | } 17 | } 18 | 19 | const FilterLink = connect(mapStateToProps, mapDispatchToProps)(Link) 20 | 21 | export default FilterLink 22 | -------------------------------------------------------------------------------- /src/pages/Home.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Helmet } from 'react-helmet' 3 | 4 | const HomePage = () => { 5 | const content = { 6 | title: 'Welcome to CRRS-APP', 7 | article: 'This project was bootstrapped with Create React App and Redux + SASS Structure.' 8 | } 9 | 10 | return [ 11 | 12 | 13 | , 14 |
15 |

{content.title}

16 |
{content.article}
17 |
18 | ] 19 | } 20 | 21 | export default HomePage 22 | -------------------------------------------------------------------------------- /src/styles/components/_demo.sass: -------------------------------------------------------------------------------- 1 | .demo 2 | &-card-wide 3 | &.mdl-card 4 | margin: 0 auto 5 | width: 90% 6 | max-width: 512px 7 | 8 | .mdl-card 9 | &__title 10 | color: #fff 11 | height: 176px 12 | background: url('https://getmdl.io/assets/demos/welcome_card.jpg') center / cover 13 | &__menu 14 | color: #fff 15 | 16 | &-card-square 17 | &.mdl-card 18 | margin: 0 auto 19 | width: 320px 20 | height: 380px 21 | .mdl-card 22 | &__title 23 | color: #fff 24 | background: url('https://getmdl.io/assets/demos/dog.png') bottom right 15% no-repeat #46B6AC 25 | -------------------------------------------------------------------------------- /src/pages/Todo.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Helmet } from 'react-helmet' 3 | import TodoFooter from '../components/TodoFooter' 4 | import AddTodo from '../containers/AddTodo' 5 | import VisibleTodoList from '../containers/VisibleTodoList' 6 | 7 | const TodoPage = () => [ 8 | 9 | 13 | , 14 |
15 |

Todo List - Sample Practice!

16 | 17 | 18 | 19 |
20 | ] 21 | export default TodoPage 22 | -------------------------------------------------------------------------------- /src/actions/weatherActions.js: -------------------------------------------------------------------------------- 1 | import weatherApi from '../services/weatherApi' 2 | 3 | export const showWeather = response => ({ 4 | type: 'WEATHER_SHOW_WEATHER', 5 | response 6 | }) 7 | 8 | export const loadWeather = () => ({ 9 | type: 'WEATHER_SHOW_LOADING' 10 | }) 11 | 12 | export const getWeather = () => async dispatch => { 13 | dispatch(loadWeather()) 14 | const request = { 15 | city: 'taipei', 16 | search_type: 'yql', 17 | env: 'store://datatables.org/alltableswithkeys' 18 | } 19 | try { 20 | const response = await weatherApi.getWeather(request) 21 | dispatch(showWeather(response)) 22 | } catch(e) { 23 | console.error(e) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/reducers/todos.js: -------------------------------------------------------------------------------- 1 | const todo = (state = {}, action) => { 2 | switch (action.type) { 3 | case 'ADD_TODO': 4 | return { 5 | id: action.id, 6 | text: action.text, 7 | completed: false 8 | } 9 | case 'TOGGLE_TODO': 10 | if (state.id !== action.id) { 11 | return state 12 | } 13 | 14 | return Object.assign({}, state, { 15 | completed: !state.completed 16 | }) 17 | 18 | default: 19 | return state 20 | } 21 | } 22 | 23 | const todos = (state = [], action) => { 24 | switch (action.type) { 25 | case 'ADD_TODO': 26 | return [...state, todo(undefined, action)] 27 | case 'TOGGLE_TODO': 28 | return state.map(t => todo(t, action)) 29 | default: 30 | return state 31 | } 32 | } 33 | 34 | export default todos 35 | -------------------------------------------------------------------------------- /src/styles/components/_todo.sass: -------------------------------------------------------------------------------- 1 | .c-todo 2 | &__section 3 | position: relative 4 | color: #666 5 | margin: 10px auto 6 | max-width: 600px 7 | padding: 10px 8 | &__input 9 | width: 100% 10 | border-radius: 2em 11 | padding: 10px 20px 12 | &:focus 13 | border: solid 1px #A082D1 14 | 15 | &__icon 16 | position: absolute 17 | color: #ccc 18 | right: 0 19 | background-color: rgba(0,0,0,0) 20 | border: 0 21 | line-height: 0 22 | width: 60px 23 | height: 40px 24 | &:hover 25 | color: #A082D1 26 | background-color: rgba(0,0,0,0) 27 | 28 | &__list 29 | padding: 0 20px 30 | width: 100% 31 | &_item 32 | display: block 33 | margin: 0 auto 34 | height: 40px 35 | line-height: 40px 36 | border: solid 1px #ccc 37 | padding: 0 20px 38 | cursor: pointer 39 | 40 | 41 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import 'babel-polyfill' 2 | import React from 'react' 3 | import ReactDOM from 'react-dom' 4 | import { Provider } from 'react-redux' 5 | // If your server supports server rendering, use browserHistory replace with hashHistory. 6 | import { Router, hashHistory } from 'react-router' 7 | import { syncHistoryWithStore } from 'react-router-redux' 8 | import { configureStore } from './store' 9 | import registerServiceWorker from './registerServiceWorker' 10 | import routes from './routes' 11 | import './styles/main.css' 12 | 13 | const state = window.__initialState__ || undefined 14 | const store = configureStore(hashHistory, state) 15 | const history = syncHistoryWithStore(hashHistory, store) 16 | 17 | ReactDOM.render( 18 | 19 | 20 | , 21 | document.getElementById('root') 22 | ) 23 | registerServiceWorker() 24 | -------------------------------------------------------------------------------- /src/containers/VisibleTodoList.js: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux' 2 | import { toggleTodo } from '../actions/todoActions' 3 | import TodoList from '../components/TodoList' 4 | 5 | const getVisibleTodos = (todos, filter) => { 6 | switch (filter) { 7 | case 'SHOW_ALL': 8 | return todos 9 | case 'SHOW_COMPLETED': 10 | return todos.filter(t => t.completed) 11 | case 'SHOW_ACTIVE': 12 | return todos.filter(t => !t.completed) 13 | default: 14 | break 15 | } 16 | } 17 | 18 | const mapStateToProps = state => { 19 | return { 20 | todos: getVisibleTodos(state.todos, state.visibilityFilter) 21 | } 22 | } 23 | 24 | const mapDispatchToProps = dispatch => { 25 | return { 26 | onTodoClick: id => { 27 | dispatch(toggleTodo(id)) 28 | } 29 | } 30 | } 31 | 32 | const VisibleTodoList = connect(mapStateToProps, mapDispatchToProps)(TodoList) 33 | 34 | export default VisibleTodoList 35 | -------------------------------------------------------------------------------- /src/reducers/weatherReducer.js: -------------------------------------------------------------------------------- 1 | const initialState = { 2 | loading: false, 3 | city: 'Initial', 4 | date: 'Initial', 5 | temp: 'Initial', 6 | text: 'Initial' 7 | } 8 | 9 | const weatherReducer = (state = initialState, action) => { 10 | switch (action.type) { 11 | case 'WEATHER_SHOW_LOADING': 12 | return Object.assign({}, state, { 13 | loading: true 14 | }) 15 | 16 | case 'WEATHER_SHOW_WEATHER': 17 | const location = action.response.query.results.channel.location 18 | const condition = action.response.query.results.channel.item.condition 19 | return Object.assign({}, state, { 20 | loading: false, 21 | city: location.city, 22 | date: condition.date, 23 | temp: condition.temp, 24 | text: condition.text 25 | }) 26 | 27 | default: 28 | return Object.assign({}, state, initialState) 29 | } 30 | } 31 | 32 | export default weatherReducer 33 | -------------------------------------------------------------------------------- /src/services/weatherApi.js: -------------------------------------------------------------------------------- 1 | import ApiBook from 'api-book' 2 | const headers = { 3 | 'Content-Type': 'text/plain;charset=UTF-8' 4 | } 5 | const options = {} 6 | const book = { 7 | getWeather: { 8 | url: '/v1/public/{{search_type}}', 9 | method: 'GET', 10 | payload: { 11 | query: { 12 | q: 13 | 'select * from weather.forecast where woeid in (select woeid from geo.places(1) where text="{{city}}, ak")', 14 | format: 'json', 15 | env: '{{env}}' 16 | } 17 | }, 18 | headers: headers, 19 | options: options 20 | } 21 | } 22 | function statusMiddleware(response) { 23 | if (response.status > 399) { 24 | throw response 25 | } 26 | return response 27 | } 28 | function toJsonMiddleware(response) { 29 | return response.json() 30 | } 31 | const Apis = new ApiBook.ApiCreator(book, { 32 | host: 'https://query.yahooapis.com', 33 | fetchAndThen: [statusMiddleware, toJsonMiddleware] 34 | }) 35 | export default Apis 36 | -------------------------------------------------------------------------------- /src/store.js: -------------------------------------------------------------------------------- 1 | import { createStore, combineReducers, compose, applyMiddleware } from 'redux' 2 | import { routerReducer, routerMiddleware } from 'react-router-redux' 3 | import thunkMiddleware from 'redux-thunk' 4 | 5 | import todos from './reducers/todos' 6 | import visibilityFilter from './reducers/visibilityFilter' 7 | import weatherReducer from './reducers/weatherReducer' 8 | 9 | export function configureStore(history, initialState) { 10 | 11 | const reducer = combineReducers({ 12 | todos, 13 | visibilityFilter, 14 | weatherReducer, 15 | routing: routerReducer 16 | }) 17 | 18 | const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose 19 | 20 | const store = createStore( 21 | reducer, 22 | initialState, 23 | composeEnhancers( 24 | applyMiddleware( 25 | thunkMiddleware, 26 | routerMiddleware(history) 27 | ) 28 | ) 29 | ) 30 | 31 | return store 32 | } 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Ying-Ray Lu 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 | -------------------------------------------------------------------------------- /src/images/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/containers/AddTodo.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { connect } from 'react-redux' 3 | import { addTodo } from '../actions/todoActions' 4 | 5 | class AddTodo extends React.Component { 6 | constructor(props) { 7 | super(props) 8 | this.state = { 9 | input: '' 10 | } 11 | this.handleChange = this.handleChange.bind(this) 12 | this.handleSubmit = this.handleSubmit.bind(this) 13 | } 14 | 15 | handleChange(e) { 16 | this.setState({ input: e.target.value }) 17 | } 18 | 19 | handleSubmit(e) { 20 | const { props: { dispatch }, state: { input } } = this 21 | e.preventDefault() 22 | if (!input.trim()) { 23 | return 24 | } 25 | dispatch(addTodo(input)) 26 | this.setState({ input: '' }) 27 | } 28 | 29 | render() { 30 | return ( 31 |
32 | 33 | 36 |
37 | ) 38 | } 39 | } 40 | 41 | AddTodo = connect()(AddTodo) 42 | 43 | export default AddTodo 44 | -------------------------------------------------------------------------------- /src/styles/base/_typography.sass: -------------------------------------------------------------------------------- 1 | /*----------------------------- 2 | /*----------------------------- 3 | * Typography Settings 4 | -----------------------------*/ 5 | body 6 | font-family: 'Source Sans Pro', sans-serif 7 | background-color: #eee 8 | margin: 0 auto 9 | color: $base_font_color 10 | 11 | h1, h2, h3, h4, h5, h6 12 | // text-align: center 13 | margin-top: 10px 14 | margin-bottom: 10px 15 | 16 | label 17 | display: block 18 | a 19 | color: inherit 20 | 21 | input 22 | display: inline-flex 23 | width: 300px 24 | outline: none 25 | border: solid 1px #ccc 26 | border-radius: 2px 27 | box-sizing: border-box 28 | padding: 10px 29 | 30 | ::-webkit-input-placeholder, :-moz-placeholder, ::-moz-placeholder, :-ms-input-placeholder 31 | color: #ccc 32 | input:focus 33 | color: $base_font_color 34 | 35 | button 36 | background-color: #fff 37 | width: 100px 38 | height: 100px 39 | outline: none 40 | border: none 41 | border-radius: 50% 42 | cursor: pointer 43 | transition: all 1s ease 0s 44 | text-transform: uppercase 45 | color: #888 46 | &:hover 47 | background-color: #ddd 48 | &:disabled 49 | cursor: auto 50 | 51 | strong 52 | display: block 53 | margin-bottom: 10px 54 | 55 | main 56 | width: 100% 57 | max-width: 600px 58 | margin: 0 auto 59 | padding: 20px 60 | box-sizing: border-box 61 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "create-react-redux-app", 3 | "version": "1.0.0", 4 | "homepage": "http://yingray.github.io/create-react-redux-app", 5 | "scripts": { 6 | "prestart": "npm run build:css", 7 | "start": "npm run watch:css & react-scripts start", 8 | "build": "npm run build:css && react-scripts build", 9 | "build:css": "node-sass-chokidar src/styles -o src/styles", 10 | "watch:css": "node-sass-chokidar src/styles -o src/styles --watch --recursive", 11 | "predeploy": "npm run build", 12 | "deploy": "gh-pages -d build", 13 | "test": "react-scripts test --env=jsdom", 14 | "eject": "react-scripts eject" 15 | }, 16 | "dependencies": { 17 | "react": "^16.8.6", 18 | "react-dom": "^16.8.6" 19 | }, 20 | "devDependencies": { 21 | "api-book": "^0.2.4", 22 | "babel-polyfill": "^6.26.0", 23 | "expect": "^24.8.0", 24 | "gh-pages": "^2.0.1", 25 | "node-sass-chokidar": "^1.3.4", 26 | "react-helmet": "^5.2.0", 27 | "react-redux": "^5.0.7", 28 | "react-router": "^3.2.1", 29 | "react-router-redux": "^4.0.8", 30 | "react-scripts": "^3.0.1", 31 | "redux": "^4.0.1", 32 | "redux-thunk": "^2.3.0" 33 | }, 34 | "browserslist": { 35 | "production": [ 36 | ">0.2%", 37 | "not dead", 38 | "not op_mini all" 39 | ], 40 | "development": [ 41 | "last 1 chrome version", 42 | "last 1 firefox version", 43 | "last 1 safari version" 44 | ] 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 15 | 16 | 17 | 18 | 19 | React Redux Sass Project Starter 20 | 21 | 22 | 25 |
26 | 27 | 36 | 37 | -------------------------------------------------------------------------------- /tests/reducers/weatherReducer.spec.js: -------------------------------------------------------------------------------- 1 | import expect from 'expect' 2 | import reducer from '../../src/reducers/weatherReducer' 3 | 4 | describe('weather reducer:', () => { 5 | const initialState = { 6 | loading: false, 7 | city: 'Initial', 8 | date: 'Initial', 9 | temp: 'Initial', 10 | text: 'Initial' 11 | } 12 | 13 | it('initial', () => { 14 | const expectedInitState = initialState 15 | expect(reducer(undefined, {})).toEqual(expectedInitState) 16 | }) 17 | 18 | it('show loading', () => { 19 | const expectedInitState = Object.assign(initialState, { 20 | loading: true 21 | }) 22 | expect( 23 | reducer(initialState, { 24 | type: 'WEATHER_SHOW_LOADING' 25 | }) 26 | ).toEqual(expectedInitState) 27 | }) 28 | 29 | it('show weather', () => { 30 | const expectedInitState = Object.assign(initialState, { 31 | loading: false, 32 | city: 'Taipei City', 33 | date: 'Fri, 28 Oct 2016 05:00 PM CST', 34 | temp: '80', 35 | text: 'Sunny' 36 | }) 37 | 38 | const apiResponse = { 39 | query: { 40 | results: { 41 | channel: { 42 | location: { 43 | city: 'Taipei City' 44 | }, 45 | item: { 46 | condition: { 47 | date: 'Fri, 28 Oct 2016 05:00 PM CST', 48 | temp: '80', 49 | text: 'Sunny' 50 | } 51 | } 52 | } 53 | } 54 | } 55 | } 56 | 57 | expect( 58 | reducer(initialState, { 59 | type: 'WEATHER_SHOW_WEATHER', 60 | response: apiResponse 61 | }) 62 | ).toEqual(expectedInitState) 63 | }) 64 | }) 65 | -------------------------------------------------------------------------------- /src/containers/Weather.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { connect } from 'react-redux' 3 | import { bindActionCreators } from 'redux' 4 | import * as weatherActions from '../actions/weatherActions' 5 | 6 | class Weather extends React.Component { 7 | constructor(props) { 8 | super(props) 9 | this.handleClick = this.handleClick.bind(this) 10 | this.hasLoaded = this.hasLoaded.bind(this) 11 | this.isLoading = this.isLoading.bind(this) 12 | this.Spinner = this.Spinner.bind(this) 13 | this.Button = this.Button.bind(this) 14 | this.Information = this.Information.bind(this) 15 | } 16 | 17 | handleClick() { 18 | this.props.actions.getWeather() 19 | } 20 | 21 | hasLoaded() { 22 | return this.props.weatherState.city === 'Initial' 23 | } 24 | 25 | isLoading() { 26 | return this.props.weatherState.loading 27 | } 28 | 29 | Spinner() { 30 | return
31 | } 32 | 33 | Button() { 34 | const { loading } = this.props.weatherState 35 | return loading ? this.Spinner() : 36 | } 37 | 38 | Information() { 39 | const { city, temp, date, text } = this.props.weatherState 40 | return ( 41 |
42 |

43 | {city} - {temp}℉ 44 |

45 |

46 | {date} 47 |

48 |

49 | {text} 50 |

51 |
52 | ) 53 | } 54 | 55 | render() { 56 | return
57 | {this.hasLoaded() ? this.Button() : this.Information() } 58 |
59 | } 60 | } 61 | 62 | export default connect( 63 | state => ({ 64 | weatherState: state.weatherReducer 65 | }), 66 | dispatch => ({ 67 | actions: bindActionCreators(weatherActions, dispatch) 68 | }) 69 | )(Weather) 70 | -------------------------------------------------------------------------------- /src/registerServiceWorker.js: -------------------------------------------------------------------------------- 1 | // In production, we register a service worker to serve assets from local cache. 2 | 3 | // This lets the app load faster on subsequent visits in production, and gives 4 | // it offline capabilities. However, it also means that developers (and users) 5 | // will only see deployed updates on the "N+1" visit to a page, since previously 6 | // cached resources are updated in the background. 7 | 8 | // To learn more about the benefits of this model, read https://goo.gl/KwvDNy. 9 | // This link also includes instructions on opting out of this behavior. 10 | 11 | export default function register() { 12 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { 13 | window.addEventListener('load', () => { 14 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; 15 | navigator.serviceWorker 16 | .register(swUrl) 17 | .then(registration => { 18 | registration.onupdatefound = () => { 19 | const installingWorker = registration.installing; 20 | installingWorker.onstatechange = () => { 21 | if (installingWorker.state === 'installed') { 22 | if (navigator.serviceWorker.controller) { 23 | // At this point, the old content will have been purged and 24 | // the fresh content will have been added to the cache. 25 | // It's the perfect time to display a "New content is 26 | // available; please refresh." message in your web app. 27 | console.log('New content is available; please refresh.'); 28 | } else { 29 | // At this point, everything has been precached. 30 | // It's the perfect time to display a 31 | // "Content is cached for offline use." message. 32 | console.log('Content is cached for offline use.'); 33 | } 34 | } 35 | }; 36 | }; 37 | }) 38 | .catch(error => { 39 | console.error('Error during service worker registration:', error); 40 | }); 41 | }); 42 | } 43 | } 44 | 45 | export function unregister() { 46 | if ('serviceWorker' in navigator) { 47 | navigator.serviceWorker.ready.then(registration => { 48 | registration.unregister(); 49 | }); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # [![Create React Redux App](readme/cover.png)](https://yingray.github.io/create-react-redux-app/) 2 | 3 | [![Build Status](https://travis-ci.org/yingray/create-react-redux-app.svg?branch=master)](https://travis-ci.org/yingray/create-react-redux-app) 4 | [![Coverage Status](https://coveralls.io/repos/github/yingray/create-react-redux-app/badge.svg?branch=master)](https://coveralls.io/github/yingray/create-react-redux-app?branch=master) 5 | 6 | This project was bootstrapped with Create React App and Redux, Sass Structure. 7 | 8 | Demo: [https://yingray.github.io/create-react-redux-app](https://yingray.github.io/create-react-redux-app/#/) 9 | 10 | # Todo List 11 | 12 | * react + redux sample practice 13 | 14 | ![demo](readme/demo.gif) 15 | 16 | # fetch API 17 | 18 | * Handle async event 19 | 20 | * async/await + api-book sample practice 21 | 22 | ![demo-api](readme/demo-api.gif) 23 | 24 | # Getting Started 25 | 26 | ## Dependencies 27 | 28 | * React 29 | * react 30 | * react-dom 31 | 32 | * Create React App 33 | * react-scripts 34 | 35 | * Redux 36 | * redux 37 | * react-redux 38 | * react-router 39 | * react-router-redux 40 | * redux-thunk 41 | 42 | * Sass 43 | * node-sass-chokidar 44 | 45 | * Fetch 46 | * api-book 47 | * babel-polyfill 48 | 49 | * Github Deployment Tool 50 | * gh-pages 51 | 52 | ## Installation 53 | 54 | To create a new react-redux-sass app, run: 55 | 56 | ```sh 57 | git clone https://github.com/yingray/create-react-redux-app.git ~/create-react-redux-app 58 | cd ~/create-react-redux-app 59 | # yarn or npm install 60 | yarn 61 | ``` 62 | 63 | It will create a directory called **create-react-redux-app** inside the current folder. 64 | Inside that directory, it will generate the initial project structure and install the transitive dependencies: 65 | 66 | ``` 67 | create-react-redux-app/ 68 | src/ 69 | actions/ 70 | todoActions.js 71 | weatherActions.js 72 | components/ 73 | App.js 74 | Header.js 75 | Link.js 76 | Navigation.js 77 | Todo.js 78 | TodoFooter.js 79 | TodoList.js 80 | constants/ 81 | ActionTypes.js 82 | containers/ 83 | AddTodo.js 84 | FilterLink.js 85 | VisibleTodoList.js 86 | Weather.js 87 | images/ 88 | logo.svg 89 | pages/ 90 | Home.js 91 | Todo.js 92 | Weather.js 93 | reducers/ 94 | todos.js 95 | visibilityFilter.js 96 | weatherReducer.js 97 | services/ 98 | weatherApi.js 99 | styles/ 100 | base/ 101 | components/ 102 | config/ 103 | layout/ 104 | pages/ 105 | utils/ 106 | vendors/ 107 | main.scss 108 | index.js 109 | registerServiceWorker.js 110 | routes.js 111 | store.js 112 | -- 113 | tests/ 114 | components/ 115 | App.spec.js 116 | reducers/ 117 | weatherReducer.spec.js 118 | -- 119 | public/ 120 | favicon.ico 121 | index.html 122 | -- 123 | node_modules/ 124 | .editorconfig 125 | package.json 126 | README.md 127 | ``` 128 | 129 | ### Runs the app in development mode 130 | 131 | ```bash 132 | npm start 133 | ``` 134 | 135 | Open `http://localhost:3000` to view it in the browser. 136 | 137 | ### Runs the test watcher in an interactive mode 138 | 139 | ```bash 140 | npm test 141 | ``` 142 | 143 | By default, runs tests related to files changes since the last commit. 144 | 145 | ### Builds the app for production to the build folder: 146 | 147 | ```bash 148 | npm run build 149 | ``` 150 | 151 | It correctly bundles React in production mode and optimizes the build for the best performance. 152 | The build is minified and the filenames include the hashes. 153 | Your app is ready to be deployed! 154 | 155 | Reference: https://github.com/facebookincubator/create-react-app 156 | 157 | ### Deploys the production to github.io page 158 | 159 | Changes package.json 160 | 161 | ```js 162 | "homepage": "http://{account_name}.github.io/{project_name}", 163 | "scripts": { 164 | "predeploy": "npm run build" 165 | } 166 | ``` 167 | 168 | And runs 169 | 170 | ```bash 171 | npm run deploy 172 | ``` 173 | 174 | ## MIT License 175 | 176 | Copyright (c) 2016 Ying-Ray Lu 177 | 178 | Permission is hereby granted, free of charge, to any person obtaining a copy 179 | of this software and associated documentation files (the "Software"), to deal 180 | in the Software without restriction, including without limitation the rights 181 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 182 | copies of the Software, and to permit persons to whom the Software is 183 | furnished to do so, subject to the following conditions: 184 | 185 | The above copyright notice and this permission notice shall be included in all 186 | copies or substantial portions of the Software. 187 | 188 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 189 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 190 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 191 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 192 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 193 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 194 | SOFTWARE. 195 | -------------------------------------------------------------------------------- /src/styles/base/_normalize.scss: -------------------------------------------------------------------------------- 1 | /*! normalize.css v5.0.0 | MIT License | github.com/necolas/normalize.css */ 2 | 3 | /** 4 | * 1. Change the default font family in all browsers (opinionated). 5 | * 2. Correct the line height in all browsers. 6 | * 3. Prevent adjustments of font size after orientation changes in 7 | * IE on Windows Phone and in iOS. 8 | */ 9 | 10 | /* Document 11 | ========================================================================== */ 12 | 13 | html { 14 | font-family: sans-serif; /* 1 */ 15 | line-height: 1.15; /* 2 */ 16 | -ms-text-size-adjust: 100%; /* 3 */ 17 | -webkit-text-size-adjust: 100%; /* 3 */ 18 | } 19 | 20 | /* Sections 21 | ========================================================================== */ 22 | 23 | /** 24 | * Remove the margin in all browsers (opinionated). 25 | */ 26 | 27 | body { 28 | margin: 0; 29 | } 30 | 31 | /** 32 | * Add the correct display in IE 9-. 33 | */ 34 | 35 | article, 36 | aside, 37 | footer, 38 | header, 39 | nav, 40 | section { 41 | display: block; 42 | } 43 | 44 | /** 45 | * Correct the font size and margin on `h1` elements within `section` and 46 | * `article` contexts in Chrome, Firefox, and Safari. 47 | */ 48 | 49 | h1 { 50 | font-size: 2em; 51 | margin: 0.67em 0; 52 | } 53 | 54 | /* Grouping content 55 | ========================================================================== */ 56 | 57 | /** 58 | * Add the correct display in IE 9-. 59 | * 1. Add the correct display in IE. 60 | */ 61 | 62 | figcaption, 63 | figure, 64 | main { /* 1 */ 65 | display: block; 66 | } 67 | 68 | /** 69 | * Add the correct margin in IE 8. 70 | */ 71 | 72 | figure { 73 | margin: 1em 40px; 74 | } 75 | 76 | /** 77 | * 1. Add the correct box sizing in Firefox. 78 | * 2. Show the overflow in Edge and IE. 79 | */ 80 | 81 | hr { 82 | box-sizing: content-box; /* 1 */ 83 | height: 0; /* 1 */ 84 | overflow: visible; /* 2 */ 85 | } 86 | 87 | /** 88 | * 1. Correct the inheritance and scaling of font size in all browsers. 89 | * 2. Correct the odd `em` font sizing in all browsers. 90 | */ 91 | 92 | pre { 93 | font-family: monospace, monospace; /* 1 */ 94 | font-size: 1em; /* 2 */ 95 | } 96 | 97 | /* Text-level semantics 98 | ========================================================================== */ 99 | 100 | /** 101 | * 1. Remove the gray background on active links in IE 10. 102 | * 2. Remove gaps in links underline in iOS 8+ and Safari 8+. 103 | */ 104 | 105 | a { 106 | background-color: transparent; /* 1 */ 107 | -webkit-text-decoration-skip: objects; /* 2 */ 108 | } 109 | 110 | /** 111 | * Remove the outline on focused links when they are also active or hovered 112 | * in all browsers (opinionated). 113 | */ 114 | 115 | a:active, 116 | a:hover { 117 | outline-width: 0; 118 | } 119 | 120 | /** 121 | * 1. Remove the bottom border in Firefox 39-. 122 | * 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari. 123 | */ 124 | 125 | abbr[title] { 126 | border-bottom: none; /* 1 */ 127 | text-decoration: underline; /* 2 */ 128 | text-decoration: underline dotted; /* 2 */ 129 | } 130 | 131 | /** 132 | * Prevent the duplicate application of `bolder` by the next rule in Safari 6. 133 | */ 134 | 135 | b, 136 | strong { 137 | font-weight: inherit; 138 | } 139 | 140 | /** 141 | * Add the correct font weight in Chrome, Edge, and Safari. 142 | */ 143 | 144 | b, 145 | strong { 146 | font-weight: bolder; 147 | } 148 | 149 | /** 150 | * 1. Correct the inheritance and scaling of font size in all browsers. 151 | * 2. Correct the odd `em` font sizing in all browsers. 152 | */ 153 | 154 | code, 155 | kbd, 156 | samp { 157 | font-family: monospace, monospace; /* 1 */ 158 | font-size: 1em; /* 2 */ 159 | } 160 | 161 | /** 162 | * Add the correct font style in Android 4.3-. 163 | */ 164 | 165 | dfn { 166 | font-style: italic; 167 | } 168 | 169 | /** 170 | * Add the correct background and color in IE 9-. 171 | */ 172 | 173 | mark { 174 | background-color: #ff0; 175 | color: #000; 176 | } 177 | 178 | /** 179 | * Add the correct font size in all browsers. 180 | */ 181 | 182 | small { 183 | font-size: 80%; 184 | } 185 | 186 | /** 187 | * Prevent `sub` and `sup` elements from affecting the line height in 188 | * all browsers. 189 | */ 190 | 191 | sub, 192 | sup { 193 | font-size: 75%; 194 | line-height: 0; 195 | position: relative; 196 | vertical-align: baseline; 197 | } 198 | 199 | sub { 200 | bottom: -0.25em; 201 | } 202 | 203 | sup { 204 | top: -0.5em; 205 | } 206 | 207 | /* Embedded content 208 | ========================================================================== */ 209 | 210 | /** 211 | * Add the correct display in IE 9-. 212 | */ 213 | 214 | audio, 215 | video { 216 | display: inline-block; 217 | } 218 | 219 | /** 220 | * Add the correct display in iOS 4-7. 221 | */ 222 | 223 | audio:not([controls]) { 224 | display: none; 225 | height: 0; 226 | } 227 | 228 | /** 229 | * Remove the border on images inside links in IE 10-. 230 | */ 231 | 232 | img { 233 | border-style: none; 234 | } 235 | 236 | /** 237 | * Hide the overflow in IE. 238 | */ 239 | 240 | svg:not(:root) { 241 | overflow: hidden; 242 | } 243 | 244 | /* Forms 245 | ========================================================================== */ 246 | 247 | /** 248 | * 1. Change the font styles in all browsers (opinionated). 249 | * 2. Remove the margin in Firefox and Safari. 250 | */ 251 | 252 | button, 253 | input, 254 | optgroup, 255 | select, 256 | textarea { 257 | font-family: sans-serif; /* 1 */ 258 | font-size: 100%; /* 1 */ 259 | line-height: 1.15; /* 1 */ 260 | margin: 0; /* 2 */ 261 | } 262 | 263 | /** 264 | * Show the overflow in IE. 265 | * 1. Show the overflow in Edge. 266 | */ 267 | 268 | button, 269 | input { /* 1 */ 270 | overflow: visible; 271 | } 272 | 273 | /** 274 | * Remove the inheritance of text transform in Edge, Firefox, and IE. 275 | * 1. Remove the inheritance of text transform in Firefox. 276 | */ 277 | 278 | button, 279 | select { /* 1 */ 280 | text-transform: none; 281 | } 282 | 283 | /** 284 | * 1. Prevent a WebKit bug where (2) destroys native `audio` and `video` 285 | * controls in Android 4. 286 | * 2. Correct the inability to style clickable types in iOS and Safari. 287 | */ 288 | 289 | button, 290 | html [type="button"], /* 1 */ 291 | [type="reset"], 292 | [type="submit"] { 293 | -webkit-appearance: button; /* 2 */ 294 | } 295 | 296 | /** 297 | * Remove the inner border and padding in Firefox. 298 | */ 299 | 300 | button::-moz-focus-inner, 301 | [type="button"]::-moz-focus-inner, 302 | [type="reset"]::-moz-focus-inner, 303 | [type="submit"]::-moz-focus-inner { 304 | border-style: none; 305 | padding: 0; 306 | } 307 | 308 | /** 309 | * Restore the focus styles unset by the previous rule. 310 | */ 311 | 312 | button:-moz-focusring, 313 | [type="button"]:-moz-focusring, 314 | [type="reset"]:-moz-focusring, 315 | [type="submit"]:-moz-focusring { 316 | outline: 1px dotted ButtonText; 317 | } 318 | 319 | /** 320 | * Change the border, margin, and padding in all browsers (opinionated). 321 | */ 322 | 323 | fieldset { 324 | border: 1px solid #c0c0c0; 325 | margin: 0 2px; 326 | padding: 0.35em 0.625em 0.75em; 327 | } 328 | 329 | /** 330 | * 1. Correct the text wrapping in Edge and IE. 331 | * 2. Correct the color inheritance from `fieldset` elements in IE. 332 | * 3. Remove the padding so developers are not caught out when they zero out 333 | * `fieldset` elements in all browsers. 334 | */ 335 | 336 | legend { 337 | box-sizing: border-box; /* 1 */ 338 | color: inherit; /* 2 */ 339 | display: table; /* 1 */ 340 | max-width: 100%; /* 1 */ 341 | padding: 0; /* 3 */ 342 | white-space: normal; /* 1 */ 343 | } 344 | 345 | /** 346 | * 1. Add the correct display in IE 9-. 347 | * 2. Add the correct vertical alignment in Chrome, Firefox, and Opera. 348 | */ 349 | 350 | progress { 351 | display: inline-block; /* 1 */ 352 | vertical-align: baseline; /* 2 */ 353 | } 354 | 355 | /** 356 | * Remove the default vertical scrollbar in IE. 357 | */ 358 | 359 | textarea { 360 | overflow: auto; 361 | } 362 | 363 | /** 364 | * 1. Add the correct box sizing in IE 10-. 365 | * 2. Remove the padding in IE 10-. 366 | */ 367 | 368 | [type="checkbox"], 369 | [type="radio"] { 370 | box-sizing: border-box; /* 1 */ 371 | padding: 0; /* 2 */ 372 | } 373 | 374 | /** 375 | * Correct the cursor style of increment and decrement buttons in Chrome. 376 | */ 377 | 378 | [type="number"]::-webkit-inner-spin-button, 379 | [type="number"]::-webkit-outer-spin-button { 380 | height: auto; 381 | } 382 | 383 | /** 384 | * 1. Correct the odd appearance in Chrome and Safari. 385 | * 2. Correct the outline style in Safari. 386 | */ 387 | 388 | [type="search"] { 389 | -webkit-appearance: textfield; /* 1 */ 390 | outline-offset: -2px; /* 2 */ 391 | } 392 | 393 | /** 394 | * Remove the inner padding and cancel buttons in Chrome and Safari on macOS. 395 | */ 396 | 397 | [type="search"]::-webkit-search-cancel-button, 398 | [type="search"]::-webkit-search-decoration { 399 | -webkit-appearance: none; 400 | } 401 | 402 | /** 403 | * 1. Correct the inability to style clickable types in iOS and Safari. 404 | * 2. Change font properties to `inherit` in Safari. 405 | */ 406 | 407 | ::-webkit-file-upload-button { 408 | -webkit-appearance: button; /* 1 */ 409 | font: inherit; /* 2 */ 410 | } 411 | 412 | /* Interactive 413 | ========================================================================== */ 414 | 415 | /* 416 | * Add the correct display in IE 9-. 417 | * 1. Add the correct display in Edge, IE, and Firefox. 418 | */ 419 | 420 | details, /* 1 */ 421 | menu { 422 | display: block; 423 | } 424 | 425 | /* 426 | * Add the correct display in all browsers. 427 | */ 428 | 429 | summary { 430 | display: list-item; 431 | } 432 | 433 | /* Scripting 434 | ========================================================================== */ 435 | 436 | /** 437 | * Add the correct display in IE 9-. 438 | */ 439 | 440 | canvas { 441 | display: inline-block; 442 | } 443 | 444 | /** 445 | * Add the correct display in IE. 446 | */ 447 | 448 | template { 449 | display: none; 450 | } 451 | 452 | /* Hidden 453 | ========================================================================== */ 454 | 455 | /** 456 | * Add the correct display in IE 10-. 457 | */ 458 | 459 | [hidden] { 460 | display: none; 461 | } --------------------------------------------------------------------------------