├── .gitignore ├── package-lock.json ├── package.json ├── public ├── favicon.ico ├── index.html ├── logo192.png ├── logo512.png ├── manifest.json └── robots.txt └── src ├── App.css ├── App.js ├── App.test.js ├── components ├── HeaderBar.jsx ├── Home.jsx ├── NavList.jsx ├── SwipeDrawer.jsx ├── archives │ ├── Archive.jsx │ └── Archives.jsx ├── delete │ ├── DeleteNote.jsx │ └── DeleteNotes.jsx └── notes │ ├── EmptyNotes.jsx │ ├── Form.jsx │ ├── Note.jsx │ └── Notes.jsx ├── context └── DataProvider.jsx ├── index.css ├── index.js ├── logo.svg ├── reportWebVitals.js ├── setupTests.js └── utils └── common-utils.js /.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 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "google-keep-clone-use-react", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@emotion/react": "^11.7.1", 7 | "@emotion/styled": "^11.6.0", 8 | "@material-ui/core": "^4.12.4", 9 | "@mui/icons-material": "^5.3.1", 10 | "@mui/material": "^5.3.1", 11 | "@testing-library/jest-dom": "^5.16.1", 12 | "@testing-library/react": "^12.1.2", 13 | "@testing-library/user-event": "^13.5.0", 14 | "react": "^17.0.2", 15 | "react-beautiful-dnd": "^13.1.0", 16 | "react-dom": "^17.0.2", 17 | "react-router-dom": "^6.2.1", 18 | "react-scripts": "5.0.0", 19 | "uuid": "^8.3.2", 20 | "web-vitals": "^2.1.4" 21 | }, 22 | "scripts": { 23 | "start": "react-scripts start", 24 | "build": "react-scripts build", 25 | "test": "react-scripts test", 26 | "eject": "react-scripts eject" 27 | }, 28 | "eslintConfig": { 29 | "extends": [ 30 | "react-app", 31 | "react-app/jest" 32 | ] 33 | }, 34 | "browserslist": { 35 | "production": [ 36 | ">0.2%", 37 | "not dead", 38 | "not op_mini all" 39 | ], 40 | "development": [ 41 | "last 1 chrome version", 42 | "last 1 firefox version", 43 | "last 1 safari version" 44 | ] 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mr-dev-dragon/google-keep-clone-use-react/08845fb8fe574de9a414f6f68d67f345fe0bd637/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 27 | React App 28 | 29 | 30 | 31 |
32 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mr-dev-dragon/google-keep-clone-use-react/08845fb8fe574de9a414f6f68d67f345fe0bd637/public/logo192.png -------------------------------------------------------------------------------- /public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mr-dev-dragon/google-keep-clone-use-react/08845fb8fe574de9a414f6f68d67f345fe0bd637/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-logo { 6 | height: 40vmin; 7 | pointer-events: none; 8 | } 9 | 10 | @media (prefers-reduced-motion: no-preference) { 11 | .App-logo { 12 | animation: App-logo-spin infinite 20s linear; 13 | } 14 | } 15 | 16 | .App-header { 17 | background-color: #282c34; 18 | min-height: 100vh; 19 | display: flex; 20 | flex-direction: column; 21 | align-items: center; 22 | justify-content: center; 23 | font-size: calc(10px + 2vmin); 24 | color: white; 25 | } 26 | 27 | .App-link { 28 | color: #61dafb; 29 | } 30 | 31 | @keyframes App-logo-spin { 32 | from { 33 | transform: rotate(0deg); 34 | } 35 | to { 36 | transform: rotate(360deg); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | 2 | //components 3 | import Home from './components/Home'; 4 | import DataProvider from './context/DataProvider'; 5 | 6 | function App() { 7 | return ( 8 | 9 | 10 | 11 | ); 12 | } 13 | 14 | export default App; 15 | -------------------------------------------------------------------------------- /src/App.test.js: -------------------------------------------------------------------------------- 1 | import { render, screen } from '@testing-library/react'; 2 | import App from './App'; 3 | 4 | test('renders learn react link', () => { 5 | render(); 6 | const linkElement = screen.getByText(/learn react/i); 7 | expect(linkElement).toBeInTheDocument(); 8 | }); 9 | -------------------------------------------------------------------------------- /src/components/HeaderBar.jsx: -------------------------------------------------------------------------------- 1 | 2 | import { AppBar, Toolbar, Typography, IconButton } from '@mui/material'; 3 | import { Menu } from '@mui/icons-material'; 4 | import { styled } from '@mui/material/styles'; 5 | 6 | const Header = styled(AppBar)` 7 | z-index: 1201; 8 | background: #fff; 9 | height: 70px; 10 | box-shadow: inset 0 -1px 0 0 #dadce0; 11 | ` 12 | 13 | const Heading = styled(Typography)` 14 | color: #5F6368; 15 | font-size: 24px; 16 | margin-left: 25px; 17 | ` 18 | 19 | 20 | const HeaderBar = ({ open, handleDrawer }) => { 21 | const logo = 'https://seeklogo.com/images/G/google-keep-logo-0BC92EBBBD-seeklogo.com.png'; 22 | 23 | return ( 24 |
25 | 26 | handleDrawer()} 28 | sx={{ marginRight: '20px'}} 29 | edge="start" 30 | > 31 | 32 | 33 | logo 34 | Keep 35 | 36 |
37 | ) 38 | } 39 | 40 | export default HeaderBar; -------------------------------------------------------------------------------- /src/components/Home.jsx: -------------------------------------------------------------------------------- 1 | 2 | import { BrowserRouter as Router, Routes, Route } from 'react-router-dom'; 3 | import { Box } from '@mui/material'; 4 | 5 | //components 6 | import SwipeDrawer from './SwipeDrawer'; 7 | import Notes from './notes/Notes'; 8 | import Archives from './archives/Archives'; 9 | import DeleteNotes from './delete/DeleteNotes'; 10 | 11 | const Home = () => { 12 | return ( 13 | 14 | 15 | 16 | 17 | } /> 18 | } /> 19 | } /> 20 | 21 | 22 | 23 | ) 24 | } 25 | 26 | export default Home; -------------------------------------------------------------------------------- /src/components/NavList.jsx: -------------------------------------------------------------------------------- 1 | 2 | 3 | import { List, ListItem, ListItemIcon, ListItemText } from '@mui/material'; 4 | import { LightbulbOutlined as Lightbulb, ArchiveOutlined as Archive, DeleteOutlineOutlined as Delete } from '@mui/icons-material'; 5 | import { Link } from 'react-router-dom'; 6 | 7 | const NavList = () => { 8 | 9 | const navList = [ 10 | { id: 1, name: 'Notes', icon: , route: '/' }, 11 | { id: 2, name: 'Archives', icon: , route: '/archive' }, 12 | { id: 3, name: 'Trash', icon: , route: '/delete' }, 13 | ] 14 | 15 | return ( 16 | 17 | { 18 | navList.map(list => ( 19 | 20 | 21 | 22 | {list.icon} 23 | 24 | 25 | 26 | 27 | )) 28 | } 29 | 30 | ) 31 | } 32 | 33 | export default NavList; -------------------------------------------------------------------------------- /src/components/SwipeDrawer.jsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { styled, useTheme } from '@mui/material/styles'; 3 | import Box from '@mui/material/Box'; 4 | import MuiDrawer from '@mui/material/Drawer'; 5 | 6 | //components 7 | import HeaderBar from './HeaderBar'; 8 | import NavList from './NavList'; 9 | 10 | const drawerWidth = 240; 11 | 12 | const openedMixin = (theme) => ({ 13 | width: drawerWidth, 14 | transition: theme.transitions.create('width', { 15 | easing: theme.transitions.easing.sharp, 16 | duration: theme.transitions.duration.enteringScreen, 17 | }), 18 | overflowX: 'hidden', 19 | }); 20 | 21 | const closedMixin = (theme) => ({ 22 | transition: theme.transitions.create('width', { 23 | easing: theme.transitions.easing.sharp, 24 | duration: theme.transitions.duration.leavingScreen, 25 | }), 26 | overflowX: 'hidden', 27 | width: `calc(${theme.spacing(7)} + 1px)`, 28 | [theme.breakpoints.up('sm')]: { 29 | width: `calc(${theme.spacing(9)} + 1px)`, 30 | }, 31 | }); 32 | 33 | const DrawerHeader = styled('div')(({ theme }) => ({ 34 | ...theme.mixins.toolbar, 35 | })); 36 | 37 | 38 | const Drawer = styled(MuiDrawer, { shouldForwardProp: (prop) => prop !== 'open' })( 39 | ({ theme, open }) => ({ 40 | width: drawerWidth, 41 | flexShrink: 0, 42 | whiteSpace: 'nowrap', 43 | boxSizing: 'border-box', 44 | ...(open && { 45 | ...openedMixin(theme), 46 | '& .MuiDrawer-paper': openedMixin(theme), 47 | }), 48 | ...(!open && { 49 | ...closedMixin(theme), 50 | '& .MuiDrawer-paper': closedMixin(theme), 51 | }), 52 | }), 53 | ); 54 | 55 | function SwipeDrawer() { 56 | const theme = useTheme(); 57 | const [open, setOpen] = React.useState(true); 58 | 59 | const handleDrawer = () => { 60 | setOpen(prevState => !prevState); 61 | }; 62 | 63 | return ( 64 | 65 | 69 | 70 | 71 | 72 | 73 | 74 | ); 75 | } 76 | 77 | export default SwipeDrawer; -------------------------------------------------------------------------------- /src/components/archives/Archive.jsx: -------------------------------------------------------------------------------- 1 | import { useContext } from 'react'; 2 | 3 | import { Card, CardContent, CardActions, Typography } from '@mui/material'; 4 | import { styled } from '@mui/material/styles'; 5 | import { UnarchiveOutlined as Unarchive, DeleteOutlineOutlined as Delete } from '@mui/icons-material'; 6 | 7 | import { DataContext } from '../../context/DataProvider'; 8 | 9 | const StyledCard = styled(Card)` 10 | border: 1px solid #e0e0e0; 11 | border-radius: 8px; 12 | width: 240px; 13 | margin: 8px; 14 | box-shadow: none; 15 | ` 16 | 17 | const Archive = ({ archive }) => { 18 | 19 | const { archiveNotes, setNotes, setAcrchiveNotes, setDeleteNotes } = useContext(DataContext); 20 | 21 | const unArchiveNote = (archive) => { 22 | const updatedNotes = archiveNotes.filter(data => data.id !== archive.id); 23 | setAcrchiveNotes(updatedNotes); 24 | setNotes(prevArr => [archive, ...prevArr]); 25 | } 26 | 27 | const deleteNote = (archive) => { 28 | const updatedNotes = archiveNotes.filter(data => data.id !== archive.id); 29 | setAcrchiveNotes(updatedNotes); 30 | setDeleteNotes(prevArr => [archive, ...prevArr]); 31 | } 32 | 33 | return ( 34 | 35 | 36 | {archive.heading} 37 | {archive.text} 38 | 39 | 40 | unArchiveNote(archive)} 44 | /> 45 | deleteNote(archive)} 48 | /> 49 | 50 | 51 | ) 52 | } 53 | 54 | export default Archive; -------------------------------------------------------------------------------- /src/components/archives/Archives.jsx: -------------------------------------------------------------------------------- 1 | import { useContext } from 'react'; 2 | 3 | import { Box, Grid } from '@mui/material'; 4 | import { styled } from '@mui/material/styles'; 5 | 6 | import { DataContext } from '../../context/DataProvider'; 7 | 8 | //components 9 | import Archive from './Archive'; 10 | 11 | const DrawerHeader = styled('div')(({ theme }) => ({ 12 | ...theme.mixins.toolbar, 13 | })); 14 | 15 | const Archives = () => { 16 | 17 | const { archiveNotes } = useContext(DataContext); 18 | 19 | return ( 20 | 21 | 22 | 23 | 24 | { 25 | archiveNotes.map(archive => ( 26 | 27 | 28 | 29 | )) 30 | } 31 | 32 | 33 | 34 | ) 35 | } 36 | 37 | export default Archives; -------------------------------------------------------------------------------- /src/components/delete/DeleteNote.jsx: -------------------------------------------------------------------------------- 1 | import { useContext } from 'react'; 2 | 3 | import { Card, CardContent, CardActions, Typography } from '@mui/material'; 4 | import { styled } from '@mui/material/styles'; 5 | import { RestoreFromTrashOutlined as Restore, DeleteForeverOutlined as Delete } from '@mui/icons-material'; 6 | import { DataContext } from '../../context/DataProvider'; 7 | 8 | const StyledCard = styled(Card)` 9 | border: 1px solid #e0e0e0; 10 | border-radius: 8px; 11 | width: 240px; 12 | margin: 8px; 13 | box-shadow: none; 14 | ` 15 | 16 | const DeleteNote = ({ deleteNote }) => { 17 | 18 | const { deleteNotes, setNotes, setAcrchiveNotes, setDeleteNotes } = useContext(DataContext); 19 | 20 | const restoreNote = (deleteNote) => { 21 | const updatedNotes = deleteNotes.filter(data => data.id !== deleteNote.id); 22 | setDeleteNotes(updatedNotes); 23 | setNotes(prevArr => [deleteNote, ...prevArr]); 24 | } 25 | 26 | const removeNote = (deleteNote) => { 27 | const updatedNotes = deleteNotes.filter(data => data.id !== deleteNote.id); 28 | setDeleteNotes(updatedNotes); 29 | } 30 | 31 | return ( 32 | 33 | 34 | {deleteNote.heading} 35 | {deleteNote.text} 36 | 37 | 38 | removeNote(deleteNote)} 42 | /> 43 | restoreNote(deleteNote)} 46 | /> 47 | 48 | 49 | ) 50 | } 51 | 52 | export default DeleteNote; -------------------------------------------------------------------------------- /src/components/delete/DeleteNotes.jsx: -------------------------------------------------------------------------------- 1 | import { useContext } from 'react'; 2 | 3 | import { Box, Grid } from '@mui/material'; 4 | import { styled } from '@mui/material/styles'; 5 | 6 | import { DataContext } from '../../context/DataProvider'; 7 | 8 | //components 9 | import DeleteNote from './DeleteNote'; 10 | 11 | const DrawerHeader = styled('div')(({ theme }) => ({ 12 | ...theme.mixins.toolbar, 13 | })); 14 | 15 | const DeleteNotes = () => { 16 | 17 | const { deleteNotes } = useContext(DataContext); 18 | 19 | return ( 20 | 21 | 22 | 23 | 24 | { 25 | deleteNotes.map(deleteNote => ( 26 | 27 | 28 | 29 | )) 30 | } 31 | 32 | 33 | 34 | ) 35 | } 36 | 37 | export default DeleteNotes; -------------------------------------------------------------------------------- /src/components/notes/EmptyNotes.jsx: -------------------------------------------------------------------------------- 1 | 2 | 3 | import { LightbulbOutlined as Lightbulb } from '@mui/icons-material'; 4 | import { Typography, Box, styled } from '@mui/material'; 5 | 6 | const Light = styled(Lightbulb)` 7 | font-size: 120px; 8 | color: #F5F5F5; 9 | ` 10 | 11 | const Text = styled(Typography)` 12 | color: #80868b; 13 | font-size: 22px 14 | ` 15 | 16 | const Container = styled(Box)` 17 | display: flex; 18 | flex-direction: column; 19 | align-items: center; 20 | margin-top: 20vh 21 | ` 22 | 23 | const EmptyNotes = () => { 24 | return ( 25 | 26 | 27 | Notes you add appear here 28 | 29 | ) 30 | } 31 | 32 | export default EmptyNotes; -------------------------------------------------------------------------------- /src/components/notes/Form.jsx: -------------------------------------------------------------------------------- 1 | import { useState, useRef, useContext } from 'react'; 2 | 3 | import { Box, TextField, ClickAwayListener } from '@mui/material'; 4 | import { styled } from '@mui/material/styles'; 5 | import { v4 as uuid } from 'uuid'; 6 | 7 | import { DataContext } from '../../context/DataProvider'; 8 | 9 | const Container = styled(Box)` 10 | display: flex; 11 | flex-direction: column; 12 | margin: auto; 13 | box-shadow: 0 1px 2px 0 rgb(60 64 67 / 30%), 0 2px 6px 2px rgb(60 64 67 / 15%); 14 | border-color: #e0e0e0; 15 | width: 600px; 16 | border-radius: 8px; 17 | min-height: 30px; 18 | padding: 10px 15px; 19 | ` 20 | 21 | const note = { 22 | id: '', 23 | heading: '', 24 | text: '' 25 | } 26 | 27 | const Form = () => { 28 | 29 | const [showTextField, setShowTextField] = useState(false); 30 | const [addNote, setAddNote] = useState({ ...note, id: uuid() }); 31 | 32 | const { setNotes } = useContext(DataContext); 33 | 34 | const containerRef = useRef(); 35 | 36 | const handleClickAway = () => { 37 | setShowTextField(false); 38 | containerRef.current.style.minheight = '30px' 39 | setAddNote({ ...note, id: uuid() }); 40 | 41 | if (addNote.heading || addNote.text) { 42 | setNotes(prevArr => [addNote, ...prevArr]) 43 | } 44 | } 45 | 46 | const onTextAreaClick = () => { 47 | setShowTextField(true); 48 | containerRef.current.style.minheight = '70px' 49 | } 50 | 51 | const onTextChange = (e) => { 52 | let changedNote = { ...addNote, [e.target.name]: e.target.value }; 53 | setAddNote(changedNote); 54 | } 55 | 56 | return ( 57 | 58 | 59 | { showTextField && 60 | onTextChange(e)} 66 | name='heading' 67 | value={addNote.heading} 68 | /> 69 | } 70 | onTextChange(e)} 78 | name='text' 79 | value={addNote.text} 80 | /> 81 | 82 | 83 | ) 84 | } 85 | 86 | export default Form; -------------------------------------------------------------------------------- /src/components/notes/Note.jsx: -------------------------------------------------------------------------------- 1 | import { useContext } from 'react'; 2 | 3 | import { Card, CardContent, CardActions, Typography } from '@mui/material'; 4 | import { styled } from '@mui/material/styles'; 5 | import { ArchiveOutlined as Archive, DeleteOutlineOutlined as Delete } from '@mui/icons-material'; 6 | 7 | import { DataContext } from '../../context/DataProvider'; 8 | 9 | const StyledCard = styled(Card)` 10 | border: 1px solid #e0e0e0; 11 | border-radius: 8px; 12 | width: 240px; 13 | margin: 8px; 14 | box-shadow: none; 15 | ` 16 | 17 | const Note = ({ note }) => { 18 | 19 | const { notes, setNotes, setAcrchiveNotes, setDeleteNotes } = useContext(DataContext); 20 | 21 | const archiveNote = (note) => { 22 | const updatedNotes = notes.filter(data => data.id !== note.id); 23 | setNotes(updatedNotes); 24 | setAcrchiveNotes(prevArr => [note, ...prevArr]); 25 | } 26 | 27 | const deleteNote = (note) => { 28 | const updatedNotes = notes.filter(data => data.id !== note.id); 29 | setNotes(updatedNotes); 30 | setDeleteNotes(prevArr => [note, ...prevArr]); 31 | } 32 | 33 | return ( 34 | 35 | 36 | {note.heading} 37 | {note.text} 38 | 39 | 40 | archiveNote(note)} 44 | /> 45 | deleteNote(note)} 48 | /> 49 | 50 | 51 | ) 52 | } 53 | 54 | export default Note; -------------------------------------------------------------------------------- /src/components/notes/Notes.jsx: -------------------------------------------------------------------------------- 1 | import { useContext } from 'react'; 2 | 3 | import { Box, Grid } from '@mui/material'; 4 | import { styled } from '@mui/material/styles'; 5 | import { DragDropContext, Draggable, Droppable } from 'react-beautiful-dnd'; 6 | 7 | import { DataContext } from '../../context/DataProvider'; 8 | import { reorder } from '../../utils/common-utils'; 9 | 10 | //components 11 | import Form from './Form'; 12 | import Note from './Note'; 13 | import EmptyNotes from './EmptyNotes'; 14 | 15 | const DrawerHeader = styled('div')(({ theme }) => ({ 16 | ...theme.mixins.toolbar, 17 | })); 18 | 19 | const Notes = () => { 20 | 21 | const { notes, setNotes } = useContext(DataContext); 22 | 23 | const onDragEnd = (result) => { 24 | if (!result.destination) 25 | return; 26 | 27 | const items = reorder(notes, result.source.index, result.destination.index); 28 | setNotes(items); 29 | } 30 | 31 | return ( 32 | 33 | 34 | 35 |
36 | { notes.length > 0 ? 37 | 38 | 39 | {(provided, snapshot) => ( 40 | 44 | { 45 | notes.map((note, index) => ( 46 | 47 | {(provided, snapshot) => ( 48 | 53 | 54 | 55 | )} 56 | 57 | )) 58 | } 59 | 60 | )} 61 | 62 | 63 | : } 64 | 65 | 66 | ) 67 | } 68 | 69 | export default Notes; -------------------------------------------------------------------------------- /src/context/DataProvider.jsx: -------------------------------------------------------------------------------- 1 | import { createContext, useState } from 'react'; 2 | 3 | export const DataContext = createContext(null); 4 | 5 | const DataProvider = ({ children }) => { 6 | 7 | const [notes, setNotes] = useState([]); 8 | const [archiveNotes, setAcrchiveNotes] = useState([]); 9 | const [deleteNotes, setDeleteNotes] = useState([]); 10 | 11 | return ( 12 | 21 | {children} 22 | 23 | ) 24 | } 25 | 26 | export default DataProvider; -------------------------------------------------------------------------------- /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 | } 9 | 10 | code { 11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', 12 | monospace; 13 | } 14 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import './index.css'; 4 | import App from './App'; 5 | import reportWebVitals from './reportWebVitals'; 6 | 7 | ReactDOM.render( 8 | 9 | 10 | , 11 | document.getElementById('root') 12 | ); 13 | 14 | // If you want to start measuring performance in your app, pass a function 15 | // to log results (for example: reportWebVitals(console.log)) 16 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals 17 | reportWebVitals(); 18 | -------------------------------------------------------------------------------- /src/logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/reportWebVitals.js: -------------------------------------------------------------------------------- 1 | const reportWebVitals = onPerfEntry => { 2 | if (onPerfEntry && onPerfEntry instanceof Function) { 3 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { 4 | getCLS(onPerfEntry); 5 | getFID(onPerfEntry); 6 | getFCP(onPerfEntry); 7 | getLCP(onPerfEntry); 8 | getTTFB(onPerfEntry); 9 | }); 10 | } 11 | }; 12 | 13 | export default reportWebVitals; 14 | -------------------------------------------------------------------------------- /src/setupTests.js: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import '@testing-library/jest-dom'; 6 | -------------------------------------------------------------------------------- /src/utils/common-utils.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | export const reorder = (list, startIndex, endIndex) => { 4 | const result = Array.from(list); 5 | const [removed] = result.splice(startIndex, 1); 6 | result.splice(endIndex, 0, removed); 7 | 8 | return result; 9 | }; --------------------------------------------------------------------------------