diff --git a/README.md b/README.md index 0c83cde..d3bfe04 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,4 @@ -# Getting Started with Create React App - -This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). +# Getting Started with Advanced React Patterns ## Available Scripts @@ -14,11 +12,6 @@ Open [http://localhost:3000](http://localhost:3000) to view it in the browser. The page will reload if you make edits.\ You will also see any lint errors in the console. -### `npm test` - -Launches the test runner in the interactive watch mode.\ -See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. - ### `npm run build` Builds the app for production to the `build` folder.\ @@ -29,42 +22,3 @@ Your app is ready to be deployed! See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. -### `npm run eject` - -**Note: this is a one-way operation. Once you `eject`, you can’t go back!** - -If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. - -Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own. - -You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it. - -## Learn More - -You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). - -To learn React, check out the [React documentation](https://reactjs.org/). - -### Code Splitting - -This section has moved here: [https://facebook.github.io/create-react-app/docs/code-splitting](https://facebook.github.io/create-react-app/docs/code-splitting) - -### Analyzing the Bundle Size - -This section has moved here: [https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size](https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size) - -### Making a Progressive Web App - -This section has moved here: [https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app](https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app) - -### Advanced Configuration - -This section has moved here: [https://facebook.github.io/create-react-app/docs/advanced-configuration](https://facebook.github.io/create-react-app/docs/advanced-configuration) - -### Deployment - -This section has moved here: [https://facebook.github.io/create-react-app/docs/deployment](https://facebook.github.io/create-react-app/docs/deployment) - -### `npm run build` fails to minify - -This section has moved here: [https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify](https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify) diff --git a/package-lock.json b/package-lock.json index 40c1401..f392fe4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1152,6 +1152,29 @@ "resolved": "https://registry.npmjs.org/@csstools/normalize.css/-/normalize.css-10.1.0.tgz", "integrity": "sha512-ij4wRiunFfaJxjB0BdrYHIH8FxBJpOwNPhhAcunlmPdXudL1WQV1qoP9un6JsEBAgQH+7UXyyjh0g7jTxXK6tg==" }, + "@emotion/is-prop-valid": { + "version": "0.8.8", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-0.8.8.tgz", + "integrity": "sha512-u5WtneEAr5IDG2Wv65yhunPSMLIpuKsbuOktRojfrEiEvRyC85LgPMZI63cr7NUqT8ZIGdSVg8ZKGxIug4lXcA==", + "requires": { + "@emotion/memoize": "0.7.4" + } + }, + "@emotion/memoize": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.7.4.tgz", + "integrity": "sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw==" + }, + "@emotion/stylis": { + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/@emotion/stylis/-/stylis-0.8.5.tgz", + "integrity": "sha512-h6KtPihKFn3T9fuIrwvXXUOwlx3rfUvfZIcP5a6rh8Y7zjE3O06hT5Ss4S/YI1AYhuZ1kjaE/5EaOOI2NqSylQ==" + }, + "@emotion/unitless": { + "version": "0.7.5", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.5.tgz", + "integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==" + }, "@eslint/eslintrc": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.0.tgz", @@ -1183,6 +1206,35 @@ } } }, + "@fortawesome/fontawesome-common-types": { + "version": "0.2.35", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-0.2.35.tgz", + "integrity": "sha512-IHUfxSEDS9dDGqYwIW7wTN6tn/O8E0n5PcAHz9cAaBoZw6UpG20IG/YM3NNLaGPwPqgjBAFjIURzqoQs3rrtuw==" + }, + "@fortawesome/fontawesome-svg-core": { + "version": "1.2.35", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-1.2.35.tgz", + "integrity": "sha512-uLEXifXIL7hnh2sNZQrIJWNol7cTVIzwI+4qcBIq9QWaZqUblm0IDrtSqbNg+3SQf8SMGHkiSigD++rHmCHjBg==", + "requires": { + "@fortawesome/fontawesome-common-types": "^0.2.35" + } + }, + "@fortawesome/free-solid-svg-icons": { + "version": "5.15.3", + "resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-5.15.3.tgz", + "integrity": "sha512-XPeeu1IlGYqz4VWGRAT5ukNMd4VHUEEJ7ysZ7pSSgaEtNvSo+FLurybGJVmiqkQdK50OkSja2bfZXOeyMGRD8Q==", + "requires": { + "@fortawesome/fontawesome-common-types": "^0.2.35" + } + }, + "@fortawesome/react-fontawesome": { + "version": "0.1.14", + "resolved": "https://registry.npmjs.org/@fortawesome/react-fontawesome/-/react-fontawesome-0.1.14.tgz", + "integrity": "sha512-4wqNb0gRLVaBm/h+lGe8UfPPivcbuJ6ecI4hIgW0LjI7kzpYB9FkN0L9apbVzg+lsBdcTf0AlBtODjcSX5mmKA==", + "requires": { + "prop-types": "^15.7.2" + } + }, "@hapi/address": { "version": "2.1.4", "resolved": "https://registry.npmjs.org/@hapi/address/-/address-2.1.4.tgz", @@ -1986,177 +2038,11 @@ "loader-utils": "^2.0.0" } }, - "@testing-library/dom": { - "version": "7.30.0", - "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-7.30.0.tgz", - "integrity": "sha512-v4GzWtltaiDE0yRikLlcLAfEiiK8+ptu6OuuIebm9GdC2XlZTNDPGEfM2UkEtnH7hr9TRq2sivT5EA9P1Oy7bw==", - "requires": { - "@babel/code-frame": "^7.10.4", - "@babel/runtime": "^7.12.5", - "@types/aria-query": "^4.2.0", - "aria-query": "^4.2.2", - "chalk": "^4.1.0", - "dom-accessibility-api": "^0.5.4", - "lz-string": "^1.4.4", - "pretty-format": "^26.6.2" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "@testing-library/jest-dom": { - "version": "5.11.9", - "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-5.11.9.tgz", - "integrity": "sha512-Mn2gnA9d1wStlAIT2NU8J15LNob0YFBVjs2aEQ3j8rsfRQo+lAs7/ui1i2TGaJjapLmuNPLTsrm+nPjmZDwpcQ==", - "requires": { - "@babel/runtime": "^7.9.2", - "@types/testing-library__jest-dom": "^5.9.1", - "aria-query": "^4.2.2", - "chalk": "^3.0.0", - "css": "^3.0.0", - "css.escape": "^1.5.1", - "lodash": "^4.17.15", - "redent": "^3.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", - "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "css": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/css/-/css-3.0.0.tgz", - "integrity": "sha512-DG9pFfwOrzc+hawpmqX/dHYHJG+Bsdb0klhyi1sDneOgGOXy9wQIC8hzyVp1e4NRYDBdxcylvywPkkXCHAzTyQ==", - "requires": { - "inherits": "^2.0.4", - "source-map": "^0.6.1", - "source-map-resolve": "^0.6.0" - } - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" - }, - "source-map-resolve": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.6.0.tgz", - "integrity": "sha512-KXBr9d/fO/bWo97NXsPIAW1bFSBOuCnjbNTBMO7N59hsv5i9yzRDfcYwwt0l04+VqnKC+EwzvJZIP/qkuMgR/w==", - "requires": { - "atob": "^2.1.2", - "decode-uri-component": "^0.2.0" - } - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "@testing-library/react": { - "version": "11.2.5", - "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-11.2.5.tgz", - "integrity": "sha512-yEx7oIa/UWLe2F2dqK0FtMF9sJWNXD+2PPtp39BvE0Kh9MJ9Kl0HrZAgEuhUJR+Lx8Di6Xz+rKwSdEPY2UV8ZQ==", - "requires": { - "@babel/runtime": "^7.12.5", - "@testing-library/dom": "^7.28.1" - } - }, - "@testing-library/user-event": { - "version": "12.8.3", - "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-12.8.3.tgz", - "integrity": "sha512-IR0iWbFkgd56Bu5ZI/ej8yQwrkCv8Qydx6RzwbKz9faXazR/+5tvYKsZQgyXJiwgpcva127YO6JcWy7YlCfofQ==", - "requires": { - "@babel/runtime": "^7.12.5" - } - }, "@types/anymatch": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/@types/anymatch/-/anymatch-1.3.1.tgz", "integrity": "sha512-/+CRPXpBDpo2RK9C68N3b2cOvO0Cf5B9aPijHsoDQTHivnGSObdOF2BRQOYjojWTDy6nQvMjmqRXIxH55VjxxA==" }, - "@types/aria-query": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-4.2.1.tgz", - "integrity": "sha512-S6oPal772qJZHoRZLFc/XoZW2gFvwXusYUmXPXkgxJLuEk2vOt7jc4Yo6z/vtI0EBkbPBVrJJ0B+prLIKiWqHg==" - }, "@types/babel__core": { "version": "7.1.14", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.14.tgz", @@ -2251,15 +2137,6 @@ "@types/istanbul-lib-report": "*" } }, - "@types/jest": { - "version": "26.0.21", - "resolved": "https://registry.npmjs.org/@types/jest/-/jest-26.0.21.tgz", - "integrity": "sha512-ab9TyM/69yg7eew9eOwKMUmvIZAKEGZYlq/dhe5/0IMUd/QLJv5ldRMdddSn+u22N13FP3s5jYyktxuBwY0kDA==", - "requires": { - "jest-diff": "^26.0.0", - "pretty-format": "^26.0.0" - } - }, "@types/json-schema": { "version": "7.0.7", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.7.tgz", @@ -2323,14 +2200,6 @@ "resolved": "https://registry.npmjs.org/@types/tapable/-/tapable-1.0.6.tgz", "integrity": "sha512-W+bw9ds02rAQaMvaLYxAbJ6cvguW/iJXNT6lTssS1ps6QdrMKttqEAMEG/b5CR8TZl3/L7/lH0ZV5nNR1LXikA==" }, - "@types/testing-library__jest-dom": { - "version": "5.9.5", - "resolved": "https://registry.npmjs.org/@types/testing-library__jest-dom/-/testing-library__jest-dom-5.9.5.tgz", - "integrity": "sha512-ggn3ws+yRbOHog9GxnXiEZ/35Mow6YtPZpd7Z5mKDeZS/o7zx3yAle0ov/wjhVB5QT4N2Dt+GNoGCdqkBGCajQ==", - "requires": { - "@types/jest": "*" - } - }, "@types/uglify-js": { "version": "3.13.0", "resolved": "https://registry.npmjs.org/@types/uglify-js/-/uglify-js-3.13.0.tgz", @@ -3229,6 +3098,22 @@ "@babel/helper-define-polyfill-provider": "^0.1.5" } }, + "babel-plugin-styled-components": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/babel-plugin-styled-components/-/babel-plugin-styled-components-1.12.0.tgz", + "integrity": "sha512-FEiD7l5ZABdJPpLssKXjBUJMYqzbcNzBowfXDCdJhOpbhWiewapUaY+LZGT8R4Jg2TwOjGjG4RKeyrO5p9sBkA==", + "requires": { + "@babel/helper-annotate-as-pure": "^7.0.0", + "@babel/helper-module-imports": "^7.0.0", + "babel-plugin-syntax-jsx": "^6.18.0", + "lodash": "^4.17.11" + } + }, + "babel-plugin-syntax-jsx": { + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz", + "integrity": "sha1-CvMqmm4Tyno/1QaeYtew9Y0NiUY=" + }, "babel-plugin-syntax-object-rest-spread": { "version": "6.13.0", "resolved": "https://registry.npmjs.org/babel-plugin-syntax-object-rest-spread/-/babel-plugin-syntax-object-rest-spread-6.13.0.tgz", @@ -3902,6 +3787,11 @@ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.0.tgz", "integrity": "sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==" }, + "camelize": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.0.tgz", + "integrity": "sha1-FkpUg+Yw+kMh5a8HAg5TGDGyYJs=" + }, "caniuse-api": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz", @@ -4469,6 +4359,11 @@ "postcss": "^7.0.5" } }, + "css-color-keywords": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz", + "integrity": "sha1-/qJhbcZ2spYmhrOvjb2+GAskTgU=" + }, "css-color-names": { "version": "0.0.4", "resolved": "https://registry.npmjs.org/css-color-names/-/css-color-names-0.0.4.tgz", @@ -4552,6 +4447,16 @@ "resolved": "https://registry.npmjs.org/css-select-base-adapter/-/css-select-base-adapter-0.1.1.tgz", "integrity": "sha512-jQVeeRG70QI08vSTwf1jHxp74JoZsr2XSgETae8/xC8ovSnL2WF87GTLO86Sbwdt2lK4Umg4HnnwMO4YF3Ce7w==" }, + "css-to-react-native": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.0.0.tgz", + "integrity": "sha512-Ro1yETZA813eoyUp2GDBhG2j+YggidUmzO1/v9eYBKR2EHVEniE2MI/NqpTQ954BMpTPZFsGNPm46qFB9dpaPQ==", + "requires": { + "camelize": "^1.0.0", + "css-color-keywords": "^1.0.0", + "postcss-value-parser": "^4.0.2" + } + }, "css-tree": { "version": "1.0.0-alpha.37", "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.0.0-alpha.37.tgz", @@ -4573,11 +4478,6 @@ "resolved": "https://registry.npmjs.org/css-what/-/css-what-3.4.2.tgz", "integrity": "sha512-ACUm3L0/jiZTqfzRM3Hi9Q8eZqd6IK37mMWPLz9PJxkLWllYeRf+EHUSHYEtFop2Eqytaq1FizFVh7XfBnXCDQ==" }, - "css.escape": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz", - "integrity": "sha1-QuJ9T6BK4y+TGktNQZH6nN3ul8s=" - }, "cssdb": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/cssdb/-/cssdb-4.4.0.tgz", @@ -5060,11 +4960,6 @@ "esutils": "^2.0.2" } }, - "dom-accessibility-api": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.4.tgz", - "integrity": "sha512-TvrjBckDy2c6v6RLxPv5QXOnU+SmF9nBII5621Ve5fu6Z/BDrENurBEvlC1f44lKEUVqOpK4w9E5Idc5/EgkLQ==" - }, "dom-converter": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/dom-converter/-/dom-converter-0.2.0.tgz", @@ -7083,6 +6978,14 @@ "minimalistic-crypto-utils": "^1.0.1" } }, + "hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "requires": { + "react-is": "^16.7.0" + } + }, "hoopy": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/hoopy/-/hoopy-0.1.4.tgz", @@ -9772,11 +9675,6 @@ "yallist": "^4.0.0" } }, - "lz-string": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.4.4.tgz", - "integrity": "sha1-wNjq82BZ9wV5bh40SBHPTEmNOiY=" - }, "magic-string": { "version": "0.25.7", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.7.tgz", @@ -9948,11 +9846,6 @@ "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==" }, - "min-indent": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", - "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==" - }, "mini-css-extract-plugin": { "version": "0.11.3", "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-0.11.3.tgz", @@ -12570,15 +12463,6 @@ "minimatch": "3.0.4" } }, - "redent": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", - "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", - "requires": { - "indent-string": "^4.0.0", - "strip-indent": "^3.0.0" - } - }, "regenerate": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", @@ -13449,6 +13333,11 @@ "safe-buffer": "^5.0.1" } }, + "shallowequal": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz", + "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==" + }, "shebang-command": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", @@ -14104,14 +13993,6 @@ "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==" }, - "strip-indent": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", - "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", - "requires": { - "min-indent": "^1.0.0" - } - }, "strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", @@ -14126,6 +14007,23 @@ "schema-utils": "^2.7.0" } }, + "styled-components": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-5.2.1.tgz", + "integrity": "sha512-sBdgLWrCFTKtmZm/9x7jkIabjFNVzCUeKfoQsM6R3saImkUnjx0QYdLwJHBjY9ifEcmjDamJDVfknWm1yxZPxQ==", + "requires": { + "@babel/helper-module-imports": "^7.0.0", + "@babel/traverse": "^7.4.5", + "@emotion/is-prop-valid": "^0.8.8", + "@emotion/stylis": "^0.8.4", + "@emotion/unitless": "^0.7.4", + "babel-plugin-styled-components": ">= 1", + "css-to-react-native": "^3.0.0", + "hoist-non-react-statics": "^3.0.0", + "shallowequal": "^1.1.0", + "supports-color": "^5.5.0" + } + }, "stylehacks": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-4.0.3.tgz", @@ -15255,11 +15153,6 @@ "minimalistic-assert": "^1.0.0" } }, - "web-vitals": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/web-vitals/-/web-vitals-1.1.1.tgz", - "integrity": "sha512-jYOaqu01Ny1NvMwJ3dBJDUOJ2PGWknZWH4AUnvFOscvbdHMERIKT2TlgiAey5rVyfOePG7so2JcXXZdSnBvioQ==" - }, "webidl-conversions": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-6.1.0.tgz", diff --git a/package.json b/package.json index 0c2599c..d84eb36 100644 --- a/package.json +++ b/package.json @@ -3,19 +3,17 @@ "version": "0.1.0", "private": true, "dependencies": { - "@testing-library/jest-dom": "^5.11.9", - "@testing-library/react": "^11.2.5", - "@testing-library/user-event": "^12.8.3", + "@fortawesome/fontawesome-svg-core": "^1.2.35", + "@fortawesome/free-solid-svg-icons": "^5.15.3", + "@fortawesome/react-fontawesome": "^0.1.14", "react": "^17.0.1", "react-dom": "^17.0.1", "react-scripts": "4.0.3", - "web-vitals": "^1.1.1" + "styled-components": "^5.2.1" }, "scripts": { "start": "react-scripts start", - "build": "react-scripts build", - "test": "react-scripts test", - "eject": "react-scripts eject" + "build": "react-scripts build" }, "eslintConfig": { "extends": [ diff --git a/src/App.css b/src/App.css deleted file mode 100644 index 74b5e05..0000000 --- a/src/App.css +++ /dev/null @@ -1,38 +0,0 @@ -.App { - text-align: center; -} - -.App-logo { - height: 40vmin; - pointer-events: none; -} - -@media (prefers-reduced-motion: no-preference) { - .App-logo { - animation: App-logo-spin infinite 20s linear; - } -} - -.App-header { - background-color: #282c34; - min-height: 100vh; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - font-size: calc(10px + 2vmin); - color: white; -} - -.App-link { - color: #61dafb; -} - -@keyframes App-logo-spin { - from { - transform: rotate(0deg); - } - to { - transform: rotate(360deg); - } -} diff --git a/src/App.js b/src/App.js index 3784575..6915cca 100644 --- a/src/App.js +++ b/src/App.js @@ -1,25 +1,88 @@ -import logo from './logo.svg'; -import './App.css'; +import React from "react"; +import styled from "styled-components"; +import { Usage as Compound } from "./patterns/compound/Usage"; +import { Usage as ControlProps } from "./patterns/control-props/Usage"; +import { Usage as CustomHooks } from "./patterns/custom-hooks/Usage"; +import { Usage as PropsGetters } from "./patterns/props-getters/Usage"; +import { Usage as StateReducer } from "./patterns/state-reducer/Usage"; -function App() { +import { library } from "@fortawesome/fontawesome-svg-core"; +import { + faPlus, + faPlusCircle, + faPlusSquare, + faMinus, + faMinusCircle, + faMinusSquare +} from "@fortawesome/free-solid-svg-icons"; +library.add( + faPlus, + faPlusCircle, + faPlusSquare, + faMinus, + faMinusCircle, + faMinusSquare +); + +export default function App() { return ( -
-
- logo -

