├── .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 |
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 |
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
;
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 | }>Find Me
57 | }>
58 |
59 |
60 |
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 ReactJS
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 |

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(() => );
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 |

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 ?
:
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 | }>Add ToDo
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 |

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 &&

}
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 | })
--------------------------------------------------------------------------------