├── .all-contributorsrc ├── .eslintrc.js ├── .github ├── labeler.yml └── workflows │ ├── labeler.yml │ ├── lint.yml │ └── master_deploy.yml ├── .gitignore ├── CONTRIBUTING.md ├── README.md ├── ideas.md ├── package-lock.json ├── package.json ├── public ├── favicon.ico ├── index.html ├── logo192.png ├── logo512.png ├── manifest.json └── robots.txt └── src ├── App.css ├── App.js ├── components ├── AlternateGrid │ ├── components │ │ ├── GridCell │ │ │ ├── index.js │ │ │ └── styles.module.css │ │ └── GridRow │ │ │ ├── index.js │ │ │ └── styles.module.css │ ├── config.js │ ├── index.js │ └── styles.module.css ├── BillGenerator │ ├── components │ │ ├── Bill │ │ │ ├── index.js │ │ │ └── styles.module.css │ │ └── TacoMenu │ │ │ ├── index.js │ │ │ └── styles.module.css │ ├── index.js │ └── styles.module.css ├── Calculator │ ├── components │ │ ├── InputButton │ │ │ ├── index.js │ │ │ └── styles.module.css │ │ └── InputRow │ │ │ ├── index.js │ │ │ └── styles.module.css │ ├── index.js │ ├── rows.js │ └── styles.module.css ├── Carousel │ ├── components │ │ └── CarouselCard │ │ │ ├── index.js │ │ │ └── styles.module.css │ ├── index.js │ └── styles.module.css ├── ColorPicker │ ├── components │ │ └── ColorCard │ │ │ ├── index.js │ │ │ └── styles.module.css │ ├── index.js │ └── styles.module.css ├── GitHubProfileViewer │ ├── components │ │ ├── Profile │ │ │ ├── index.js │ │ │ └── styles.module.css │ │ ├── RepoCard │ │ │ ├── index.js │ │ │ └── styles.module.css │ │ └── Repos │ │ │ ├── index.js │ │ │ └── styles.module.css │ ├── index.js │ └── styles.module.css ├── Home │ ├── components │ │ └── ProjectCard │ │ │ ├── index.js │ │ │ └── styles.module.css │ ├── index.js │ ├── projects.js │ └── styles.module.css ├── HoverBoard │ ├── components │ │ └── BoardCell │ │ │ ├── index.js │ │ │ └── styles.module.css │ ├── index.js │ └── styles.module.css ├── KeyCodeSequence │ ├── components │ │ └── CodeCard │ │ │ ├── index.js │ │ │ └── styles.module.css │ ├── index.js │ └── styles.module.css ├── MealGenerator │ ├── index.js │ └── styles.module.css ├── PasswordGenerator │ ├── index.js │ └── styles.module.css ├── Pokedex │ ├── index.js │ └── styles.module.css ├── PricingCards │ ├── components │ │ └── Card │ │ │ ├── index.js │ │ │ └── styles.module.css │ ├── index.js │ └── styles.module.css ├── RandomJokes │ ├── components │ │ ├── AllJokes │ │ │ ├── index.js │ │ │ └── styles.module.css │ │ ├── JokeCard │ │ │ ├── index.js │ │ │ └── styles.module.css │ │ └── LikedJokes │ │ │ ├── index.js │ │ │ └── styles.module.css │ ├── index.js │ └── styles.module.css ├── RandomQuotes │ ├── components │ │ └── QuoteCard │ │ │ ├── index.js │ │ │ └── styles.module.css │ ├── index.js │ └── styles.module.css ├── SkeletonLoader │ ├── components │ │ ├── Post │ │ │ ├── index.js │ │ │ └── styles.module.css │ │ ├── Shimmer │ │ │ ├── index.js │ │ │ └── styles.module.css │ │ ├── Skeleton │ │ │ ├── index.js │ │ │ └── styles.module.css │ │ └── SkeletonWrapper │ │ │ ├── index.js │ │ │ └── styles.module.css │ ├── index.js │ └── styles.module.css ├── StonePaperScissor │ ├── components │ │ ├── GameCard │ │ │ ├── index.js │ │ │ └── styles.module.css │ │ ├── GameView │ │ │ ├── index.js │ │ │ └── styles.module.css │ │ └── ResultView │ │ │ ├── index.js │ │ │ └── styles.module.css │ ├── index.js │ └── styles.module.css ├── Todos │ ├── components │ │ └── Todo │ │ │ ├── index.js │ │ │ └── styles.module.css │ ├── index.js │ └── styles.module.css ├── TwitterUIClone │ ├── components │ │ └── TweetBlock │ │ │ ├── index.js │ │ │ └── styles.module.css │ ├── index.js │ ├── styles.module.css │ └── tweets.js ├── ValidatedForm │ ├── components │ │ └── RadioGroup │ │ │ ├── index.js │ │ │ └── styles.module.css │ ├── index.js │ └── styles.module.css └── VotingPoll │ ├── components │ └── PollOption │ │ ├── index.js │ │ └── styles.module.css │ ├── index.js │ ├── poll.js │ └── styles.module.css ├── images ├── bill_dark.jpg ├── bill_light.jpg ├── calculator_dark.jpg ├── calculator_light.jpg ├── carousel_dark.jpg ├── carousel_light.jpg ├── form_dark.jpg ├── form_light.jpg ├── game_dark.jpg ├── game_light.jpg ├── grid_dark.jpg ├── grid_light.jpg ├── hoverboard_dark.jpg ├── hoverboard_light.jpg ├── jokes_dark.jpg ├── jokes_light.jpg ├── keycode_dark.jpg ├── keycode_light.jpg ├── meal_dark.jpg ├── meal_light.jpg ├── password_dark.jpg ├── password_light.jpg ├── picker_dark.jpg ├── picker_light.jpg ├── pokedex_dark.jpg ├── pokedex_light.jpg ├── poll_dark.jpg ├── poll_light.jpg ├── pricing_dark.jpg ├── pricing_light.jpg ├── profile_viewer_dark.jpg ├── profile_viewer_light.jpg ├── quotes_dark.jpg ├── quotes_light.jpg ├── skeleton_dark.jpg ├── skeleton_light.jpg ├── todos_dark.jpg ├── todos_light.jpg ├── twitter_dark.jpg └── twitter_light.jpg ├── index.css ├── index.js ├── resources ├── colors.js └── constants.js ├── routes └── index.js ├── script.js ├── ui-components ├── Button │ ├── Iconbutton │ │ └── index.js │ └── index.js ├── CheckBox │ └── index.js ├── Input │ └── index.js ├── Label │ ├── index.js │ └── styles.module.css ├── Page │ ├── index.js │ └── styles.module.css ├── Radio │ ├── index.js │ └── styles.module.css ├── SnackBar │ └── index.js ├── SpinnerLoader │ ├── index.js │ └── styles.module.css └── Switch │ └── index.js └── utils ├── index.js └── theme.js /.all-contributorsrc: -------------------------------------------------------------------------------- 1 | { 2 | "files": [ 3 | "README.md" 4 | ], 5 | "imageSize": 100, 6 | "commit": false, 7 | "contributors": [ 8 | { 9 | "login": "DemonDaddy22", 10 | "name": "Rohan Gupta", 11 | "avatar_url": "https://avatars1.githubusercontent.com/u/39908472?v=4", 12 | "profile": "https://shades-of-demon.herokuapp.com/", 13 | "contributions": [ 14 | "code", 15 | "ideas", 16 | "doc", 17 | "review" 18 | ] 19 | }, 20 | { 21 | "login": "vineetnilawar", 22 | "name": "Vineet Nilawar", 23 | "avatar_url": "https://avatars0.githubusercontent.com/u/55659836?v=4", 24 | "profile": "https://github.com/vineetnilawar", 25 | "contributions": [ 26 | "doc" 27 | ] 28 | }, 29 | { 30 | "login": "Jaikishann", 31 | "name": "Jaikishan", 32 | "avatar_url": "https://avatars2.githubusercontent.com/u/23214005?v=4", 33 | "profile": "https://github.com/Jaikishann", 34 | "contributions": [ 35 | "code" 36 | ] 37 | }, 38 | { 39 | "login": "neupanedipen", 40 | "name": "Dipendra Neupane", 41 | "avatar_url": "https://avatars2.githubusercontent.com/u/38071091?v=4", 42 | "profile": "https://www.neupanedipendra.com.np", 43 | "contributions": [ 44 | "doc" 45 | ] 46 | }, 47 | { 48 | "login": "Ritika0126", 49 | "name": "Ritika0126", 50 | "avatar_url": "https://avatars3.githubusercontent.com/u/51254896?v=4", 51 | "profile": "https://github.com/Ritika0126", 52 | "contributions": [ 53 | "doc" 54 | ] 55 | }, 56 | { 57 | "login": "Aqsa48", 58 | "name": "Aqsa Umar", 59 | "avatar_url": "https://avatars0.githubusercontent.com/u/21342218?v=4", 60 | "profile": "https://aqsa-portfolio.herokuapp.com/", 61 | "contributions": [ 62 | "doc" 63 | ] 64 | }, 65 | { 66 | "login": "mustafamasvi", 67 | "name": "Mustafa Masvi", 68 | "avatar_url": "https://avatars3.githubusercontent.com/u/18447601?v=4", 69 | "profile": "https://www.linkedin.com/in/mustafamasvi/", 70 | "contributions": [ 71 | "doc" 72 | ] 73 | }, 74 | { 75 | "login": "phanlyhuynh", 76 | "name": "phanlyhuynh", 77 | "avatar_url": "https://avatars1.githubusercontent.com/u/32766920?v=4", 78 | "profile": "https://github.com/phanlyhuynh", 79 | "contributions": [ 80 | "code" 81 | ] 82 | } 83 | ], 84 | "contributorsPerLine": 7, 85 | "projectName": "all-about-reactJS", 86 | "projectOwner": "DemonDaddy22", 87 | "repoType": "github", 88 | "repoHost": "https://github.com", 89 | "skipCi": true 90 | } 91 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "parser": "babel-eslint", 3 | "parserOptions": { 4 | "ecmaVersion": 6, 5 | "sourceType": "module" 6 | }, 7 | "settings": { 8 | "import/resolver": "webpack" 9 | }, 10 | "env": { 11 | "es6": true, 12 | "browser": true 13 | }, 14 | "globals": {}, 15 | "plugins": [ 16 | "react", 17 | "react-hooks" 18 | ], 19 | "rules": { 20 | "comma-dangle": 0, 21 | "react/jsx-uses-vars": 1, 22 | "no-unused-vars": 1, 23 | "no-undef": 1, 24 | "no-unexpected-multiline": 1, 25 | "no-debugger": 1, 26 | "no-alert": 0, 27 | "no-await-in-loop": 1, 28 | "no-return-assign": ["error", "except-parens"], 29 | "no-unused-expressions": [ 30 | 2, 31 | { 32 | "allowTaggedTemplates": true, 33 | }, 34 | ], 35 | "import/prefer-default-export": 0, 36 | "import": 0, 37 | "react/require-default-props": 0, 38 | "quotes": [ 39 | 2, 40 | "single", 41 | { 42 | "avoidEscape": true, 43 | "allowTemplateLiterals": true, 44 | }, 45 | ], 46 | "eqeqeq": 1, 47 | }, 48 | "settings": { 49 | "react": { 50 | "createClass": "createReactClass", 51 | "pragma": "React", 52 | "fragment": "Fragment", 53 | "version": "detect", 54 | "flowVersion": "0.53" 55 | }, 56 | "propWrapperFunctions": [ 57 | "forbidExtraProps", 58 | { "property": "freeze", "object": "Object" }, 59 | { "property": "myFavoriteWrapper" } 60 | ], 61 | "linkComponents": [ 62 | "Hyperlink", 63 | { "name": "Link", "linkAttribute": "to" } 64 | ] 65 | } 66 | }; 67 | -------------------------------------------------------------------------------- /.github/labeler.yml: -------------------------------------------------------------------------------- 1 | repo: 2 | - ./* 3 | - '**' 4 | 5 | modules: 6 | - node_modules/* 7 | - node_modules/**/* 8 | 9 | source: 10 | - src/**/* 11 | 12 | documentation: 13 | - '*.md' -------------------------------------------------------------------------------- /.github/workflows/labeler.yml: -------------------------------------------------------------------------------- 1 | # This workflow will triage pull requests and apply a label based on the 2 | # paths that are modified in the pull request. 3 | # 4 | # To use this workflow, you will need to set up a .github/labeler.yml 5 | # file with configuration. For more information, see: 6 | # https://github.com/actions/labeler 7 | 8 | name: Labeler 9 | on: 10 | - pull_request_target 11 | 12 | jobs: 13 | pr_label: 14 | # prevent running it in the forks 15 | if: github.repository == 'DemonDaddy22/all-about-reactJS' 16 | runs-on: ubuntu-latest 17 | 18 | steps: 19 | - name: Adding label 20 | uses: actions/labeler@main 21 | with: 22 | sync-labels: true 23 | repo-token: ${{ secrets.PERSONAL_ACCESS_TOKEN }} 24 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: Lint 2 | 3 | on: 4 | - pull_request_target 5 | 6 | jobs: 7 | eslint: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v2 11 | - uses: actions/setup-node@v1 12 | with: 13 | node-version: '12' 14 | - run: yarn 15 | - run: ./node_modules/.bin/eslint src/ 16 | -------------------------------------------------------------------------------- /.github/workflows/master_deploy.yml: -------------------------------------------------------------------------------- 1 | name: AllAboutReactJSCI 2 | on: 3 | push: 4 | branches: 5 | - master 6 | jobs: 7 | build: 8 | runs-on: ubuntu-latest 9 | strategy: 10 | matrix: 11 | node-version: [10.x, 14.x] 12 | steps: 13 | - uses: actions/checkout@v1 14 | - name: Use Node.js ${{ matrix.node-version }} 15 | uses: actions/setup-node@v1 16 | with: 17 | node-version: ${{ matrix.node-version }} 18 | - name: Install Packages 19 | run: npm install 20 | - name: Deploy Application to GH Pages 21 | run: | 22 | git config user.email ${{ secrets.EMAIL }} 23 | git config user.name ${{ secrets.USERNAME }} 24 | git remote set-url origin https://${{ secrets.PERSONAL_ACCESS_TOKEN }}@github.com/${{ secrets.USERNAME }}/${{ secrets.REPO_NAME }}.git 25 | npm run deploy 26 | -------------------------------------------------------------------------------- /.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 | .package-lock.json 14 | 15 | # misc 16 | .DS_Store 17 | .env.local 18 | .env.development.local 19 | .env.test.local 20 | .env.production.local 21 | 22 | npm-debug.log* 23 | yarn-debug.log* 24 | yarn-error.log* -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Read this before making any contributions to this project 2 | --- 3 |
4 | 5 | **Fork** this repository and preferably create a new branch (using ```git checkout -b ${BRANCH_NAME}```) on your system and start working on the change there. 6 | 7 | - If there's any grammatical mistake, changes in the documentation, raise a PR for the same. 8 | 9 | - If you want to suggest ideas on which others can work upon, please add them to the [IDEAS.md](IDEAS.md), so that people can use them to create new and interesting stuff. 10 | 11 | - If there's a bug fix, code improvements or feature addition, kindly open an **Issue** first, so that others can also pick up that issue and start working on it. 12 | 13 |
14 | 15 | ### Guidelines for PRs and Issues 16 | --- 17 |
18 | 19 | - Please attach necessary screenshots and try to give a thorough description of the change you are proposing. This will ensure an easy and quick merging of your PR. 20 | 21 | - Please try not to delete any currently implemented functionality or add additional packages for minor changes. 22 | 23 |
24 | 25 | **Make sure you add yourself to the Contributors list using [@all-contributors](https://github.com/all-contributors/all-contributors)** 26 | 27 |
28 | 29 | ### *Happy Coding!* 😃 30 | -------------------------------------------------------------------------------- /ideas.md: -------------------------------------------------------------------------------- 1 | ## Fire a spark, and watch it turn into flames! 2 | --- 3 | 4 |
5 | 6 | Please add your ideas at the end of the list 7 | 8 |
9 | 10 | - *Sample format - Here's an idea to work upon, try creating a carousel using Vanilla JS and CSS. (Add some links for reference)* 11 | 12 |
13 | 14 | ### Weather App 15 | The weather app is one of the best beginner-friendly projects to get started with React (also for any other programming languages.) 16 |
17 | Links for references 18 | - [https://levelup.gitconnected.com/weather-app-in-react-js-ca668ae86b14](https://levelup.gitconnected.com/weather-app-in-react-js-ca668ae86b14) 19 | - [https://www.youtube.com/watch?v=GuA0_Z1llYU](https://www.youtube.com/watch?v=GuA0_Z1llYU) 20 | 21 | ### Moving Hamburger Animation 22 | Moving Hamburger Animation is a good project to hold command on React. 23 |
24 | Links for references 25 | - [https://codepen.io/FlorinPop17/full/wvvZvWp](https://codepen.io/FlorinPop17/full/wvvZvWp) 26 | - [https://www.youtube.com/watch?v=dIyVTjJAkLw](https://www.youtube.com/watch?v=dIyVTjJAkLw) 27 | - [https://hamburger-react.netlify.app/](https://hamburger-react.netlify.app/) 28 | 29 | ### Portfolio Creator 30 | Portfolio creator allows the user to create own portfolio by adding up their own data and later on by clicking download it can be also be downloaded in pdf form.) 31 |
32 | Links for references 33 | - [https://github.com/Aqsa48/Aqsa48-MyPortfolio](https://github.com/Aqsa48/Aqsa48-MyPortfolio) 34 | - [https://aqsa-portfolio.herokuapp.com/](https://aqsa-portfolio.herokuapp.com/) 35 | 36 | ### Card-Memory-Game 37 | Card memory is a game where you have to click on a card to see what image is underneath it and try to find the matching image underneath the other cards 38 |
39 | Links for references 40 | - [Wikipedia](https://en.wikipedia.org/wiki/Concentration_(card_game)) 41 | - [Flip — card memory game](https://codepen.io/zerospree/full/bNWbvW) 42 | - [Memory Game](https://jdmedlock.github.io/memorygame/) 43 | - [SMB3 Memory Card Game](https://codepen.io/hexagoncircle/full/OXBJxV) -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-projects", 3 | "version": "0.1.0", 4 | "homepage": "https://DemonDaddy22.github.io/all-about-reactJS/", 5 | "private": true, 6 | "dependencies": { 7 | "@material-ui/core": "^4.12.1", 8 | "@material-ui/icons": "^4.9.1", 9 | "@material-ui/lab": "^4.0.0-alpha.56", 10 | "@testing-library/jest-dom": "^4.2.4", 11 | "@testing-library/react": "^9.3.2", 12 | "@testing-library/user-event": "^7.1.2", 13 | "react": "^16.13.1", 14 | "react-dom": "^16.13.1", 15 | "react-router-dom": "^5.2.0", 16 | "react-scripts": "3.4.3" 17 | }, 18 | "scripts": { 19 | "start": "react-scripts start", 20 | "build": "react-scripts build", 21 | "test": "react-scripts test", 22 | "eject": "react-scripts eject", 23 | "predeploy": "npm run build", 24 | "deploy": "gh-pages -d build" 25 | }, 26 | "eslintConfig": { 27 | "extends": "react-app" 28 | }, 29 | "browserslist": { 30 | "production": [ 31 | ">0.2%", 32 | "not dead", 33 | "not op_mini all" 34 | ], 35 | "development": [ 36 | "last 1 chrome version", 37 | "last 1 firefox version", 38 | "last 1 safari version" 39 | ] 40 | }, 41 | "devDependencies": { 42 | "babel-eslint": "^10.1.0", 43 | "eslint-plugin-react": "^7.21.4", 44 | "eslint-plugin-react-hooks": "^4.1.2", 45 | "gh-pages": "^3.1.0" 46 | } 47 | } -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DemonDaddy22/all-about-reactJS/74c9c9d79ed38359cfc96064979d2bd38eba4d47/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 15 | 16 | 25 | 26 | All About ReactJS 27 | 28 | 29 | 30 | 31 |
32 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DemonDaddy22/all-about-reactJS/74c9c9d79ed38359cfc96064979d2bd38eba4d47/public/logo192.png -------------------------------------------------------------------------------- /public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DemonDaddy22/all-about-reactJS/74c9c9d79ed38359cfc96064979d2bd38eba4d47/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/App.css: -------------------------------------------------------------------------------- 1 | .App { 2 | text-align: center; 3 | } 4 | 5 | .App-header { 6 | background-color: #282c34; 7 | min-height: 100vh; 8 | display: flex; 9 | flex-direction: column; 10 | align-items: center; 11 | justify-content: center; 12 | font-size: calc(10px + 2vmin); 13 | color: white; 14 | } 15 | 16 | .App-link { 17 | color: #61dafb; 18 | } -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import './App.css'; 3 | import { HashRouter } from 'react-router-dom'; 4 | import Routes from './routes'; 5 | 6 | export default class App extends React.Component { 7 | 8 | render = () => { 9 | return ; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/components/AlternateGrid/components/GridCell/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import classes from './styles.module.css'; 4 | 5 | const GridCell = React.memo(({ children, className }) =>
6 | {children} 7 |
); 8 | 9 | export default GridCell; -------------------------------------------------------------------------------- /src/components/AlternateGrid/components/GridCell/styles.module.css: -------------------------------------------------------------------------------- 1 | .cell { 2 | flex: 1; 3 | } -------------------------------------------------------------------------------- /src/components/AlternateGrid/components/GridRow/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { getPathValue, isEmptyObject } from '../../../../utils'; 3 | import GridCell from '../GridCell'; 4 | 5 | import classes from './styles.module.css'; 6 | 7 | const GridRow = React.memo(({ row, firstOrderClass, secondOrderClass }) =>
8 | 9 |
10 | {row?.model &&
{row.model}
} 11 | {row?.specifications && !isEmptyObject(row.specifications) && Object.keys(row.specifications).map((entry, index) =>
12 |
{entry}
13 |
{row.specifications[entry]}
14 |
)} 15 |
16 |
17 | 18 | Lamborghini 19 | 20 |
); 21 | 22 | export default GridRow; -------------------------------------------------------------------------------- /src/components/AlternateGrid/components/GridRow/styles.module.css: -------------------------------------------------------------------------------- 1 | .row { 2 | display: flex; 3 | flex-direction: column; 4 | background-color: var(--card-bg); 5 | transition: background-color 0.5s ease-in-out; 6 | } 7 | 8 | .infoContainer { 9 | padding: 1rem; 10 | display: flex; 11 | flex-direction: column; 12 | align-items: center; 13 | justify-content: center; 14 | } 15 | 16 | .model { 17 | margin-bottom: 1rem; 18 | font-weight: 600; 19 | font-size: 1.5rem; 20 | letter-spacing: -0.25px; 21 | color: var(--theme-color); 22 | transition: color 0.5s ease-in-out; 23 | } 24 | 25 | .specification { 26 | margin: 0.5rem 0; 27 | text-align: center; 28 | } 29 | 30 | .title { 31 | font-size: 0.8rem; 32 | font-weight: 600; 33 | letter-spacing: -0.25px; 34 | color: var(--text-prominent); 35 | transition: color 0.5s ease-in-out; 36 | } 37 | 38 | .value { 39 | letter-spacing: 0.25px; 40 | color: var(--text); 41 | transition: color 0.5s ease-in-out; 42 | } 43 | 44 | .image { 45 | height: 100%; 46 | width: 100%; 47 | } 48 | 49 | @media (min-width: 768px) { 50 | .row { 51 | flex-direction: row; 52 | } 53 | } -------------------------------------------------------------------------------- /src/components/AlternateGrid/config.js: -------------------------------------------------------------------------------- 1 | export const data = [ 2 | { 3 | id: 3256405204, 4 | model: 'Huracán Evo Spyder', 5 | specifications: { 6 | 'Max Power': '640 CV (470 kW) @ 8.000 rpm', 7 | 'Top Speed': '325 km/h (202 mph)', 8 | 'Acceleration': '3.1s (0-100 km/h)', 9 | 'Engine': 'V10 90° IDS, 40 valves', 10 | 'Displacement': '5,204 cm³' 11 | }, 12 | image: 'https://images.hgmsites.net/hug/lamborghini-huracan-evo-rear-wheel-drive-spyder_100746276_h.jpg' 13 | }, 14 | { 15 | id: 3505666498, 16 | model: 'Aventador SVJ', 17 | specifications: { 18 | 'Max Power': '566 kW (770 CV) at 8.500 rpm', 19 | 'Top Speed': '>350 km/h', 20 | 'Acceleration': '2.8s (0-100 km/h)', 21 | 'Engine': 'V12, 60°, MPI', 22 | 'Displacement': '6,498 cm³' 23 | }, 24 | image: 'https://img.indianautosblog.com/2019/01/21/lamborghini-aventador-svj-front-three-quarters-gre-2539.jpg' 25 | }, 26 | { 27 | id: 3056503996, 28 | model: 'Urus Pearl Capsule', 29 | specifications: { 30 | 'Max Power': '650 CV (478 kW) @ 6,000 rpm', 31 | 'Top Speed': '305 km/h', 32 | 'Acceleration': '3.6s (0-100 km/h)', 33 | 'Max Engine Speed': '6,800 rpm', 34 | 'Displacement': '3,996 cm³' 35 | }, 36 | image: 'https://cdn.motor1.com/images/mgl/7JXYo/s1/urus.jpg' 37 | }, 38 | { 39 | id: 3508196498, 40 | model: 'Sián FKP 37', 41 | specifications: { 42 | 'Max Power': '819 CV (602 kW) @ 8,500 rpm', 43 | 'Top Speed': '>350 km/h (217 mph)', 44 | 'Acceleration': '<2.8s (0-100 km/h)', 45 | 'Engine': 'V12, 60°, MPI', 46 | 'Displacement': '6,498 cm³' 47 | }, 48 | image: 'https://cdnb.artstation.com/p/assets/images/images/021/633/263/large/russ-schwenkler-lamborghini-sian-01.jpg?1572395705' 49 | }, 50 | { 51 | id: 120105204, 52 | model: 'Huracán GT3 Evo', 53 | specifications: { 54 | 'Aerodynamics': 'Manually adjustable rear wing', 55 | 'Fuel Tank': '120 liters, FT3 Spec', 56 | 'Engine': '10 cylinders engine (90° V angle) naturally aspirated, gasoline direct injection IDS, dry sump lubrication', 57 | 'Displacement': '5,204 cm³' 58 | }, 59 | image: 'https://www.britishgt.com/timthumb.php?w=1400&src=%2Fimages%2Fcars%2F18-01.jpg' 60 | }, 61 | { 62 | id: 3106405204, 63 | model: 'Huracán STO', 64 | specifications: { 65 | 'Max Power': '470/640 at 8000 rpm', 66 | 'Top Speed': '310 km/h', 67 | 'Acceleration': '3.0s (0-100 km/h)', 68 | 'Engine': 'V10, 90°, MPI + DSI', 69 | 'Displacement': '5,204 cm³' 70 | }, 71 | image: 'https://www.automobilemag.com/uploads/sites/11/2020/11/2021-Lamborghini-Huracan-STO-1a.jpg' 72 | } 73 | ]; -------------------------------------------------------------------------------- /src/components/AlternateGrid/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import classes from './styles.module.css'; 4 | import Page from '../../ui-components/Page'; 5 | import { data } from './config'; 6 | import GridRow from './components/GridRow'; 7 | import { themed } from '../../utils/theme'; 8 | import { GREEN_400, GREEN_600 } from '../../resources/colors'; 9 | import { getPathValue } from '../../utils'; 10 | 11 | export default class AlternateGrid extends React.Component { 12 | 13 | updateComponent = (refresher = null) => refresher && this.setState({ refresher }); 14 | 15 | render = () => { 16 | 17 | return 18 |
LamboLove
19 |
20 | {data.map((row, index) => )} 22 |
23 |
; 24 | } 25 | } -------------------------------------------------------------------------------- /src/components/AlternateGrid/styles.module.css: -------------------------------------------------------------------------------- 1 | .title { 2 | margin-top: 3rem; 3 | font-size: 2rem; 4 | font-weight: 600; 5 | text-align: center; 6 | letter-spacing: -0.25px; 7 | color: var(--text-prominent); 8 | transition: color 0.5s ease-in-out; 9 | } 10 | 11 | .container { 12 | margin-top: 2rem; 13 | } 14 | 15 | .firstOrder, .secondOrder { 16 | order: inherit; 17 | } 18 | 19 | @media (min-width: 768px) { 20 | .title { 21 | font-size: 3rem; 22 | } 23 | .container { 24 | width: 90%; 25 | margin: 3rem auto; 26 | } 27 | .firstOrder { 28 | order: 1; 29 | } 30 | .secondOrder { 31 | order: 2; 32 | } 33 | } 34 | 35 | @media (min-width: 1200px) { 36 | .title { 37 | font-size: 4rem; 38 | } 39 | .container { 40 | width: 75%; 41 | } 42 | } -------------------------------------------------------------------------------- /src/components/BillGenerator/components/Bill/styles.module.css: -------------------------------------------------------------------------------- 1 | .title { 2 | margin-top: 1rem; 3 | font-weight: 100; 4 | font-size: 1.5rem; 5 | text-align: center; 6 | letter-spacing: 2px; 7 | text-transform: uppercase; 8 | color: var(--theme-color); 9 | transition: color 0.5s ease-in-out; 10 | } 11 | 12 | .noData { 13 | position: absolute; 14 | top: 50%; 15 | left: 50%; 16 | max-width: 90%; 17 | font-weight: 100; 18 | font-size: 1.5rem; 19 | text-align: center; 20 | letter-spacing: 0.4px; 21 | word-break: break-word; 22 | transform: translate(-50%, -50%); 23 | color: var(--text-obscure); 24 | transition: color 0.5s ease-in-out; 25 | } 26 | 27 | .contentWrapper { 28 | flex: 1; 29 | display: flex; 30 | flex-direction: column; 31 | } 32 | 33 | .billWrapper { 34 | flex: 1; 35 | margin: 2rem; 36 | } 37 | 38 | .cartItemWrapper { 39 | margin-bottom: 0.25rem; 40 | display: flex; 41 | } 42 | 43 | .cartItemWrapper:last-child { 44 | margin-bottom: 0; 45 | } 46 | 47 | .item { 48 | margin-right: 4px; 49 | color: var(--text); 50 | transition: color ease-in-out 0.5s; 51 | } 52 | 53 | .quantity { 54 | flex: 1; 55 | font-size: 0.9rem; 56 | color: var(--text-obscurer); 57 | transition: color ease-in-out 0.5s; 58 | } 59 | 60 | .price { 61 | font-weight: 600; 62 | color: var(--text-prominent); 63 | transition: color ease-in-out 0.5s; 64 | } 65 | 66 | .divider { 67 | margin: 1rem auto; 68 | border-top: 1px dashed var(--text-obscurer); 69 | transition: border-top ease-in-out 0.5s; 70 | } 71 | 72 | .amountWrapper { 73 | display: flex; 74 | } 75 | 76 | .amountText { 77 | flex: 1; 78 | composes: item; 79 | } 80 | 81 | .totalAmountWrapper { 82 | display: flex; 83 | color: #FFF; 84 | font-size: 1.25rem; 85 | padding: 0.75rem 1rem; 86 | border-bottom-left-radius: 4px; 87 | border-bottom-right-radius: 4px; 88 | background-color: var(--theme-color); 89 | transition: background-color ease-in-out 0.5s; 90 | } 91 | 92 | .totalAmountText { 93 | flex: 1; 94 | } 95 | 96 | .totalAmount { 97 | font-weight: 600; 98 | } 99 | 100 | @media (min-width: 768px) { 101 | .cartItemWrapper { 102 | margin-bottom: 0.5rem; 103 | } 104 | } -------------------------------------------------------------------------------- /src/components/BillGenerator/components/TacoMenu/styles.module.css: -------------------------------------------------------------------------------- 1 | .menuTitle { 2 | font-size: 2rem; 3 | font-weight: 600; 4 | letter-spacing: -0.2px; 5 | text-align: center; 6 | color: var(--theme-color); 7 | transition: color 0.5s ease-in-out; 8 | } 9 | 10 | .divider { 11 | border: 0; 12 | height: 1px; 13 | margin: 0.5rem 0; 14 | background-color: var(--tag-color); 15 | transition: background-color 0.5s ease-in-out; 16 | } 17 | 18 | .categoryContainer { 19 | margin-top: 1rem; 20 | } 21 | 22 | .categoryTitle { 23 | font-size: 1rem; 24 | font-weight: 600; 25 | color: var(--text-obscure); 26 | transition: color 0.5s ease-in-out; 27 | } 28 | 29 | .categoryContent { 30 | margin-top: 1rem; 31 | display: grid; 32 | grid-template-columns: 1fr; 33 | gap: 1rem; 34 | } 35 | 36 | .adornmentLabel { 37 | margin-left: 0.5rem; 38 | font-size: 0.8rem; 39 | color: var(--text-obscurer); 40 | transition: color 0.5s ease-in-out; 41 | } 42 | 43 | .addOnContainer { 44 | display: flex; 45 | align-items: center; 46 | padding: 0.5rem; 47 | padding-left: 1rem; 48 | border-radius: 4px; 49 | border: 1px solid var(--border-color); 50 | transition: border 0.5s ease-in-out; 51 | } 52 | 53 | .addOn { 54 | flex: 1; 55 | font-size: 0.9rem; 56 | color: var(--text); 57 | transition: color 0.5s ease-in-out; 58 | } 59 | 60 | .addOnPrice { 61 | font-weight: 400; 62 | font-size: 0.8rem; 63 | color: var(--text-obscurer); 64 | } 65 | 66 | @media (min-width: 900px) { 67 | .categoryContent { 68 | grid-template-columns: 1fr 1fr; 69 | } 70 | } -------------------------------------------------------------------------------- /src/components/BillGenerator/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import classes from './styles.module.css'; 4 | import Page from '../../ui-components/Page'; 5 | import TacoMenu from './components/TacoMenu'; 6 | import Bill from './components/Bill'; 7 | 8 | export default class BillGenerator extends React.Component { 9 | 10 | state = { 11 | tacos: { 12 | beans: { name: '3 Beans Taco', price: 2.99, count: 0 }, 13 | chicken: { name: 'Grilled Chicken Breast', price: 3.49, count: 0 }, 14 | fish: { name: 'Crispy Fish Taco', price: 3.49, count: 0 }, 15 | pork: { name: 'Pulled Pork Taco', price: 3.99, count: 0 } 16 | }, 17 | sides: { 18 | vegQuesadilla: { name: 'Veg Quesadilla', price: 1.99, count: 0 }, 19 | chickenQuesadilla: { name: 'Chicken Quesadilla', price: 2.49, count: 0 }, 20 | fries: { name: 'Mexican Fries', price: 2.49, count: 0 }, 21 | soda: { name: 'Soda', price: 2.49, count: 0 } 22 | }, 23 | addOns: { 24 | cheese: false, 25 | beans: false 26 | } 27 | } 28 | 29 | handleTacoChange = (e, taco) => { 30 | const count = parseInt(e.target.value); 31 | if (count < 0) return; 32 | this.setState(prevState => ({ tacos: { ...prevState.tacos, [taco]: { ...prevState['tacos'][taco], count } } })); 33 | } 34 | 35 | handleSideChange = (e, side) => { 36 | const count = parseInt(e.target.value); 37 | if (count < 0) return; 38 | this.setState(prevState => ({ sides: { ...prevState.sides, [side]: { ...prevState['sides'][side], count } } })); 39 | } 40 | 41 | handleAddOnChange = (e, addOn) => this.setState(prevState => ({ addOns: { ...prevState.addOns, [addOn]: !prevState['addOns'][addOn] } })); 42 | 43 | render = () => { 44 | const { tacos, sides, addOns } = this.state; 45 | 46 | return 47 |
Taco Shack
48 |
For the Mexican inside you!
49 |
50 |
51 | 53 |
54 |
55 |
56 |
57 | 58 |
59 |
60 |
61 | } 62 | } -------------------------------------------------------------------------------- /src/components/BillGenerator/styles.module.css: -------------------------------------------------------------------------------- 1 | .title { 2 | font-size: 3rem; 3 | font-weight: 600; 4 | letter-spacing: -0.2px; 5 | margin-top: 4rem; 6 | text-align: center; 7 | color: var(--text-prominent); 8 | transition: color 0.5s ease-in-out; 9 | } 10 | 11 | .subtitle { 12 | font-weight: 100; 13 | text-align: center; 14 | letter-spacing: -0.2px; 15 | color: var(--text-obscure); 16 | transition: color 0.5s ease-in-out; 17 | } 18 | 19 | .container { 20 | width: 90%; 21 | margin: 2rem auto; 22 | display: flex; 23 | flex-direction: column; 24 | } 25 | 26 | .menuContainer, .billContainer { 27 | position: relative; 28 | padding: 0.5rem; 29 | border-radius: 4px; 30 | box-sizing: border-box; 31 | box-shadow: var(--card-shadow); 32 | background-color: var(--card-bg); 33 | border: 1px solid var(--border-color); 34 | transition: box-shadow 0.5s ease-in-out, background-color 0.5s ease-in-out, border 0.5s ease-in-out; 35 | } 36 | 37 | .menuContainer { 38 | margin-bottom: 2rem; 39 | } 40 | 41 | .billContainer { 42 | padding: 0; 43 | min-height: 15rem; 44 | display: flex; 45 | flex-direction: column; 46 | } 47 | 48 | .circle { 49 | position: absolute; 50 | top: 50%; 51 | width: 2rem; 52 | height: 2rem; 53 | border-radius: 50%; 54 | transform: translateY(-50%); 55 | background-color: var(--page-bg); 56 | transition: background-color 0.5s ease-in-out; 57 | } 58 | 59 | .leftCircle { 60 | left: -0.8rem; 61 | box-shadow: inset -2px 0px 0px 1px rgba(125, 125, 125, 0.1), inset -2px 0px 0px 0px rgba(125, 125, 125, 0.08), inset -2px 0px 1px 0px rgba(125, 125, 125, 0.06); 62 | composes: circle; 63 | } 64 | 65 | .rightCircle { 66 | right: -0.8rem; 67 | box-shadow: inset 2px 0px 0px 1px rgba(125, 125, 125, 0.1), inset 2px 0px 0px 0px rgba(125, 125, 125, 0.08), inset 2px 0px 1px 0px rgba(125, 125, 125, 0.06); 68 | composes: circle; 69 | } 70 | 71 | @media (min-width: 768px) { 72 | .title { 73 | font-size: 4rem; 74 | margin-top: 3rem; 75 | } 76 | .subtitle { 77 | font-size: 1.25rem; 78 | } 79 | .container { 80 | flex-direction: row; 81 | } 82 | .billContainer { 83 | flex: 1; 84 | } 85 | .menuContainer { 86 | flex: 1; 87 | padding: 0.75rem; 88 | margin-bottom: 0; 89 | margin-right: 1rem; 90 | } 91 | } 92 | 93 | @media (min-width: 900px) { 94 | .container { 95 | margin-top: 4rem; 96 | } 97 | .billContainer { 98 | flex: 2; 99 | } 100 | .menuContainer { 101 | flex: 3; 102 | padding: 1rem; 103 | margin-right: 1.5rem; 104 | } 105 | } 106 | 107 | @media (min-width: 1200px) { 108 | .billContainer { 109 | flex: 1; 110 | } 111 | .menuContainer { 112 | flex: 2; 113 | margin-right: 2rem; 114 | } 115 | } -------------------------------------------------------------------------------- /src/components/Calculator/components/InputButton/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { BLACK, GREY_30, GREY_40, GREY_60, GREY_80, ORANGE_400, ORANGE_500, WHITE } from '../../../../resources/colors'; 3 | import { themed } from '../../../../utils/theme'; 4 | 5 | import classes from './styles.module.css'; 6 | 7 | export default class InputButton extends React.Component { 8 | 9 | render = () => { 10 | const { flex, buttonContent, position } = this.props; 11 | let buttonColor, textColor; 12 | 13 | switch(position) { 14 | case 'top': 15 | buttonColor = themed(GREY_40, GREY_60); 16 | textColor = themed(WHITE, BLACK); 17 | break 18 | 19 | case 'right': 20 | buttonColor = themed(ORANGE_500, ORANGE_400); 21 | textColor = themed(BLACK, WHITE); 22 | break; 23 | 24 | case 'center': default: 25 | buttonColor = themed(GREY_80, GREY_30); 26 | textColor = themed(BLACK, WHITE); 27 | break; 28 | } 29 | 30 | return
this.props.onButtonClick(buttonContent)}>{buttonContent}
31 | } 32 | } -------------------------------------------------------------------------------- /src/components/Calculator/components/InputButton/styles.module.css: -------------------------------------------------------------------------------- 1 | .inputButton { 2 | font-weight: 600; 3 | border-radius: 20px; 4 | padding: 6px 14px; 5 | margin-left: 8px; 6 | border: 1px solid var(--border-color); 7 | box-shadow: var(--card-shadow); 8 | transition: color 0.5s ease-in-out, border 0.5s ease-in-out, background-color 0.5s ease-in-out, box-shadow 0.5s ease-in-out, filter 0.2s ease-in-out; 9 | -webkit-tap-highlight-color: transparent; 10 | -webkit-touch-callout: none; 11 | -webkit-user-select: none; 12 | -khtml-user-select: none; 13 | -moz-user-select: none; 14 | -ms-user-select: none; 15 | user-select: none; 16 | } 17 | 18 | .inputButton:hover { 19 | cursor: pointer; 20 | box-shadow: var(--shadow); 21 | filter: brightness(0.9); 22 | } 23 | 24 | .inputButton:nth-child(4n+1) { 25 | margin-left: 0; 26 | } -------------------------------------------------------------------------------- /src/components/Calculator/components/InputRow/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import InputButton from '../InputButton'; 3 | 4 | import classes from './styles.module.css'; 5 | 6 | export default class extends React.Component { 7 | 8 | render = () => { 9 | const { row, index } = this.props; 10 | 11 | return
12 | {row.map((entry, i) => )} 13 |
; 14 | } 15 | } -------------------------------------------------------------------------------- /src/components/Calculator/components/InputRow/styles.module.css: -------------------------------------------------------------------------------- 1 | .rowContainer { 2 | flex: 1; 3 | display: flex; 4 | align-items: center; 5 | margin-top: 0.75rem; 6 | } 7 | 8 | .rowContainer:first-child { 9 | margin-top: 0; 10 | } -------------------------------------------------------------------------------- /src/components/Calculator/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import classes from './styles.module.css'; 4 | import Page from '../../ui-components/Page'; 5 | import { rows } from './rows'; 6 | import InputRow from './components/InputRow'; 7 | import SnackBar from '../../ui-components/SnackBar'; 8 | import { isEmptyString } from '../../utils'; 9 | 10 | const operators = ['+', '-', '×', '÷', '%']; 11 | const operatorMap = Object.freeze({ 12 | '+': '+', 13 | '-': '-', 14 | '×': '*', 15 | '÷': '/', 16 | '%': '%' 17 | }); 18 | 19 | export default class Calculator extends React.Component { 20 | 21 | state = { 22 | expression: '0' 23 | } 24 | 25 | // invoked whenever theme changes in order to trigger themed function call 26 | updateComponent = (refresher = null) => refresher && this.setState({ refresher }); 27 | 28 | onButtonClick = button => { 29 | let expression = this.state.expression === '0' ? '' : this.state.expression; 30 | switch (button) { 31 | case '=': 32 | // evaluate the expression 33 | try { 34 | // eval is not recommended as it can execute any expression which is passed on as value 35 | // eslint-disable-next-line 36 | expression = eval(expression); 37 | this.setState({ expression: `${expression}` }); 38 | } catch (e) { 39 | const snack = { 40 | snackOpen: true, 41 | snackMessage: e.message, 42 | snackSeverity: 'error' 43 | } 44 | this.setState({ snack }); 45 | } finally { 46 | break; 47 | } 48 | 49 | case 'AC': 50 | this.clearExpression(); 51 | break; 52 | 53 | case 'DEL': 54 | // removes the trailing character 55 | expression = !isEmptyString(expression) ? expression.slice(0, expression.length - 1) : ''; 56 | // if the string becomes empty after removal, replace with 0 57 | expression = isEmptyString(expression) ? '0' : expression; 58 | this.setState({ expression }); 59 | break; 60 | 61 | default: 62 | // add any other input to the expression string 63 | if (this.isOperator(button)) expression += this.getOperator(button); 64 | else expression += button; 65 | this.setState({ expression }); 66 | } 67 | } 68 | 69 | isOperator = button => operators.includes(button); 70 | 71 | getOperator = button => operatorMap[button]; 72 | 73 | clearExpression = () => this.setState({ expression: '0' }); 74 | 75 | handleSnackClose = () => this.setState({ snack: { ...this.state.snack, snackOpen: false } }); 76 | 77 | render = () => 78 |
79 |
80 |
81 |
{this.state.expression}
82 |
83 | {rows.map((row, index) => )} 84 |
85 |
86 | {this.state?.snack?.snackMessage && } 87 |
88 | } -------------------------------------------------------------------------------- /src/components/Calculator/rows.js: -------------------------------------------------------------------------------- 1 | export const rows = [ 2 | [{ flex: 1, buttonContent: 'AC', position: 'top' }, { flex: 1, buttonContent: 'DEL', position: 'top' }, { flex: 1, buttonContent: '%', position: 'top' }, { flex: 1, buttonContent: '÷', position: 'right' }], 3 | [{ flex: 1, buttonContent: '7', position: 'center' }, { flex: 1, buttonContent: '8', position: 'center' }, { flex: 1, buttonContent: '9', position: 'center' }, { flex: 1, buttonContent: '×', position: 'right' }], 4 | [{ flex: 1, buttonContent: '4', position: 'center' }, { flex: 1, buttonContent: '5', position: 'center' }, { flex: 1, buttonContent: '6', position: 'center' }, { flex: 1, buttonContent: '-', position: 'right' }], 5 | [{ flex: 1, buttonContent: '1', position: 'center' }, { flex: 1, buttonContent: '2', position: 'center' }, { flex: 1, buttonContent: '3', position: 'center' }, { flex: 1, buttonContent: '+', position: 'right' }], 6 | [{ flex: '2 1 11%', buttonContent: '0', position: 'center' }, { flex: 1, buttonContent: '.', position: 'center' }, { flex: 1, buttonContent: '=', position: 'right' }], 7 | ]; -------------------------------------------------------------------------------- /src/components/Calculator/styles.module.css: -------------------------------------------------------------------------------- 1 | .calculatorContainer { 2 | display: flex; 3 | align-items: center; 4 | justify-content: center; 5 | min-height: inherit; 6 | } 7 | 8 | .calculatorWrapper { 9 | width: 80%; 10 | padding: 1rem; 11 | border-radius: 6px; 12 | border: 1px solid var(--border-color); 13 | box-shadow: var(--shadow-opposite); 14 | transition: border 0.5s ease-in-out, box-shadow 0.5s ease-in-out; 15 | } 16 | 17 | .displayWrapper { 18 | padding: 1.5rem; 19 | margin-bottom: 16px; 20 | border-radius: 6px; 21 | background-color: var(--text-obscure); 22 | transition: background-color 0.5s ease-in-out; 23 | } 24 | 25 | .displayText { 26 | display: flex; 27 | justify-content: flex-end; 28 | word-break: break-all; 29 | white-space: normal; 30 | font-size: 1.25rem; 31 | font-weight: 600; 32 | letter-spacing: 6px; 33 | color: var(--text-contrast); 34 | transition: color 0.5s ease-in-out; 35 | } 36 | 37 | @media (min-width: 768px) { 38 | .calculatorWrapper { 39 | width: 40%; 40 | } 41 | } 42 | 43 | @media (min-width: 900px) { 44 | .calculatorWrapper { 45 | width: 25%; 46 | } 47 | } -------------------------------------------------------------------------------- /src/components/Carousel/components/CarouselCard/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import classes from './styles.module.css'; 4 | 5 | const CarouselCard = React.memo(({ img, style, className }) => { 6 | 7 | return carousel; 8 | }); 9 | 10 | export default CarouselCard; -------------------------------------------------------------------------------- /src/components/Carousel/components/CarouselCard/styles.module.css: -------------------------------------------------------------------------------- 1 | .img { 2 | width: 100%; 3 | height: 100%; 4 | border-radius: inherit; 5 | } -------------------------------------------------------------------------------- /src/components/Carousel/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import classes from './styles.module.css'; 4 | import Page from '../../ui-components/Page'; 5 | import CarouselCard from './components/CarouselCard'; 6 | import ChevronLeftRoundedIcon from '@material-ui/icons/ChevronLeftRounded'; 7 | import ChevronRightRoundedIcon from '@material-ui/icons/ChevronRightRounded'; 8 | 9 | const images = ['https://images.unsplash.com/photo-1432847712612-926caafaa802?ixlib=rb-1.2.1&auto=format&fit=crop&w=2850&q=80', 10 | 'https://images.unsplash.com/photo-1513151233558-d860c5398176?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=2850&q=80', 11 | 'https://images.unsplash.com/photo-1488229297570-58520851e868?ixlib=rb-1.2.1&auto=format&fit=crop&w=2849&q=80', 12 | 'https://images.unsplash.com/photo-1541701494587-cb58502866ab?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=2850&q=80', 13 | 'https://images.unsplash.com/photo-1535478044878-3ed83d5456ef?ixlib=rb-1.2.1&auto=format&fit=crop&w=2869&q=80', 14 | 'https://images.unsplash.com/photo-1484268234627-2278797bec04?ixlib=rb-1.2.1&auto=format&fit=crop&w=2850&q=80']; 15 | 16 | export default class Carousel extends React.Component { 17 | 18 | state = { 19 | index: 0 20 | } 21 | 22 | handleBtnClick = index => this.setState({ index: index < 0 ? images.length - 1 : index === images.length ? 0 : index }); 23 | 24 | render = () => 25 |
26 |
27 |
this.handleBtnClick(this.state.index - 1)}>
28 |
this.handleBtnClick(this.state.index + 1)}>
29 |
30 | {images.map((image, index) => )} 31 |
32 |
33 | {images.map((image, index) =>
this.handleBtnClick(index)}>
)} 34 |
35 |
36 |
37 |
38 | } -------------------------------------------------------------------------------- /src/components/Carousel/styles.module.css: -------------------------------------------------------------------------------- 1 | .wrapper { 2 | display: flex; 3 | align-items: center; 4 | justify-content: center; 5 | flex-direction: column; 6 | min-height: inherit; 7 | } 8 | 9 | .carouselWrapper { 10 | position: relative; 11 | width: 90%; 12 | height: 40vh; 13 | } 14 | 15 | .carouselContainer { 16 | width: 100%; 17 | height: 100%; 18 | display: flex; 19 | overflow: hidden; 20 | border-radius: 6px; 21 | box-shadow: var(--card-shadow-hover); 22 | transition: box-shadow 0.5s ease-in-out; 23 | } 24 | 25 | .slide { 26 | transition: transform 0.3s ease-in-out; 27 | } 28 | 29 | .slideControl { 30 | z-index: 100; 31 | display: flex; 32 | cursor: pointer; 33 | position: absolute; 34 | top: 50%; 35 | padding: 0.25rem; 36 | border-radius: 10%; 37 | transform: translateY(-50%); 38 | color: #fff; 39 | background-color: rgba(0, 0, 0, 0.25); 40 | transition: background-color ease-in-out 0.15s; 41 | } 42 | 43 | .slideControl:hover { 44 | background-color: rgba(0, 0, 0, 0.4); 45 | } 46 | 47 | .leftControl { 48 | left: 10px; 49 | } 50 | 51 | .rightControl { 52 | right: 10px; 53 | } 54 | 55 | .roundButtonsWrapper { 56 | left: 50%; 57 | width: 60%; 58 | z-index: 100; 59 | bottom: 12px; 60 | display: flex; 61 | flex-wrap: wrap; 62 | position: absolute; 63 | justify-content: center; 64 | transform: translateX(-50%); 65 | } 66 | 67 | .roundButton { 68 | box-sizing: border-box; 69 | height: 1rem; 70 | width: 1rem; 71 | cursor: pointer; 72 | border-radius: 50%; 73 | margin: 4px 4px 0 0; 74 | border: 2px solid #fff; 75 | transition: background-color ease-in-out 0.15s; 76 | } 77 | 78 | .activeButton { 79 | cursor: auto; 80 | background-color: #fff; 81 | } 82 | 83 | @media (min-width: 768px) { 84 | .carouselWrapper { 85 | width: 80%; 86 | height: 60vh; 87 | } 88 | .carouselContainer { 89 | width: 100%; 90 | } 91 | } 92 | 93 | @media (min-width: 1200px) { 94 | .carouselWrapper { 95 | max-width: 80vw; 96 | height: 80vh; 97 | } 98 | } -------------------------------------------------------------------------------- /src/components/ColorPicker/components/ColorCard/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import classes from './styles.module.css'; 3 | import FileCopyOutlinedIcon from '@material-ui/icons/FileCopyOutlined'; 4 | import { copyTextToClipboard } from '../../../../utils'; 5 | 6 | export default class ColorCard extends React.Component { 7 | 8 | onClickCopy = e => { 9 | e.stopPropagation(); 10 | copyTextToClipboard(this.props.colour); 11 | } 12 | 13 | render = () => { 14 | 15 | return
this.props.handleColourChange(this.props.colour)}> 16 |
17 |
{this.props.colour}
18 |
19 | 20 |
21 |
22 |
; 23 | } 24 | } -------------------------------------------------------------------------------- /src/components/ColorPicker/components/ColorCard/styles.module.css: -------------------------------------------------------------------------------- 1 | .card { 2 | height: 10rem; 3 | position: relative; 4 | transition: background-color 0.25s ease-in-out; 5 | } 6 | 7 | .card .copy { 8 | display: none; 9 | } 10 | 11 | .card:hover .copy { 12 | display: flex; 13 | } 14 | 15 | .copy { 16 | bottom: 0px; 17 | right: 0px; 18 | padding: 10px 20px; 19 | position: absolute; 20 | color: #fff; 21 | background: rgba(0, 0, 0, 0.4); 22 | border-top-left-radius: 20px; 23 | border-bottom-right-radius: 10px; 24 | } 25 | 26 | .code { 27 | margin-right: 10px; 28 | opacity: 0.8; 29 | } 30 | 31 | .icon { 32 | cursor: pointer; 33 | opacity: 0.8; 34 | transition: opacity 0.25s ease-in-out; 35 | } 36 | 37 | .icon:hover { 38 | opacity: 1; 39 | } 40 | 41 | 42 | @media (min-width: 768px) { 43 | .card { 44 | border-radius: 10px; 45 | height: 12rem; 46 | } 47 | .card:last-child { 48 | margin-bottom: 2rem; 49 | } 50 | } -------------------------------------------------------------------------------- /src/components/ColorPicker/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import classes from './styles.module.css'; 4 | import Iconbutton from '../../ui-components/Button/Iconbutton'; 5 | import Page from '../../ui-components/Page'; 6 | import { getColours, isColourDark, rgbToHex } from '../../utils'; 7 | import ColorCard from './components/ColorCard'; 8 | import ColorLensRoundedIcon from '@material-ui/icons/ColorLensRounded'; 9 | import Input from '../../ui-components/Input'; 10 | 11 | export default class ColorPicker extends React.Component { 12 | 13 | state = { 14 | colours: [], 15 | selectedColour: null, 16 | headerColour: null 17 | } 18 | 19 | componentDidMount = () => { 20 | this.getColours(); 21 | } 22 | 23 | getColours = () => this.setState({ colours: getColours(9) }); 24 | 25 | handleColourChange = colour => this.setState({ selectedColour: colour }, () => this.updateHeaderColour(colour)); 26 | 27 | updateHeaderColour = colour => this.setState({ headerColour: isColourDark(colour) ? '#fff' : '#000' }); 28 | 29 | render = () => { 30 | const { colours, selectedColour, headerColour } = this.state; 31 | 32 | return 33 |
34 |
35 |
36 | Color Picker 37 |
38 |
39 | } style={{ marginRight: 8 }}> 40 | this.handleColourChange(e.target.value)} rootInputStyles={{ height: '2rem' }} /> 41 |
42 |
43 |
44 | {colours.map((colour, index) => )} 45 |
46 |
47 |
; 48 | } 49 | } -------------------------------------------------------------------------------- /src/components/ColorPicker/styles.module.css: -------------------------------------------------------------------------------- 1 | .headerContainer { 2 | height: 40vh; 3 | display: flex; 4 | flex-direction: column; 5 | align-items: center; 6 | justify-content: center; 7 | background-color: var(--theme-color); 8 | border-bottom: 4px solid var(--text-prominent); 9 | transition: background-color 0.5s ease-in-out, border-bottom 0.5s ease-in-out; 10 | } 11 | 12 | .header { 13 | font-size: 3rem; 14 | font-weight: 600; 15 | letter-spacing: -0.4px; 16 | color: var(--text); 17 | transition: color 0.5s ease-in-out; 18 | } 19 | 20 | .palette { 21 | width: 100%; 22 | height: 10rem; 23 | display: grid; 24 | grid-template-columns: 1fr; 25 | } 26 | 27 | .inputWrapper { 28 | display: flex; 29 | align-items: center; 30 | width: 30%; 31 | margin-top: 0.75rem; 32 | padding: 2px 14px 10px 2px; 33 | border-radius: 6px; 34 | background-color: rgba(0, 0, 0, 0.2); 35 | box-shadow: 0 0 10px rgba(0, 0, 0, 0.2); 36 | } 37 | 38 | @media (min-width: 768px) { 39 | .headerContainer { 40 | border-bottom: none; 41 | } 42 | .palette { 43 | margin-top: 2rem; 44 | width: 80%; 45 | margin-left: auto; 46 | margin-right: auto; 47 | grid-template-columns: 1fr 1fr; 48 | gap: 1rem; 49 | } 50 | .inputWrapper { 51 | max-width: 15%; 52 | } 53 | } 54 | 55 | @media (min-width: 900px) { 56 | .header { 57 | font-size: 5rem; 58 | } 59 | .palette { 60 | width: 75%; 61 | grid-template-columns: 1fr 1fr 1fr; 62 | gap: 2rem; 63 | } 64 | .inputWrapper { 65 | max-width: 10%; 66 | transition: background-color 0.25s ease-in-out, box-shadow 0.25s ease-in-out, transform 0.25s ease-in-out; 67 | } 68 | .inputWrapper:hover { 69 | background-color: rgba(0, 0, 0, 0.3); 70 | box-shadow: 0 0 12px rgba(0, 0, 0, 0.3); 71 | transform: scale(1.02); 72 | } 73 | } -------------------------------------------------------------------------------- /src/components/GitHubProfileViewer/components/Profile/styles.module.css: -------------------------------------------------------------------------------- 1 | .noData { 2 | color: var(--text-obscure); 3 | } 4 | 5 | .profilePic { 6 | display: block; 7 | height: 250px; 8 | width: 250px; 9 | margin: 0 auto; 10 | border-radius: 50%; 11 | } 12 | 13 | .name { 14 | margin-top: 1.5rem; 15 | font-size: 1.75rem; 16 | font-weight: 600; 17 | color: var(--text-prominent); 18 | transition: color 0.5s ease-in-out; 19 | } 20 | 21 | .nameActive:hover { 22 | cursor: pointer; 23 | text-decoration: underline; 24 | } 25 | 26 | .username { 27 | font-size: 1.2rem; 28 | color: var(--text); 29 | transition: color 0.5s ease-in-out; 30 | } 31 | 32 | .bio, .company, .location, .blog, .twitter { 33 | margin-top: 0.25rem; 34 | word-break: break-word; 35 | font-size: 0.9rem; 36 | display: flex; 37 | align-items: center; 38 | color: var(--text); 39 | transition: color 0.5s ease-in-out; 40 | } 41 | 42 | .bio { 43 | margin-top: 0.5rem; 44 | font-size: 1rem; 45 | } 46 | 47 | .blogActive:hover { 48 | cursor: pointer; 49 | text-decoration: underline; 50 | } 51 | 52 | .company { 53 | font-weight: 600; 54 | } 55 | 56 | .stats { 57 | flex: 1; 58 | display: flex; 59 | font-size: 0.9rem; 60 | margin: 1.25rem 0 1rem; 61 | color: var(--text); 62 | transition: color 0.5s ease-in-out; 63 | } 64 | 65 | .following { 66 | margin-left: 8px; 67 | } 68 | 69 | .stat { 70 | font-weight: 600; 71 | margin-right: 4px; 72 | } 73 | 74 | .loader { 75 | display: flex; 76 | align-items: center; 77 | justify-content: center; 78 | } 79 | -------------------------------------------------------------------------------- /src/components/GitHubProfileViewer/components/RepoCard/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import classes from './styles.module.css'; 4 | import { getPathValue, isEmptyString } from '../../../../utils'; 5 | import SubjectRoundedIcon from '@material-ui/icons/SubjectRounded'; 6 | import StarBorderRoundedIcon from '@material-ui/icons/StarBorderRounded'; 7 | import AccountTreeRoundedIcon from '@material-ui/icons/AccountTreeRounded'; 8 | 9 | export default class RepoCard extends React.Component { 10 | 11 | handleLinkClick = url => { 12 | if (isEmptyString(url)) return; 13 | window.open(url); 14 | } 15 | 16 | render = () => { 17 | const repo = this.props.repo; 18 | 19 | return
20 | {repo?.name &&
repo?.html_url ? this.handleLinkClick(repo.html_url) : {}}> 22 | 23 | {repo.name} 24 |
} 25 |
{repo?.description}
26 |
27 | {repo?.language &&
{repo.language}
} 28 | {getPathValue(repo, 'stargazers_count', 0) > 0 &&
29 | 30 | {repo.stargazers_count} 31 |
} 32 | {getPathValue(repo, 'forks_count', 0) > 0 &&
33 | 34 | {repo.forks_count} 35 |
} 36 |
37 |
; 38 | } 39 | } -------------------------------------------------------------------------------- /src/components/GitHubProfileViewer/components/RepoCard/styles.module.css: -------------------------------------------------------------------------------- 1 | .repoCard { 2 | padding: 1rem; 3 | display: flex; 4 | flex-direction: column; 5 | border-radius: 6px; 6 | border: 1px solid var(--component-border); 7 | transition: border 0.5s ease-in-out; 8 | } 9 | 10 | .repoName { 11 | display: flex; 12 | align-items: center; 13 | font-size: 1rem; 14 | font-weight: 600; 15 | color: var(--theme-color); 16 | transition: color 0.5s ease-in-out; 17 | } 18 | 19 | .repoNameActive:hover { 20 | cursor: pointer; 21 | text-decoration: underline; 22 | } 23 | 24 | .repoIcon { 25 | font-size: 1rem !important; 26 | margin-right: 8px; 27 | color: var(--text-obscurer); 28 | transition: color 0.5s ease-in-out !important; 29 | } 30 | 31 | .repoDescription { 32 | flex: 1; 33 | font-size: 0.85rem; 34 | margin-top: 1rem; 35 | word-break: break-word; 36 | color: var(--text-obscure); 37 | transition: color 0.5s ease-in-out; 38 | } 39 | 40 | .bottomContent { 41 | display: flex; 42 | align-items: center; 43 | margin-top: 1rem; 44 | } 45 | 46 | .repoLanguage, .repoStars, .repoForks { 47 | display: flex; 48 | align-items: center; 49 | font-size: 0.85rem; 50 | margin-right: 1.25rem; 51 | color: var(--text-obscure); 52 | transition: color 0.5s ease-in-out; 53 | } -------------------------------------------------------------------------------- /src/components/GitHubProfileViewer/components/Repos/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { GITHUB_API_BASE } from '../../../../resources/constants'; 3 | import SnackBar from '../../../../ui-components/SnackBar'; 4 | import SpinnerLoader from '../../../../ui-components/SpinnerLoader'; 5 | import { handleError, isEmptyList } from '../../../../utils'; 6 | import RepoCard from '../RepoCard'; 7 | 8 | import classes from './styles.module.css'; 9 | 10 | export default class Repos extends React.Component { 11 | 12 | state = { 13 | reposData: null, 14 | loader: false, 15 | snack: null 16 | } 17 | 18 | componentDidMount = () => this.props.searchUser && this.fetchReposData(this.props.username); 19 | 20 | componentDidUpdate = prevProps => { 21 | if (prevProps.searchUser !== this.props.searchUser && this.props.searchUser) this.fetchReposData(this.props.username); 22 | } 23 | 24 | fetchReposData = username => this.setState({ loader: true }, () => fetch(GITHUB_API_BASE + `/users/${username}/repos?sort=pushed`) 25 | .then(handleError) 26 | .then(res => res.json()) 27 | .then(data => this.setState({ loader: false, reposData: data, snack: null }, () => this.props.clearUsername())) 28 | .catch(error => this.setState({ 29 | loader: false, reposData: null, snack: { 30 | open: true, 31 | message: error.message, 32 | severity: 'error' 33 | } 34 | }, () => this.props.clearUsername(username)))); 35 | 36 | handleSnackClose = () => this.setState({ snack: { ...this.state.snack, open: false } }); 37 | 38 | render = () => { 39 | const reposData = this.state.reposData; 40 | 41 | return <> 42 | {this.state.loader ? 43 |
44 | : isEmptyList(reposData) ? 45 |
No repos data available
46 | :
47 | {reposData.map((repo, index) => )} 48 |
} 49 | {this.state?.snack?.message && } 50 | ; 51 | } 52 | } -------------------------------------------------------------------------------- /src/components/GitHubProfileViewer/components/Repos/styles.module.css: -------------------------------------------------------------------------------- 1 | .noData { 2 | color: var(--text-obscure); 3 | } 4 | 5 | .loader { 6 | display: flex; 7 | align-items: center; 8 | justify-content: center; 9 | } 10 | 11 | .reposGrid { 12 | display: grid; 13 | gap: 1rem; 14 | grid-template-columns: 1fr; 15 | } 16 | 17 | @media (min-width: 1024px) { 18 | .reposGrid { 19 | grid-template-columns: 1fr 1fr; 20 | } 21 | } -------------------------------------------------------------------------------- /src/components/GitHubProfileViewer/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Button from '../../ui-components/Button'; 3 | import Iconbutton from '../../ui-components/Button/Iconbutton'; 4 | import Input from '../../ui-components/Input'; 5 | import Page from '../../ui-components/Page'; 6 | 7 | import classes from './styles.module.css'; 8 | import GitHubIcon from '@material-ui/icons/GitHub'; 9 | import { isEmptyString } from '../../utils'; 10 | import Profile from './components/Profile'; 11 | import Repos from './components/Repos'; 12 | import SnackBar from '../../ui-components/SnackBar'; 13 | 14 | export default class GitHubProfileViewer extends React.Component { 15 | 16 | state = { 17 | username: '', 18 | searchUser: false, 19 | snack: null 20 | } 21 | 22 | componentDidMount = () => window.addEventListener('keydown', this.handleKeyDown); 23 | 24 | componentWillUnmount = () => window.removeEventListener('keydown', this.handleKeyDown); 25 | 26 | handleKeyDown = e => e.key === 'Enter' && this.handleSearchUser(); 27 | 28 | handleValueChange = e => this.setState({ username: e.target.value }); 29 | 30 | handleSearchUser = () => { 31 | if (isEmptyString(this.state.username)) { 32 | this.setState({ snack: { 33 | open: true, 34 | message: `Uh-oh! I don't like empty strings`, 35 | severity: 'warning' 36 | } }); 37 | } else { 38 | this.setState({ searchUser: true, refresher: new Date().getTime() }); 39 | } 40 | } 41 | 42 | clearUsername = (username = '') => { 43 | this.setState({ username, searchUser: false }); 44 | document.querySelector('#search-user').focus(); 45 | } 46 | 47 | handleSnackClose = () => this.setState({ snack: { ...this.state.snack, open: false } }); 48 | 49 | render = () => { 50 | 51 | return 52 |
53 |
54 |
55 | 56 | 57 | }> 58 |
59 |
60 |
61 |
62 | 63 |
64 |
65 | 66 |
67 |
68 |
69 | {this.state?.snack?.message && } 70 |
; 71 | } 72 | } -------------------------------------------------------------------------------- /src/components/GitHubProfileViewer/styles.module.css: -------------------------------------------------------------------------------- 1 | .viewerContainer { 2 | margin: 5rem 0; 3 | } 4 | 5 | .searchInputContainer { 6 | background-color: var(--card-bg); 7 | padding: 16px 8px 16px 16px; 8 | width: 85%; 9 | margin: 2rem auto 0; 10 | border-radius: 6px; 11 | border: 1px solid rgba(0, 0, 0, 0.1); 12 | box-shadow: 0 0 10px rgba(0, 0, 0, 0.2); 13 | transition: box-shadow 0.2s ease-in-out, transform 0.2s ease-in-out, background-color 0.5s ease-in-out; 14 | } 15 | 16 | .searchInputContainer:hover { 17 | box-shadow: 0 0 12px rgba(0, 0, 0, 0.3); 18 | transform: scale(1.02); 19 | } 20 | 21 | ::placeholder { 22 | /* Chrome, Firefox, Opera, Safari 10.1+ */ 23 | color: var(--text); 24 | opacity: 1; 25 | /* Firefox */ 26 | } 27 | 28 | :-ms-input-placeholder { 29 | /* Internet Explorer 10-11 */ 30 | color: var(--text); 31 | } 32 | 33 | ::-ms-input-placeholder { 34 | /* Microsoft Edge */ 35 | color: var(--text); 36 | } 37 | 38 | .searchWrapper { 39 | display: flex; 40 | align-items: center; 41 | } 42 | 43 | .desktopBtn { 44 | display: none !important; 45 | } 46 | 47 | .mobileBtn { 48 | margin-left: 8px !important; 49 | display: block !important; 50 | } 51 | 52 | .contentContainer { 53 | display: flex; 54 | flex-direction: column; 55 | width: 85%; 56 | margin: 2rem auto 0; 57 | } 58 | 59 | .profileWrapper, .reposWrapper { 60 | padding: 1rem; 61 | border-radius: 6px; 62 | position: relative; 63 | height: fit-content; 64 | border: 1px solid var(--component-border); 65 | transition: border 0.5s ease-in-out; 66 | } 67 | 68 | .reposWrapper { 69 | flex: 1; 70 | margin-top: 1rem; 71 | } 72 | 73 | @media (min-width: 768px) { 74 | .searchInputContainer { 75 | max-width: 900px; 76 | margin-left: 1.5rem; 77 | } 78 | .contentContainer { 79 | flex-direction: row; 80 | margin-left: 1.5rem; 81 | width: 95%; 82 | } 83 | .reposWrapper { 84 | margin-top: 0; 85 | margin-left: 2rem; 86 | } 87 | .profileWrapper { 88 | width: 260px; 89 | } 90 | } 91 | 92 | @media (min-width: 900px) { 93 | .desktopBtn { 94 | display: block !important; 95 | margin-left: 8px !important; 96 | min-width: 150px !important; 97 | padding: 16px 0 !important; 98 | } 99 | .mobileBtn { 100 | margin-left: 0 !important; 101 | display: none !important; 102 | } 103 | .profileWrapper { 104 | width: 300px; 105 | } 106 | } -------------------------------------------------------------------------------- /src/components/Home/components/ProjectCard/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import classes from './styles.module.css'; 4 | 5 | export default class ProjectCard extends React.Component { 6 | 7 | handleClick = e => url => { 8 | e.preventDefault(); 9 | e.stopPropagation(); 10 | window.open(url); 11 | } 12 | 13 | render = () => { 14 | const { title, image, projectPath, githubLink } = this.props; 15 | 16 | return
this.handleClick(e)(projectPath)}> 17 |
19 |
20 |
{title}
21 |
this.handleClick(e)(githubLink)}>View on GitHub
22 |
23 |
; 24 | } 25 | } -------------------------------------------------------------------------------- /src/components/Home/components/ProjectCard/styles.module.css: -------------------------------------------------------------------------------- 1 | .cardContainer { 2 | border-radius: 6px; 3 | border: 1px solid var(--border-color); 4 | background-color: var(--card-bg); 5 | box-shadow: var(--card-shadow); 6 | transition: border 0.5s ease-in-out, background-color 0.5s ease-in-out, box-shadow 0.5s ease-in-out, transform 0.2s ease-in-out; 7 | } 8 | 9 | .cardContainer:hover { 10 | cursor: pointer; 11 | } 12 | 13 | .cardContainer:hover > .cardImage { 14 | filter: contrast(1.05); 15 | } 16 | 17 | .cardImage { 18 | height: 275px; 19 | width: 100%; 20 | border-top-left-radius: inherit; 21 | border-top-right-radius: inherit; 22 | background-color: var(--page-bg); 23 | transition: background-image 0.5s ease-in-out, filter 0.2s ease-in-out; 24 | } 25 | 26 | .cardContent { 27 | padding: 16px; 28 | } 29 | 30 | .cardTitle { 31 | font-size: 1.5rem; 32 | letter-spacing: -0.4px; 33 | margin-bottom: 12px; 34 | color: var(--text); 35 | transition: color 0.5s ease-in-out; 36 | } 37 | 38 | .cardLink { 39 | width: fit-content; 40 | font-size: 0.75rem; 41 | letter-spacing: 0.4px; 42 | text-transform: uppercase; 43 | color: var(--text-obscure); 44 | transition: color 0.5s ease-in-out; 45 | } 46 | 47 | .cardLink:hover { 48 | cursor: pointer; 49 | color: var(--text-obscurer); 50 | } 51 | 52 | .linkUnderline { 53 | color: #0ff; 54 | } 55 | 56 | @media (min-width: 900px) { 57 | .cardContainer:hover { 58 | box-shadow: var(--card-shadow-hover); 59 | transform: scale(1.02); 60 | transition: transform 0.2s ease-in-out; 61 | } 62 | } -------------------------------------------------------------------------------- /src/components/Home/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import classes from './styles.module.css'; 4 | import Page from '../../ui-components/Page'; 5 | import StarBorderRoundedIcon from '@material-ui/icons/StarBorderRounded'; 6 | import GitHubIcon from '@material-ui/icons/GitHub'; 7 | import ProjectCard from './components/ProjectCard'; 8 | import { projects } from './projects'; 9 | import { GITHUB_BASE } from '../../resources/constants'; 10 | import { themed } from '../../utils/theme'; 11 | export default class Home extends React.Component { 12 | 13 | // invoked whenever theme changes in order to trigger themed function call 14 | updateComponent = (refresher = null) => refresher && this.setState({ refresher }); 15 | 16 | handleGithubButtonClick = path => window.open(GITHUB_BASE + path); 17 | 18 | render = () => 19 |
20 |
21 |
22 | All about React
JS 23 |
24 |
25 |
this.handleGithubButtonClick('/45DaysOfReactJS')}> 26 | 27 | Star 28 |
29 |
this.handleGithubButtonClick('/')}> 30 | 31 | Follow 32 |
33 |
34 |
35 |
36 | {projects.map(project => )} 38 |
39 |
40 |
; 41 | } -------------------------------------------------------------------------------- /src/components/Home/styles.module.css: -------------------------------------------------------------------------------- 1 | .homeContainer { 2 | position: relative; 3 | margin: 8px; 4 | } 5 | 6 | .headerWrapper { 7 | margin-top: 4rem; 8 | display: flex; 9 | flex-direction: column; 10 | align-items: center; 11 | } 12 | 13 | .header { 14 | color: var(--text); 15 | font-size: 2rem; 16 | letter-spacing: 0.4px; 17 | font-weight: bold; 18 | display: flex; 19 | flex: 1; 20 | transition: color 0.5s ease-in-out; 21 | } 22 | 23 | .headerText { 24 | position: relative; 25 | } 26 | 27 | .underline { 28 | width: 100%; 29 | height: 4px; 30 | margin-top: -6px; 31 | border-radius: 2px; 32 | background-color: var(--text-opposite); 33 | transition: background-color 0.5s ease-in-out; 34 | } 35 | 36 | .headerHighlight { 37 | color: #ab87d4; 38 | margin-left: 6px; 39 | margin-top: -5px; 40 | font-size: 1.4em; 41 | animation: colorChange 20s ease-in-out infinite; 42 | } 43 | 44 | .githubLinks { 45 | display: flex; 46 | align-items: center; 47 | margin-top: 16px; 48 | } 49 | 50 | .linkButton { 51 | padding: 6px 10px; 52 | margin-left: 8px; 53 | border-radius: 4px; 54 | display: flex; 55 | align-items: center; 56 | color: var(--text-contrast); 57 | background-color: var(--text); 58 | transition: color 0.5s ease-in-out, background-color 0.5s ease-in-out, box-shadow 0.2s ease-in-out; 59 | } 60 | 61 | .linkButton:hover { 62 | cursor: pointer; 63 | box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.25); 64 | } 65 | 66 | .buttonLabel { 67 | margin-left: 4px; 68 | letter-spacing: -0.2px; 69 | font-weight: bold; 70 | } 71 | 72 | .projectsContainer { 73 | margin-top: 2rem; 74 | display: grid; 75 | grid-template-columns: 1fr; 76 | gap: 1rem; 77 | } 78 | 79 | @-webkit-keyframes colorChange { 80 | 0% { color: #da708a; } 81 | 10% { color: #d36ecb; } 82 | 20% { color: #ab87d4; } 83 | 30% { color: #858fe4; } 84 | 40% { color: #87bcee; } 85 | 60% { color: #87d4bd; } 86 | 70% { color: #d6e099; } 87 | 80% { color: #e0bb74; } 88 | 90% { color: #e2786a; } 89 | 100% { color: #da708a; } 90 | } 91 | 92 | @keyframes colorChange { 93 | 0% { color: #da708a; } 94 | 10% { color: #d36ecb; } 95 | 20% { color: #ab87d4; } 96 | 30% { color: #858fe4; } 97 | 40% { color: #87bcee; } 98 | 60% { color: #87d4bd; } 99 | 70% { color: #d6e099; } 100 | 80% { color: #e0bb74; } 101 | 90% { color: #e2786a; } 102 | 100% { color: #da708a; } 103 | } 104 | 105 | @media (min-width: 768px) { 106 | .projectsContainer { 107 | grid-template-columns: 1fr 1fr; 108 | } 109 | } 110 | 111 | @media (min-width: 900px) { 112 | .homeContainer { 113 | margin: 4rem; 114 | } 115 | .headerWrapper { 116 | flex-direction: row; 117 | margin-top: 0; 118 | } 119 | .header { 120 | font-size: 2.5rem; 121 | } 122 | .githubLinks { 123 | margin-top: 0; 124 | } 125 | .projectsContainer { 126 | gap: 1.5rem; 127 | } 128 | } 129 | 130 | @media (min-width: 1200px) { 131 | .projectsContainer { 132 | grid-template-columns: 1fr 1fr 1fr; 133 | } 134 | } -------------------------------------------------------------------------------- /src/components/HoverBoard/components/BoardCell/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { BLUE_400, BLUE_600, GREEN_400, GREEN_600, INDIGO_400, INDIGO_600, ORANGE_400, ORANGE_600, PURPLE_400, PURPLE_600, RED_400, RED_600, TEAL_400, TEAL_600, YELLOW_400, YELLOW_600 } from '../../../../resources/colors'; 3 | import { themed } from '../../../../utils/theme'; 4 | 5 | import classes from './styles.module.css'; 6 | 7 | const COLORS = Object.freeze({ 8 | RED: themed(RED_600, RED_400), 9 | PURPLE: themed(PURPLE_600, PURPLE_400), 10 | BLUE: themed(BLUE_600, BLUE_400), 11 | GREEN: themed(GREEN_600, GREEN_400), 12 | ORANGE: themed(ORANGE_600, ORANGE_400), 13 | YELLOW: themed(YELLOW_600, YELLOW_400), 14 | TEAL: themed(TEAL_600, TEAL_400), 15 | INDIGO: themed(INDIGO_600, INDIGO_400) 16 | }); 17 | 18 | const getColor = () => { 19 | const color = Object.keys(COLORS)[Math.floor(Object.keys(COLORS).length * Math.random())]; 20 | return COLORS[color]; 21 | } 22 | 23 | export default class BoardCell extends React.Component { 24 | 25 | state = { 26 | isHovering: false, 27 | isClicked: false, 28 | color: null 29 | } 30 | 31 | toggleHovering = hover => !this.state.isClicked && this.setState({ isHovering: hover, color: getColor() }); 32 | 33 | toggleClicked = () => this.setState({ isClicked: !this.state.isClicked }); 34 | 35 | render = () => { 36 | const { color } = this.state; 37 | const style = { backgroundColor: this.state.isHovering ? color : 'var(--card-bg)', boxShadow: this.state.isHovering ? `0 0 2px ${color}, 0 0 8px ${color}` : '0 0 2px var(--tag-color)' }; 38 | 39 | return
this.toggleHovering(true)} onMouseLeave={() => this.toggleHovering(false)} 40 | onClick={() => this.toggleClicked()} className={classes.cell} style={style}>
41 | } 42 | } -------------------------------------------------------------------------------- /src/components/HoverBoard/components/BoardCell/styles.module.css: -------------------------------------------------------------------------------- 1 | .cell { 2 | padding: 8px; 3 | border-radius: 2px; 4 | border: 1px solid var(--tag-color); 5 | background-color: var(--card-bg); 6 | box-shadow: 0 0 2px var(--tag-color); 7 | transition: border 0.5s ease-in-out, background-color 0.5s ease-in-out, box-shadow 0.5s ease-in-out; 8 | } 9 | 10 | .cell:hover { 11 | transition: background-color 0s, box-shadow 0s; 12 | } -------------------------------------------------------------------------------- /src/components/HoverBoard/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import classes from './styles.module.css'; 4 | import Page from '../../ui-components/Page'; 5 | import BoardCell from './components/BoardCell'; 6 | 7 | export default class HoverBoard extends React.Component { 8 | 9 | state = { 10 | boardSize: 0 11 | } 12 | 13 | componentDidMount = () => { 14 | window.addEventListener('resize', this.handleWindowResize); 15 | this.setBoardSize(this.getDeviceWidth()); 16 | } 17 | 18 | componentWillUnmount = () => { 19 | window.removeEventListener('resize', this.handleWindowResize); 20 | } 21 | 22 | handleWindowResize = () => this.setBoardSize(this.getDeviceWidth()); 23 | 24 | setBoardSize = width => this.setState({ boardSize: this.getBoardSize(width) }); 25 | 26 | getBoardSize = width => { 27 | if (width >= 900) return 500; 28 | else if (width >= 768) return 400; 29 | return 200; 30 | } 31 | 32 | getDeviceWidth = () => window.innerWidth || document.body.clientWidth; 33 | 34 | updateComponent = (refresher = null) => refresher && this.setState({ refresher }); 35 | 36 | render = () => { 37 | let cells = []; 38 | const deviceWidth = this.getDeviceWidth(); 39 | for (let i = 0; i < this.state.boardSize; i++) cells.push(); 40 | 41 | return 42 |
43 | {deviceWidth < 768 &&
44 | If you are viewing this on mobile phone or tablet device, then hovering over the cell won't be possible. 45 |
} 46 |
47 | {cells} 48 |
49 |
50 |
; 51 | } 52 | } -------------------------------------------------------------------------------- /src/components/HoverBoard/styles.module.css: -------------------------------------------------------------------------------- 1 | .boardWrapper { 2 | margin: 2rem 0; 3 | min-height: inherit; 4 | display: flex; 5 | align-items: center; 6 | justify-content: center; 7 | flex-direction: column; 8 | } 9 | 10 | .board { 11 | display: grid; 12 | grid-template-columns: 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr; 13 | gap: 0.25rem; 14 | } 15 | 16 | .infoText { 17 | width: 60%; 18 | margin: 0 auto 2rem; 19 | word-break: break-word; 20 | color: var(--text-obscure); 21 | transition: color 0.5s ease-in-out; 22 | } 23 | 24 | @media (min-width: 768px) { 25 | .board { 26 | grid-template-columns: 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr; 27 | } 28 | } -------------------------------------------------------------------------------- /src/components/KeyCodeSequence/components/CodeCard/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import classes from './styles.module.css'; 4 | 5 | const CodeCard = React.memo(({ title, subTitle, info }) => { 6 | 7 | return
8 |
9 |
{title}
10 |
{subTitle || ''}
11 |
12 |
{info}
13 |
14 | }); 15 | 16 | export default CodeCard; -------------------------------------------------------------------------------- /src/components/KeyCodeSequence/components/CodeCard/styles.module.css: -------------------------------------------------------------------------------- 1 | .cardWrapper { 2 | min-width: 20rem; 3 | font-weight: 600; 4 | border-radius: 6px; 5 | box-shadow: var(--card-shadow); 6 | transition: box-shadow 0.5s ease-in-out; 7 | } 8 | 9 | .titleWrapper { 10 | display: flex; 11 | align-items: center; 12 | padding: 0.4rem 1rem; 13 | border-top-left-radius: inherit; 14 | border-top-right-radius: inherit; 15 | background-color: var(--secondary-theme-color); 16 | transition: background-color 0.5s ease-in-out; 17 | } 18 | 19 | .title { 20 | font-weight: 600; 21 | color: var(--text-contrast); 22 | transition: color 0.5s ease-in-out; 23 | } 24 | 25 | .subTitle { 26 | margin-top: 0.1rem; 27 | font-size: 0.8rem; 28 | font-weight: normal; 29 | color: var(--tag-color); 30 | transition: color 0.5s ease-in-out; 31 | } 32 | 33 | .info { 34 | flex: 1; 35 | text-align: center; 36 | padding: 1.25rem; 37 | font-size: 3rem; 38 | color: var(--text); 39 | border-bottom-left-radius: inherit; 40 | border-bottom-right-radius: inherit; 41 | background-color: var(--card-bg); 42 | transition: color 0.5s ease-in-out, background-color 0.5s ease-in-out; 43 | } 44 | 45 | @media (min-width: 900px) { 46 | .title { 47 | font-size: 1.25rem; 48 | } 49 | .subTitle { 50 | font-size: 1rem; 51 | } 52 | .info { 53 | font-size: 4rem; 54 | } 55 | } -------------------------------------------------------------------------------- /src/components/KeyCodeSequence/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import classes from './styles.module.css'; 4 | import Page from '../../ui-components/Page'; 5 | import CodeCard from './components/CodeCard'; 6 | 7 | export default class KeyCodeSequence extends React.Component { 8 | 9 | // show an option to list of all the options 10 | 11 | state = { 12 | eventInfo: null 13 | } 14 | 15 | componentDidMount = () => { 16 | window.addEventListener('keydown', this.handleKeyPress); 17 | } 18 | 19 | componentWillUnmount = () => { 20 | window.removeEventListener('keydown', this.handleKeyPress); 21 | } 22 | 23 | handleKeyPress = e => { 24 | this.setState({ eventInfo: e }); 25 | }; 26 | // e.code - 'KeyK' 27 | // e.key - 'k' 28 | // e.keyCode - 75 29 | // which - 75 (deprecated) 30 | 31 | render = () => { 32 | const { eventInfo } = this.state; 33 | 34 | return 35 |
36 | {!eventInfo ? 37 |
Press a key to get details of the pressed key
: 38 |
39 | {eventInfo?.code && } 40 | {eventInfo?.key && } 41 | {eventInfo?.keyCode && } 42 | {eventInfo?.which && } 43 |
} 44 |
45 |
; 46 | } 47 | } -------------------------------------------------------------------------------- /src/components/KeyCodeSequence/styles.module.css: -------------------------------------------------------------------------------- 1 | .sequenceWrapper { 2 | min-height: inherit; 3 | display: flex; 4 | align-items: center; 5 | justify-content: center; 6 | } 7 | 8 | .sequenceContainer { 9 | display: grid; 10 | grid-template-columns: 1fr; 11 | gap: 0.5rem; 12 | } 13 | 14 | .noData { 15 | max-width: 90%; 16 | margin: 0 auto; 17 | font-size: 1.5rem; 18 | letter-spacing: 0.2px; 19 | text-align: center; 20 | color: var(--text); 21 | transition: color 0.5s ease-in-out; 22 | } 23 | 24 | @media (min-width: 768px) { 25 | .noData { 26 | font-size: 2rem; 27 | } 28 | .sequenceContainer { 29 | grid-template-columns: 1fr 1fr; 30 | } 31 | } 32 | 33 | @media (min-width: 900px) { 34 | .noData { 35 | font-size: 2.5rem; 36 | } 37 | } -------------------------------------------------------------------------------- /src/components/MealGenerator/styles.module.css: -------------------------------------------------------------------------------- 1 | .loader { 2 | opacity: 1; 3 | position: fixed; 4 | top: 50%; 5 | left: 50%; 6 | transform: translate(-50%, -50%); 7 | transition: opacity 0.25s ease-in-out; 8 | } 9 | 10 | .hideLoader { 11 | opacity: 0; 12 | } 13 | 14 | .title { 15 | font-size: 3rem; 16 | font-weight: 600; 17 | letter-spacing: -0.2px; 18 | margin-top: 4rem; 19 | text-align: center; 20 | color: var(--text-prominent); 21 | transition: color 0.5s ease-in-out; 22 | } 23 | 24 | .subtitle { 25 | font-weight: 100; 26 | text-align: center; 27 | letter-spacing: -0.2px; 28 | color: var(--text-obscure); 29 | transition: color 0.5s ease-in-out; 30 | } 31 | 32 | .buttonWrapper { 33 | width: fit-content; 34 | margin: 2rem auto 0; 35 | } 36 | 37 | .container { 38 | width: 90%; 39 | margin: 2rem auto; 40 | display: flex; 41 | flex-direction: column; 42 | } 43 | 44 | .secondarySection { 45 | flex: 1; 46 | } 47 | 48 | .mealImage { 49 | width: 100%; 50 | } 51 | 52 | .tagsWrapper { 53 | display: flex; 54 | flex-wrap: wrap; 55 | align-items: baseline; 56 | color: var(--text); 57 | transition: color 0.5s ease-in-out; 58 | } 59 | 60 | .mealTag { 61 | color: #FFF; 62 | font-weight: 600; 63 | margin: 0.5rem 0.5rem 0 0; 64 | padding: 0.5rem 1rem; 65 | border-radius: 32px; 66 | box-shadow: var(--card-shadow); 67 | background-color: var(--secondary-theme-color); 68 | transition: background-color 0.5s ease-in-out, box-shadow 0.5s ease-in-out; 69 | } 70 | 71 | .sourceButtonsWrapper { 72 | margin-top: -0.5rem; 73 | color: var(--text); 74 | transition: color 0.5s ease-in-out; 75 | } 76 | 77 | .mealTitleWrapper { 78 | display: flex; 79 | } 80 | 81 | .primarySection { 82 | margin-top: 1.5rem; 83 | } 84 | 85 | .mealTitle { 86 | flex: 1; 87 | font-size: 1.5rem; 88 | font-weight: 600; 89 | color: var(--text-prominent); 90 | transition: color 0.5s ease-in-out; 91 | } 92 | 93 | .divider { 94 | border: 0; 95 | height: 1px; 96 | margin: 0.5rem 0; 97 | background-color: var(--tag-color); 98 | transition: background-color 0.5s ease-in-out; 99 | } 100 | 101 | .ingredientsTitle, .instructionsTitle { 102 | margin-top: 1rem; 103 | font-weight: 600; 104 | font-size: 1.25rem; 105 | color: var(--text); 106 | transition: color 0.5s ease-in-out; 107 | } 108 | 109 | .ingredients { 110 | margin: 1rem 0; 111 | } 112 | 113 | .ingredient { 114 | display: flex; 115 | align-items: baseline; 116 | font-size: 0.85rem; 117 | line-height: 2rem; 118 | color: var(--secondary-theme-color); 119 | transition: color 0.5s ease-in-out; 120 | } 121 | 122 | .ingredientName, .measurement { 123 | margin: 0 4px; 124 | font-size: 1rem; 125 | color: var(--text); 126 | transition: color 0.5s ease-in-out; 127 | } 128 | 129 | .instructions { 130 | margin: 1rem 0; 131 | line-height: 1.5rem; 132 | color: var(--text); 133 | transition: color 0.5s ease-in-out; 134 | } 135 | 136 | .noData { 137 | position: absolute; 138 | top: 50%; 139 | left: 50%; 140 | max-width: 90%; 141 | font-weight: 100; 142 | font-size: 1.5rem; 143 | letter-spacing: 0.4px; 144 | word-break: break-word; 145 | transform: translate(-50%, -50%); 146 | color: var(--text-obscure); 147 | transition: color 0.5s ease-in-out; 148 | } 149 | 150 | @media (min-width: 768px) { 151 | .title { 152 | font-size: 4rem; 153 | margin-top: 3rem; 154 | } 155 | .subtitle { 156 | font-size: 1.25rem; 157 | } 158 | .container { 159 | width: 80%; 160 | margin: 4rem auto; 161 | flex-direction: row; 162 | } 163 | .tagsWrapper { 164 | margin-top: 1rem; 165 | } 166 | .primarySection { 167 | flex: 2; 168 | margin-top: 0; 169 | padding-left: 2.5rem; 170 | } 171 | .mealTitle { 172 | font-size: 2rem; 173 | } 174 | .divider { 175 | margin: 0.5rem 0; 176 | } 177 | .ingredientsTitle, .instructionsTitle { 178 | margin-top: 2rem; 179 | } 180 | } -------------------------------------------------------------------------------- /src/components/PasswordGenerator/styles.module.css: -------------------------------------------------------------------------------- 1 | .generatorWrapper { 2 | min-width: inherit; 3 | min-height: inherit; 4 | display: flex; 5 | align-items: center; 6 | justify-content: center; 7 | } 8 | 9 | .generatorContainer { 10 | padding: 16px; 11 | width: 300px; 12 | border-radius: 6px; 13 | border: 1px solid var(--component-border); 14 | background-color: var(--card-bg); 15 | box-shadow: 0 0 10px var(--text); 16 | transition: border 0.5s ease-in-out, background-color 0.5s ease-in-out, box-shadow 0.5s ease-in-out; 17 | } 18 | 19 | .passwordContainer { 20 | min-height: 60px; 21 | border-radius: 4px; 22 | font-size: 1.125rem; 23 | padding: 1rem 0.75rem; 24 | word-wrap: break-word; 25 | box-sizing: border-box; 26 | display: flex; 27 | color: var(--text); 28 | box-shadow: var(--card-shadow); 29 | background-color: var(--tag-color); 30 | transition: color 0.5s ease-in-out, background-color 0.5s ease-in-out, box-shadow 0.5s ease-in-out; 31 | } 32 | 33 | .passwordText { 34 | flex: 1; 35 | font-weight: 600; 36 | margin-right: 6px; 37 | letter-spacing: 0.3px; 38 | } 39 | 40 | .copy { 41 | opacity: 1; 42 | } 43 | 44 | .cursorPointer { 45 | cursor: pointer; 46 | } 47 | 48 | .passwordContainer:hover { 49 | box-shadow: var(--card-shadow-hover); 50 | } 51 | 52 | .optionsContainer { 53 | flex: 1; 54 | padding: 16px; 55 | margin-top: 1.5rem; 56 | } 57 | 58 | .optionRow { 59 | display: flex; 60 | align-items: center; 61 | margin-top: 1rem; 62 | } 63 | 64 | .optionRow:first-child { 65 | margin-top: 0; 66 | } 67 | 68 | .validation { 69 | font-size: 0.75rem !important; 70 | transition: color 0.5s ease-in-out; 71 | } 72 | 73 | .optionText { 74 | flex: 1; 75 | color: var(--text); 76 | transition: color 0.5s ease-in-out; 77 | } 78 | 79 | .generateButton { 80 | width: 100% !important; 81 | height: 3rem !important; 82 | margin-top: 1.5rem !important; 83 | transition: background-color 0.5s ease-in-out !important; 84 | } 85 | 86 | @media (min-width: 768px) { 87 | .generatorContainer { 88 | width: 500px; 89 | } 90 | .copy { 91 | opacity: 0; 92 | transition: opacity 0.25s ease-in-out; 93 | } 94 | .passwordContainer:hover .copy { 95 | opacity: 1; 96 | } 97 | } -------------------------------------------------------------------------------- /src/components/Pokedex/styles.module.css: -------------------------------------------------------------------------------- 1 | .title { 2 | text-align: center; 3 | margin-top: 2rem; 4 | font-size: 2rem; 5 | font-weight: 600; 6 | letter-spacing: -1px; 7 | color: rgb(247, 231, 18); 8 | text-shadow: 3px 0px rgb(41, 45, 255), 0px 3px rgb(41, 45, 255), -3px 0px rgb(41, 45, 255), 0px -3px rgb(41, 45, 255); 9 | } 10 | 11 | .inputContainer, .pokedexContainer { 12 | width: 80%; 13 | padding: 1rem; 14 | margin: 1rem auto; 15 | border-radius: 4px; 16 | background-color: var(--card-bg); 17 | border: 1px solid var(--border-color); 18 | transition: background-color 0.5s ease-in-out, border 0.5s ease-in-out; 19 | } 20 | 21 | .label { 22 | color: var(--text-obscure); 23 | transition: color 0.5s ease-in-out; 24 | } 25 | 26 | .pokemonTitle { 27 | composes: title; 28 | margin: 0 4px; 29 | font-size: 1.25rem !important; 30 | text-shadow: 1.5px 0px rgb(41, 45, 255), 0px 1.5px rgb(41, 45, 255), -1.5px 0px rgb(41, 45, 255), 0px -1.5px rgb(41, 45, 255); 31 | } 32 | 33 | .inputWrapper { 34 | margin-top: 1rem; 35 | display: flex; 36 | flex-direction: column; 37 | } 38 | 39 | .searchInput { 40 | flex: 1; 41 | } 42 | 43 | .searchInput:first-child { 44 | margin-bottom: 1rem; 45 | } 46 | 47 | .imageContainer { 48 | display: flex; 49 | align-items: center; 50 | justify-content: center; 51 | padding: 1rem; 52 | border-radius: 4px; 53 | } 54 | 55 | .sprite { 56 | width: 125%; 57 | } 58 | 59 | .name { 60 | margin: 1rem 0; 61 | font-weight: 600; 62 | font-size: 1.5rem; 63 | letter-spacing: -0.25px; 64 | text-transform: capitalize; 65 | color: var(--text-prominent); 66 | transition: color 0.5s ease-in-out; 67 | } 68 | 69 | .infoWrapper { 70 | display: flex; 71 | } 72 | 73 | .infoLabel { 74 | font-weight: 600; 75 | font-size: 0.8rem; 76 | letter-spacing: -0.25px; 77 | text-transform: capitalize; 78 | color: var(--text-obscure); 79 | transition: color 0.5s ease-in-out; 80 | } 81 | 82 | .infoValue { 83 | margin-top: 0.4rem; 84 | font-size: 1.2rem; 85 | color: var(--text-prominent); 86 | transition: color 0.5s ease-in-out; 87 | } 88 | 89 | .indexWrapper { 90 | flex: 2; 91 | } 92 | 93 | .xpWrapper { 94 | flex: 3; 95 | } 96 | 97 | .typesWrapper { 98 | flex: 4; 99 | } 100 | 101 | .typesContainer { 102 | display: flex; 103 | } 104 | 105 | .typeChip { 106 | margin: 0.25rem 0.5rem 0 0; 107 | padding: 4px 8px; 108 | font-weight: 600; 109 | font-size: 0.9rem; 110 | text-transform: uppercase; 111 | box-shadow: var(--card-shadow); 112 | transition: box-shadow 0.5s ease-in-out; 113 | } 114 | 115 | .statsWrapper { 116 | margin-top: 1.5rem; 117 | } 118 | 119 | .statsContainer { 120 | margin-top: 0.5rem; 121 | padding: 0.5rem; 122 | border-radius: 4px; 123 | background-color: var(--tag-color); 124 | border: 1px solid var(--border-color); 125 | transition: background-color 0.5s ease-in-out, border 0.5s ease-in-out; 126 | } 127 | 128 | .statWrapper { 129 | margin-top: 0.5rem; 130 | } 131 | 132 | .statWrapper:first-of-type { 133 | margin-top: 0; 134 | } 135 | 136 | .statLabel { 137 | composes: infoLabel; 138 | color: var(--text) !important; 139 | } 140 | 141 | .statBar { 142 | display: flex; 143 | flex-basis: 100%; 144 | position: relative; 145 | height: 1.25rem; 146 | margin-top: 0.25rem; 147 | border-radius: 4px; 148 | background-color: var(--text-obscurer); 149 | transition: background-color 0.5s ease-in-out; 150 | } 151 | 152 | .statBarFilled { 153 | flex-basis: 0; 154 | border-radius: inherit; 155 | transition: flex-basis 0.5s ease-in-out; 156 | } 157 | 158 | .loader { 159 | opacity: 1; 160 | position: absolute; 161 | top: 50%; 162 | left: 50%; 163 | transform: translate(-50%, -50%); 164 | transition: opacity 0.25s ease-in-out; 165 | } 166 | 167 | .hideLoader { 168 | opacity: 0; 169 | } 170 | 171 | @media (min-width: 768px) { 172 | .title { 173 | font-size: 2.5rem; 174 | } 175 | .inputContainer, .pokedexContainer { 176 | width: 60%; 177 | margin: 2rem auto; 178 | } 179 | .inputWrapper { 180 | flex-direction: row; 181 | } 182 | .searchInput:first-child { 183 | margin-bottom: 0; 184 | margin-right: 1rem; 185 | } 186 | .sprite { 187 | width: 50%; 188 | } 189 | .statsContainer { 190 | padding: 1rem; 191 | } 192 | } 193 | 194 | @media (min-width: 1200px) { 195 | .title { 196 | font-size: 3rem; 197 | } 198 | .inputContainer, .pokedexContainer { 199 | width: 50%; 200 | } 201 | .name { 202 | font-size: 2rem; 203 | } 204 | .statLabel { 205 | font-size: 1rem !important; 206 | } 207 | .statBar { 208 | height: 1.5rem; 209 | margin-top: 0.5rem; 210 | } 211 | .statWrapper { 212 | margin-top: 1rem; 213 | } 214 | } -------------------------------------------------------------------------------- /src/components/PricingCards/components/Card/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import classes from './styles.module.css'; 3 | import Button from '../../../../ui-components/Button'; 4 | import { isEmptyString } from '../../../../utils'; 5 | import { THEME_COLOR, THEME_COLOR_DARK } from '../../../../resources/colors'; 6 | import { themed } from '../../../../utils/theme'; 7 | 8 | export default class Card extends React.Component { 9 | 10 | render = () => { 11 | const { cardContainerClass, title = {}, subtitle = {}, price = {}, features = {}, buttonColor = themed(THEME_COLOR, THEME_COLOR_DARK) } = this.props; 12 | 13 | return
14 |
{title.label}
15 |
{!isEmptyString(subtitle.label) ? subtitle.label : ''}
16 |
17 |
{price.label}
18 |
per month
19 |
20 |
21 | {features.features.map((feature, index) =>
- {feature}
)} 22 |
23 |
24 | 25 |
26 |
27 | } 28 | } -------------------------------------------------------------------------------- /src/components/PricingCards/components/Card/styles.module.css: -------------------------------------------------------------------------------- 1 | .card { 2 | width: 300px; 3 | background-color: var(--card-bg); 4 | border-radius: 4px; 5 | position: relative; 6 | box-sizing: border-box; 7 | box-shadow: 0 0 10px rgba(0, 0, 0, 0.2); 8 | transition: background-color 0.5s ease-in-out, color 0.5s ease-in-out; 9 | } 10 | 11 | .cardTitle { 12 | color: #fff; 13 | width: 100%; 14 | font-size: 20px; 15 | letter-spacing: 0.4px; 16 | text-transform: uppercase; 17 | text-align: center; 18 | padding: 10px 0px; 19 | border-top-left-radius: inherit; 20 | border-top-right-radius: inherit; 21 | background-color: var(--theme-color); 22 | transition: background-color 0.5s ease-in-out; 23 | } 24 | 25 | .cardSubtitle { 26 | font-size: 12px; 27 | text-align: center; 28 | margin-top: 4px; 29 | } 30 | 31 | .cardHero { 32 | padding: 16px; 33 | } 34 | 35 | .cardPrice { 36 | text-align: center; 37 | font-size: 4.5rem; 38 | font-weight: 600; 39 | color: cornflowerblue; 40 | } 41 | 42 | .cardPriceDuration { 43 | text-align: center; 44 | font-weight: 100; 45 | margin-top: -6px; 46 | color: #888; 47 | } 48 | 49 | .cardFeaturesWrapper { 50 | padding: 32px; 51 | color: cornflowerblue; 52 | line-height: 24px; 53 | } 54 | 55 | .cardButton { 56 | width: 100%; 57 | padding: 12px !important; 58 | border-top-left-radius: inherit !important; 59 | border-top-right-radius: inherit !important; 60 | } -------------------------------------------------------------------------------- /src/components/PricingCards/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import classes from './styles.module.css'; 4 | import Card from './components/Card'; 5 | import { themed } from '../../utils/theme'; 6 | import { GREEN_500, GREEN_300, INDIGO_300, INDIGO_500 } from '../../resources/colors'; 7 | import Page from '../../ui-components/Page'; 8 | 9 | export default class PricingCards extends React.Component { 10 | 11 | // invoked whenever theme changes in order to trigger themed function call 12 | updateComponent = (refresher = null) => refresher && this.setState({ refresher }); 13 | 14 | render = () => 15 |
16 |
17 | 20 | 23 | 26 |
27 |
28 |
; 29 | } -------------------------------------------------------------------------------- /src/components/PricingCards/styles.module.css: -------------------------------------------------------------------------------- 1 | .pricingCardsContainer { 2 | position: relative; 3 | min-width: inherit; 4 | min-height: inherit; 5 | display: flex; 6 | align-items: center; 7 | justify-content: center; 8 | } 9 | 10 | .cardMargin { 11 | margin-top: 16px; 12 | } 13 | 14 | .cardMargin:first-child { 15 | margin-top: 3.5rem; 16 | } 17 | 18 | .cardsContainer { 19 | display: flex; 20 | align-items: center; 21 | justify-content: center; 22 | flex-direction: column; 23 | margin: 24px 0; 24 | } 25 | 26 | @media(min-width: 900px) { 27 | .cardsContainer { 28 | flex-direction: row; 29 | margin: 0; 30 | } 31 | .cardMargin { 32 | margin-top: 0; 33 | margin-left: 16px; 34 | } 35 | .cardMargin:first-child { 36 | margin: 0; 37 | } 38 | .centerCard { 39 | transform: scale(1.2); 40 | z-index: 100; 41 | box-shadow: 8px 0 8px rgba(0, 0, 0, 0.1), -8px 0 8px rgba(0, 0, 0, 0.1) !important; 42 | } 43 | } -------------------------------------------------------------------------------- /src/components/RandomJokes/components/AllJokes/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import classes from './styles.module.css'; 4 | import { Collapse } from '@material-ui/core'; 5 | import { isEmptyList } from '../../../../utils'; 6 | import JokeCard from '../JokeCard'; 7 | 8 | export default class AllJokes extends React.Component { 9 | 10 | render = () => { 11 | const { jokes, loader, handleCopyJoke, handleLikeJoke } = this.props; 12 | 13 | return <>{isEmptyList(jokes) && !loader ?
Couldn't find any jokes to amuse you, LoL!
: 14 |
15 | {jokes.map((joke, index) => 16 | 17 | )} 18 |
19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /src/components/RandomJokes/components/AllJokes/styles.module.css: -------------------------------------------------------------------------------- 1 | .noData { 2 | position: absolute; 3 | top: 50%; 4 | left: 50%; 5 | transform: translate(-50%, -50%); 6 | max-width: 90%; 7 | font-weight: 100; 8 | font-size: 1.5rem; 9 | letter-spacing: 0.4px; 10 | word-break: break-word; 11 | color: var(--text-obscure); 12 | transition: color 0.5s ease-in-out; 13 | } 14 | 15 | .jokesContainer { 16 | width: 90%; 17 | margin: 2rem auto; 18 | composes: animateFadeGrow from global; 19 | } 20 | 21 | /* pretty weird but using custom class name (collapseWrapper) instead of .MuiCollapse-container makes it work */ 22 | 23 | .jokesContainer>.collapseWrapper .jokeWrapper { 24 | border: 1px solid var(--border-color); 25 | border-top: 0; 26 | background-color: var(--card-bg); 27 | transition: background-color 0.5s ease-in-out, border 0.5s ease-in-out; 28 | } 29 | 30 | .jokesContainer>.collapseWrapper:first-of-type .jokeWrapper { 31 | border-top-left-radius: 6px; 32 | border-top-right-radius: 6px; 33 | border-top: 1px solid var(--border-color); 34 | } 35 | 36 | .jokesContainer>.collapseWrapper:last-of-type .jokeWrapper { 37 | border-bottom-left-radius: 6px; 38 | border-bottom-right-radius: 6px; 39 | } 40 | 41 | @media (min-width: 900px) { 42 | .jokesContainer { 43 | width: 80%; 44 | } 45 | } -------------------------------------------------------------------------------- /src/components/RandomJokes/components/JokeCard/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import classes from './styles.module.css'; 4 | import FileCopyOutlinedIcon from '@material-ui/icons/FileCopyOutlined'; 5 | import FavoriteBorderRoundedIcon from '@material-ui/icons/FavoriteBorderRounded'; 6 | import FavoriteRoundedIcon from '@material-ui/icons/FavoriteRounded'; 7 | import { getPathValue } from '../../../../utils'; 8 | 9 | const JokeCard = React.memo(({ joke, className, handleCopyJoke, handleLikeJoke }) => { 10 | const jokeLiked = getPathValue(joke, 'liked', false); 11 | 12 | return
13 | {joke?.joke &&
{joke.joke}
} 14 |
15 |
handleLikeJoke(joke?.id)}> 16 | {jokeLiked ? 17 | : 18 | } 19 |
20 |
handleCopyJoke(joke?.joke)}> 21 | 22 |
23 |
24 |
25 | }) 26 | 27 | export default JokeCard; -------------------------------------------------------------------------------- /src/components/RandomJokes/components/JokeCard/styles.module.css: -------------------------------------------------------------------------------- 1 | .jokeContainer { 2 | padding: 16px; 3 | } 4 | 5 | .joke { 6 | color: var(--text); 7 | transition: color 0.5s ease-in-out; 8 | } 9 | 10 | .buttonsWrapper { 11 | margin-top: 1rem; 12 | display: flex; 13 | align-items: center; 14 | } 15 | 16 | .button { 17 | cursor: pointer; 18 | margin-right: 1rem; 19 | color: var(--text); 20 | opacity: 0.9; 21 | transition: color 0.5s ease-in-out, opacity 0.5s ease-in-out; 22 | } 23 | 24 | .button:hover { 25 | opacity: 1; 26 | } 27 | 28 | .likedJoke { 29 | opacity: 1 !important; 30 | color: var(--secondary-theme-color) !important; 31 | } 32 | 33 | .button:last-child { 34 | margin-right: 0; 35 | } -------------------------------------------------------------------------------- /src/components/RandomJokes/components/LikedJokes/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import classes from './styles.module.css'; 4 | import { Collapse } from '@material-ui/core'; 5 | import { isEmptyList } from '../../../../utils'; 6 | import JokeCard from '../JokeCard'; 7 | 8 | export default class LikedJokes extends React.Component { 9 | 10 | state = { 11 | likedJokes: [] 12 | } 13 | 14 | componentDidMount = () => this.getLikedJokes(this.props.jokes); 15 | 16 | componentDidUpdate = prevProps => prevProps.jokes !== this.props.jokes && this.getLikedJokes(this.props.jokes); 17 | 18 | getLikedJokes = jokes => !isEmptyList(jokes) && this.setState({ likedJokes: jokes.filter(joke => joke.liked) }); 19 | 20 | render = () => { 21 | const { loader, handleCopyJoke, handleLikeJoke } = this.props; 22 | 23 | return <>{isEmptyList(this.state.likedJokes) && !loader ?
Isn't this clear that you have to like some jokes first, LoL!
: 24 |
25 | {this.state.likedJokes.map((joke, index) => 26 | 27 | )} 28 |
29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /src/components/RandomJokes/components/LikedJokes/styles.module.css: -------------------------------------------------------------------------------- 1 | .noData { 2 | position: absolute; 3 | top: 50%; 4 | left: 50%; 5 | transform: translate(-50%, -50%); 6 | max-width: 90%; 7 | font-weight: 100; 8 | font-size: 1.5rem; 9 | letter-spacing: 0.4px; 10 | word-break: break-word; 11 | color: var(--text-obscure); 12 | transition: color 0.5s ease-in-out; 13 | } 14 | 15 | .jokesContainer { 16 | width: 90%; 17 | margin: 2rem auto; 18 | composes: animateFadeGrow from global; 19 | } 20 | 21 | /* pretty weird but using custom class name (collapseWrapper) instead of .MuiCollapse-container makes it work */ 22 | 23 | .jokesContainer>.collapseWrapper .jokeWrapper { 24 | border-top: 0; 25 | border: 1px solid var(--border-color); 26 | background-color: var(--card-bg); 27 | transition: background-color 0.5s ease-in-out, border 0.5s ease-in-out; 28 | } 29 | 30 | .jokesContainer>.collapseWrapper:first-of-type .jokeWrapper { 31 | border-top-left-radius: 6px; 32 | border-top-right-radius: 6px; 33 | border-top: 1px solid var(--border-color); 34 | } 35 | 36 | .jokesContainer>.collapseWrapper:last-of-type .jokeWrapper { 37 | border-bottom-left-radius: 6px; 38 | border-bottom-right-radius: 6px; 39 | } 40 | 41 | @media (min-width: 900px) { 42 | .jokesContainer { 43 | width: 80%; 44 | } 45 | } -------------------------------------------------------------------------------- /src/components/RandomJokes/styles.module.css: -------------------------------------------------------------------------------- 1 | .hideLoader { 2 | opacity: 0 !important; 3 | } 4 | 5 | .loader { 6 | z-index: 100; 7 | opacity: 1; 8 | position: fixed; 9 | top: 50%; 10 | left: 50%; 11 | transform: translate(-50%, -50%); 12 | transition: opacity 0.25s ease-in-out; 13 | } 14 | 15 | .tabsRoot { 16 | margin: 5rem auto 0; 17 | width: 90%; 18 | } 19 | 20 | .tabIndicator { 21 | transition: color 0.5s ease-in-out; 22 | } 23 | 24 | .tabRoot { 25 | color: var(--text) !important; 26 | transition: color 0.5s ease-in-out; 27 | } 28 | 29 | .btnWrapper { 30 | width: 90%; 31 | display: flex; 32 | align-items: center; 33 | margin: 0 auto 2rem; 34 | composes: animateFadeGrow from global; 35 | } 36 | 37 | .scrollToTopBtn { 38 | margin-left: 1rem !important; 39 | } 40 | 41 | @media (min-width: 900px) { 42 | .tabsRoot { 43 | width: 80%; 44 | } 45 | .btnWrapper { 46 | width: 80%; 47 | } 48 | } -------------------------------------------------------------------------------- /src/components/RandomQuotes/components/QuoteCard/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import classes from './styles.module.css'; 4 | import FileCopyOutlinedIcon from '@material-ui/icons/FileCopyOutlined'; 5 | 6 | const QuoteCard = React.memo(({ quote, author, className, handleCopyQuote }) =>
handleCopyQuote(quote)}> 7 |
8 |
{quote}
9 |
10 | 11 |
12 |
13 | {author &&
- {author}
} 14 |
); 15 | 16 | export default QuoteCard; -------------------------------------------------------------------------------- /src/components/RandomQuotes/components/QuoteCard/styles.module.css: -------------------------------------------------------------------------------- 1 | .quoteCard { 2 | cursor: pointer; 3 | padding: 1rem; 4 | } 5 | 6 | .quoteCard:hover { 7 | border-radius: 6px; 8 | transform: scale(1.01); 9 | filter: contrast(95%); 10 | } 11 | 12 | .quoteWrapper { 13 | display: flex; 14 | } 15 | 16 | .author { 17 | margin-top: 4px; 18 | font-size: 0.9rem; 19 | color: var(--text-obscurer); 20 | transition: color 0.5s ease-in-out; 21 | } 22 | 23 | .quote { 24 | flex: 1; 25 | margin-right: 4px; 26 | } -------------------------------------------------------------------------------- /src/components/RandomQuotes/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import classes from './styles.module.css'; 4 | import { Collapse } from '@material-ui/core'; 5 | import Page from '../../ui-components/Page'; 6 | import SpinnerLoader from '../../ui-components/SpinnerLoader'; 7 | import { copyTextToClipboard, getPathValue, handleError, isEmptyList } from '../../utils'; 8 | import QuoteCard from './components/QuoteCard'; 9 | import SnackBar from '../../ui-components/SnackBar'; 10 | 11 | export default class RandomQuotes extends React.Component { 12 | 13 | bottomRef = null; 14 | 15 | state = { 16 | loader: false, 17 | quotes: [], 18 | page: 0, 19 | hasMoreQuotes: false 20 | } 21 | 22 | componentDidMount = () => { 23 | this.fetchQuotes(this.state.page); 24 | window.addEventListener('scroll', this.handleScroll); 25 | } 26 | 27 | componentWillUnmount = () => { 28 | window.removeEventListener('scroll', this.handleScroll); 29 | } 30 | 31 | handleScroll = () => { 32 | if (!this.state.loader && this.state.hasMoreQuotes && window.pageYOffset + window.innerHeight === this.bottomRef.offsetTop) { 33 | this.fetchQuotes(this.state.page); 34 | } 35 | } 36 | 37 | setCallbackRef = element => { 38 | this.bottomRef = element; 39 | } 40 | 41 | fetchQuotes = (page, limit = 10) => this.setState({ loader: true }, () => fetch(`https://api.quotable.io/quotes?limit=${limit}&skip=${page * limit}`) 42 | .then(handleError) 43 | .then(res => res.json()) 44 | .then(data => this.setState(prevState => ({ 45 | loader: false, quotes: [...prevState.quotes, ...getPathValue(data, 'results', [])], page: prevState.page + 1, hasMoreQuotes: [...prevState.quotes, ...getPathValue(data, 'results', [])].length < getPathValue(data, 'totalCount', 0) 46 | }), () => getPathValue(data, 'results', []).forEach((quote, index) => setTimeout(() => this.toggleQuoteVisibility(quote._id, true), index * 50)))) 47 | .catch(error => this.setState({ 48 | loader: false, snack: { 49 | open: true, 50 | message: error.message, 51 | severity: 'error' 52 | } 53 | }))); 54 | 55 | toggleQuoteVisibility = (id, visible, callback) => this.setState(prevState => ({ 56 | quotes: prevState.quotes.map(quote => id && quote._id === id ? { ...quote, visible } : quote) 57 | }), callback); 58 | 59 | handleCopyQuote = quote => quote && copyTextToClipboard(quote); 60 | 61 | handleSnackClose = () => this.setState({ snack: { ...this.state.snack, open: false } }); 62 | 63 | render = () => 64 |
65 | {!this.state.loader && isEmptyList(this.state.quotes) ?
No quotes could be fetched right now, please try again later
66 | :
67 | {this.state.quotes.map((quote, index) => 68 | 69 | )} 70 |
} 71 |
72 | {this.state?.snack?.message && } 73 |
74 | } -------------------------------------------------------------------------------- /src/components/RandomQuotes/styles.module.css: -------------------------------------------------------------------------------- 1 | .quotesContainer { 2 | width: 90%; 3 | margin: 5rem auto; 4 | } 5 | 6 | .quotesContainer>.collapseWrapper .quoteWrapper { 7 | border: 1px solid var(--border-color); 8 | border-top: 0; 9 | color: var(--text); 10 | background-color: var(--card-bg); 11 | transition: transform 0.25s ease-in-out, color 0.5s ease-in-out, background-color 0.5s ease-in-out, border 0.5s ease-in-out, border-radius 0.25s ease-in-out, filter 0.25s ease-in-out; 12 | } 13 | 14 | .quotesContainer>.collapseWrapper:first-of-type .quoteWrapper { 15 | border-top-left-radius: 6px; 16 | border-top-right-radius: 6px; 17 | border-top: 1px solid var(--border-color); 18 | } 19 | 20 | .quotesContainer>.collapseWrapper:last-of-type .quoteWrapper { 21 | border-bottom-left-radius: 6px; 22 | border-bottom-right-radius: 6px; 23 | } 24 | 25 | .loader { 26 | opacity: 1; 27 | position: fixed; 28 | top: 50%; 29 | left: 50%; 30 | transform: translate(-50%, -50%); 31 | transition: opacity 0.25s ease-in-out; 32 | } 33 | 34 | .hideLoader { 35 | opacity: 0; 36 | } 37 | 38 | .noData { 39 | position: absolute; 40 | top: 50%; 41 | left: 50%; 42 | transform: translate(-50%, -50%); 43 | max-width: 90%; 44 | font-weight: 100; 45 | font-size: 1.5rem; 46 | letter-spacing: 0.4px; 47 | word-break: break-word; 48 | color: var(--text-obscure); 49 | transition: color 0.5s ease-in-out; 50 | } 51 | 52 | @media (min-width: 768px) { 53 | .quotesContainer { 54 | width: 80%; 55 | } 56 | } -------------------------------------------------------------------------------- /src/components/SkeletonLoader/components/Post/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import classes from './styles.module.css'; 4 | 5 | const Post = React.memo(({ name, avatar, title, body }) =>
6 |
7 | {title} 8 |
{name || ''}
9 |
10 |
11 |
{title || ''}
12 |
{body || ''}
13 |
14 |
); 15 | 16 | export default Post; -------------------------------------------------------------------------------- /src/components/SkeletonLoader/components/Post/styles.module.css: -------------------------------------------------------------------------------- 1 | .animateFade { 2 | opacity: 0; 3 | animation: fade 0.25s ease-in-out forwards; 4 | } 5 | 6 | .post { 7 | display: flex; 8 | flex-direction: column; 9 | border-radius: 4px; 10 | background-color: var(--card-bg); 11 | border: 1px solid var(--border-color); 12 | transition: background-color 0.5s ease-in-out, border 0.5s ease-in-out; 13 | } 14 | 15 | .col1 { 16 | padding: 0.5rem; 17 | display: flex; 18 | flex-direction: column; 19 | align-items: center; 20 | justify-content: center; 21 | composes: animateFade; 22 | } 23 | 24 | .col2 { 25 | flex: 1; 26 | padding: 0.5rem; 27 | composes: animateFade; 28 | } 29 | 30 | .avatar { 31 | width: 6rem; 32 | height: 6rem; 33 | border-radius: 50%; 34 | } 35 | 36 | .text { 37 | color: var(--text); 38 | transition: color 0.5s ease-in-out; 39 | } 40 | 41 | .name { 42 | composes: text; 43 | margin-top: 0.25rem; 44 | font-size: 0.8rem; 45 | font-weight: 600; 46 | } 47 | 48 | .title { 49 | composes: text; 50 | font-size: 1.2rem; 51 | font-weight: 600; 52 | letter-spacing: -0.2px; 53 | color: var(--text-prominent); 54 | } 55 | 56 | .body { 57 | composes: text; 58 | margin-top: 0.5rem; 59 | } 60 | 61 | @media (min-width: 768px) { 62 | .post { 63 | flex-direction: row; 64 | } 65 | .col1 { 66 | min-width: 10rem; 67 | } 68 | } 69 | 70 | @media (min-width: 1200px) { 71 | .avatar { 72 | width: 8rem; 73 | height: 8rem; 74 | border-radius: 50%; 75 | } 76 | } 77 | 78 | @keyframes fade { 79 | from { 80 | opacity: 0; 81 | } 82 | to { 83 | opacity: 1; 84 | } 85 | } 86 | 87 | @-webkit-keyframes fade { 88 | from { 89 | opacity: 0; 90 | } 91 | to { 92 | opacity: 1; 93 | } 94 | } -------------------------------------------------------------------------------- /src/components/SkeletonLoader/components/Shimmer/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import classes from './styles.module.css'; 4 | 5 | const Shimmer = React.memo(() =>
6 |
7 |
); 8 | 9 | export default Shimmer; -------------------------------------------------------------------------------- /src/components/SkeletonLoader/components/Shimmer/styles.module.css: -------------------------------------------------------------------------------- 1 | .shimmerWrapper { 2 | position: absolute; 3 | top: 0; 4 | left: 0; 5 | width: 100%; 6 | height: 100%; 7 | animation: loading 2.5s infinite; 8 | } 9 | 10 | .shimmer { 11 | width: 50%; 12 | height: 100%; 13 | transform: skewX(-deg20); 14 | background-color: var(--shimmer-color); 15 | box-shadow: 0 0 2rem 2rem var(--shimmer-color); 16 | transition: background-color 0.5s ease-in-out, box-shadow 0.5s ease-in-out; 17 | } 18 | 19 | @keyframes loading { 20 | 0% { 21 | transform: translateX(-100%) 22 | } 23 | 50% { 24 | transform: translateX(-60%) 25 | } 26 | 100% { 27 | transform: translate(150%) 28 | } 29 | } -------------------------------------------------------------------------------- /src/components/SkeletonLoader/components/Skeleton/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | 4 | import classes from './styles.module.css'; 5 | 6 | export const SKELETON_TYPES = Object.freeze({ 7 | TEXT: 'skeletonText', 8 | TITLE: 'skeletonTitle', 9 | IMAGE: 'skeletonImage', 10 | AVATAR: 'skeletonAvatar' 11 | }); 12 | 13 | const Skeleton = React.memo(({ type, style }) => { 14 | const skeletonClass = `${classes[type]}`; 15 | 16 | return
; 17 | }); 18 | 19 | export default Skeleton; 20 | 21 | Skeleton.prototype = { 22 | type: PropTypes.string.isRequired, 23 | style: PropTypes.object 24 | } 25 | 26 | Skeleton.defaultProps = { 27 | type: SKELETON_TYPES.TEXT 28 | } -------------------------------------------------------------------------------- /src/components/SkeletonLoader/components/Skeleton/styles.module.css: -------------------------------------------------------------------------------- 1 | .skeleton { 2 | background-color: var(--skeleton-color); 3 | transition: background-color 0.5s ease-in-out; 4 | } 5 | 6 | .skeletonText { 7 | composes: skeleton; 8 | width: 100%; 9 | height: 0.75rem; 10 | border-radius: 0.5rem; 11 | } 12 | 13 | .skeletonTitle { 14 | composes: skeleton; 15 | width: 40%; 16 | height: 1.5rem; 17 | border-radius: 0.5rem; 18 | } 19 | 20 | .skeletonImage { 21 | composes: skeleton; 22 | width: 100%; 23 | height: 10rem; 24 | border-radius: 0.5rem; 25 | } 26 | 27 | .skeletonAvatar { 28 | composes: skeleton; 29 | width: 6rem; 30 | height: 6rem; 31 | border-radius: 50%; 32 | } 33 | 34 | @media (min-width: 768px) { 35 | .skeletonText { 36 | height: 1rem; 37 | } 38 | .skeletonTitle { 39 | height: 2rem; 40 | } 41 | .skeletonImage { 42 | height: 20rem; 43 | } 44 | } 45 | 46 | @media (min-width: 1200px) { 47 | .skeletonImage { 48 | height: 30rem; 49 | } 50 | .skeletonAvatar { 51 | width: 8rem; 52 | height: 8rem; 53 | } 54 | } -------------------------------------------------------------------------------- /src/components/SkeletonLoader/components/SkeletonWrapper/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import classes from './styles.module.css'; 4 | import Skeleton, { SKELETON_TYPES } from '../Skeleton'; 5 | import Shimmer from '../Shimmer'; 6 | 7 | const SkeletonWrapper = React.memo(() =>
8 |
9 | 10 | 11 |
12 |
13 | 14 | 15 | 16 | 17 |
18 | 19 |
); 20 | 21 | export default SkeletonWrapper; -------------------------------------------------------------------------------- /src/components/SkeletonLoader/components/SkeletonWrapper/styles.module.css: -------------------------------------------------------------------------------- 1 | .skeletonWrapper { 2 | position: relative; 3 | overflow: hidden; 4 | display: flex; 5 | flex-direction: column; 6 | border-radius: 4px; 7 | background-color: var(--card-bg); 8 | border: 1px solid var(--border-color); 9 | transition: background-color 0.5s ease-in-out, border 0.5s ease-in-out; 10 | } 11 | 12 | .col1 { 13 | padding: 0.5rem; 14 | display: flex; 15 | flex-direction: column; 16 | align-self: center; 17 | justify-content: center; 18 | } 19 | 20 | .col2 { 21 | padding: 0.5rem; 22 | flex: 1; 23 | } 24 | 25 | @media (min-width: 768px) { 26 | .skeletonWrapper { 27 | flex-direction: row; 28 | } 29 | .col1, .col2 { 30 | padding: 0.75rem; 31 | } 32 | } 33 | 34 | @media (min-width: 1000px) { 35 | .col1, .col2 { 36 | padding: 1rem; 37 | } 38 | } -------------------------------------------------------------------------------- /src/components/SkeletonLoader/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import classes from './styles.module.css'; 4 | import Page from '../../ui-components/Page'; 5 | import SkeletonWrapper from './components/SkeletonWrapper'; 6 | import { handleError, isEmptyList } from '../../utils'; 7 | import { PLACEHOLDER_POSTS_API, RANDOM_AVATAR_API } from '../../resources/constants'; 8 | import Post from './components/Post'; 9 | import SnackBar from '../../ui-components/SnackBar'; 10 | 11 | const DATA_SET_SIZE = 12; // must be less than 30 12 | export default class SkeletonLoader extends React.Component { 13 | 14 | state = { 15 | loader: true, 16 | data: null 17 | } 18 | 19 | componentDidMount = () => { 20 | setTimeout(this.fetchPosts, 2000); 21 | }; 22 | 23 | fetchPosts = () => this.setState({ loader: true }, () => fetch(PLACEHOLDER_POSTS_API) 24 | .then(handleError) 25 | .then(res => res.json()) 26 | .then(data => this.setPosts(isEmptyList(data) ? [] : data.slice(0, DATA_SET_SIZE))) 27 | .catch(error => this.setState({ 28 | loader: false, data: null, snack: { 29 | open: true, 30 | message: error.message, 31 | severity: 'error' 32 | } 33 | }))); 34 | 35 | fetchAvatars = async () => { 36 | try { 37 | const res = await fetch(`${RANDOM_AVATAR_API}?limit=${DATA_SET_SIZE}`, { 38 | method: 'GET', 39 | headers: { 40 | 'X-API-KEY': 'D971CE3E-28144F59-9149581A-BA42ED96', 41 | 'Accept': 'application/json', 42 | 'Cache-Control': 'no-cache' 43 | } 44 | }); 45 | const data = await res.json(); 46 | return data; 47 | } catch (e) { 48 | return null; 49 | } 50 | } 51 | 52 | setPosts = async data => { 53 | try { 54 | const avatars = await this.fetchAvatars(); 55 | const posts = isEmptyList(data) ? [] : data.map((post, index) => ({ ...post, name: index < avatars.length ? avatars[index]['name'] : '', avatar: index < avatars.length ? avatars[index]['photo'] : '' })) 56 | this.setState({ loader: false, data: posts }); 57 | } catch (e) { 58 | this.setState({ 59 | loader: false, data, snack: { 60 | open: true, 61 | message: e.message, 62 | severity: 'error' 63 | } 64 | }) 65 | } 66 | } 67 | 68 | handleSnackClose = () => this.setState({ snack: { ...this.state.snack, open: false } }); 69 | 70 | render = () => { 71 | const { data, loader, snack } = this.state; 72 | 73 | return 74 | {loader ?
75 | {new Array(DATA_SET_SIZE).fill(null).map((_, index) => )} 76 |
:
77 | {!isEmptyList(data) && data.map(post => )} 78 |
} 79 | {snack?.message && } 80 |
; 81 | } 82 | } -------------------------------------------------------------------------------- /src/components/SkeletonLoader/styles.module.css: -------------------------------------------------------------------------------- 1 | .contentWrapper, .postsWrapper { 2 | margin-top: 3rem; 3 | padding: 1rem; 4 | display: grid; 5 | grid-template-columns: 1fr; 6 | gap: 1rem; 7 | } 8 | 9 | @media (min-width: 768px) { 10 | .contentWrapper, .postsWrapper { 11 | padding: 1rem 2rem 2rem; 12 | } 13 | } 14 | 15 | @media (min-width: 1000px) { 16 | .contentWrapper, .postsWrapper { 17 | padding: 1rem 3rem 3rem; 18 | grid-template-columns: 1fr 1fr; 19 | } 20 | } 21 | 22 | @media (min-width: 1500px) { 23 | .contentWrapper, .postsWrapper { 24 | grid-template-columns: 1fr 1fr 1fr; 25 | } 26 | } -------------------------------------------------------------------------------- /src/components/StonePaperScissor/components/GameCard/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import classes from './styles.module.css'; 4 | 5 | const GameCard = React.memo(({option, cardStyle, selectOption, toggleView}) => { 6 | 7 | const handleCardClick = () => { 8 | if (typeof selectOption === 'function') { 9 | selectOption(option); 10 | toggleView(); 11 | } 12 | } 13 | 14 | return
15 |
16 | {option.title} 17 |
18 |
{option.title}
19 |
20 | }); 21 | 22 | export default GameCard; -------------------------------------------------------------------------------- /src/components/StonePaperScissor/components/GameCard/styles.module.css: -------------------------------------------------------------------------------- 1 | .card { 2 | padding: 1rem; 3 | margin-top: 1rem; 4 | border-radius: 4px; 5 | border: 1px solid var(--border-color); 6 | background-color: var(--card-bg); 7 | box-shadow: var(--card-shadow); 8 | transition: border 0.5s ease-in-out, background-color 0.5s ease-in-out, box-shadow 0.5s ease-in-out; 9 | } 10 | 11 | .card:first-child { 12 | margin-top: 0; 13 | } 14 | 15 | .card:hover { 16 | box-shadow: var(--card-shadow-hover); 17 | } 18 | 19 | .cardImageWrapper { 20 | padding: 1rem 1rem 0; 21 | margin: 0 auto; 22 | width: 280px; 23 | height: 280px; 24 | border-radius: 50%; 25 | overflow: hidden; 26 | display: flex; 27 | align-items: center; 28 | justify-content: center; 29 | background-color: var(--overlay-bg); 30 | transition: background-color 0.5s ease-in-out; 31 | } 32 | 33 | .cardImage { 34 | display: block; 35 | height: inherit; 36 | width: inherit; 37 | filter: var(--svg-color); 38 | transition: filter 0.5s ease-in-out; 39 | } 40 | 41 | .cardTitle { 42 | margin-top: 1rem; 43 | text-align: center; 44 | font-size: 2rem; 45 | letter-spacing: -0.3px; 46 | text-transform: uppercase; 47 | color: var(--text); 48 | transition: color 0.5s ease-in-out; 49 | } 50 | 51 | @media (min-width: 768px) { 52 | .card { 53 | margin-top: 0; 54 | margin-left: 1rem; 55 | } 56 | .card:first-child { 57 | margin-left: 0; 58 | } 59 | .cardImageWrapper { 60 | width: 200px; 61 | height: 200px; 62 | } 63 | } 64 | 65 | @media (min-width: 900px) { 66 | .cardImageWrapper { 67 | width: 250px; 68 | height: 250px; 69 | } 70 | } 71 | 72 | @media (min-width: 1200px) { 73 | .cardImageWrapper { 74 | width: 300px; 75 | height: 300px; 76 | } 77 | } -------------------------------------------------------------------------------- /src/components/StonePaperScissor/components/GameView/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import GameCard from '../GameCard'; 3 | 4 | import classes from './styles.module.css'; 5 | 6 | const GameView = React.memo(({ options, selectOption, toggleView }) => { 7 | 8 | return
9 | {Object.keys(options).map(option => )} 10 |
; 11 | }); 12 | 13 | export default GameView; -------------------------------------------------------------------------------- /src/components/StonePaperScissor/components/GameView/styles.module.css: -------------------------------------------------------------------------------- 1 | .cardsWrapper { 2 | display: flex; 3 | flex-direction: column; 4 | align-items: center; 5 | } 6 | 7 | @media (min-width: 768px) { 8 | .cardsWrapper { 9 | margin-top: 2rem; 10 | flex-direction: row; 11 | } 12 | } -------------------------------------------------------------------------------- /src/components/StonePaperScissor/components/ResultView/index.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react'; 2 | import { GREEN_400, ORANGE_400, RED_400 } from '../../../../resources/colors'; 3 | import Button from '../../../../ui-components/Button'; 4 | import { isEmptyObject } from '../../../../utils'; 5 | import GameCard from '../GameCard'; 6 | 7 | import classes from './styles.module.css'; 8 | 9 | const results = Object.freeze({ 10 | DRAW: 0, 11 | LOSE: 1, 12 | WIN: 2 13 | }); 14 | 15 | const ResultView = React.memo(({ selectedOption, options, score, setScore, toggleView }) => { 16 | 17 | const [randomOption, setRandomOption] = useState(null); 18 | const [playerResult, setPlayerResult] = useState(results.DRAW); 19 | 20 | useEffect(() => { 21 | if (!isEmptyObject(options)) { 22 | const computerOption = options[getRandomOptionKey(options)]; 23 | const result = getResult(computerOption); 24 | 25 | setRandomOption(computerOption); 26 | setPlayerResult(result); 27 | 28 | if (result === results.WIN) { 29 | localStorage.setItem('score', JSON.stringify(score + 1)); 30 | setScore(score + 1); 31 | } else { 32 | localStorage.setItem('score', JSON.stringify(score)); 33 | } 34 | } 35 | // eslint-disable-next-line react-hooks/exhaustive-deps 36 | }, []); 37 | 38 | const getRandomOptionKey = options => Object.keys(options)[Math.floor(Math.random() * Object.keys(options).length)]; 39 | 40 | const getResult = (computerOption = {}) => { 41 | if (selectedOption.title === computerOption.title) return results.DRAW; 42 | else if ((selectedOption.title === 'paper' && computerOption.title === 'stone') || 43 | (selectedOption.title === 'stone' && computerOption.title === 'scissor') || 44 | (selectedOption.title === 'scissor' && computerOption.title === 'paper')) return results.WIN; 45 | else return results.LOSE; 46 | } 47 | 48 | return
49 | {(randomOption && selectedOption) && 50 | <> 51 |
52 | {playerResult === results.WIN ? 'You won!' 53 | : playerResult === results.LOSE ? 'You lost!' 54 | : 'You drew!'} 55 |
56 |
57 | 58 | 59 |
60 | 62 | } 63 |
; 64 | }); 65 | 66 | export default ResultView; -------------------------------------------------------------------------------- /src/components/StonePaperScissor/components/ResultView/styles.module.css: -------------------------------------------------------------------------------- 1 | .resultWrapper { 2 | display: flex; 3 | flex-direction: column; 4 | align-items: center; 5 | } 6 | 7 | .resultText { 8 | font-size: 2rem; 9 | font-weight: 100; 10 | letter-spacing: 0.4px; 11 | text-transform: uppercase; 12 | color: var(--text); 13 | transition: color 0.5s ease-in-out; 14 | } 15 | 16 | .cardsWrapper { 17 | margin-top: 2rem; 18 | display: flex; 19 | flex-direction: column; 20 | align-items: center; 21 | justify-content: center; 22 | } 23 | 24 | .resetBtn { 25 | margin-top: 2rem !important; 26 | } 27 | 28 | @media (min-width: 768px) { 29 | .cardsWrapper { 30 | flex-direction: row; 31 | } 32 | } -------------------------------------------------------------------------------- /src/components/StonePaperScissor/index.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | 3 | import classes from './styles.module.css'; 4 | import Page from '../../ui-components/Page'; 5 | import Button from '../../ui-components/Button'; 6 | import ResultView from './components/ResultView'; 7 | import GameView from './components/GameView'; 8 | 9 | const OPTIONS = Object.freeze({ 10 | STONE: { title: 'stone', image: 'https://svgsilh.com/svg/2385848.svg' }, 11 | PAPER: { title: 'paper', image: 'https://svgsilh.com/svg/643456.svg' }, 12 | SCISSOR: { title: 'scissor', image: 'https://svgsilh.com/svg/643631.svg' } 13 | }); 14 | 15 | const StonePaperScissor = React.memo(props => { 16 | 17 | const [showResult, setShowResult] = useState(false); 18 | const [score, setScore] = useState(JSON.parse(localStorage.getItem('score') || 0)); 19 | const [selectedOption, setSelectedOption] = useState(null); 20 | 21 | const resetScore = () => { 22 | localStorage.setItem('score', JSON.stringify(0)); 23 | setScore(0); 24 | setShowResult(false); 25 | } 26 | 27 | return 28 |
29 |
30 | Stone-Paper-Scissor 31 |
32 |
33 |
34 | Score: {score} 35 |
36 | 38 |
39 |
40 | {showResult ? 41 | setShowResult(false)} /> : 42 | setShowResult(true)} />} 43 |
44 |
45 |
46 | }); 47 | 48 | export default StonePaperScissor; -------------------------------------------------------------------------------- /src/components/StonePaperScissor/styles.module.css: -------------------------------------------------------------------------------- 1 | .gameContainer { 2 | margin-top: 5rem; 3 | } 4 | 5 | .gameHeader { 6 | text-align: center; 7 | font-size: 2rem; 8 | color: var(--text); 9 | transition: color 0.5s ease-in-out; 10 | } 11 | 12 | .scoreWrapper { 13 | display: flex; 14 | flex-direction: column; 15 | align-items: center; 16 | justify-content: center; 17 | margin-top: 2rem; 18 | } 19 | 20 | .score { 21 | padding: 0.45rem 1rem; 22 | border-radius: 4px; 23 | font-size: 0.9rem; 24 | font-weight: 600; 25 | letter-spacing: 0.3px; 26 | text-transform: uppercase; 27 | color: var(--secondary-theme-color); 28 | border: 1px solid var(--secondary-theme-color); 29 | transition: color 0.5s ease-in-out, border 0.5s ease-in-out; 30 | } 31 | 32 | .resetBtn { 33 | margin-top: 1rem !important; 34 | } 35 | 36 | .contentWrapper { 37 | margin: 2rem 0; 38 | display: flex; 39 | align-items: center; 40 | justify-content: center; 41 | } 42 | 43 | @media (min-width: 768px) { 44 | .gameHeader { 45 | font-size: 3rem; 46 | font-weight: 600; 47 | } 48 | .scoreWrapper { 49 | flex-direction: row; 50 | } 51 | .resetBtn { 52 | margin-top: 0 !important; 53 | margin-left: 2rem !important; 54 | } 55 | } 56 | 57 | @media (min-width: 900px) { 58 | .gameHeader { 59 | font-size: 4rem; 60 | } 61 | } -------------------------------------------------------------------------------- /src/components/Todos/components/Todo/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import classes from './styles.module.css'; 4 | import CheckBox from '../../../../ui-components/CheckBox'; 5 | import Iconbutton from '../../../../ui-components/Button/Iconbutton'; 6 | import EditRoundedIcon from '@material-ui/icons/EditRounded'; 7 | import DeleteOutlineRoundedIcon from '@material-ui/icons/DeleteOutlineRounded'; 8 | import CloseRoundedIcon from '@material-ui/icons/CloseRounded'; 9 | import CheckRoundedIcon from '@material-ui/icons/CheckRounded'; 10 | import { GREEN_700, RED_700 } from '../../../../resources/colors'; 11 | import Input from '../../../../ui-components/Input'; 12 | 13 | export default class Todo extends React.Component { 14 | 15 | state = { 16 | value: this.props.todo.value 17 | } 18 | 19 | componentDidUpdate = prevProps => { 20 | // eslint-disable-next-line eqeqeq 21 | if (prevProps.todo != this.props.todo) this.setState({ value: this.props.todo.value }); 22 | } 23 | 24 | handleValueChange = e => this.setState({ value: e.target.value }); 25 | 26 | handleEditTodo = index => { 27 | const updatedTodo = this.state.value; 28 | this.setState({ value: '' }, () => this.props.handleEditTodo(index, updatedTodo)); 29 | } 30 | 31 | render = () => { 32 | const { index, todo } = this.props; 33 | const { value, checked, isEditable } = todo; 34 | 35 | return
36 | {!isEditable && this.props.toggleCheckBox(index)}>} 38 | {!isEditable ?
39 |
{value}
40 |
:
41 | 42 |
} 43 | {!isEditable ? <> 44 | this.props.handleEditClick(index)} icon={}> 45 | this.props.handleDeleteTodo(index)} icon={}> 46 | : <> 47 | this.handleEditTodo(index)} icon={}> 48 | this.props.handleEditClick(index)} icon={}> 49 | } 50 |
; 51 | } 52 | } -------------------------------------------------------------------------------- /src/components/Todos/components/Todo/styles.module.css: -------------------------------------------------------------------------------- 1 | .todo { 2 | display: flex; 3 | align-items: center; 4 | padding: 8px; 5 | color: #212121; 6 | border-bottom: 1px solid rgba(0, 0, 0, 0.1); 7 | background-color: var(--card-bg); 8 | transition: background-color 0.5s ease-in-out; 9 | composes: animateFadeUp from global; 10 | } 11 | 12 | .todo:first-child { 13 | border-top-right-radius: 6px; 14 | border-top-left-radius: 6px; 15 | } 16 | 17 | .todo:last-child { 18 | border-bottom: none; 19 | border-bottom-right-radius: 6px; 20 | border-bottom-left-radius: 6px; 21 | } 22 | 23 | .labelWrapper, .todoInputWrapper { 24 | flex: 1; 25 | margin-left: 4px; 26 | } 27 | 28 | .label { 29 | color: var(--text-obscure); 30 | width: fit-content; 31 | white-space: pre-wrap; 32 | word-break: break-word; 33 | } 34 | 35 | .iconBtn { 36 | display: inline !important; 37 | } 38 | 39 | .label.strike { 40 | color: var(--text-obscurer); 41 | transition: color 0.1s ease; 42 | } 43 | 44 | .strike { 45 | position: relative; 46 | } 47 | 48 | .strike:after { 49 | content: ' '; 50 | position: absolute; 51 | top: 50%; 52 | left: 0; 53 | width: 100%; 54 | height: 1px; 55 | background: var(--text-obscurer); 56 | animation-name: strike; 57 | animation-duration: 0.1s; 58 | animation-timing-function: ease; 59 | animation-iteration-count: 1; 60 | animation-fill-mode: both; 61 | } 62 | 63 | @keyframes strike { 64 | 0% { 65 | width: 0; 66 | background-color: #000; 67 | } 68 | 100% { 69 | width: 100%; 70 | background-color: var(--text-obscure); 71 | } 72 | } 73 | 74 | @media (min-width: 900px) { 75 | .labelWrapper { 76 | margin-left: 8px; 77 | } 78 | } -------------------------------------------------------------------------------- /src/components/Todos/index.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-restricted-globals */ 2 | import React from 'react'; 3 | 4 | import classes from './styles.module.css'; 5 | import Input from '../../ui-components/Input'; 6 | import Button from '../../ui-components/Button'; 7 | import SnackBar from '../../ui-components/SnackBar'; 8 | import NoteAddIcon from '@material-ui/icons/NoteAdd'; 9 | import Iconbutton from '../../ui-components/Button/Iconbutton'; 10 | import { isEmptyList, isEmptyString } from '../../utils'; 11 | import Todo from './components/Todo'; 12 | import Page from '../../ui-components/Page'; 13 | 14 | export default class Todos extends React.Component { 15 | 16 | state = { 17 | todos: JSON.parse(localStorage.getItem('todos')) || [], 18 | value: '', 19 | snackOpen: false, 20 | snackMessage: '', 21 | snackSeverity: '' 22 | } 23 | 24 | componentDidMount = () => { 25 | window.addEventListener('keydown', this.handleKeyDown); 26 | } 27 | 28 | componentWillUnmount = () => { 29 | window.removeEventListener('keydown', this.handleKeyDown); 30 | } 31 | 32 | handleKeyDown = e => e.key === 'Enter' && this.handleAddTodo(); 33 | 34 | handleValueChange = e => this.setState({ value: e.target.value }); 35 | 36 | handleAddTodo = () => { 37 | if (isEmptyString(this.state.value)) { 38 | this.setState({ snackOpen: true, snackMessage: 'You need to add something first!', snackSeverity: 'info' }); 39 | } else { 40 | const todo = { value: this.state.value, checked: false, isEditable: false }; 41 | let todos = [...this.state.todos, todo]; 42 | this.setState({ todos, value: '', snackOpen: true, snackMessage: 'ToDo added successfully!', snackSeverity: 'success' }, 43 | () => localStorage.setItem('todos', JSON.stringify(todos))); 44 | } 45 | // shifts the focus back to input field so that the user doesn't have to make an extra click 46 | document.querySelector('#add-todo').focus(); 47 | }; 48 | 49 | handleSnackClose = () => this.setState({ snackOpen: false }); 50 | 51 | toggleCheckBox = index => this.setState({ todos: this.state.todos.map((todo, i) => i === index ? { ...todo, checked: !todo.checked } : todo) }, 52 | () => localStorage.setItem('todos', JSON.stringify(this.state.todos))); 53 | 54 | handleEditClick = index => this.setState({ todos: this.state.todos.map((todo, i) => i === index ? { ...todo, isEditable: !todo.isEditable } : todo) }, 55 | () => localStorage.setItem('todos', JSON.stringify(this.state.todos))); 56 | 57 | handleEditTodo = (index, value) => this.setState({ 58 | snackOpen: true, snackMessage: 'ToDo edited successfully!', snackSeverity: 'success', 59 | todos: this.state.todos.map((todo, i) => i === index ? { value, checked: false, isEditable: false } : todo) 60 | }, () => localStorage.setItem('todos', JSON.stringify(this.state.todos))); 61 | 62 | handleDeleteTodo = index => this.setState({ 63 | snackOpen: true, snackMessage: 'ToDo deleted successfully!', snackSeverity: 'success', 64 | todos: this.state.todos.filter((todo, i) => i !== index) 65 | }, () => localStorage.setItem('todos', JSON.stringify(this.state.todos))); 66 | 67 | render = () => { 68 | const { value, todos, snackMessage, snackOpen, snackSeverity } = this.state; 69 | const { containerStyle } = this.props; 70 | 71 | return 72 |
73 |
ToDos Manager
74 |
75 |
76 | 77 | 78 | }> 79 |
80 |
81 | {!isEmptyList(todos) &&
82 | {todos.map((todo, index) => { 83 | return 85 | })} 86 |
} 87 | 88 |
89 |
; 90 | } 91 | } -------------------------------------------------------------------------------- /src/components/Todos/styles.module.css: -------------------------------------------------------------------------------- 1 | .todosContainer { 2 | min-width: inherit; 3 | min-height: inherit; 4 | display: flex; 5 | flex-direction: column; 6 | align-items: center; 7 | justify-content: center; 8 | } 9 | 10 | .todosHeader { 11 | font-size: 2rem; 12 | letter-spacing: 0.5px; 13 | font-weight: bold; 14 | text-align: center; 15 | color: var(--text); 16 | text-shadow: 6px 4px rgba(0, 0, 0, 0.2); 17 | transition: color 0.5s ease-in-out; 18 | } 19 | 20 | .addTodosContainer { 21 | background-color: var(--card-bg); 22 | padding: 16px; 23 | width: 85%; 24 | margin: 24px auto 0; 25 | border-radius: 6px; 26 | border: 1px solid rgba(0, 0, 0, 0.1); 27 | box-shadow: 0 0 10px rgba(0, 0, 0, 0.2); 28 | transition: box-shadow 0.2s ease-in-out, transform 0.2s ease-in-out, background-color 0.5s ease-in-out; 29 | } 30 | 31 | .addTodosContainer:hover { 32 | box-shadow: 0 0 12px rgba(0, 0, 0, 0.3); 33 | transform: scale(1.02); 34 | } 35 | 36 | ::placeholder { 37 | /* Chrome, Firefox, Opera, Safari 10.1+ */ 38 | color: var(--text); 39 | opacity: 1; 40 | /* Firefox */ 41 | } 42 | 43 | :-ms-input-placeholder { 44 | /* Internet Explorer 10-11 */ 45 | color: var(--text); 46 | } 47 | 48 | ::-ms-input-placeholder { 49 | /* Microsoft Edge */ 50 | color: var(--text); 51 | } 52 | 53 | .todosWrapper { 54 | margin: 16px auto 0; 55 | width: 85%; 56 | border-radius: 6px; 57 | border: 1px solid rgba(0, 0, 0, 0.1); 58 | box-shadow: 0 0 10px rgba(0, 0, 0, 0.2); 59 | } 60 | 61 | .addTodoWrapper { 62 | display: flex; 63 | align-items: center; 64 | } 65 | 66 | .desktopBtn { 67 | display: none !important; 68 | } 69 | 70 | .mobileBtn { 71 | margin-left: 8px !important; 72 | display: block !important; 73 | } 74 | 75 | @media (min-width: 900px) { 76 | .todosHeader { 77 | font-size: 3rem; 78 | } 79 | .addTodosContainer { 80 | max-width: 800px; 81 | } 82 | .todosWrapper { 83 | max-height: 400px; 84 | max-width: 800px; 85 | overflow: hidden auto; 86 | } 87 | .desktopBtn { 88 | display: block !important; 89 | margin-left: 8px !important; 90 | min-width: 150px !important; 91 | padding: 16px 0 !important; 92 | } 93 | .mobileBtn { 94 | margin-left: 0 !important; 95 | display: none !important; 96 | } 97 | } -------------------------------------------------------------------------------- /src/components/TwitterUIClone/components/TweetBlock/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import classes from './styles.module.css'; 4 | import { getPathValue } from '../../../../utils'; 5 | import Iconbutton from '../../../../ui-components/Button/Iconbutton'; 6 | import CheckCircleRoundedIcon from '@material-ui/icons/CheckCircleRounded'; 7 | import ChatBubbleOutlineRoundedIcon from '@material-ui/icons/ChatBubbleOutlineRounded'; 8 | import FavoriteBorderRoundedIcon from '@material-ui/icons/FavoriteBorderRounded'; 9 | import LoopRoundedIcon from '@material-ui/icons/LoopRounded'; 10 | import ShareRoundedIcon from '@material-ui/icons/ShareRounded'; 11 | 12 | const TweetBlock = React.memo(({ tweet, blockBackground, color, secondaryColor, borderColor }) => 13 |
14 |
15 | {tweet.username} 16 |
17 |
18 |
19 |
{getPathValue(tweet, 'name', '')}
20 | {tweet?.is_verified &&
} 21 |
@{getPathValue(tweet, 'username', '')}
22 |
·
23 |
{getPathValue(tweet, 'post_time', '')}
24 |
25 |
{getPathValue(tweet, 'content', '')}
26 | {tweet?.content_img && content} 27 |
28 |
29 | { }} icon={}> 30 |
{getPathValue(tweet, 'comments', 0)}
31 |
32 |
33 | { }} icon={}> 34 |
{getPathValue(tweet, 'retweets', 0)}
35 |
36 |
37 | { }} icon={}> 38 |
{getPathValue(tweet, 'likes', 0)}
39 |
40 |
41 | { }} icon={}> 42 |
43 |
44 |
45 |
); 46 | 47 | export default TweetBlock; -------------------------------------------------------------------------------- /src/components/TwitterUIClone/components/TweetBlock/styles.module.css: -------------------------------------------------------------------------------- 1 | .tweet { 2 | display: flex; 3 | border-bottom: 1px solid #37454d; 4 | transition: background-color 0.5s ease-in-out, border 0.5s ease-in-out; 5 | } 6 | 7 | .tweet:last-child { 8 | border-bottom: 0; 9 | } 10 | 11 | .column { 12 | padding: 0.75rem; 13 | } 14 | 15 | .column:last-child { 16 | flex: 1; 17 | padding-left: 0; 18 | } 19 | 20 | .profilePic { 21 | border-radius: 50%; 22 | width: 3rem; 23 | height: 3rem; 24 | } 25 | 26 | .titleWrapper { 27 | display: flex; 28 | align-items: baseline; 29 | } 30 | 31 | .name { 32 | font-weight: 600; 33 | margin-right: 0.25rem; 34 | transition: color 0.5s ease-in-out; 35 | } 36 | 37 | .verified { 38 | position: relative; 39 | bottom: -3px; 40 | font-size: 1rem; 41 | margin-right: 0.25rem; 42 | } 43 | 44 | .content { 45 | margin-top: 0.25rem; 46 | transition: color 0.5s ease-in-out; 47 | } 48 | 49 | .contentImg { 50 | width: 100%; 51 | margin-top: 1rem; 52 | border-radius: 1rem; 53 | transition: border 0.5s ease-in-out; 54 | } 55 | 56 | .username, .divider, .time, .buttonGroup { 57 | font-size: 0.85rem; 58 | margin-right: 0.25rem; 59 | letter-spacing: -0.2px; 60 | transition: color 0.5s ease-in-out; 61 | } 62 | 63 | .buttonsWrapper { 64 | display: flex; 65 | } 66 | 67 | .buttonGroup { 68 | display: flex; 69 | align-items: center; 70 | flex: 1; 71 | } -------------------------------------------------------------------------------- /src/components/TwitterUIClone/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import classes from './styles.module.css'; 4 | import { tweets } from './tweets'; 5 | import Page from '../../ui-components/Page'; 6 | import TweetBlock from './components/TweetBlock'; 7 | import { themed } from '../../utils/theme'; 8 | 9 | export default class TwitterUIClone extends React.Component { 10 | 11 | updateComponent = (refresher = null) => refresher && this.setState({ refresher }); 12 | 13 | render = () => { 14 | 15 | return 16 |
17 | {tweets.map((tweet, index) => )} 20 |
21 |
; 22 | } 23 | } -------------------------------------------------------------------------------- /src/components/TwitterUIClone/styles.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | margin: 4rem 0; 3 | border-top: 1px solid #37454d; 4 | border-bottom: 1px solid #37454d; 5 | transition: border 0.5s ease-in-out; 6 | } 7 | 8 | @media (min-width: 768px) { 9 | .container { 10 | width: 80%; 11 | margin: 5rem auto; 12 | border: 1px solid #37454d; 13 | } 14 | } 15 | 16 | @media (min-width: 1200px) { 17 | .container { 18 | width: 50%; 19 | margin: 6rem auto; 20 | } 21 | } -------------------------------------------------------------------------------- /src/components/TwitterUIClone/tweets.js: -------------------------------------------------------------------------------- 1 | export const tweets = [ 2 | { 3 | id: 1, 4 | username: 'Showstopper_RG', 5 | name: 'Rohan Gupta', 6 | profile_pic: 'https://pbs.twimg.com/profile_images/1263531193770754048/QTaz5CRM_400x400.jpg', 7 | post_time: 'Dec 4', 8 | content: `It's all about the path you take to reverse 'One Day' to 'Day One'.`, 9 | content_img: null, 10 | likes: '1.2k', 11 | comments: '258', 12 | retweets: '122', 13 | is_verified: false 14 | }, 15 | { 16 | id: 2, 17 | username: 'Twitter', 18 | name: 'Twitter', 19 | profile_pic: 'https://pbs.twimg.com/profile_images/1308010958862905345/-SGZioPb_400x400.jpg', 20 | post_time: 'Sep 22', 21 | content: `The only thing we want going viral is this Tweet`, 22 | content_img: 'https://pbs.twimg.com/media/EidSPNYXsAsx70i?format=jpg&name=large', 23 | likes: '163.7k', 24 | comments: '7.5k', 25 | retweets: '39.7k', 26 | is_verified: true 27 | }, 28 | { 29 | id: 3, 30 | username: 'Twitter', 31 | name: 'Twitter', 32 | profile_pic: 'https://pbs.twimg.com/profile_images/1308010958862905345/-SGZioPb_400x400.jpg', 33 | post_time: 'Dec 3', 34 | content: `2020 in one word`, 35 | content_img: null, 36 | likes: '242.2k', 37 | comments: '110.3k', 38 | retweets: '108.2k', 39 | is_verified: true 40 | }, 41 | { 42 | id: 4, 43 | username: 'MKBHD', 44 | name: 'Marques Brownlee', 45 | profile_pic: 'https://pbs.twimg.com/profile_images/1212149592403382281/cI0-xyss_400x400.jpg', 46 | post_time: 'Nov 24', 47 | content: `Oh my god I love getting good ideas when I can actually write them down`, 48 | content_img: null, 49 | likes: '26.2k', 50 | comments: '214', 51 | retweets: '506', 52 | is_verified: true 53 | }, 54 | { 55 | id: 5, 56 | username: 'Trendulkar', 57 | name: 'Trendulkar', 58 | profile_pic: 'https://pbs.twimg.com/profile_images/1316435144618635264/VfVNhAD6_400x400.jpg', 59 | post_time: 'Dec 3', 60 | content: `On the bright side, nobody is going to ask you 'Bhai, New Year's ka plan hai?' this year.`, 61 | content_img: null, 62 | likes: '1.1k', 63 | comments: '9', 64 | retweets: '56', 65 | is_verified: true 66 | }, 67 | { 68 | id: 6, 69 | username: 'JeffBezos', 70 | name: 'Jeff Bezos', 71 | profile_pic: 'https://pbs.twimg.com/profile_images/669103856106668033/UF3cgUk4_400x400.jpg', 72 | post_time: 'Feb 28', 73 | content: `Discussing climate, sustainability, and preserving the natural world with President Emmanuel Macron today in Paris.`, 74 | content_img: 'https://pbs.twimg.com/media/ER4QQJ6U8AAqJD3?format=jpg&name=large', 75 | likes: '17.8k', 76 | comments: '874', 77 | retweets: '1.4k', 78 | is_verified: true 79 | }, 80 | { 81 | id: 7, 82 | username: 'Trendulkar', 83 | name: 'Trendulkar', 84 | profile_pic: 'https://pbs.twimg.com/profile_images/1316435144618635264/VfVNhAD6_400x400.jpg', 85 | post_time: 'Dec 3', 86 | content: `I used to spend around 10K per month on daily cab rides before COVID. Turns out I now have unexpected savings of additional 100K in the last 10 months of WFH among other things...`, 87 | content_img: null, 88 | likes: '2.4k', 89 | comments: '32', 90 | retweets: '21', 91 | is_verified: true 92 | }, 93 | { 94 | id: 8, 95 | username: 'bewabisabix', 96 | name: 'x', 97 | profile_pic: 'https://pbs.twimg.com/profile_images/1329373939672391682/14Q0dxw7_400x400.jpg', 98 | post_time: 'Dec 2', 99 | content: `girls posing for farewell pictures`, 100 | content_img: 'https://pbs.twimg.com/media/EoPzH2lVcAE3008?format=jpg&name=900x900', 101 | likes: '5.3k', 102 | comments: '29', 103 | retweets: '417', 104 | is_verified: false 105 | } 106 | ]; -------------------------------------------------------------------------------- /src/components/ValidatedForm/components/RadioGroup/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import classes from './styles.module.css'; 4 | import { isEmptyList } from '../../../../utils'; 5 | import RadioButton from '../../../../ui-components/Radio'; 6 | 7 | const RadioGroup = React.memo(({ options, selectedOption, onChange, header, className }) =>
8 | {header &&
{header}
} 9 | {!isEmptyList(options) &&
10 | {options.map(option => onChange(option)} />)} 11 |
} 12 |
); 13 | 14 | export default RadioGroup; -------------------------------------------------------------------------------- /src/components/ValidatedForm/components/RadioGroup/styles.module.css: -------------------------------------------------------------------------------- 1 | .radioGroupWrapper { 2 | display: flex; 3 | flex-direction: column; 4 | } 5 | 6 | .header { 7 | color: var(--text); 8 | transition: color 0.5s ease-in-out; 9 | } 10 | 11 | .radioGroup { 12 | display: flex; 13 | align-items: center; 14 | margin-left: 0.5rem; 15 | } 16 | 17 | @media (min-width: 768px) { 18 | .radioGroupWrapper { 19 | flex-direction: row; 20 | align-items: center; 21 | } 22 | .header { 23 | font-size: 1.1rem; 24 | margin-right: 1rem; 25 | } 26 | .radioGroup { 27 | margin-left: 0; 28 | } 29 | } -------------------------------------------------------------------------------- /src/components/ValidatedForm/styles.module.css: -------------------------------------------------------------------------------- 1 | .formWrapper { 2 | min-height: inherit; 3 | display: flex; 4 | flex-direction: column; 5 | justify-content: center; 6 | align-items: center; 7 | } 8 | 9 | .formContainer { 10 | width: 90%; 11 | padding: 0.5rem; 12 | margin: 5rem 0; 13 | border-radius: 6px; 14 | box-sizing: border-box; 15 | background-color: var(--card-bg); 16 | box-shadow: var(--shadow-opposite); 17 | border: 1px solid var(--component-border); 18 | transition: background-color 0.5s ease-in-out, border 0.5s ease-in-out, box-shadow 0.5s ease-in-out; 19 | } 20 | 21 | .heading { 22 | font-weight: 600; 23 | font-size: 1.5rem; 24 | letter-spacing: -0.25px; 25 | color: var(--text-prominent); 26 | transition: color 0.5s ease-in-out; 27 | } 28 | 29 | .info { 30 | font-size: 0.8rem; 31 | color: var(--text-obscure); 32 | transition: color 0.5s ease-in-out; 33 | } 34 | 35 | .formComponents { 36 | margin-top: 1rem; 37 | display: grid; 38 | grid-template-columns: 1fr; 39 | gap: 1rem; 40 | grid-template-areas: 41 | 'fname' 42 | 'lname' 43 | 'username' 44 | 'password' 45 | 'contact' 46 | 'email' 47 | 'addressField' 48 | 'age' 49 | 'group'; 50 | } 51 | 52 | .submitButton { 53 | margin-top: 1rem !important; 54 | min-width: 12rem !important; 55 | } 56 | 57 | .fname { 58 | grid-area: fname; 59 | } 60 | 61 | .lname { 62 | grid-area: lname; 63 | } 64 | 65 | .username { 66 | grid-area: username; 67 | } 68 | 69 | .password { 70 | grid-area: password; 71 | } 72 | 73 | .contact { 74 | grid-area: contact; 75 | } 76 | 77 | .email { 78 | grid-area: email; 79 | } 80 | 81 | .address { 82 | grid-area: addressField; 83 | } 84 | 85 | .age { 86 | grid-area: age; 87 | } 88 | 89 | .group { 90 | grid-area: group; 91 | } 92 | 93 | @media (min-width: 768px) { 94 | .formContainer { 95 | width: 75%; 96 | padding: 0.75rem; 97 | } 98 | .formComponents { 99 | margin-top: 1.5rem; 100 | } 101 | } 102 | 103 | @media (min-width: 900px) { 104 | .formContainer { 105 | width: 80%; 106 | margin: 0; 107 | padding: 1rem; 108 | } 109 | .formComponents { 110 | margin-top: 2rem; 111 | grid-template-columns: 1fr 1fr; 112 | grid-template-areas: 113 | 'fname lname' 114 | 'username password' 115 | 'contact email' 116 | 'addressField addressField' 117 | 'age group'; 118 | } 119 | .submitButton { 120 | margin-top: 1.5rem !important; 121 | } 122 | } 123 | 124 | @media (min-width: 1200px) { 125 | .heading { 126 | font-size: 2rem; 127 | } 128 | .formContainer { 129 | width: 60%; 130 | } 131 | .submitButton { 132 | margin-top: 2rem !important; 133 | } 134 | } -------------------------------------------------------------------------------- /src/components/VotingPoll/components/PollOption/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import classes from './styles.module.css'; 4 | 5 | const PollOption = React.memo(({ option, percentage, isSelected, onClick, backgroundColor, color, selectedColor }) =>
onClick(option)}> 6 |
7 |
8 |
{option?.label ? option.label : ''}
9 |
{percentage ? `${percentage}%` : '0.00%'}
10 |
11 |
); 12 | 13 | export default PollOption; -------------------------------------------------------------------------------- /src/components/VotingPoll/components/PollOption/styles.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | height: 2.5rem; 3 | cursor: pointer; 4 | display: flex; 5 | flex-basis: 100%; 6 | position: relative; 7 | margin-top: 0.5rem; 8 | border-radius: inherit; 9 | background-color: var(--card-bg); 10 | border: 1px solid var(--border-color); 11 | transition: border 0.5s ease-in-out, background-color 0.5s ease-in-out; 12 | } 13 | 14 | .container:first-of-type { 15 | margin-top: 0; 16 | } 17 | 18 | .filledBar { 19 | border-radius: inherit; 20 | transition: background-color 0.5s ease-in-out, flex-basis 0.5s ease-in-out, border 0.2s ease-in-out; 21 | } 22 | 23 | .content { 24 | position: absolute; 25 | display: flex; 26 | width: 90%; 27 | top: 50%; 28 | left: 50%; 29 | transform: translate(-50%, -50%); 30 | transition: color 0.5s ease-in-out; 31 | } 32 | 33 | .label { 34 | flex: 1; 35 | font-weight: 600; 36 | letter-spacing: 0.2px; 37 | } 38 | 39 | .percentage { 40 | font-weight: 600; 41 | font-size: 0.9rem; 42 | letter-spacing: -0.2px; 43 | } 44 | 45 | @media (min-width: 768px) { 46 | .container { 47 | height: 3rem; 48 | } 49 | .label { 50 | font-size: 1.1rem; 51 | } 52 | .percentage { 53 | font-size: 1rem; 54 | } 55 | } 56 | 57 | @media (min-width: 1200px) { 58 | .container { 59 | height: 4rem; 60 | } 61 | .label { 62 | font-size: 1.2rem; 63 | } 64 | .percentage { 65 | font-size: 1.1rem; 66 | } 67 | } -------------------------------------------------------------------------------- /src/components/VotingPoll/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import classes from './styles.module.css'; 4 | import Page from '../../ui-components/Page'; 5 | import { poll } from './poll'; 6 | import PollOption from './components/PollOption'; 7 | import { getPathValue, isEmptyList, isEmptyObject } from '../../utils'; 8 | import { themed } from '../../utils/theme'; 9 | import { BLUE_400, BLUE_600, BLUE_700, BLUE_900 } from '../../resources/colors'; 10 | 11 | export default class VotingPoll extends React.Component { 12 | 13 | state = { 14 | poll: poll, 15 | selectedOption: null, 16 | prevOption: null 17 | } 18 | 19 | updateComponent = (refresher = null) => refresher && this.setState({ refresher }); 20 | 21 | getPercentage = (option, options) => { 22 | if (isEmptyList(options) || isEmptyObject(option)) return 0; 23 | 24 | const total = options.reduce((accu, curr) => accu + getPathValue(curr, 'count', 0), 0); 25 | return ((getPathValue(option, 'count', 0) / total) * 100).toFixed(2); 26 | } 27 | 28 | getTotalVotes = options => isEmptyList(options) ? 0 : options.reduce((accu, curr) => accu + getPathValue(curr, 'count', 0), 0); 29 | 30 | handleOptionClick = option => { 31 | if (isEmptyObject(option) || (getPathValue(option, 'id') === getPathValue(this.state.selectedOption, 'id'))) return; 32 | 33 | const prevOption = this.state.selectedOption ? { ...this.state.selectedOption, count: getPathValue(this.state.selectedOption, 'count', 1) - 1 } : null; 34 | const selectedOption = { ...option, count: getPathValue(option, 'count', 0) + 1 }; 35 | 36 | let options = this.updateOptions(this.updateOptions([...getPathValue(this.state.poll, 'options', [])], selectedOption), prevOption); 37 | this.setState({ prevOption, selectedOption, poll: { ...this.state.poll, options } }); 38 | } 39 | 40 | updateOptions = (options, option) => { 41 | if (isEmptyList(options)) return []; 42 | if (isEmptyObject(option)) return options; 43 | return options.map(entry => entry?.id === option?.id ? option : entry); 44 | } 45 | 46 | render = () => { 47 | const { title, options } = this.state.poll; 48 | const totalVotes = this.getTotalVotes(options); 49 | 50 | return 51 |
52 |
53 |
{title}
54 | {options.map(option => )} 57 | {totalVotes &&
Total votes: {totalVotes}
} 58 |
59 |
60 |
; 61 | } 62 | } -------------------------------------------------------------------------------- /src/components/VotingPoll/poll.js: -------------------------------------------------------------------------------- 1 | const INITIAL_COUNT = 1000; 2 | 3 | export const poll = { 4 | title: `What is the name of Thor's Hammer?`, 5 | options: [ 6 | { 7 | id: 1, 8 | label: 'Vanir', 9 | count: Math.floor(Math.random() * INITIAL_COUNT) 10 | }, 11 | { 12 | id: 2, 13 | label: 'Mjolnir', 14 | count: Math.floor(Math.random() * INITIAL_COUNT) 15 | }, 16 | { 17 | id: 3, 18 | label: 'Aesir', 19 | count: Math.floor(Math.random() * INITIAL_COUNT) 20 | }, 21 | { 22 | id: 4, 23 | label: 'Norn', 24 | count: Math.floor(Math.random() * INITIAL_COUNT) 25 | } 26 | ] 27 | }; -------------------------------------------------------------------------------- /src/components/VotingPoll/styles.module.css: -------------------------------------------------------------------------------- 1 | .wrapper { 2 | min-height: 100vh; 3 | margin-top: 2rem; 4 | display: flex; 5 | align-items: center; 6 | justify-content: center; 7 | } 8 | 9 | .container { 10 | width: 90%; 11 | padding: 0.8rem; 12 | border-radius: 4px; 13 | box-shadow: var(--shadow-opposite); 14 | border: 1px solid var(--border-color); 15 | transition: box-shadow 0.5s ease-in-out, border 0.5s ease-in-out; 16 | } 17 | 18 | .title { 19 | margin-bottom: 1rem; 20 | font-weight: 600; 21 | font-size: 1.2rem; 22 | letter-spacing: -0.25px; 23 | color: var(--text-prominent); 24 | transition: color 0.5s ease-in-out; 25 | } 26 | 27 | .info { 28 | margin-top: 0.5rem; 29 | font-size: 0.8rem; 30 | color: var(--text-obscurer); 31 | transition: color 0.5s ease-in-out; 32 | } 33 | 34 | @media (min-width: 768px) { 35 | .wrapper { 36 | margin-top: 0; 37 | } 38 | .container { 39 | width: 60%; 40 | padding: 1rem; 41 | } 42 | .title { 43 | font-size: 1.4rem; 44 | } 45 | } 46 | 47 | @media (min-width: 1200px) { 48 | .container { 49 | width: 50%; 50 | max-width: 600px; 51 | } 52 | .title { 53 | font-size: 1.6rem; 54 | margin-bottom: 1.5rem; 55 | } 56 | } -------------------------------------------------------------------------------- /src/images/bill_dark.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DemonDaddy22/all-about-reactJS/74c9c9d79ed38359cfc96064979d2bd38eba4d47/src/images/bill_dark.jpg -------------------------------------------------------------------------------- /src/images/bill_light.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DemonDaddy22/all-about-reactJS/74c9c9d79ed38359cfc96064979d2bd38eba4d47/src/images/bill_light.jpg -------------------------------------------------------------------------------- /src/images/calculator_dark.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DemonDaddy22/all-about-reactJS/74c9c9d79ed38359cfc96064979d2bd38eba4d47/src/images/calculator_dark.jpg -------------------------------------------------------------------------------- /src/images/calculator_light.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DemonDaddy22/all-about-reactJS/74c9c9d79ed38359cfc96064979d2bd38eba4d47/src/images/calculator_light.jpg -------------------------------------------------------------------------------- /src/images/carousel_dark.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DemonDaddy22/all-about-reactJS/74c9c9d79ed38359cfc96064979d2bd38eba4d47/src/images/carousel_dark.jpg -------------------------------------------------------------------------------- /src/images/carousel_light.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DemonDaddy22/all-about-reactJS/74c9c9d79ed38359cfc96064979d2bd38eba4d47/src/images/carousel_light.jpg -------------------------------------------------------------------------------- /src/images/form_dark.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DemonDaddy22/all-about-reactJS/74c9c9d79ed38359cfc96064979d2bd38eba4d47/src/images/form_dark.jpg -------------------------------------------------------------------------------- /src/images/form_light.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DemonDaddy22/all-about-reactJS/74c9c9d79ed38359cfc96064979d2bd38eba4d47/src/images/form_light.jpg -------------------------------------------------------------------------------- /src/images/game_dark.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DemonDaddy22/all-about-reactJS/74c9c9d79ed38359cfc96064979d2bd38eba4d47/src/images/game_dark.jpg -------------------------------------------------------------------------------- /src/images/game_light.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DemonDaddy22/all-about-reactJS/74c9c9d79ed38359cfc96064979d2bd38eba4d47/src/images/game_light.jpg -------------------------------------------------------------------------------- /src/images/grid_dark.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DemonDaddy22/all-about-reactJS/74c9c9d79ed38359cfc96064979d2bd38eba4d47/src/images/grid_dark.jpg -------------------------------------------------------------------------------- /src/images/grid_light.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DemonDaddy22/all-about-reactJS/74c9c9d79ed38359cfc96064979d2bd38eba4d47/src/images/grid_light.jpg -------------------------------------------------------------------------------- /src/images/hoverboard_dark.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DemonDaddy22/all-about-reactJS/74c9c9d79ed38359cfc96064979d2bd38eba4d47/src/images/hoverboard_dark.jpg -------------------------------------------------------------------------------- /src/images/hoverboard_light.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DemonDaddy22/all-about-reactJS/74c9c9d79ed38359cfc96064979d2bd38eba4d47/src/images/hoverboard_light.jpg -------------------------------------------------------------------------------- /src/images/jokes_dark.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DemonDaddy22/all-about-reactJS/74c9c9d79ed38359cfc96064979d2bd38eba4d47/src/images/jokes_dark.jpg -------------------------------------------------------------------------------- /src/images/jokes_light.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DemonDaddy22/all-about-reactJS/74c9c9d79ed38359cfc96064979d2bd38eba4d47/src/images/jokes_light.jpg -------------------------------------------------------------------------------- /src/images/keycode_dark.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DemonDaddy22/all-about-reactJS/74c9c9d79ed38359cfc96064979d2bd38eba4d47/src/images/keycode_dark.jpg -------------------------------------------------------------------------------- /src/images/keycode_light.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DemonDaddy22/all-about-reactJS/74c9c9d79ed38359cfc96064979d2bd38eba4d47/src/images/keycode_light.jpg -------------------------------------------------------------------------------- /src/images/meal_dark.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DemonDaddy22/all-about-reactJS/74c9c9d79ed38359cfc96064979d2bd38eba4d47/src/images/meal_dark.jpg -------------------------------------------------------------------------------- /src/images/meal_light.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DemonDaddy22/all-about-reactJS/74c9c9d79ed38359cfc96064979d2bd38eba4d47/src/images/meal_light.jpg -------------------------------------------------------------------------------- /src/images/password_dark.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DemonDaddy22/all-about-reactJS/74c9c9d79ed38359cfc96064979d2bd38eba4d47/src/images/password_dark.jpg -------------------------------------------------------------------------------- /src/images/password_light.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DemonDaddy22/all-about-reactJS/74c9c9d79ed38359cfc96064979d2bd38eba4d47/src/images/password_light.jpg -------------------------------------------------------------------------------- /src/images/picker_dark.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DemonDaddy22/all-about-reactJS/74c9c9d79ed38359cfc96064979d2bd38eba4d47/src/images/picker_dark.jpg -------------------------------------------------------------------------------- /src/images/picker_light.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DemonDaddy22/all-about-reactJS/74c9c9d79ed38359cfc96064979d2bd38eba4d47/src/images/picker_light.jpg -------------------------------------------------------------------------------- /src/images/pokedex_dark.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DemonDaddy22/all-about-reactJS/74c9c9d79ed38359cfc96064979d2bd38eba4d47/src/images/pokedex_dark.jpg -------------------------------------------------------------------------------- /src/images/pokedex_light.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DemonDaddy22/all-about-reactJS/74c9c9d79ed38359cfc96064979d2bd38eba4d47/src/images/pokedex_light.jpg -------------------------------------------------------------------------------- /src/images/poll_dark.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DemonDaddy22/all-about-reactJS/74c9c9d79ed38359cfc96064979d2bd38eba4d47/src/images/poll_dark.jpg -------------------------------------------------------------------------------- /src/images/poll_light.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DemonDaddy22/all-about-reactJS/74c9c9d79ed38359cfc96064979d2bd38eba4d47/src/images/poll_light.jpg -------------------------------------------------------------------------------- /src/images/pricing_dark.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DemonDaddy22/all-about-reactJS/74c9c9d79ed38359cfc96064979d2bd38eba4d47/src/images/pricing_dark.jpg -------------------------------------------------------------------------------- /src/images/pricing_light.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DemonDaddy22/all-about-reactJS/74c9c9d79ed38359cfc96064979d2bd38eba4d47/src/images/pricing_light.jpg -------------------------------------------------------------------------------- /src/images/profile_viewer_dark.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DemonDaddy22/all-about-reactJS/74c9c9d79ed38359cfc96064979d2bd38eba4d47/src/images/profile_viewer_dark.jpg -------------------------------------------------------------------------------- /src/images/profile_viewer_light.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DemonDaddy22/all-about-reactJS/74c9c9d79ed38359cfc96064979d2bd38eba4d47/src/images/profile_viewer_light.jpg -------------------------------------------------------------------------------- /src/images/quotes_dark.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DemonDaddy22/all-about-reactJS/74c9c9d79ed38359cfc96064979d2bd38eba4d47/src/images/quotes_dark.jpg -------------------------------------------------------------------------------- /src/images/quotes_light.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DemonDaddy22/all-about-reactJS/74c9c9d79ed38359cfc96064979d2bd38eba4d47/src/images/quotes_light.jpg -------------------------------------------------------------------------------- /src/images/skeleton_dark.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DemonDaddy22/all-about-reactJS/74c9c9d79ed38359cfc96064979d2bd38eba4d47/src/images/skeleton_dark.jpg -------------------------------------------------------------------------------- /src/images/skeleton_light.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DemonDaddy22/all-about-reactJS/74c9c9d79ed38359cfc96064979d2bd38eba4d47/src/images/skeleton_light.jpg -------------------------------------------------------------------------------- /src/images/todos_dark.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DemonDaddy22/all-about-reactJS/74c9c9d79ed38359cfc96064979d2bd38eba4d47/src/images/todos_dark.jpg -------------------------------------------------------------------------------- /src/images/todos_light.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DemonDaddy22/all-about-reactJS/74c9c9d79ed38359cfc96064979d2bd38eba4d47/src/images/todos_light.jpg -------------------------------------------------------------------------------- /src/images/twitter_dark.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DemonDaddy22/all-about-reactJS/74c9c9d79ed38359cfc96064979d2bd38eba4d47/src/images/twitter_dark.jpg -------------------------------------------------------------------------------- /src/images/twitter_light.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DemonDaddy22/all-about-reactJS/74c9c9d79ed38359cfc96064979d2bd38eba4d47/src/images/twitter_light.jpg -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --page-bg: #f7f7f7; 3 | --card-bg: #fff; 4 | --card-container-bg: #fff; 5 | --dialog-bg: #fff; 6 | --overlay-bg: #f0f0f070; 7 | --text-contrast: #fcfaf2; 8 | --text-prominent: #000; 9 | --text-light: #fff; 10 | --text: #202124; 11 | --text-obscure: #666; 12 | --text-obscurer: #888; 13 | --text-opposite: #e6e6e6; 14 | --card-shadow: 0px 2px 1px -1px rgba(255, 255, 255, 0.2), 0px 1px 1px 0px rgba(0, 0, 0, 0.14), 0px 1px 3px 0px rgba(0, 0, 0, 0.12); 15 | --card-shadow-hover: 0px 4px 3px -3px rgba(255, 255, 255, 0.2), 0px 3px 3px 0px rgba(0, 0, 0, 0.14), 0px 3px 5px 0px rgba(0, 0, 0, 0.12); 16 | --shadow: 0px 0px 6px rgba(0, 0, 0, 0.15); 17 | --shadow-opposite: 0 0 8px rgba(0, 0, 0, 0.2); 18 | --card-border-mild-light-only: 0.5px solid #e0e0e0; 19 | --border-color: rgba(0, 0, 0, 0.1); 20 | --theme-color: #1976d2; 21 | --theme-color-obscure: #1976d29f; 22 | --secondary-theme-color: #d32f66; 23 | --secondary-theme-color: #d32f66d0; 24 | --component-border-disabled: rgba(0, 0, 0, 0.12); 25 | --component-border-hover: rgba(0, 0, 0, 0.87); 26 | --component-border: rgba(0, 0, 0, 0.23); 27 | --component-text-disabled: rgba(0, 0, 0, 0.38); 28 | --hover-ripple: #0000000D; 29 | --ripple: #00000066; 30 | --tag-color: #e0e0e0; 31 | --border-bottom-color: rgba(224, 224, 224, 1); 32 | --skeleton-color: #e7e7e7; 33 | --shimmer-color: rgba(255, 255, 255, 0.2); 34 | --svg-color: invert(9%) sepia(3%) saturate(1524%) hue-rotate(189deg) brightness(101%) contrast(92%); 35 | } 36 | 37 | [theme-mode="dark"] { 38 | --page-bg: #161616; 39 | --card-bg: #272727; 40 | --card-container-bg: #363636; 41 | --dialog-bg: #161616; 42 | --overlay-bg: #2f2f2fb3; 43 | --text-contrast: #161616; 44 | --text-prominent: #fff; 45 | --text-light: #f7f7f7; 46 | --text: #d4d4d4; 47 | --text-obscure: #b1b1b1; 48 | --text-obscurer: #868686; 49 | --text-opposite: #202124; 50 | --card-shadow: 0px 2px 1px -1px rgba(0, 0, 0, 0.2), 0px 1px 1px 0px rgba(0, 0, 0, 0.14), 0px 1px 3px 0px rgba(0, 0, 0, 0.12); 51 | --card-shadow-hover: 0px 4px 3px -3px rgba(0, 0, 0, 0.2), 0px 3px 3px 0px rgba(0, 0, 0, 0.14), 0px 3px 5px 0px rgba(0, 0, 0, 0.12); 52 | --shadow: 0px 0px 8px #191919; 53 | --shadow-opposite: 0 0 10px rgba(255, 255, 255, 0.2); 54 | --card-border-mild-light-only: 0.5px solid transparent; 55 | --border-color: rgba(163, 163, 163, 0.1); 56 | --theme-color: #1b7fe4; 57 | --theme-color-obscure: #1461af9f; 58 | --secondary-theme-color: #da4375; 59 | --secondary-theme-color: #da4375d3; 60 | --component-border-disabled: #FFFFFF22; 61 | --component-border-hover: #fff; 62 | --component-border: #FFFFFF59; 63 | --component-text-disabled: #ffffff80; 64 | --hover-ripple: #ffffff18; 65 | --ripple: #fff6; 66 | --tag-color: #3e3d3d; 67 | --border-bottom-color: rgba(81, 81, 81, 1); 68 | --skeleton-color: #202020; 69 | --shimmer-color: rgba(40, 40, 40, 0.2); 70 | --svg-color: invert(98%) sepia(10%) saturate(94%) hue-rotate(187deg) brightness(109%) contrast(81%); 71 | } 72 | 73 | body { 74 | margin: 0; 75 | background-color: var(--page-bg); 76 | font-family: 'Nunito', sans-serif; 77 | -webkit-font-smoothing: antialiased; 78 | -moz-osx-font-smoothing: grayscale; 79 | } 80 | 81 | code { 82 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', monospace; 83 | } 84 | 85 | .center { 86 | position: absolute; 87 | -webkit-transform: translate(-50%,-50%); 88 | transform: translate(-50%, -50%); 89 | top: 50%; 90 | left: 50%; 91 | } 92 | 93 | .animateFadeUp { 94 | position: relative; 95 | top: 0px; 96 | opacity: 0; 97 | animation: fadeAndUp 0.5s cubic-bezier(0.19, 1, 0.22, 1) forwards; 98 | } 99 | 100 | @-webkit-keyframes fadeAndUp { 101 | from { 102 | top: 20px; 103 | opacity: 0; 104 | } 105 | to { 106 | top: 0px; 107 | opacity: 1; 108 | } 109 | } 110 | 111 | @keyframes fadeAndUp { 112 | from { 113 | top: 20px; 114 | opacity: 0; 115 | } 116 | to { 117 | top: 0px; 118 | opacity: 1; 119 | } 120 | } 121 | 122 | .animateFadeGrow { 123 | transform: scale(0.7); 124 | opacity: 0; 125 | animation: fadeAndGrow 0.5s cubic-bezier(0.19, 1, 0.22, 1) forwards; 126 | } 127 | 128 | @-webkit-keyframes fadeAndGrow { 129 | from { 130 | transform: scale(0.7); 131 | opacity: 0; 132 | } 133 | to { 134 | transform: scale(1); 135 | opacity: 1; 136 | } 137 | } 138 | 139 | @keyframes fadeAndGrow { 140 | from { 141 | transform: scale(0.7); 142 | opacity: 0; 143 | } 144 | to { 145 | transform: scale(1); 146 | opacity: 1; 147 | } 148 | } 149 | 150 | #root { 151 | min-height: 100vh; 152 | min-width: 100vw; 153 | overflow: auto; 154 | } -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /src/resources/constants.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable eqeqeq */ 2 | export const GITHUB_BASE = 'https://github.com/DemonDaddy22'; 3 | // eslint-disable-next-line no-undef 4 | export const BASE_URL = process.env.NODE_ENV == 'production' ? '/all-about-reactJS' : ''; 5 | export const GITHUB_API_BASE = 'https://api.github.com'; 6 | export const POKEMON_API_BASE = 'https://pokeapi.co/api/v2/pokemon/'; 7 | export const POKEMON_TYPES = Object.freeze({ 8 | electric: { bgColor: '#f6c913', color: '#FFF' }, 9 | grass: { bgColor: '#6ac23d', color: '#FFF' }, 10 | water: { bgColor: '#4578ec', color: '#FFF' }, 11 | fire: { bgColor: '#ed6d11', color: '#FFF' }, 12 | ground: { bgColor: '#dcb44c', color: '#FFF' }, 13 | bug: { bgColor: '#98a51c', color: '#FFF' }, 14 | normal: { bgColor: '#9d9c63', color: '#FFF' }, 15 | poison: { bgColor: '#913992', color: '#FFF' }, 16 | flying: { bgColor: '#8f6feb', color: '#FFF' }, 17 | ice: { bgColor: '#7fcece', color: '#FFF' }, 18 | rock: { bgColor: '#a59031', color: '#FFF' }, 19 | steel: { bgColor: '#a0a0bf', color: '#FFF' }, 20 | fairy: { bgColor: '#e87990', color: '#FFF' }, 21 | ghost: { bgColor: '#654e87', color: '#FFF' }, 22 | psychic: { bgColor: '#f7366f', color: '#FFF' }, 23 | fighting: { bgColor: '#ae2b24', color: '#FFF' }, 24 | dark: { bgColor: '#654e40', color: '#FFF' }, 25 | dragon: { bgColor: '#5e1cf6', color: '#FFF' }, 26 | }); 27 | export const PLACEHOLDER_POSTS_API = 'https://jsonplaceholder.typicode.com/posts'; 28 | export const RANDOM_AVATAR_API = 'https://uifaces.co/api'; -------------------------------------------------------------------------------- /src/routes/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { Route, Switch, Redirect } from 'react-router-dom'; 4 | import Home from '../components/Home'; 5 | import Todos from '../components/Todos'; 6 | import PricingCards from '../components/PricingCards'; 7 | import Calculator from '../components/Calculator'; 8 | import ColorPicker from '../components/ColorPicker'; 9 | import GitHubProfileViewer from '../components/GitHubProfileViewer'; 10 | import StonePaperScissor from '../components/StonePaperScissor'; 11 | import PasswordGenerator from '../components/PasswordGenerator'; 12 | import RandomJokes from '../components/RandomJokes'; 13 | import HoverBoard from '../components/HoverBoard'; 14 | import KeyCodeSequence from '../components/KeyCodeSequence'; 15 | import RandomQuotes from '../components/RandomQuotes'; 16 | import Carousel from '../components/Carousel'; 17 | import MealGenerator from '../components/MealGenerator'; 18 | import BillGenerator from '../components/BillGenerator'; 19 | import ValidatedForm from '../components/ValidatedForm'; 20 | import AlternateGrid from '../components/AlternateGrid'; 21 | import Pokedex from '../components/Pokedex'; 22 | import TwitterUIClone from '../components/TwitterUIClone'; 23 | import VotingPoll from '../components/VotingPoll'; 24 | import SkeletonLoader from '../components/SkeletonLoader'; 25 | 26 | export default class Routes extends React.Component { 27 | 28 | render = () => { 29 | return ( 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | ); 76 | } 77 | } -------------------------------------------------------------------------------- /src/script.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DemonDaddy22/all-about-reactJS/74c9c9d79ed38359cfc96064979d2bd38eba4d47/src/script.js -------------------------------------------------------------------------------- /src/ui-components/Button/Iconbutton/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import IconButton from '@material-ui/core/IconButton'; 3 | import { THEME_COLOR } from '../../../resources/colors'; 4 | import { withStyles } from '@material-ui/core'; 5 | 6 | const styles = { 7 | root: props => ({ 8 | color: props.iconColor ? props.iconColor : THEME_COLOR, 9 | ...props.iconStyles, 10 | '&:hover': { 11 | color: props.iconhovercolor ? props.iconhovercolor : THEME_COLOR 12 | } 13 | }) 14 | } 15 | 16 | class Iconbutton extends React.Component { 17 | 18 | render = () => 19 | {this.props.icon} 20 | ; 21 | } 22 | 23 | export default withStyles(styles)(Iconbutton); -------------------------------------------------------------------------------- /src/ui-components/Button/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import MuiButton from '@material-ui/core/Button' 4 | import { withStyles } from '@material-ui/core'; 5 | import { TRANSPARENT, WHITE, WHITE_TRANSPARENT_70, THEME_COLOR } from '../../resources/colors'; 6 | import { getPathValue, isEmptyString } from '../../utils'; 7 | 8 | const styles = { 9 | root: props => ({ 10 | borderColor: `${props.disabled ? TRANSPARENT : props.variant === 'outlined' ? props.borderColor : TRANSPARENT} !important`, 11 | backgroundColor: props.variant === 'outlined' ? TRANSPARENT : props.disabled ? '#aaa' : props.backgroundColor, 12 | transition: 'box-shadow 0.35s ease-in-out', 13 | '&:hover': { 14 | backgroundColor: props.disabled ? '#aaa' : props.variant === 'outlined' ? TRANSPARENT : props.backgroundColor, 15 | boxShadow: props.disabled || props.noShadow ? null : `0px 0px 6px ${props.backgroundColor}`, 16 | }, 17 | '&:active': { 18 | boxShadow: props.disabled || props.noShadow ? null : `0px 0px 4px ${props.backgroundColor}`, 19 | }, 20 | '& > .MuiButton-label': { 21 | color: props.variant === 'outlined' ? props.disabled ? 'rgba(0,0,0,.26)' : getPathValue(props, 'labelColor', 'var(--text-obscure)') : `${WHITE} !important`, 22 | padding: '0px 8px 0px 8px', 23 | fontWeight: 600, 24 | ...props.labelStyles, 25 | }, 26 | '& > .MuiTouchRipple-root': { 27 | width: `calc(100% + ${props.variant === 'outlined' ? 2 : 0}px)`, 28 | height: `calc(100% + ${props.variant === 'outlined' ? 2 : 0}px)`, 29 | transform: `translate(${props.variant === 'outlined' ? '-1px, -1px' : '0px, 0px'})`, 30 | color: !isEmptyString(props.rippleColor) ? props.rippleColor : props.variant === 'outlined' ? 'var(--ripple)' : WHITE_TRANSPARENT_70, 31 | 32 | '& .MuiTouchRipple-rippleVisible': { 33 | animationDuration: '0.75s', 34 | animationTimingFunction: 'cubic-bezier(0,.84,.91,.86)', 35 | }, 36 | 37 | }, 38 | ...props.rootStyles, 39 | }) 40 | }; 41 | 42 | class Button extends React.Component { 43 | 44 | render = () => ; 45 | } 46 | 47 | Button.defaultProps = { 48 | backgroundColor: THEME_COLOR 49 | }; 50 | 51 | export default withStyles(styles)(Button); -------------------------------------------------------------------------------- /src/ui-components/CheckBox/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import FormControlLabel from '@material-ui/core/FormControlLabel'; 4 | import Checkbox from '@material-ui/core/Checkbox'; 5 | import { THEME_COLOR } from '../../resources/colors'; 6 | import { withStyles } from '@material-ui/core'; 7 | 8 | const styles = { 9 | root: props => ({ 10 | borderRadius: '50%', 11 | height: props.height ? props.height : 16, 12 | width: props.width ? props.width : 16, 13 | color: props.color ? props.color : THEME_COLOR, 14 | '&$checked': { 15 | color: props.color ? props.color : THEME_COLOR 16 | }, 17 | ...props.boxStyles 18 | }) 19 | } 20 | 21 | class CheckBox extends React.Component { 22 | 23 | render = () => { 24 | const { checked, disabled, label, labelProps } = this.props; 25 | 26 | return 28 | } {...labelProps}> 29 | } 30 | } 31 | 32 | export default withStyles(styles)(CheckBox); -------------------------------------------------------------------------------- /src/ui-components/Input/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import TextField from '@material-ui/core/TextField' 4 | import { withStyles } from '@material-ui/core'; 5 | 6 | const styles = { 7 | root: props => ({ 8 | '& label': { 9 | color: 'var(--text)' 10 | }, 11 | '& .MuiOutlinedInput-input': { 12 | ...props.rootInputStyles 13 | }, 14 | '& .MuiInput-input': { 15 | ...props.rootInputStyles 16 | }, 17 | '& .MuiOutlinedInput-notchedOutline': { 18 | boxShadow: 'var(--shadow)', 19 | borderColor: 'var(--text-obscurer)', 20 | transition: 'box-shadow 0.5s ease-in-out, border-color 0.25s ease-in-out' 21 | }, 22 | '&:hover .MuiOutlinedInput-notchedOutline': { 23 | borderColor: 'var(--text-obscure)', 24 | }, 25 | '& .Mui-focused .MuiOutlinedInput-notchedOutline': { 26 | borderColor: 'var(--theme-color)', 27 | }, 28 | '& .Mui-focused:before': { 29 | borderColor: 'var(--theme-color)', 30 | }, 31 | '& .MuiFormHelperText-root': { 32 | color: props?.helpertextcolor ? props.helpertextcolor : 'var(--text-obscurer)' 33 | } 34 | }) 35 | }; 36 | 37 | class Input extends React.Component { 38 | 39 | render = () => { 40 | const { placeHolder, value, variant, style } = this.props; 41 | 42 | return ; 44 | } 45 | } 46 | 47 | export default withStyles(styles)(Input); -------------------------------------------------------------------------------- /src/ui-components/Label/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import classes from './styles.module.css'; 4 | 5 | export default class Label extends React.Component { 6 | 7 | render = () => { 8 | const { label, className, style } = this.props; 9 | 10 | return
{label}
11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/ui-components/Label/styles.module.css: -------------------------------------------------------------------------------- 1 | .label { 2 | color: var(--text); 3 | font-size: 1rem; 4 | } -------------------------------------------------------------------------------- /src/ui-components/Page/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import './styles.module.css'; 3 | import classes from './styles.module.css'; 4 | import { GREY_50 } from '../../resources/colors'; 5 | import Iconbutton from '../Button/Iconbutton'; 6 | import Brightness4RoundedIcon from '@material-ui/icons/Brightness4Rounded'; 7 | import Brightness7RoundedIcon from '@material-ui/icons/Brightness7Rounded'; 8 | import { getTheme, themes, setTheme } from '../../utils/theme'; 9 | 10 | export default class Page extends React.Component { 11 | 12 | state = { 13 | darkMode: false 14 | } 15 | 16 | componentDidMount = () => this.loadTheme(); 17 | 18 | loadTheme = () => { 19 | const currTheme = getTheme(); 20 | this.setState({ darkMode: currTheme === themes.LIGHT ? false : true}, 21 | () => setTheme(currTheme)); 22 | } 23 | 24 | handleThemeChange = () => this.setState({ darkMode: !this.state.darkMode }, 25 | () => { 26 | setTheme(this.state.darkMode ? themes.DARK : themes.LIGHT); 27 | // pass this function as a prop whenever a component needs to be re-mounted on theme change to trigger themed function 28 | typeof this.props.shouldComponentUpdate === 'function' && this.props.shouldComponentUpdate(new Date().getTime()); 29 | // window.location.reload(); 30 | }); 31 | 32 | render = () =>
33 | {/* add home button, github links */} 34 |
35 | : }> 36 |
37 | {this.props.children} 38 |
39 | } -------------------------------------------------------------------------------- /src/ui-components/Page/styles.module.css: -------------------------------------------------------------------------------- 1 | .page { 2 | min-width: inherit; 3 | min-height: inherit; 4 | overflow-x: hidden; 5 | overflow-y: inherit; 6 | position: relative; 7 | background-color: var(--page-bg); 8 | transition: background-color 0.5s ease-in-out, color 0.5s ease-in-out; 9 | } 10 | 11 | .switchWrapper { 12 | display: flex; 13 | align-items: center; 14 | position: absolute; 15 | top: 4px; 16 | right: 4px; 17 | z-index: 100; 18 | } -------------------------------------------------------------------------------- /src/ui-components/Radio/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { FormControlLabel, withStyles } from '@material-ui/core'; 4 | import Radio from '@material-ui/core/Radio'; 5 | import { THEME_COLOR } from '../../resources/colors'; 6 | import classes from './styles.module.css'; 7 | 8 | const styles = { 9 | root: props => ({ 10 | height: props.height ? props.height : 16, 11 | width: props.width ? props.width : 16, 12 | color: props.color ? props.color : THEME_COLOR, 13 | '&$checked': { 14 | color: props.color ? props.color : THEME_COLOR 15 | }, 16 | ...props.radioStyles 17 | }) 18 | }; 19 | 20 | class RadioButton extends React.Component { 21 | 22 | render = () => { 23 | const { checked, disabled, label, labelProps } = this.props; 24 | 25 | return 27 | } {...labelProps}> 28 | } 29 | } 30 | 31 | export default withStyles(styles)(RadioButton); -------------------------------------------------------------------------------- /src/ui-components/Radio/styles.module.css: -------------------------------------------------------------------------------- 1 | .label { 2 | color: var(--text); 3 | transition: color 0.5s ease-in-out; 4 | } -------------------------------------------------------------------------------- /src/ui-components/SnackBar/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import Snackbar from '@material-ui/core/Snackbar'; 4 | import MuiAlert from '@material-ui/lab/Alert'; 5 | import { Slide } from '@material-ui/core'; 6 | 7 | class SnackBar extends React.Component { 8 | 9 | render = () => { 10 | const { open, hideDuration, vertical, horizontal, elevation, variant, severity, message, handleClose } = this.props; 11 | 12 | return 16 | 17 | {message} 18 | 19 | ; 20 | } 21 | } 22 | 23 | SnackBar.defaultProps = { 24 | hideDuration: 3000, 25 | vertical: 'bottom', 26 | horizontal: 'left', 27 | elevation: 5, 28 | variant: 'filled', 29 | severity: 'info' 30 | } 31 | 32 | export default SnackBar; -------------------------------------------------------------------------------- /src/ui-components/SpinnerLoader/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import classes from './styles.module.css'; 4 | 5 | export default class SpinnerLoader extends React.Component { 6 | render = () =>
7 | } -------------------------------------------------------------------------------- /src/ui-components/SpinnerLoader/styles.module.css: -------------------------------------------------------------------------------- 1 | .tripleSpinner { 2 | display: block; 3 | position: relative; 4 | width: 75px; 5 | height: 75px; 6 | border-radius: 50%; 7 | border: 4px solid transparent; 8 | border-top: 4px solid #FF5722; 9 | -webkit-animation: spin 2s linear infinite; 10 | animation: spin 2s linear infinite; 11 | } 12 | 13 | .tripleSpinner::before, .tripleSpinner::after { 14 | content: ""; 15 | position: absolute; 16 | border-radius: 50%; 17 | border: 4px solid transparent; 18 | } 19 | 20 | .tripleSpinner::before { 21 | top: 5px; 22 | left: 5px; 23 | right: 5px; 24 | bottom: 5px; 25 | border-top-color: #FF9800; 26 | -webkit-animation: spin 3s linear infinite; 27 | animation: spin 3.5s linear infinite; 28 | } 29 | 30 | .tripleSpinner::after { 31 | top: 15px; 32 | left: 15px; 33 | right: 15px; 34 | bottom: 15px; 35 | border-top-color: #FFC107; 36 | -webkit-animation: spin 1.5s linear infinite; 37 | animation: spin 1.75s linear infinite; 38 | } 39 | 40 | @-webkit-keyframes spin { 41 | from { 42 | -webkit-transform: rotate(0deg); 43 | transform: rotate(0deg); 44 | } 45 | to { 46 | -webkit-transform: rotate(360deg); 47 | transform: rotate(360deg); 48 | } 49 | } 50 | 51 | @keyframes spin { 52 | from { 53 | -webkit-transform: rotate(0deg); 54 | transform: rotate(0deg); 55 | } 56 | to { 57 | -webkit-transform: rotate(360deg); 58 | transform: rotate(360deg); 59 | } 60 | } -------------------------------------------------------------------------------- /src/ui-components/Switch/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { Switch, FormControlLabel, withStyles } from '@material-ui/core'; 4 | import { BLUE_300, BLUE_700, BLUE_800, BLUE_200 } from '../../resources/colors'; 5 | import { themed } from '../../utils/theme'; 6 | 7 | const styles = { 8 | switchBase: props => ({ 9 | // color: props.buttoncolor ? props.buttoncolor : BLUE_500, 10 | '&.Mui-checked': { 11 | color: props.buttoncolor ? props.buttoncolor : themed(BLUE_800, BLUE_700) 12 | }, 13 | '& + .MuiSwitch-track': { 14 | backgroundColor: 'var(--text-obscurer)' 15 | }, 16 | '&.Mui-checked + .MuiSwitch-track': { 17 | backgroundColor: props.trackcolor ? props.trackcolor : themed(BLUE_300, BLUE_200) 18 | }, 19 | '&.MuiSwitch-colorSecondary.Mui-checked:hover': { 20 | backgroundColor: 'var(--hover-ripple)' 21 | }, 22 | }), 23 | checked: {}, 24 | track: {} 25 | } 26 | 27 | class CustomSwitch extends React.Component { 28 | 29 | render = () => { 30 | const { checked, disabled, handleChange, label, containerStyle } = this.props; 31 | 32 | return } 34 | label={label} style={containerStyle} 35 | /> 36 | } 37 | } 38 | 39 | export default withStyles(styles)(CustomSwitch); -------------------------------------------------------------------------------- /src/utils/theme.js: -------------------------------------------------------------------------------- 1 | import { isEmptyString } from "."; 2 | import { createMuiTheme } from "@material-ui/core"; 3 | import { THEME_COLOR, THEME_COLOR_DARK, TEXT_COLOR, TEXT_COLOR_DARK } from "../resources/colors"; 4 | 5 | export const themes = Object.freeze({ 6 | LIGHT: 'light', 7 | DARK: 'dark' 8 | }); 9 | 10 | export const themed = (lightVal, darkVal) => getTheme() === themes.LIGHT ? lightVal : darkVal; 11 | 12 | export const getTheme = () => { 13 | const theme = !isEmptyString(JSON.parse(localStorage.getItem('pricingTheme'))) ? JSON.parse(localStorage.getItem('pricingTheme')) : themes.LIGHT; 14 | return theme; 15 | } 16 | 17 | export const setTheme = theme => { 18 | localStorage.setItem('pricingTheme', JSON.stringify(theme)); 19 | document.documentElement.setAttribute('theme-mode', theme); 20 | } 21 | 22 | export const getMuiTheme = () => createMuiTheme({ 23 | palette: { 24 | type: getTheme(), 25 | primary: { 26 | main: THEME_COLOR, 27 | light: THEME_COLOR, 28 | dark: THEME_COLOR_DARK 29 | }, 30 | text: { 31 | primary: themed(TEXT_COLOR, TEXT_COLOR_DARK), 32 | } 33 | } 34 | }) --------------------------------------------------------------------------------