- Edit src/App.js and save to reload. -

- - Learn React - -
-
+ + +

Advanced React Pattern

+
+ + +

Compound component pattern

+ +
+ + +

Control props pattern

+ +
+ + +

Custom hooks pattern

+ +
+ + +

Props PropsGetters pattern

+ +
+ + +

State reducer Pattern

+ +
+
); } -export default App; +const StyledContainer = styled.div` + text-align: center; + font-family: sans-serif; +`; + +const StyledTitleContainer = styled.div` + background-color: #1428a0; + color: white; + padding: 35px; + + > h1 { + margin: 0; + } +`; + +const StyledPatternContainer = styled.div` + padding: 30px; + border-bottom: 2px solid #d3d3d3; + + &:last-child { + border: none; + } + + > h2 { + margin-top: 0; + } +`; diff --git a/src/index.css b/src/index.css deleted file mode 100644 index ec2585e..0000000 --- a/src/index.css +++ /dev/null @@ -1,13 +0,0 @@ -body { - margin: 0; - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', - 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', - sans-serif; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; -} - -code { - font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', - monospace; -} diff --git a/src/index.js b/src/index.js index ef2edf8..abefd0a 100644 --- a/src/index.js +++ b/src/index.js @@ -1,8 +1,6 @@ import React from 'react'; import ReactDOM from 'react-dom'; -import './index.css'; import App from './App'; -import reportWebVitals from './reportWebVitals'; ReactDOM.render( @@ -11,7 +9,3 @@ ReactDOM.render( document.getElementById('root') ); -// If you want to start measuring performance in your app, pass a function -// to log results (for example: reportWebVitals(console.log)) -// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals -reportWebVitals(); diff --git a/src/logo.svg b/src/logo.svg deleted file mode 100644 index 9dfc1c0..0000000 --- a/src/logo.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/patterns/compound/Counter.js b/src/patterns/compound/Counter.js new file mode 100644 index 0000000..8b25c11 --- /dev/null +++ b/src/patterns/compound/Counter.js @@ -0,0 +1,45 @@ +import React, { useEffect, useRef, useState } from "react"; +import styled from "styled-components"; +import { CounterProvider } from "./useCounterContext"; +import { Count, Label, Decrement, Increment } from "./components"; + +function Counter({ children, onChange, initialValue = 0 }) { + const [count, setCount] = useState(initialValue); + + const firstMounded = useRef(true); + useEffect(() => { + if (!firstMounded.current) { + onChange && onChange(count); + } + firstMounded.current = false; + }, [count, onChange]); + + const handleIncrement = () => { + setCount(count + 1); + }; + + const handleDecrement = () => { + setCount(Math.max(0, count - 1)); + }; + + return ( + + {children} + + ); +} + +const StyledCounter = styled.div` + display: inline-flex; + border: 1px solid #17a2b8; + line-height: 1.5; + border-radius: 0.25rem; + overflow: hidden; +`; + +Counter.Count = Count; +Counter.Label = Label; +Counter.Increment = Increment; +Counter.Decrement = Decrement; + +export { Counter }; diff --git a/src/patterns/compound/Usage.js b/src/patterns/compound/Usage.js new file mode 100644 index 0000000..657fc49 --- /dev/null +++ b/src/patterns/compound/Usage.js @@ -0,0 +1,19 @@ +import React from "react"; +import { Counter } from "./Counter"; + +function Usage() { + const handleChangeCounter = (count) => { + console.log("count", count); + }; + + return ( + + + Counter + + + + ); +} + +export { Usage }; diff --git a/src/patterns/compound/components/Count.js b/src/patterns/compound/components/Count.js new file mode 100644 index 0000000..c4bef92 --- /dev/null +++ b/src/patterns/compound/components/Count.js @@ -0,0 +1,19 @@ +import React from "react"; +import styled from "styled-components"; +import { useCounterContext } from "../useCounterContext"; + +function Count({ max }) { + const { count } = useCounterContext(); + + const hasError = max ? count >= max : false; + + return {count}; +} + +const StyledCount = styled.div` + background-color: ${({ hasError }) => (hasError ? "#bd2130" : "#17a2b8")}; + color: white; + padding: 5px 7px; +`; + +export { Count }; diff --git a/src/patterns/compound/components/Decrement.js b/src/patterns/compound/components/Decrement.js new file mode 100644 index 0000000..b9e3316 --- /dev/null +++ b/src/patterns/compound/components/Decrement.js @@ -0,0 +1,15 @@ +import React from "react"; +import { StyledButton } from "./styles.js"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { useCounterContext } from "../useCounterContext"; + +function Decrement({ icon = "minus" }) { + const { handleDecrement } = useCounterContext(); + return ( + + + + ); +} + +export { Decrement }; diff --git a/src/patterns/compound/components/Increment.js b/src/patterns/compound/components/Increment.js new file mode 100644 index 0000000..4fae07b --- /dev/null +++ b/src/patterns/compound/components/Increment.js @@ -0,0 +1,15 @@ +import React from "react"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { StyledButton } from "./styles.js"; +import { useCounterContext } from "../useCounterContext"; + +function Increment({ icon = "plus" }) { + const { handleIncrement } = useCounterContext(); + return ( + + + + ); +} + +export { Increment }; diff --git a/src/patterns/compound/components/Label.js b/src/patterns/compound/components/Label.js new file mode 100644 index 0000000..e50d683 --- /dev/null +++ b/src/patterns/compound/components/Label.js @@ -0,0 +1,14 @@ +import React from "react"; +import styled from "styled-components"; + +function Label({ children }) { + return {children}; +} + +const StyledLabel = styled.div` + background-color: #e9ecef; + color: #495057; + padding: 5px 7px; +`; + +export { Label }; diff --git a/src/patterns/compound/components/index.js b/src/patterns/compound/components/index.js new file mode 100644 index 0000000..5cbfacb --- /dev/null +++ b/src/patterns/compound/components/index.js @@ -0,0 +1,4 @@ +export * from "./Count"; +export * from "./Decrement"; +export * from "./Increment"; +export * from "./Label"; diff --git a/src/patterns/compound/components/styles.js b/src/patterns/compound/components/styles.js new file mode 100644 index 0000000..4e78599 --- /dev/null +++ b/src/patterns/compound/components/styles.js @@ -0,0 +1,15 @@ +import styled from "styled-components"; + +const StyledButton = styled.button` + background-color: white; + border: none; + &:hover { + cursor: pointer; + } + &:active, + &:focus { + outline: none; + } +`; + +export { StyledButton }; diff --git a/src/patterns/compound/useCounterContext.js b/src/patterns/compound/useCounterContext.js new file mode 100644 index 0000000..45c79f9 --- /dev/null +++ b/src/patterns/compound/useCounterContext.js @@ -0,0 +1,19 @@ +import React from "react"; + +const CounterContext = React.createContext(undefined); + +function CounterProvider({ children, value }) { + return ( + {children} + ); +} + +function useCounterContext() { + const context = React.useContext(CounterContext); + if (context === undefined) { + throw new Error("useCounterContext must be used within a CounterProvider"); + } + return context; +} + +export { CounterProvider, useCounterContext }; diff --git a/src/patterns/control-props/Counter.js b/src/patterns/control-props/Counter.js new file mode 100644 index 0000000..9d34990 --- /dev/null +++ b/src/patterns/control-props/Counter.js @@ -0,0 +1,55 @@ +import React, { useState, useRef, useEffect } from "react"; +import styled from "styled-components"; +import { CounterProvider } from "./useCounterContext"; +import { Count, Label, Decrement, Increment } from "./components"; + +function Counter({ children, value = null, initialValue = 0, onChange }) { + const [count, setCount] = useState(initialValue); + + const isControlled = value !== null && !!onChange; + + const getCount = () => (isControlled ? value : count); + + const firstMounded = useRef(true); + useEffect(() => { + if (!firstMounded.current && !isControlled) { + onChange && onChange(count); + } + firstMounded.current = false; + }, [count, onChange, isControlled]); + + const handleIncrement = () => { + handleCountChange(getCount() + 1); + }; + + const handleDecrement = () => { + handleCountChange(Math.max(0, getCount() - 1)); + }; + + const handleCountChange = (newValue) => { + isControlled ? onChange(newValue) : setCount(newValue); + }; + + return ( + + {children} + + ); +} + +const StyledCounter = styled.div` + display: inline-flex; + border: 1px solid #17a2b8; + line-height: 1.5; + border-radius: 0.25rem; + overflow: hidden; +`; + +Counter.Count = Count; +Counter.Label = Label; +Counter.Increment = Increment; +Counter.Decrement = Decrement; + +export { Counter }; diff --git a/src/patterns/control-props/Usage.js b/src/patterns/control-props/Usage.js new file mode 100644 index 0000000..24c4056 --- /dev/null +++ b/src/patterns/control-props/Usage.js @@ -0,0 +1,20 @@ +import React, { useState } from "react"; +import { Counter } from "./Counter"; + +function Usage() { + const [count, setCount] = useState(0); + + const handleChangeCounter = (newCount) => { + setCount(newCount); + }; + return ( + + + Counter + + + + ); +} + +export { Usage }; diff --git a/src/patterns/control-props/components/Count.js b/src/patterns/control-props/components/Count.js new file mode 100644 index 0000000..c4bef92 --- /dev/null +++ b/src/patterns/control-props/components/Count.js @@ -0,0 +1,19 @@ +import React from "react"; +import styled from "styled-components"; +import { useCounterContext } from "../useCounterContext"; + +function Count({ max }) { + const { count } = useCounterContext(); + + const hasError = max ? count >= max : false; + + return {count}; +} + +const StyledCount = styled.div` + background-color: ${({ hasError }) => (hasError ? "#bd2130" : "#17a2b8")}; + color: white; + padding: 5px 7px; +`; + +export { Count }; diff --git a/src/patterns/control-props/components/Decrement.js b/src/patterns/control-props/components/Decrement.js new file mode 100644 index 0000000..b9e3316 --- /dev/null +++ b/src/patterns/control-props/components/Decrement.js @@ -0,0 +1,15 @@ +import React from "react"; +import { StyledButton } from "./styles.js"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { useCounterContext } from "../useCounterContext"; + +function Decrement({ icon = "minus" }) { + const { handleDecrement } = useCounterContext(); + return ( + + + + ); +} + +export { Decrement }; diff --git a/src/patterns/control-props/components/Increment.js b/src/patterns/control-props/components/Increment.js new file mode 100644 index 0000000..4fae07b --- /dev/null +++ b/src/patterns/control-props/components/Increment.js @@ -0,0 +1,15 @@ +import React from "react"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { StyledButton } from "./styles.js"; +import { useCounterContext } from "../useCounterContext"; + +function Increment({ icon = "plus" }) { + const { handleIncrement } = useCounterContext(); + return ( + + + + ); +} + +export { Increment }; diff --git a/src/patterns/control-props/components/Label.js b/src/patterns/control-props/components/Label.js new file mode 100644 index 0000000..e50d683 --- /dev/null +++ b/src/patterns/control-props/components/Label.js @@ -0,0 +1,14 @@ +import React from "react"; +import styled from "styled-components"; + +function Label({ children }) { + return {children}; +} + +const StyledLabel = styled.div` + background-color: #e9ecef; + color: #495057; + padding: 5px 7px; +`; + +export { Label }; diff --git a/src/patterns/control-props/components/index.js b/src/patterns/control-props/components/index.js new file mode 100644 index 0000000..5cbfacb --- /dev/null +++ b/src/patterns/control-props/components/index.js @@ -0,0 +1,4 @@ +export * from "./Count"; +export * from "./Decrement"; +export * from "./Increment"; +export * from "./Label"; diff --git a/src/patterns/control-props/components/styles.js b/src/patterns/control-props/components/styles.js new file mode 100644 index 0000000..4e78599 --- /dev/null +++ b/src/patterns/control-props/components/styles.js @@ -0,0 +1,15 @@ +import styled from "styled-components"; + +const StyledButton = styled.button` + background-color: white; + border: none; + &:hover { + cursor: pointer; + } + &:active, + &:focus { + outline: none; + } +`; + +export { StyledButton }; diff --git a/src/patterns/control-props/useCounterContext.js b/src/patterns/control-props/useCounterContext.js new file mode 100644 index 0000000..45c79f9 --- /dev/null +++ b/src/patterns/control-props/useCounterContext.js @@ -0,0 +1,19 @@ +import React from "react"; + +const CounterContext = React.createContext(undefined); + +function CounterProvider({ children, value }) { + return ( + {children} + ); +} + +function useCounterContext() { + const context = React.useContext(CounterContext); + if (context === undefined) { + throw new Error("useCounterContext must be used within a CounterProvider"); + } + return context; +} + +export { CounterProvider, useCounterContext }; diff --git a/src/patterns/custom-hooks/Counter.js b/src/patterns/custom-hooks/Counter.js new file mode 100644 index 0000000..915a723 --- /dev/null +++ b/src/patterns/custom-hooks/Counter.js @@ -0,0 +1,35 @@ +import React, { useRef, useEffect } from "react"; +import styled from "styled-components"; +import { CounterProvider } from "./useCounterContext"; +import { Count, Label, Decrement, Increment } from "./components"; + +function Counter({ children, value: count, onChange }) { + const firstMounded = useRef(true); + useEffect(() => { + if (!firstMounded.current) { + onChange && onChange(count); + } + firstMounded.current = false; + }, [count, onChange]); + + return ( + + {children} + + ); +} + +const StyledCounter = styled.div` + display: inline-flex; + border: 1px solid #17a2b8; + line-height: 1.5; + border-radius: 0.25rem; + overflow: hidden; +`; + +Counter.Count = Count; +Counter.Label = Label; +Counter.Increment = Increment; +Counter.Decrement = Decrement; + +export { Counter }; diff --git a/src/patterns/custom-hooks/Usage.js b/src/patterns/custom-hooks/Usage.js new file mode 100644 index 0000000..378fe5f --- /dev/null +++ b/src/patterns/custom-hooks/Usage.js @@ -0,0 +1,46 @@ +import React from "react"; +import styled from "styled-components"; +import { Counter } from "./Counter"; +import { useCounter } from "./useCounter"; + +function Usage() { + const { count, handleIncrement, handleDecrement } = useCounter(0); + const MAX_COUNT = 10; + + const handleClickIncrement = () => { + //Put your custom logic + if (count < MAX_COUNT) { + handleIncrement(); + } + }; + + return ( + <> + + + Counter + + + + + + + + ); +} + +export { Usage }; + +const StyledContainer = styled.div` + margin-top: 20px; +`; diff --git a/src/patterns/custom-hooks/components/Count.js b/src/patterns/custom-hooks/components/Count.js new file mode 100644 index 0000000..91d6a8e --- /dev/null +++ b/src/patterns/custom-hooks/components/Count.js @@ -0,0 +1,19 @@ +import React from "react"; +import styled from "styled-components"; +import { useCounterContext } from "../useCounterContext"; + +function Count({ max }) { + const { count } = useCounterContext(); + + const hasError = max ? count > max : false; + + return {count}; +} + +const StyledCount = styled.div` + background-color: ${({ hasError }) => (hasError ? "#bd2130" : "#17a2b8")}; + color: white; + padding: 5px 7px; +`; + +export { Count }; diff --git a/src/patterns/custom-hooks/components/Decrement.js b/src/patterns/custom-hooks/components/Decrement.js new file mode 100644 index 0000000..54dfde1 --- /dev/null +++ b/src/patterns/custom-hooks/components/Decrement.js @@ -0,0 +1,13 @@ +import React from "react"; +import { StyledButton } from "./styles.js"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; + +function Decrement({ icon = "minus", onClick }) { + return ( + + + + ); +} + +export { Decrement }; diff --git a/src/patterns/custom-hooks/components/Increment.js b/src/patterns/custom-hooks/components/Increment.js new file mode 100644 index 0000000..b1e68cd --- /dev/null +++ b/src/patterns/custom-hooks/components/Increment.js @@ -0,0 +1,13 @@ +import React from "react"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { StyledButton } from "./styles.js"; + +function Increment({ icon = "plus", onClick }) { + return ( + + + + ); +} + +export { Increment }; diff --git a/src/patterns/custom-hooks/components/Label.js b/src/patterns/custom-hooks/components/Label.js new file mode 100644 index 0000000..e50d683 --- /dev/null +++ b/src/patterns/custom-hooks/components/Label.js @@ -0,0 +1,14 @@ +import React from "react"; +import styled from "styled-components"; + +function Label({ children }) { + return {children}; +} + +const StyledLabel = styled.div` + background-color: #e9ecef; + color: #495057; + padding: 5px 7px; +`; + +export { Label }; diff --git a/src/patterns/custom-hooks/components/index.js b/src/patterns/custom-hooks/components/index.js new file mode 100644 index 0000000..5cbfacb --- /dev/null +++ b/src/patterns/custom-hooks/components/index.js @@ -0,0 +1,4 @@ +export * from "./Count"; +export * from "./Decrement"; +export * from "./Increment"; +export * from "./Label"; diff --git a/src/patterns/custom-hooks/components/styles.js b/src/patterns/custom-hooks/components/styles.js new file mode 100644 index 0000000..4e78599 --- /dev/null +++ b/src/patterns/custom-hooks/components/styles.js @@ -0,0 +1,15 @@ +import styled from "styled-components"; + +const StyledButton = styled.button` + background-color: white; + border: none; + &:hover { + cursor: pointer; + } + &:active, + &:focus { + outline: none; + } +`; + +export { StyledButton }; diff --git a/src/patterns/custom-hooks/useCounter.js b/src/patterns/custom-hooks/useCounter.js new file mode 100644 index 0000000..5b1e86c --- /dev/null +++ b/src/patterns/custom-hooks/useCounter.js @@ -0,0 +1,17 @@ +import { useState } from "react"; + +function useCounter(intialeCount) { + const [count, setCount] = useState(intialeCount); + + const handleIncrement = () => { + setCount((prevCount) => prevCount + 1); + }; + + const handleDecrement = () => { + setCount((prevCount) => Math.max(0, prevCount - 1)); + }; + + return { count, handleIncrement, handleDecrement }; +} + +export { useCounter }; diff --git a/src/patterns/custom-hooks/useCounterContext.js b/src/patterns/custom-hooks/useCounterContext.js new file mode 100644 index 0000000..45c79f9 --- /dev/null +++ b/src/patterns/custom-hooks/useCounterContext.js @@ -0,0 +1,19 @@ +import React from "react"; + +const CounterContext = React.createContext(undefined); + +function CounterProvider({ children, value }) { + return ( + {children} + ); +} + +function useCounterContext() { + const context = React.useContext(CounterContext); + if (context === undefined) { + throw new Error("useCounterContext must be used within a CounterProvider"); + } + return context; +} + +export { CounterProvider, useCounterContext }; diff --git a/src/patterns/props-getters/Counter.js b/src/patterns/props-getters/Counter.js new file mode 100644 index 0000000..915a723 --- /dev/null +++ b/src/patterns/props-getters/Counter.js @@ -0,0 +1,35 @@ +import React, { useRef, useEffect } from "react"; +import styled from "styled-components"; +import { CounterProvider } from "./useCounterContext"; +import { Count, Label, Decrement, Increment } from "./components"; + +function Counter({ children, value: count, onChange }) { + const firstMounded = useRef(true); + useEffect(() => { + if (!firstMounded.current) { + onChange && onChange(count); + } + firstMounded.current = false; + }, [count, onChange]); + + return ( + + {children} + + ); +} + +const StyledCounter = styled.div` + display: inline-flex; + border: 1px solid #17a2b8; + line-height: 1.5; + border-radius: 0.25rem; + overflow: hidden; +`; + +Counter.Count = Count; +Counter.Label = Label; +Counter.Increment = Increment; +Counter.Decrement = Decrement; + +export { Counter }; diff --git a/src/patterns/props-getters/Usage.js b/src/patterns/props-getters/Usage.js new file mode 100644 index 0000000..f9e0a39 --- /dev/null +++ b/src/patterns/props-getters/Usage.js @@ -0,0 +1,49 @@ +import React from "react"; +import styled from "styled-components"; +import { Counter } from "./Counter"; +import { useCounter } from "./useCounter"; + +const MAX_COUNT = 10; + +function Usage() { + const { + count, + getCounterProps, + getIncrementProps, + getDecrementProps + } = useCounter({ + initial: 0, + max: MAX_COUNT + }); + + const handleBtn1Clicked = () => { + console.log("btn 1 clicked"); + }; + + return ( + <> + + + Counter + + + + + + + + + + + ); +} + +export { Usage }; + +const StyledContainer = styled.div` + margin-top: 20px; +`; diff --git a/src/patterns/props-getters/components/Count.js b/src/patterns/props-getters/components/Count.js new file mode 100644 index 0000000..c4bef92 --- /dev/null +++ b/src/patterns/props-getters/components/Count.js @@ -0,0 +1,19 @@ +import React from "react"; +import styled from "styled-components"; +import { useCounterContext } from "../useCounterContext"; + +function Count({ max }) { + const { count } = useCounterContext(); + + const hasError = max ? count >= max : false; + + return {count}; +} + +const StyledCount = styled.div` + background-color: ${({ hasError }) => (hasError ? "#bd2130" : "#17a2b8")}; + color: white; + padding: 5px 7px; +`; + +export { Count }; diff --git a/src/patterns/props-getters/components/Decrement.js b/src/patterns/props-getters/components/Decrement.js new file mode 100644 index 0000000..9f0dd73 --- /dev/null +++ b/src/patterns/props-getters/components/Decrement.js @@ -0,0 +1,13 @@ +import React from "react"; +import { StyledButton } from "./styles.js"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; + +function Decrement({ icon = "minus", onClick, ...props }) { + return ( + + + + ); +} + +export { Decrement }; diff --git a/src/patterns/props-getters/components/Increment.js b/src/patterns/props-getters/components/Increment.js new file mode 100644 index 0000000..c605070 --- /dev/null +++ b/src/patterns/props-getters/components/Increment.js @@ -0,0 +1,13 @@ +import React from "react"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { StyledButton } from "./styles.js"; + +function Increment({ icon = "plus", onClick, ...props }) { + return ( + + + + ); +} + +export { Increment }; diff --git a/src/patterns/props-getters/components/Label.js b/src/patterns/props-getters/components/Label.js new file mode 100644 index 0000000..e50d683 --- /dev/null +++ b/src/patterns/props-getters/components/Label.js @@ -0,0 +1,14 @@ +import React from "react"; +import styled from "styled-components"; + +function Label({ children }) { + return {children}; +} + +const StyledLabel = styled.div` + background-color: #e9ecef; + color: #495057; + padding: 5px 7px; +`; + +export { Label }; diff --git a/src/patterns/props-getters/components/index.js b/src/patterns/props-getters/components/index.js new file mode 100644 index 0000000..5cbfacb --- /dev/null +++ b/src/patterns/props-getters/components/index.js @@ -0,0 +1,4 @@ +export * from "./Count"; +export * from "./Decrement"; +export * from "./Increment"; +export * from "./Label"; diff --git a/src/patterns/props-getters/components/styles.js b/src/patterns/props-getters/components/styles.js new file mode 100644 index 0000000..4e78599 --- /dev/null +++ b/src/patterns/props-getters/components/styles.js @@ -0,0 +1,15 @@ +import styled from "styled-components"; + +const StyledButton = styled.button` + background-color: white; + border: none; + &:hover { + cursor: pointer; + } + &:active, + &:focus { + outline: none; + } +`; + +export { StyledButton }; diff --git a/src/patterns/props-getters/useCounter.js b/src/patterns/props-getters/useCounter.js new file mode 100644 index 0000000..4cccf78 --- /dev/null +++ b/src/patterns/props-getters/useCounter.js @@ -0,0 +1,51 @@ +import { useState } from "react"; + +//Function which concat all functions together +const callFnsInSequence = (...fns) => (...args) => + fns.forEach((fn) => fn && fn(...args)); + +function useCounter({ initial, max }) { + const [count, setCount] = useState(initial); + + const handleIncrement = () => { + setCount((prevCount) => Math.min(prevCount + 1, max)); + }; + + const handleDecrement = () => { + setCount((prevCount) => Math.max(0, prevCount - 1)); + }; + + //props getter for 'Counter' + const getCounterProps = ({ ...otherProps } = {}) => ({ + value: count, + "aria-valuemax": max, + "aria-valuemin": 0, + "aria-valuenow": count, + ...otherProps + }); + + //props getter for 'Decrement' + const getDecrementProps = ({ onClick, ...otherProps } = {}) => ({ + onClick: callFnsInSequence(handleDecrement, onClick), + disabled: count === 0, + ...otherProps + }); + + //props getter for 'Increment' + const getIncrementProps = ({ onClick, ...otherProps } = {}) => ({ + onClick: callFnsInSequence(handleIncrement, onClick), + disabled: count === max, + ...otherProps + }); + + return { + count, + handleIncrement, + handleDecrement, + getCounterProps, + getDecrementProps, + getIncrementProps + }; +} + +export { useCounter }; diff --git a/src/patterns/props-getters/useCounterContext.js b/src/patterns/props-getters/useCounterContext.js new file mode 100644 index 0000000..45c79f9 --- /dev/null +++ b/src/patterns/props-getters/useCounterContext.js @@ -0,0 +1,19 @@ +import React from "react"; + +const CounterContext = React.createContext(undefined); + +function CounterProvider({ children, value }) { + return ( + {children} + ); +} + +function useCounterContext() { + const context = React.useContext(CounterContext); + if (context === undefined) { + throw new Error("useCounterContext must be used within a CounterProvider"); + } + return context; +} + +export { CounterProvider, useCounterContext }; diff --git a/src/patterns/state-reducer/Counter.js b/src/patterns/state-reducer/Counter.js new file mode 100644 index 0000000..915a723 --- /dev/null +++ b/src/patterns/state-reducer/Counter.js @@ -0,0 +1,35 @@ +import React, { useRef, useEffect } from "react"; +import styled from "styled-components"; +import { CounterProvider } from "./useCounterContext"; +import { Count, Label, Decrement, Increment } from "./components"; + +function Counter({ children, value: count, onChange }) { + const firstMounded = useRef(true); + useEffect(() => { + if (!firstMounded.current) { + onChange && onChange(count); + } + firstMounded.current = false; + }, [count, onChange]); + + return ( + + {children} + + ); +} + +const StyledCounter = styled.div` + display: inline-flex; + border: 1px solid #17a2b8; + line-height: 1.5; + border-radius: 0.25rem; + overflow: hidden; +`; + +Counter.Count = Count; +Counter.Label = Label; +Counter.Increment = Increment; +Counter.Decrement = Decrement; + +export { Counter }; diff --git a/src/patterns/state-reducer/Usage.js b/src/patterns/state-reducer/Usage.js new file mode 100644 index 0000000..99a3bae --- /dev/null +++ b/src/patterns/state-reducer/Usage.js @@ -0,0 +1,47 @@ +import React from "react"; +import styled from "styled-components"; +import { Counter } from "./Counter"; +import { useCounter } from "./useCounter"; + +const MAX_COUNT = 10; +function Usage() { + const reducer = (state, action) => { + switch (action.type) { + case "decrement": + return { + count: Math.max(0, state.count - 2) //The decrement delta was changed for 2 (Default is 1) + }; + default: + return useCounter.reducer(state, action); + } + }; + + const { count, handleDecrement, handleIncrement } = useCounter( + { initial: 0, max: 10 }, + reducer + ); + + return ( + <> + + + Counter + + + + + + + + ); +} + + + +export { Usage }; + +const StyledContainer = styled.div` + margin-top: 20px; +`; diff --git a/src/patterns/state-reducer/components/Count.js b/src/patterns/state-reducer/components/Count.js new file mode 100644 index 0000000..c4bef92 --- /dev/null +++ b/src/patterns/state-reducer/components/Count.js @@ -0,0 +1,19 @@ +import React from "react"; +import styled from "styled-components"; +import { useCounterContext } from "../useCounterContext"; + +function Count({ max }) { + const { count } = useCounterContext(); + + const hasError = max ? count >= max : false; + + return {count}; +} + +const StyledCount = styled.div` + background-color: ${({ hasError }) => (hasError ? "#bd2130" : "#17a2b8")}; + color: white; + padding: 5px 7px; +`; + +export { Count }; diff --git a/src/patterns/state-reducer/components/Decrement.js b/src/patterns/state-reducer/components/Decrement.js new file mode 100644 index 0000000..9f0dd73 --- /dev/null +++ b/src/patterns/state-reducer/components/Decrement.js @@ -0,0 +1,13 @@ +import React from "react"; +import { StyledButton } from "./styles.js"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; + +function Decrement({ icon = "minus", onClick, ...props }) { + return ( + + + + ); +} + +export { Decrement }; diff --git a/src/patterns/state-reducer/components/Increment.js b/src/patterns/state-reducer/components/Increment.js new file mode 100644 index 0000000..c605070 --- /dev/null +++ b/src/patterns/state-reducer/components/Increment.js @@ -0,0 +1,13 @@ +import React from "react"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { StyledButton } from "./styles.js"; + +function Increment({ icon = "plus", onClick, ...props }) { + return ( + + + + ); +} + +export { Increment }; diff --git a/src/patterns/state-reducer/components/Label.js b/src/patterns/state-reducer/components/Label.js new file mode 100644 index 0000000..e50d683 --- /dev/null +++ b/src/patterns/state-reducer/components/Label.js @@ -0,0 +1,14 @@ +import React from "react"; +import styled from "styled-components"; + +function Label({ children }) { + return {children}; +} + +const StyledLabel = styled.div` + background-color: #e9ecef; + color: #495057; + padding: 5px 7px; +`; + +export { Label }; diff --git a/src/patterns/state-reducer/components/index.js b/src/patterns/state-reducer/components/index.js new file mode 100644 index 0000000..5cbfacb --- /dev/null +++ b/src/patterns/state-reducer/components/index.js @@ -0,0 +1,4 @@ +export * from "./Count"; +export * from "./Decrement"; +export * from "./Increment"; +export * from "./Label"; diff --git a/src/patterns/state-reducer/components/styles.js b/src/patterns/state-reducer/components/styles.js new file mode 100644 index 0000000..4e78599 --- /dev/null +++ b/src/patterns/state-reducer/components/styles.js @@ -0,0 +1,15 @@ +import styled from "styled-components"; + +const StyledButton = styled.button` + background-color: white; + border: none; + &:hover { + cursor: pointer; + } + &:active, + &:focus { + outline: none; + } +`; + +export { StyledButton }; diff --git a/src/patterns/state-reducer/useCounter.js b/src/patterns/state-reducer/useCounter.js new file mode 100644 index 0000000..5770ab9 --- /dev/null +++ b/src/patterns/state-reducer/useCounter.js @@ -0,0 +1,42 @@ +import { useReducer } from "react"; + +const internalReducer = ({ count }, { type, payload }) => { + switch (type) { + case "increment": + return { + count: Math.min(count + 1, payload.max) + }; + case "decrement": + return { + count: Math.max(0, count - 1) + }; + default: + throw new Error(`Unhandled action type: ${type}`); + } +}; + +function useCounter({ initial, max }, reducer = internalReducer) { + const [{ count }, dispatch] = useReducer(reducer, { count: initial }); + + const handleIncrement = () => { + dispatch({ type: "increment", payload: { max } }); + }; + + const handleDecrement = () => { + dispatch({ type: "decrement" }); + }; + + return { + count, + handleIncrement, + handleDecrement + }; +} + +useCounter.reducer = internalReducer; +useCounter.types = { + increment: "increment", + decrement: "decrement" +}; + +export { useCounter }; diff --git a/src/patterns/state-reducer/useCounterContext.js b/src/patterns/state-reducer/useCounterContext.js new file mode 100644 index 0000000..45c79f9 --- /dev/null +++ b/src/patterns/state-reducer/useCounterContext.js @@ -0,0 +1,19 @@ +import React from "react"; + +const CounterContext = React.createContext(undefined); + +function CounterProvider({ children, value }) { + return ( + {children} + ); +} + +function useCounterContext() { + const context = React.useContext(CounterContext); + if (context === undefined) { + throw new Error("useCounterContext must be used within a CounterProvider"); + } + return context; +} + +export { CounterProvider, useCounterContext }; diff --git a/src/reportWebVitals.js b/src/reportWebVitals.js deleted file mode 100644 index 5253d3a..0000000 --- a/src/reportWebVitals.js +++ /dev/null @@ -1,13 +0,0 @@ -const reportWebVitals = onPerfEntry => { - if (onPerfEntry && onPerfEntry instanceof Function) { - import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { - getCLS(onPerfEntry); - getFID(onPerfEntry); - getFCP(onPerfEntry); - getLCP(onPerfEntry); - getTTFB(onPerfEntry); - }); - } -}; - -export default reportWebVitals; diff --git a/src/setupTests.js b/src/setupTests.js deleted file mode 100644 index 8f2609b..0000000 --- a/src/setupTests.js +++ /dev/null @@ -1,5 +0,0 @@ -// jest-dom adds custom jest matchers for asserting on DOM nodes. -// allows you to do things like: -// expect(element).toHaveTextContent(/react/i) -// learn more: https://github.com/testing-library/jest-dom -import '@testing-library/jest-dom';