├── .eslintignore ├── .eslintrc.json ├── .gitignore ├── .node-version ├── .prettierignore ├── .prettierrc.json ├── LICENSE ├── README.md ├── package.json ├── public └── index.html ├── src ├── App.css ├── App.js ├── components │ ├── About │ │ ├── About.css │ │ └── About.js │ ├── Contact │ │ ├── Contact.css │ │ └── Contact.js │ ├── Footer │ │ ├── Footer.css │ │ └── Footer.js │ ├── Header │ │ ├── Header.css │ │ └── Header.js │ ├── Navbar │ │ ├── Navbar.css │ │ └── Navbar.js │ ├── ProjectContainer │ │ ├── ProjectContainer.css │ │ └── ProjectContainer.js │ ├── Projects │ │ ├── Projects.css │ │ └── Projects.js │ ├── ScrollToTop │ │ ├── ScrollToTop.css │ │ └── ScrollToTop.js │ └── Skills │ │ ├── Skills.css │ │ └── Skills.js ├── contexts │ └── theme.js ├── index.css ├── index.js └── portfolio.js └── yarn.lock /.eslintignore: -------------------------------------------------------------------------------- 1 | build 2 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "es2021": true 5 | }, 6 | "extends": [ 7 | "eslint:recommended", 8 | "plugin:react/recommended", 9 | "airbnb", 10 | "prettier" 11 | ], 12 | "parserOptions": { 13 | "ecmaFeatures": { 14 | "jsx": true 15 | }, 16 | "ecmaVersion": 12, 17 | "sourceType": "module" 18 | }, 19 | "plugins": ["react", "prettier"], 20 | "rules": { 21 | "no-unused-vars": "warn", 22 | "no-console": "off", 23 | "react/react-in-jsx-scope": "off", 24 | "react/jsx-filename-extension": [1, { "extensions": [".js", ".jsx"] }], 25 | "react/prop-types": "off" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /.node-version: -------------------------------------------------------------------------------- 1 | v16.7.0 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | build 2 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "useTabs": false, 3 | "semi": false, 4 | "singleQuote": true, 5 | "jsxSingleQuote": true 6 | } 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Raj Shekhar 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Cleanfolio 2 | 3 | Cleanfolio is a portfolio template built with React. However, if you prefer a template built with HTML, CSS, and JavaScript, you can check out [Cleanfolio Minimal](https://github.com/rjshkhr/cleanfolio-minimal). 4 | 5 | ## Preview 6 | 7 | [![Imgur](https://imgur.com/FwDMNEM.gif)](https://rjshkhr.github.io/cleanfolio) 8 | 9 | [Live Demo](https://rjshkhr.github.io/cleanfolio) 10 | 11 | ## Instructions 12 | 13 | ### Setup 14 | 15 | ```shell 16 | git clone https://github.com/rjshkhr/cleanfolio 17 | cd cleanfolio 18 | ``` 19 | 20 | If you use [nvm](https://github.com/nvm-sh/nvm) or [fnm](https://github.com/Schniz/fnm), execute: 21 | 22 | ```shell 23 | nvm install 24 | nvm use 25 | ``` 26 | 27 | Or: 28 | 29 | ```shell 30 | fnm install 31 | fnm use 32 | ``` 33 | 34 | To install and launch the project, run these commands: 35 | 36 | ```shell 37 | yarn 38 | yarn start 39 | ``` 40 | 41 | ### How to Use 42 | 43 | - Open the `public/index.html` file and replace: 44 | 45 | `John Smith` with `Your Name`. 46 | 47 | - Open the `src/portfolio.js` file and make the necessary changes. 48 | 49 | ### Deployment 50 | 51 | - In the `package.json` file, update: 52 | 53 | `"homepage": "https://rjshkhr.github.io/cleanfolio"` 54 | 55 | to `"homepage": "https://yourusername.github.io"`. 56 | 57 | - Push the changes to your repository. 58 | 59 | - To build and deploy, run the following commands: 60 | 61 | ```shell 62 | yarn build 63 | yarn deploy 64 | ``` 65 | 66 | ## License 67 | 68 | [MIT](https://choosealicense.com/licenses/mit/) 69 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cleanfolio", 3 | "version": "0.1.0", 4 | "private": true, 5 | "homepage": "https://rjshkhr.github.io/cleanfolio", 6 | "dependencies": { 7 | "@material-ui/core": "^4.12.3", 8 | "@material-ui/icons": "^4.11.2", 9 | "@testing-library/jest-dom": "^5.14.1", 10 | "@testing-library/react": "^12.0.0", 11 | "@testing-library/user-event": "^13.2.1", 12 | "gh-pages": "^3.2.3", 13 | "prop-types": "^15.7.2", 14 | "react": "^17.0.2", 15 | "react-dom": "^17.0.2", 16 | "react-scripts": "4.0.3", 17 | "uniqid": "^5.4.0", 18 | "web-vitals": "^2.1.0" 19 | }, 20 | "scripts": { 21 | "start": "react-scripts start --openssl-legacy-provider", 22 | "build": "react-scripts build --openssl-legacy-provider", 23 | "test": "react-scripts test --openssl-legacy-provider", 24 | "eject": "react-scripts eject --openssl-legacy-provider", 25 | "lint": "eslint .", 26 | "format": "prettier --write \"**/*.+(js|jsx|json|css|md)\"", 27 | "deploy": "gh-pages -d build" 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 | "eslint": "^7.32.0", 43 | "eslint-config-airbnb": "18.2.1", 44 | "eslint-config-prettier": "^8.3.0", 45 | "eslint-plugin-import": "^2.24.2", 46 | "eslint-plugin-jsx-a11y": "^6.4.1", 47 | "eslint-plugin-prettier": "^4.0.0", 48 | "eslint-plugin-react": "^7.25.1", 49 | "eslint-plugin-react-hooks": "^4.2.0", 50 | "prettier": "^2.3.2" 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 10 | 11 | 12 | 13 | John Smith 14 | 15 | 16 | 17 |
18 | 19 | 20 | -------------------------------------------------------------------------------- /src/App.css: -------------------------------------------------------------------------------- 1 | .app { 2 | font-family: 'Poppins', sans-serif; 3 | line-height: 1.5; 4 | color: var(--clr-fg); 5 | background-color: var(--clr-bg); 6 | } 7 | 8 | .light { 9 | --clr-bg: #fcfcfc; 10 | --clr-bg-alt: #fff; 11 | --clr-fg: #555; 12 | --clr-fg-alt: #444; 13 | --clr-primary: #2978b5; 14 | --shadow: rgba(100, 100, 111, 0.2) 0px 7px 29px 0px; 15 | } 16 | 17 | .dark { 18 | --clr-bg: #23283e; 19 | --clr-bg-alt: #2a2f4c; 20 | --clr-fg: #bdbddd; 21 | --clr-fg-alt: #cdcdff; 22 | --clr-primary: #90a0d9; 23 | --shadow: rgba(0, 0, 0, 0.16) 0px 10px 36px 0px, 24 | rgba(0, 0, 0, 0.06) 0px 0px 0px 1px; 25 | } 26 | 27 | main { 28 | max-width: 1100px; 29 | width: 95%; 30 | margin: 0 auto; 31 | } 32 | 33 | .section { 34 | margin-top: 5em; 35 | } 36 | 37 | .section__title { 38 | text-align: center; 39 | margin-bottom: 1em; 40 | text-transform: uppercase; 41 | } 42 | 43 | .center { 44 | display: flex; 45 | align-items: center; 46 | } 47 | 48 | .link { 49 | color: var(--clr-primary); 50 | padding: 0 0 0.3em 0; 51 | position: relative; 52 | } 53 | 54 | .link:hover { 55 | color: var(--clr-primary); 56 | } 57 | 58 | .link::before { 59 | content: ''; 60 | display: inline; 61 | width: 0%; 62 | height: 0.2em; 63 | position: absolute; 64 | bottom: 0; 65 | background-color: var(--clr-primary); 66 | transition: width 0.2s ease-in; 67 | } 68 | 69 | .link:hover::before, 70 | .link:focus::before { 71 | width: 100%; 72 | } 73 | 74 | .link--nav { 75 | color: var(--clr-fg); 76 | text-transform: lowercase; 77 | font-weight: 500; 78 | } 79 | 80 | .link--icon { 81 | color: var(--clr-fg); 82 | } 83 | 84 | .btn { 85 | display: block; 86 | cursor: pointer; 87 | padding: 0.8em 1.4em; 88 | font-weight: 500; 89 | font-size: 0.9rem; 90 | text-transform: lowercase; 91 | transition: transform 0.2s ease-in-out; 92 | } 93 | 94 | .btn--outline { 95 | color: var(--clr-primary); 96 | border: 2px solid var(--clr-primary); 97 | position: relative; 98 | overflow: hidden; 99 | z-index: 1; 100 | } 101 | 102 | .btn--outline:hover, 103 | .btn--outline:focus { 104 | color: var(--clr-bg); 105 | } 106 | 107 | .btn--outline:before { 108 | content: ''; 109 | position: absolute; 110 | background-color: var(--clr-primary); 111 | right: 100%; 112 | bottom: 0; 113 | left: 0; 114 | top: 0; 115 | z-index: -1; 116 | transition: right 0.2s ease-in-out; 117 | } 118 | 119 | .btn--outline:hover:before, 120 | .btn--outline:focus:before { 121 | right: 0; 122 | } 123 | 124 | .btn--plain { 125 | text-transform: initial; 126 | background-color: var(--clr-bg-alt); 127 | box-shadow: rgba(0, 0, 0, 0.15) 0px 3px 3px 0px; 128 | border: 0; 129 | } 130 | 131 | .btn--plain:hover { 132 | transform: translateY(-4px); 133 | } 134 | 135 | .btn--icon { 136 | padding: 0; 137 | } 138 | 139 | .btn--icon:hover, 140 | .btn--icon:focus { 141 | color: var(--clr-primary); 142 | } 143 | 144 | .btn--icon:active { 145 | transform: translateY(-5px); 146 | } 147 | 148 | @media (max-width: 600px) { 149 | .section { 150 | margin-top: 4em; 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | import { useContext } from 'react' 2 | import { ThemeContext } from './contexts/theme' 3 | import Header from './components/Header/Header' 4 | import About from './components/About/About' 5 | import Projects from './components/Projects/Projects' 6 | import Skills from './components/Skills/Skills' 7 | import ScrollToTop from './components/ScrollToTop/ScrollToTop' 8 | import Contact from './components/Contact/Contact' 9 | import Footer from './components/Footer/Footer' 10 | import './App.css' 11 | 12 | const App = () => { 13 | const [{ themeName }] = useContext(ThemeContext) 14 | 15 | return ( 16 |
17 |
18 | 19 |
20 | 21 | 22 | 23 | 24 |
25 | 26 | 27 |
29 | ) 30 | } 31 | 32 | export default App 33 | -------------------------------------------------------------------------------- /src/components/About/About.css: -------------------------------------------------------------------------------- 1 | .about { 2 | flex-direction: column; 3 | margin-top: 3em; 4 | } 5 | 6 | .about__name { 7 | color: var(--clr-primary); 8 | } 9 | 10 | .about__role { 11 | margin-top: 1.2em; 12 | } 13 | 14 | .about__desc { 15 | font-size: 1rem; 16 | max-width: 600px; 17 | } 18 | 19 | .about__desc, 20 | .about__contact { 21 | margin-top: 2.4em; 22 | } 23 | 24 | .about .link--icon { 25 | margin-right: 0.8em; 26 | } 27 | 28 | .about .btn--outline { 29 | margin-right: 1em; 30 | } 31 | 32 | @media (max-width: 600px) { 33 | .app .about { 34 | align-items: flex-start; 35 | margin-top: 2em; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/components/About/About.js: -------------------------------------------------------------------------------- 1 | import GitHubIcon from '@material-ui/icons/GitHub' 2 | import LinkedInIcon from '@material-ui/icons/LinkedIn' 3 | import { about } from '../../portfolio' 4 | import './About.css' 5 | 6 | const About = () => { 7 | const { name, role, description, resume, social } = about 8 | 9 | return ( 10 |
11 | {name && ( 12 |

13 | Hi, I am {name}. 14 |

15 | )} 16 | 17 | {role &&

A {role}.

} 18 |

{description && description}

19 | 20 |
21 | {resume && ( 22 | 23 | 24 | Resume 25 | 26 | 27 | )} 28 | 29 | {social && ( 30 | <> 31 | {social.github && ( 32 | 37 | 38 | 39 | )} 40 | 41 | {social.linkedin && ( 42 | 47 | 48 | 49 | )} 50 | 51 | )} 52 |
53 |
54 | ) 55 | } 56 | 57 | export default About 58 | -------------------------------------------------------------------------------- /src/components/Contact/Contact.css: -------------------------------------------------------------------------------- 1 | .contact { 2 | flex-direction: column; 3 | } 4 | -------------------------------------------------------------------------------- /src/components/Contact/Contact.js: -------------------------------------------------------------------------------- 1 | import { contact } from '../../portfolio' 2 | import './Contact.css' 3 | 4 | const Contact = () => { 5 | if (!contact.email) return null 6 | 7 | return ( 8 |
9 |

Contact

10 | 11 | 12 | Email me 13 | 14 | 15 |
16 | ) 17 | } 18 | 19 | export default Contact 20 | -------------------------------------------------------------------------------- /src/components/Footer/Footer.css: -------------------------------------------------------------------------------- 1 | .footer { 2 | padding: 3em 0; 3 | margin-top: 4em; 4 | text-align: center; 5 | } 6 | 7 | .footer__link { 8 | font-size: 0.9rem; 9 | font-weight: 600; 10 | color: var(--clr-fg); 11 | } 12 | 13 | @media (max-width: 600px) { 14 | .footer { 15 | padding: 2em; 16 | margin-top: 3em; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/components/Footer/Footer.js: -------------------------------------------------------------------------------- 1 | import './Footer.css' 2 | 3 | const Footer = () => ( 4 | 12 | ) 13 | 14 | export default Footer 15 | -------------------------------------------------------------------------------- /src/components/Header/Header.css: -------------------------------------------------------------------------------- 1 | .header { 2 | height: 8em; 3 | max-width: 1100px; 4 | width: 95%; 5 | margin: 0 auto; 6 | justify-content: space-between; 7 | } 8 | 9 | @media (max-width: 600px) { 10 | .header { 11 | height: 6em; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/components/Header/Header.js: -------------------------------------------------------------------------------- 1 | import { header } from '../../portfolio' 2 | import Navbar from '../Navbar/Navbar' 3 | import './Header.css' 4 | 5 | const Header = () => { 6 | const { homepage, title } = header 7 | 8 | return ( 9 |
10 |

11 | {homepage ? ( 12 | 13 | {title} 14 | 15 | ) : ( 16 | title 17 | )} 18 |

19 | 20 |
21 | ) 22 | } 23 | 24 | export default Header 25 | -------------------------------------------------------------------------------- /src/components/Navbar/Navbar.css: -------------------------------------------------------------------------------- 1 | .nav__list { 2 | margin-right: 1.5em; 3 | display: flex; 4 | } 5 | .nav__list-item { 6 | margin-left: 1.5em; 7 | } 8 | 9 | .app .nav__hamburger { 10 | display: none; 11 | } 12 | 13 | .nav__theme { 14 | margin-top: 0.4em; 15 | } 16 | 17 | @media (max-width: 600px) { 18 | .nav__list { 19 | display: none; 20 | flex-direction: column; 21 | justify-content: center; 22 | align-items: center; 23 | position: fixed; 24 | inset: 0; 25 | width: 100%; 26 | height: 100%; 27 | z-index: 2; 28 | } 29 | 30 | .nav__list-item { 31 | margin: 0.5em 0; 32 | } 33 | 34 | .app .nav__hamburger { 35 | display: flex; 36 | z-index: 2; 37 | margin-left: 0.8em; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/components/Navbar/Navbar.js: -------------------------------------------------------------------------------- 1 | import { useContext, useState } from 'react' 2 | import Brightness2Icon from '@material-ui/icons/Brightness2' 3 | import WbSunnyRoundedIcon from '@material-ui/icons/WbSunnyRounded' 4 | import MenuIcon from '@material-ui/icons/Menu' 5 | import CloseIcon from '@material-ui/icons/Close' 6 | import { ThemeContext } from '../../contexts/theme' 7 | import { projects, skills, contact } from '../../portfolio' 8 | import './Navbar.css' 9 | 10 | const Navbar = () => { 11 | const [{ themeName, toggleTheme }] = useContext(ThemeContext) 12 | const [showNavList, setShowNavList] = useState(false) 13 | 14 | const toggleNavList = () => setShowNavList(!showNavList) 15 | 16 | return ( 17 | 77 | ) 78 | } 79 | 80 | export default Navbar 81 | -------------------------------------------------------------------------------- /src/components/ProjectContainer/ProjectContainer.css: -------------------------------------------------------------------------------- 1 | .project { 2 | padding: 2em; 3 | margin: 0 auto; 4 | text-align: center; 5 | box-shadow: var(--shadow); 6 | transition: transform 0.2s linear; 7 | } 8 | 9 | .project:hover { 10 | transform: translateY(-7px); 11 | } 12 | 13 | .project__description { 14 | margin-top: 1em; 15 | } 16 | 17 | .project__stack { 18 | display: flex; 19 | flex-wrap: wrap; 20 | justify-content: center; 21 | margin: 1.2em 0; 22 | } 23 | 24 | .project__stack-item { 25 | margin: 0.5em; 26 | font-weight: 500; 27 | font-size: 0.8rem; 28 | color: var(--clr-fg-alt); 29 | } 30 | 31 | .project .link--icon { 32 | margin-left: 0.5em; 33 | } 34 | -------------------------------------------------------------------------------- /src/components/ProjectContainer/ProjectContainer.js: -------------------------------------------------------------------------------- 1 | import uniqid from 'uniqid' 2 | import GitHubIcon from '@material-ui/icons/GitHub' 3 | import LaunchIcon from '@material-ui/icons/Launch' 4 | import './ProjectContainer.css' 5 | 6 | const ProjectContainer = ({ project }) => ( 7 |
8 |

{project.name}

9 | 10 |

{project.description}

11 | {project.stack && ( 12 | 19 | )} 20 | 21 | {project.sourceCode && ( 22 | 27 | 28 | 29 | )} 30 | 31 | {project.livePreview && ( 32 | 37 | 38 | 39 | )} 40 |
41 | ) 42 | 43 | export default ProjectContainer 44 | -------------------------------------------------------------------------------- /src/components/Projects/Projects.css: -------------------------------------------------------------------------------- 1 | .projects__grid { 2 | max-width: 1100px; 3 | margin: 0 auto; 4 | display: grid; 5 | grid-template-columns: repeat(auto-fit, minmax(18em, 1fr)); 6 | grid-gap: 2em; 7 | } 8 | -------------------------------------------------------------------------------- /src/components/Projects/Projects.js: -------------------------------------------------------------------------------- 1 | import uniqid from 'uniqid' 2 | import { projects } from '../../portfolio' 3 | import ProjectContainer from '../ProjectContainer/ProjectContainer' 4 | import './Projects.css' 5 | 6 | const Projects = () => { 7 | if (!projects.length) return null 8 | 9 | return ( 10 |
11 |

Projects

12 | 13 |
14 | {projects.map((project) => ( 15 | 16 | ))} 17 |
18 |
19 | ) 20 | } 21 | 22 | export default Projects 23 | -------------------------------------------------------------------------------- /src/components/ScrollToTop/ScrollToTop.css: -------------------------------------------------------------------------------- 1 | .scroll-top { 2 | position: fixed; 3 | bottom: 2em; 4 | right: 4em; 5 | background-color: transparent; 6 | } 7 | 8 | @media (max-width: 900px) { 9 | .scroll-top { 10 | display: none; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/components/ScrollToTop/ScrollToTop.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react' 2 | import ArrowUpwardIcon from '@material-ui/icons/ArrowUpward' 3 | import './ScrollToTop.css' 4 | 5 | const ScrollToTop = () => { 6 | const [isVisible, setIsVisible] = useState(false) 7 | 8 | useEffect(() => { 9 | const toggleVisibility = () => 10 | window.pageYOffset > 500 ? setIsVisible(true) : setIsVisible(false) 11 | 12 | window.addEventListener('scroll', toggleVisibility) 13 | return () => window.removeEventListener('scroll', toggleVisibility) 14 | }, []) 15 | 16 | return isVisible ? ( 17 |
18 | 19 | 20 | 21 |
22 | ) : null 23 | } 24 | 25 | export default ScrollToTop 26 | -------------------------------------------------------------------------------- /src/components/Skills/Skills.css: -------------------------------------------------------------------------------- 1 | .skills__list { 2 | max-width: 450px; 3 | width: 95%; 4 | margin: 0 auto; 5 | display: flex; 6 | flex-wrap: wrap; 7 | justify-content: center; 8 | } 9 | 10 | .skills__list-item { 11 | margin: 0.5em; 12 | } 13 | -------------------------------------------------------------------------------- /src/components/Skills/Skills.js: -------------------------------------------------------------------------------- 1 | import uniqid from 'uniqid' 2 | import { skills } from '../../portfolio' 3 | import './Skills.css' 4 | 5 | const Skills = () => { 6 | if (!skills.length) return null 7 | 8 | return ( 9 |
10 |

Skills

11 | 18 |
19 | ) 20 | } 21 | 22 | export default Skills 23 | -------------------------------------------------------------------------------- /src/contexts/theme.js: -------------------------------------------------------------------------------- 1 | import { createContext, useEffect, useState } from 'react' 2 | import PropTypes from 'prop-types' 3 | 4 | const ThemeContext = createContext() 5 | 6 | const ThemeProvider = ({ children }) => { 7 | const [themeName, setThemeName] = useState('light') 8 | 9 | useEffect(() => { 10 | const darkMediaQuery = window.matchMedia('(prefers-color-scheme: dark)'); 11 | setThemeName(darkMediaQuery.matches ? 'dark' : 'light') 12 | darkMediaQuery.addEventListener('change', (e) => { 13 | setThemeName(e.matches ? 'dark' : 'light') 14 | }); 15 | }, []) 16 | 17 | const toggleTheme = () => { 18 | const name = themeName === 'dark' ? 'light' : 'dark' 19 | localStorage.setItem('themeName', name) 20 | setThemeName(name) 21 | } 22 | 23 | return ( 24 | 25 | {children} 26 | 27 | ) 28 | } 29 | 30 | ThemeProvider.propTypes = { 31 | children: PropTypes.node.isRequired, 32 | } 33 | 34 | export { ThemeProvider, ThemeContext } 35 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | * { 2 | padding: 0; 3 | margin: 0; 4 | border: 0; 5 | outline: 0; 6 | background-color: inherit; 7 | color: inherit; 8 | font-family: inherit; 9 | font-size: inherit; 10 | box-shadow: none; 11 | box-sizing: border-box; 12 | } 13 | 14 | html { 15 | scroll-behavior: smooth; 16 | } 17 | 18 | h1, 19 | h2, 20 | h3, 21 | h4 { 22 | line-height: 1.2; 23 | color: var(--clr-fg-alt); 24 | } 25 | 26 | h1 { 27 | font-size: 4rem; 28 | } 29 | 30 | h2 { 31 | font-size: 2rem; 32 | } 33 | 34 | h3 { 35 | font-size: 1.5rem; 36 | } 37 | 38 | h4 { 39 | font-size: 1.3rem; 40 | } 41 | 42 | ul { 43 | list-style-type: none; 44 | } 45 | 46 | a { 47 | text-decoration: none; 48 | } 49 | 50 | button { 51 | cursor: pointer; 52 | } 53 | 54 | @media (max-width: 900px) { 55 | h1 { 56 | font-size: 2.6rem; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import { render } from 'react-dom' 2 | import App from './App' 3 | import { ThemeProvider } from './contexts/theme' 4 | import './index.css' 5 | 6 | render( 7 | 8 | 9 | , 10 | document.getElementById('root') 11 | ) 12 | -------------------------------------------------------------------------------- /src/portfolio.js: -------------------------------------------------------------------------------- 1 | const header = { 2 | // all the properties are optional - can be left empty or deleted 3 | homepage: 'https://rjshkhr.github.io/cleanfolio', 4 | title: 'JS.', 5 | } 6 | 7 | const about = { 8 | // all the properties are optional - can be left empty or deleted 9 | name: 'John Smith', 10 | role: 'Front End Engineer', 11 | description: 12 | 'Adipisicing sit fugit ullam unde aliquid sequi Facilis soluta facilis perspiciatis corporis nulla aspernatur. Autem eligendi rerum delectus modi quisquam? Illo ut quasi nemo ipsa cumque perspiciatis! Maiores minima consectetur.', 13 | resume: 'https://example.com', 14 | social: { 15 | linkedin: 'https://linkedin.com', 16 | github: 'https://github.com', 17 | }, 18 | } 19 | 20 | const projects = [ 21 | // projects can be added an removed 22 | // if there are no projects, Projects section won't show up 23 | { 24 | name: 'Project 1', 25 | description: 26 | 'Amet asperiores et impedit aliquam consectetur? Voluptates sed a nulla ipsa officia et esse aliquam', 27 | stack: ['SASS', 'TypeScript', 'React'], 28 | sourceCode: 'https://github.com', 29 | livePreview: 'https://github.com', 30 | }, 31 | { 32 | name: 'Project 2', 33 | description: 34 | 'Amet asperiores et impedit aliquam consectetur? Voluptates sed a nulla ipsa officia et esse aliquam', 35 | stack: ['SASS', 'TypeScript', 'React'], 36 | sourceCode: 'https://github.com', 37 | livePreview: 'https://github.com', 38 | }, 39 | { 40 | name: 'Project 3', 41 | description: 42 | 'Amet asperiores et impedit aliquam consectetur? Voluptates sed a nulla ipsa officia et esse aliquam', 43 | stack: ['SASS', 'TypeScript', 'React'], 44 | sourceCode: 'https://github.com', 45 | livePreview: 'https://github.com', 46 | }, 47 | ] 48 | 49 | const skills = [ 50 | // skills can be added or removed 51 | // if there are no skills, Skills section won't show up 52 | 'HTML', 53 | 'CSS', 54 | 'JavaScript', 55 | 'TypeScript', 56 | 'React', 57 | 'Redux', 58 | 'SASS', 59 | 'Material UI', 60 | 'Git', 61 | 'CI/CD', 62 | 'Jest', 63 | 'Enzyme', 64 | ] 65 | 66 | const contact = { 67 | // email is optional - if left empty Contact section won't show up 68 | email: 'johnsmith@mail.com', 69 | } 70 | 71 | export { header, about, projects, skills, contact } 72 | --------------------------------------------------------------------------------