├── .gitignore ├── README.md ├── package-lock.json ├── package.json ├── public ├── favicon.ico ├── index.html ├── logo192.png ├── logo512.png ├── manifest.json └── robots.txt └── src ├── AddItem.js ├── App.js ├── Content.js ├── Footer.js ├── Header.js ├── ItemList.js ├── LineItem.js ├── SearchItem.js ├── index.css └── index.js /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # "React JS - React Controlled Form Inputs" 2 | 3 | ✅ [Check out my YouTube Channel with all of my tutorials](https://www.youtube.com/DaveGrayTeachesCode). 4 | 5 | **Description:** 6 | 7 | This repository shares the styles applied during the Youtube tutorial. The tutorial is part of a [Learn React Playlist](https://www.youtube.com/playlist?list=PL0Zuz27SZ-6PrE9srvEn8nbhOOyxnWXfp) on my channel. 8 | 9 | [YouTube Tutorial](https://youtu.be/r5ombQn3fHY) for this repository. 10 | 11 | I suggest completing my [8 hour JavaScript course tutorial video](https://youtu.be/EfAl9bwzVZk) if you are new to Javascript. 12 | 13 | ### Academic Honesty 14 | 15 | **DO NOT COPY FOR AN ASSIGNMENT** - Avoid plagiargism and adhere to the spirit of this [Academic Honesty Policy](https://www.freecodecamp.org/news/academic-honesty-policy/). 16 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "09tut", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@testing-library/jest-dom": "^5.14.1", 7 | "@testing-library/react": "^11.2.7", 8 | "@testing-library/user-event": "^12.8.3", 9 | "react": "^17.0.2", 10 | "react-dom": "^17.0.2", 11 | "react-icons": "^4.2.0", 12 | "react-scripts": "4.0.3", 13 | "web-vitals": "^1.1.2" 14 | }, 15 | "scripts": { 16 | "start": "react-scripts start", 17 | "build": "react-scripts build", 18 | "test": "react-scripts test", 19 | "eject": "react-scripts eject" 20 | }, 21 | "eslintConfig": { 22 | "extends": [ 23 | "react-app", 24 | "react-app/jest" 25 | ] 26 | }, 27 | "browserslist": { 28 | "production": [ 29 | ">0.2%", 30 | "not dead", 31 | "not op_mini all" 32 | ], 33 | "development": [ 34 | "last 1 chrome version", 35 | "last 1 firefox version", 36 | "last 1 safari version" 37 | ] 38 | }, 39 | "devDependencies": {} 40 | } -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gitdagray/react_controlled_inputs/547a2bea752cd5a114b1805ea780ca8ff8612232/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 27 | React App 28 | 29 | 30 | 31 |
32 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gitdagray/react_controlled_inputs/547a2bea752cd5a114b1805ea780ca8ff8612232/public/logo192.png -------------------------------------------------------------------------------- /public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gitdagray/react_controlled_inputs/547a2bea752cd5a114b1805ea780ca8ff8612232/public/logo512.png -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /src/AddItem.js: -------------------------------------------------------------------------------- 1 | import { FaPlus } from 'react-icons/fa'; 2 | import { useRef } from 'react'; 3 | 4 | const AddItem = ({ newItem, setNewItem, handleSubmit }) => { 5 | const inputRef = useRef(); 6 | 7 | return ( 8 |
9 | 10 | setNewItem(e.target.value)} 19 | /> 20 | 27 |
28 | ) 29 | } 30 | 31 | export default AddItem 32 | -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | import Header from './Header'; 2 | import SearchItem from './SearchItem'; 3 | import AddItem from './AddItem'; 4 | import Content from './Content'; 5 | import Footer from './Footer'; 6 | import { useState } from 'react'; 7 | 8 | function App() { 9 | const [items, setItems] = useState(JSON.parse(localStorage.getItem('shoppinglist'))); 10 | const [newItem, setNewItem] = useState('') 11 | const [search, setSearch] = useState('') 12 | 13 | const setAndSaveItems = (newItems) => { 14 | setItems(newItems); 15 | localStorage.setItem('shoppinglist', JSON.stringify(newItems)); 16 | } 17 | 18 | const addItem = (item) => { 19 | const id = items.length ? items[items.length - 1].id + 1 : 1; 20 | const myNewItem = { id, checked: false, item }; 21 | const listItems = [...items, myNewItem]; 22 | setAndSaveItems(listItems); 23 | } 24 | 25 | const handleCheck = (id) => { 26 | const listItems = items.map((item) => item.id === id ? { ...item, checked: !item.checked } : item); 27 | setAndSaveItems(listItems); 28 | } 29 | 30 | const handleDelete = (id) => { 31 | const listItems = items.filter((item) => item.id !== id); 32 | setAndSaveItems(listItems); 33 | } 34 | 35 | const handleSubmit = (e) => { 36 | e.preventDefault(); 37 | if (!newItem) return; 38 | addItem(newItem); 39 | setNewItem(''); 40 | } 41 | 42 | return ( 43 |
44 |
45 | 50 | 54 | ((item.item).toLowerCase()).includes(search.toLowerCase()))} 56 | handleCheck={handleCheck} 57 | handleDelete={handleDelete} 58 | /> 59 |
61 | ); 62 | } 63 | 64 | export default App; 65 | -------------------------------------------------------------------------------- /src/Content.js: -------------------------------------------------------------------------------- 1 | import ItemList from './ItemList'; 2 | 3 | const Content = ({ items, handleCheck, handleDelete }) => { 4 | return ( 5 |
6 | {items.length ? ( 7 | 12 | ) : ( 13 |

Your list is empty.

14 | )} 15 |
16 | ) 17 | } 18 | 19 | export default Content 20 | -------------------------------------------------------------------------------- /src/Footer.js: -------------------------------------------------------------------------------- 1 | const Footer = ({ length }) => { 2 | return ( 3 | 6 | ) 7 | } 8 | 9 | export default Footer 10 | -------------------------------------------------------------------------------- /src/Header.js: -------------------------------------------------------------------------------- 1 | const Header = ({ title }) => { 2 | 3 | return ( 4 |
5 |

{title}

6 |
7 | ) 8 | } 9 | 10 | Header.defaultProps = { 11 | title: "Default Title" 12 | } 13 | 14 | export default Header; 15 | -------------------------------------------------------------------------------- /src/ItemList.js: -------------------------------------------------------------------------------- 1 | import LineItem from './LineItem'; 2 | 3 | const ItemList = ({ items, handleCheck, handleDelete }) => { 4 | return ( 5 | 15 | ) 16 | } 17 | 18 | export default ItemList 19 | -------------------------------------------------------------------------------- /src/LineItem.js: -------------------------------------------------------------------------------- 1 | import { FaTrashAlt } from 'react-icons/fa'; 2 | 3 | const LineItem = ({ item, handleCheck, handleDelete }) => { 4 | return ( 5 |
  • 6 | handleCheck(item.id)} 9 | checked={item.checked} 10 | /> 11 | 15 | handleDelete(item.id)} 17 | role="button" 18 | tabIndex="0" 19 | aria-label={`Delete ${item.item}`} 20 | /> 21 |
  • 22 | ) 23 | } 24 | 25 | export default LineItem 26 | -------------------------------------------------------------------------------- /src/SearchItem.js: -------------------------------------------------------------------------------- 1 | const SearchItem = ({ search, setSearch }) => { 2 | return ( 3 |
    e.preventDefault()}> 4 | 5 | setSearch(e.target.value)} 12 | /> 13 |
    14 | ) 15 | } 16 | 17 | export default SearchItem 18 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | * { 2 | margin: 0; 3 | padding: 0; 4 | box-sizing: border-box; 5 | } 6 | 7 | html { 8 | font-size: 22px; 9 | } 10 | 11 | body { 12 | min-height: 100vh; 13 | font-family: 'Roboto', 'Oxygen', 14 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 15 | sans-serif; 16 | -webkit-font-smoothing: antialiased; 17 | -moz-osx-font-smoothing: grayscale; 18 | } 19 | 20 | .App { 21 | display: flex; 22 | flex-direction: column; 23 | justify-content: center; 24 | align-items: center; 25 | height: 100vh; 26 | width: 100%; 27 | max-width: 500px; 28 | border: 1px solid mediumblue; 29 | margin: auto; 30 | } 31 | 32 | header { 33 | width: 100%; 34 | padding: 0 0.25em; 35 | background-color: mediumblue; 36 | color: aliceblue; 37 | display: flex; 38 | justify-content: space-between; 39 | align-items: center; 40 | } 41 | 42 | main { 43 | width: 100%; 44 | display: flex; 45 | flex-direction: column; 46 | flex-grow: 1; 47 | justify-content: flex-start; 48 | align-items: center; 49 | overflow-y: auto; 50 | } 51 | 52 | footer { 53 | width: 100%; 54 | padding: 0.25em; 55 | background-color: mediumblue; 56 | color: aliceblue; 57 | display: grid; 58 | place-content: center; 59 | } 60 | 61 | ul { 62 | width: 100%; 63 | list-style: none; 64 | padding: 0 0.25rem 0.25rem; 65 | } 66 | 67 | ul li::before { 68 | content: "\200B"; 69 | } 70 | 71 | .item { 72 | display: flex; 73 | justify-content: flex-start; 74 | align-items: center; 75 | padding: 0.5rem 0 0.5rem 0.5rem; 76 | margin: 0.25rem 0; 77 | background-color: #eee; 78 | } 79 | 80 | .item:first-child { 81 | margin: 0; 82 | } 83 | 84 | .item input[type="checkbox"] { 85 | text-align: center; 86 | width: 2.5rem; 87 | width: 48px; 88 | min-width: 48px; 89 | height: 2.5rem; 90 | height: 48px; 91 | min-height: 48px; 92 | cursor: pointer; 93 | margin-right: 0.5rem; 94 | } 95 | 96 | .item input[type="checkbox"]:focus + label { 97 | text-decoration: underline; 98 | } 99 | 100 | .item > label { 101 | font-size: 0.75rem; 102 | flex-grow: 1; 103 | } 104 | 105 | .item svg { 106 | width: 48px; 107 | min-width: 48px; 108 | height: 36px; 109 | font-size: 1rem; 110 | color: steelblue; 111 | cursor: pointer; 112 | } 113 | 114 | .item svg:focus, 115 | .item svg:hover { 116 | color: red; 117 | outline: none; 118 | } 119 | 120 | .addForm { 121 | width: 100%; 122 | display: flex; 123 | justify-content: flex-start; 124 | margin: 0.5rem 0 0; 125 | padding: 0 0.5rem 0.25rem; 126 | border-bottom: 1px solid #eee; 127 | } 128 | 129 | .addForm label { 130 | position: absolute; 131 | left: -99999px; 132 | } 133 | 134 | .addForm input[type='text'] { 135 | flex-grow: 1; 136 | max-width: calc(100% - 50px); 137 | min-height: 48px; 138 | font-size: 1rem; 139 | padding: 0.25rem; 140 | border-radius: 0.25rem; 141 | margin-right: 0.25rem; 142 | outline: none; 143 | } 144 | 145 | button { 146 | height: 48px; 147 | min-width: 48px; 148 | border-radius: 0.25rem; 149 | padding: 0.5rem; 150 | font-size: 1rem; 151 | background-color: aliceblue; 152 | color: mediumblue; 153 | cursor: pointer; 154 | } 155 | 156 | button:focus, 157 | button:hover { 158 | color: white; 159 | background-color: limegreen; 160 | outline: none; 161 | } 162 | 163 | .searchForm { 164 | width: 100%; 165 | display: flex; 166 | justify-content: flex-start; 167 | margin: 0.25rem 0 0; 168 | padding: 0 0.5rem 0.25rem; 169 | border-bottom: 1px solid #eee; 170 | } 171 | 172 | .searchForm label { 173 | position: absolute; 174 | left: -99999px; 175 | } 176 | 177 | .searchForm input[type='text'] { 178 | flex-grow: 1; 179 | max-width: 100%; 180 | min-height: 48px; 181 | font-size: 1rem; 182 | padding: 0.25rem; 183 | border-radius: 0.25rem; 184 | outline: none; 185 | } 186 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import './index.css'; 4 | import App from './App'; 5 | 6 | ReactDOM.render( 7 | 8 | 9 | , 10 | document.getElementById('root') 11 | ); 12 | 13 | --------------------------------------------------------------------------------