├── 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 |
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 |
--------------------------------------------------------------------------------
/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 |
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 | # [](https://yingray.github.io/create-react-redux-app/)
2 |
3 | [](https://travis-ci.org/yingray/create-react-redux-app)
4 | [](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 | 
15 |
16 | # fetch API
17 |
18 | * Handle async event
19 |
20 | * async/await + api-book sample practice
21 |
22 | 
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 | }
--------------------------------------------------------------------------------