├── .eslintrc.cjs ├── .gitignore ├── README.md ├── index.html ├── package-lock.json ├── package.json ├── public └── vite.svg ├── src ├── App.jsx ├── components │ ├── Articles │ │ ├── Articles.jsx │ │ └── Articles.module.css │ ├── BookmarkSummary │ │ ├── BookmarkInformation.jsx │ │ ├── BookmarkSummary.jsx │ │ └── BookmarkSummary.module.css │ ├── Header │ │ ├── Header.jsx │ │ └── Header.module.css │ ├── InfoSidebar │ │ ├── InfoSidebar.jsx │ │ └── InfoSidebar.module.css │ └── News │ │ └── News.jsx ├── context │ └── bookmark-context.jsx ├── data │ └── dummy-articles.js ├── index.css └── main.jsx └── vite.config.js /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { browser: true, es2020: true }, 4 | extends: [ 5 | "eslint:recommended", 6 | "plugin:react/recommended", 7 | "plugin:react/jsx-runtime", 8 | "plugin:react-hooks/recommended", 9 | ], 10 | ignorePatterns: ["dist", ".eslintrc.cjs"], 11 | parserOptions: { ecmaVersion: "latest", sourceType: "module" }, 12 | settings: { react: { version: "18.2" } }, 13 | plugins: ["react-refresh"], 14 | rules: { 15 | "react/prop-types": "off", 16 | "react-refresh/only-export-components": [ 17 | "warn", 18 | { allowConstantExport: true }, 19 | ], 20 | }, 21 | }; 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # React + Vite 2 | 3 | This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. 4 | 5 | Currently, two official plugins are available: 6 | 7 | - [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh 8 | - [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh 9 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite + React 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite --port 3000", 8 | "build": "vite build", 9 | "lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0", 10 | "preview": "vite preview" 11 | }, 12 | "dependencies": { 13 | "react": "^18.2.0", 14 | "react-dom": "^18.2.0", 15 | "react-icons": "^4.10.1" 16 | }, 17 | "devDependencies": { 18 | "@types/react": "^18.2.15", 19 | "@types/react-dom": "^18.2.7", 20 | "@vitejs/plugin-react": "^4.0.3", 21 | "eslint": "^8.45.0", 22 | "eslint-plugin-react": "^7.32.2", 23 | "eslint-plugin-react-hooks": "^4.6.0", 24 | "eslint-plugin-react-refresh": "^0.4.3", 25 | "vite": "^4.4.5" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /public/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/App.jsx: -------------------------------------------------------------------------------- 1 | import Header from "./components/Header/Header"; 2 | import News from "./components/News/News"; 3 | 4 | function App() { 5 | return ( 6 | <> 7 |
8 |
9 | 10 |
11 | 12 | ); 13 | } 14 | 15 | export default App; 16 | -------------------------------------------------------------------------------- /src/components/Articles/Articles.jsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable react/prop-types */ 2 | import { FaBookmark, FaRegBookmark } from "react-icons/fa"; 3 | 4 | import dummyArticles from "../../data/dummy-articles"; 5 | import classes from "./Articles.module.css"; 6 | import { useBookmarkContext } from "../../context/bookmark-context"; 7 | 8 | function Articles() { 9 | const { bookmarkedArticles, bookmarkArticle, unbookmarkArticle } = 10 | useBookmarkContext(); 11 | return ( 12 | 38 | ); 39 | } 40 | 41 | export default Articles; 42 | -------------------------------------------------------------------------------- /src/components/Articles/Articles.module.css: -------------------------------------------------------------------------------- 1 | .list { 2 | margin: 3rem 0; 3 | } 4 | 5 | .list li { 6 | position: relative; 7 | margin: 1rem 0; 8 | padding: 1rem; 9 | background-color: #e3e3dd; 10 | border-radius: 8px; 11 | } 12 | 13 | .list li button { 14 | position: absolute; 15 | top: 0.5rem; 16 | right: 0.5rem; 17 | display: flex; 18 | justify-content: center; 19 | align-items: center; 20 | width: 3rem; 21 | height: 3rem; 22 | font-size: 1.5rem; 23 | background-color: transparent; 24 | border: none; 25 | cursor: pointer; 26 | color: #3b2605; 27 | z-index: 10; 28 | } 29 | -------------------------------------------------------------------------------- /src/components/BookmarkSummary/BookmarkInformation.jsx: -------------------------------------------------------------------------------- 1 | import BookmarkSummary from './BookmarkSummary'; 2 | 3 | function BookmarkInformation({ bookmarkedArticles }) { 4 | return ( 5 |
6 |
7 |

Your Bookmarks

8 |
9 | 10 |
11 | ); 12 | } 13 | 14 | export default BookmarkInformation; 15 | -------------------------------------------------------------------------------- /src/components/BookmarkSummary/BookmarkSummary.jsx: -------------------------------------------------------------------------------- 1 | import classes from "./BookmarkSummary.module.css"; 2 | import { useBookmarkContext } from "../../context/bookmark-context"; 3 | 4 | function BookmarkSummary() { 5 | const { bookmarkedArticles } = useBookmarkContext(); 6 | const numberOfArticles = bookmarkedArticles.length; 7 | 8 | return ( 9 | <> 10 |

{numberOfArticles} articles bookmarked

11 | 16 | 17 | ); 18 | } 19 | 20 | export default BookmarkSummary; 21 | -------------------------------------------------------------------------------- /src/components/BookmarkSummary/BookmarkSummary.module.css: -------------------------------------------------------------------------------- 1 | .summary { 2 | margin: 1rem 0; 3 | font-weight: bold; 4 | } 5 | 6 | .list li { 7 | margin: 0.5rem; 8 | } 9 | -------------------------------------------------------------------------------- /src/components/Header/Header.jsx: -------------------------------------------------------------------------------- 1 | import classes from './Header.module.css'; 2 | 3 | function Header() { 4 | return ( 5 |
6 |

Top React Articles

7 |
8 | ); 9 | } 10 | 11 | export default Header; 12 | -------------------------------------------------------------------------------- /src/components/Header/Header.module.css: -------------------------------------------------------------------------------- 1 | .header { 2 | width: 100%; 3 | height: 7rem; 4 | display: flex; 5 | justify-content: center; 6 | align-items: center; 7 | background-color: #f3df91; 8 | } 9 | -------------------------------------------------------------------------------- /src/components/InfoSidebar/InfoSidebar.jsx: -------------------------------------------------------------------------------- 1 | import BookmarkInformation from '../BookmarkSummary/BookmarkInformation'; 2 | import classes from './InfoSidebar.module.css'; 3 | 4 | function InfoSidebar({ bookmarkedArticles }) { 5 | return ( 6 | 9 | ); 10 | } 11 | 12 | export default InfoSidebar; 13 | -------------------------------------------------------------------------------- /src/components/InfoSidebar/InfoSidebar.module.css: -------------------------------------------------------------------------------- 1 | .sidebar { 2 | position: fixed; 3 | top: 8rem; 4 | right: 0; 5 | width: 15rem; 6 | height: calc(100vh - 8rem); 7 | padding: 2rem; 8 | } -------------------------------------------------------------------------------- /src/components/News/News.jsx: -------------------------------------------------------------------------------- 1 | import Articles from "../Articles/Articles"; 2 | import InfoSidebar from "../InfoSidebar/InfoSidebar"; 3 | import { BookmarkContextProvider } from "../../context/bookmark-context"; 4 | 5 | function News() { 6 | return ( 7 | 8 | 9 | 10 | 11 | ); 12 | } 13 | 14 | export default News; 15 | -------------------------------------------------------------------------------- /src/context/bookmark-context.jsx: -------------------------------------------------------------------------------- 1 | import { createContext, useContext, useState } from "react"; 2 | 3 | export const BookmarkContext = createContext({ 4 | bookmarkedArticles: [], 5 | bookmarkArticle: () => {}, 6 | unbookmarkArticle: () => {}, 7 | }); 8 | 9 | export const BookmarkContextProvider = ({ children }) => { 10 | const [savedArticles, setSavedArticles] = useState([]); 11 | 12 | const addArticle = (article) => 13 | setSavedArticles((prev) => [...prev, article]); 14 | 15 | const removeArticle = (articleId) => { 16 | return setSavedArticles((prev) => 17 | prev.filter((article) => article.id !== articleId) 18 | ); 19 | }; 20 | 21 | return ( 22 | 29 | {children} 30 | 31 | ); 32 | }; 33 | 34 | export const useBookmarkContext = () => { 35 | return useContext(BookmarkContext); 36 | }; 37 | -------------------------------------------------------------------------------- /src/data/dummy-articles.js: -------------------------------------------------------------------------------- 1 | const dummyArticles = [ 2 | { 3 | id: "a1", 4 | title: "Learn React - From The Ground Up", 5 | description: 6 | "Learn all the key fundamentals and basics you need to know about React!", 7 | }, 8 | { 9 | id: "a2", 10 | title: "JavaScript Foundation", 11 | description: 12 | "No React without JavaScript! This article discusses core fundamentals you must know.", 13 | }, 14 | { 15 | id: "a3", 16 | title: "Is that all?", 17 | description: "The 10 best next steps after mastering React fundamentals.", 18 | }, 19 | { 20 | id: "a4", 21 | title: "Beyond React", 22 | description: 23 | "The modern web uses React - everywhere! Really everywhere? Time to dive deeper!", 24 | }, 25 | ]; 26 | 27 | export default dummyArticles; 28 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", 4 | "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", 5 | sans-serif; 6 | -webkit-font-smoothing: antialiased; 7 | -moz-osx-font-smoothing: grayscale; 8 | color: #242424; 9 | } 10 | 11 | main { 12 | max-width: 50rem; 13 | margin: auto; 14 | padding: 0 10px; 15 | display: grid; 16 | grid-template-columns: 80% 20%; 17 | } 18 | 19 | ul { 20 | list-style: none; 21 | margin: 0; 22 | padding: 0; 23 | } 24 | -------------------------------------------------------------------------------- /src/main.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom/client' 3 | import App from './App.jsx' 4 | import './index.css' 5 | 6 | ReactDOM.createRoot(document.getElementById('root')).render( 7 | 8 | 9 | , 10 | ) 11 | -------------------------------------------------------------------------------- /vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import react from '@vitejs/plugin-react' 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | plugins: [react()], 7 | }) 8 | --------------------------------------------------------------------------------