├── .gitignore ├── src ├── App │ ├── index.js │ └── App.jsx ├── Day │ ├── index.js │ └── Day.jsx ├── NewEventModal │ ├── index.js │ └── NewEventModal.jsx ├── CalendarHeader │ ├── index.js │ └── CalendarHeader.jsx ├── DeleteEventModal │ ├── index.js │ └── DeleteEventModal.jsx ├── index.jsx └── hooks │ └── useDate.js ├── index.html ├── package.json └── style.css /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ -------------------------------------------------------------------------------- /src/App/index.js: -------------------------------------------------------------------------------- 1 | export * from './App'; 2 | -------------------------------------------------------------------------------- /src/Day/index.js: -------------------------------------------------------------------------------- 1 | export * from './Day'; 2 | -------------------------------------------------------------------------------- /src/NewEventModal/index.js: -------------------------------------------------------------------------------- 1 | export * from './NewEventModal'; 2 | -------------------------------------------------------------------------------- /src/CalendarHeader/index.js: -------------------------------------------------------------------------------- 1 | export * from './CalendarHeader'; 2 | -------------------------------------------------------------------------------- /src/DeleteEventModal/index.js: -------------------------------------------------------------------------------- 1 | export * from './DeleteEventModal'; 2 | -------------------------------------------------------------------------------- /src/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import { App } from './App'; 4 | 5 | ReactDOM.render(, document.getElementById('root')); 6 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Calendar App Vanilla JS 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/CalendarHeader/CalendarHeader.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export const CalendarHeader = ({ onNext, onBack, dateDisplay }) => { 4 | return( 5 | 12 | ); 13 | }; 14 | -------------------------------------------------------------------------------- /src/Day/Day.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export const Day = ({ day, onClick }) => { 4 | const className = `day ${day.value === 'padding' ? 'padding' : ''} ${day.isCurrentDay ? 'currentDay' : ''}`; 5 | return ( 6 |
7 | {day.value === 'padding' ? '' : day.value} 8 | 9 | {day.event &&
{day.event.title}
} 10 |
11 | ); 12 | }; 13 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-calendar", 3 | "version": "1.0.0", 4 | "description": "Converting a vanilla JS app to React", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "snowpack dev", 8 | "test": "echo \"Error: no test specified\" && exit 1" 9 | }, 10 | "author": "Zackery Wilson", 11 | "license": "MIT", 12 | "dependencies": { 13 | "react": "^17.0.1", 14 | "react-dom": "^17.0.1", 15 | "snowpack": "^3.0.11" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/DeleteEventModal/DeleteEventModal.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export const DeleteEventModal = ({ onDelete, eventText, onClose }) => { 4 | return( 5 | <> 6 |
7 |

Event

8 | 9 |

{eventText}

10 | 11 | 12 | 13 |
14 | 15 |
16 | 17 | ); 18 | }; 19 | -------------------------------------------------------------------------------- /src/NewEventModal/NewEventModal.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | 3 | export const NewEventModal = ({ onSave, onClose }) => { 4 | const [title, setTitle] = useState(''); 5 | const [error, setError] = useState(false); 6 | 7 | return( 8 | <> 9 |
10 |

New Event

11 | 12 | setTitle(e.target.value)} 16 | id="eventTitleInput" 17 | placeholder="Event Title" 18 | /> 19 | 20 | 30 | 31 | 32 | 35 |
36 | 37 |
38 | 39 | ); 40 | }; 41 | -------------------------------------------------------------------------------- /src/hooks/useDate.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react'; 2 | 3 | export const useDate = (events, nav) => { 4 | const [dateDisplay, setDateDisplay] = useState(''); 5 | const [days, setDays] = useState([]); 6 | 7 | const eventForDate = date => events.find(e => e.date === date); 8 | 9 | useEffect(() => { 10 | const weekdays = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']; 11 | const dt = new Date(); 12 | 13 | if (nav !== 0) { 14 | dt.setMonth(new Date().getMonth() + nav); 15 | } 16 | 17 | const day = dt.getDate(); 18 | const month = dt.getMonth(); 19 | const year = dt.getFullYear(); 20 | 21 | const firstDayOfMonth = new Date(year, month, 1); 22 | const daysInMonth = new Date(year, month + 1, 0).getDate(); 23 | const dateString = firstDayOfMonth.toLocaleDateString('en-us', { 24 | weekday: 'long', 25 | year: 'numeric', 26 | month: 'numeric', 27 | day: 'numeric', 28 | }); 29 | 30 | setDateDisplay(`${dt.toLocaleDateString('en-us', { month: 'long' })} ${year}`); 31 | const paddingDays = weekdays.indexOf(dateString.split(', ')[0]); 32 | 33 | const daysArr = []; 34 | 35 | for (let i = 1; i <= paddingDays + daysInMonth; i++) { 36 | const dayString = `${month + 1}/${i - paddingDays}/${year}`; 37 | 38 | if (i > paddingDays) { 39 | daysArr.push({ 40 | value: i - paddingDays, 41 | event: eventForDate(dayString), 42 | isCurrentDay: i - paddingDays === day && nav === 0, 43 | date: dayString, 44 | }); 45 | } else { 46 | daysArr.push({ 47 | value: 'padding', 48 | event: null, 49 | isCurrentDay: false, 50 | date: '', 51 | }); 52 | } 53 | } 54 | 55 | setDays(daysArr); 56 | }, [events, nav]); 57 | 58 | return { 59 | days, 60 | dateDisplay, 61 | }; 62 | }; 63 | -------------------------------------------------------------------------------- /src/App/App.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react'; 2 | import { CalendarHeader } from '../CalendarHeader'; 3 | import { Day } from '../Day'; 4 | import { NewEventModal } from '../NewEventModal'; 5 | import { DeleteEventModal } from '../DeleteEventModal'; 6 | import { useDate } from '../hooks/useDate'; 7 | 8 | export const App = () => { 9 | const [nav, setNav] = useState(0); 10 | const [clicked, setClicked] = useState(); 11 | const [events, setEvents] = useState( 12 | localStorage.getItem('events') ? 13 | JSON.parse(localStorage.getItem('events')) : 14 | [] 15 | ); 16 | 17 | const eventForDate = date => events.find(e => e.date === date); 18 | 19 | useEffect(() => { 20 | localStorage.setItem('events', JSON.stringify(events)); 21 | }, [events]); 22 | 23 | const { days, dateDisplay } = useDate(events, nav); 24 | 25 | return( 26 | <> 27 |
28 | setNav(nav + 1)} 31 | onBack={() => setNav(nav - 1)} 32 | /> 33 | 34 |
35 |
Sunday
36 |
Monday
37 |
Tuesday
38 |
Wednesday
39 |
Thursday
40 |
Friday
41 |
Saturday
42 |
43 | 44 |
45 | {days.map((d, index) => ( 46 | { 50 | if (d.value !== 'padding') { 51 | setClicked(d.date); 52 | } 53 | }} 54 | /> 55 | ))} 56 |
57 |
58 | 59 | { 60 | clicked && !eventForDate(clicked) && 61 | setClicked(null)} 63 | onSave={title => { 64 | setEvents([ ...events, { title, date: clicked }]); 65 | setClicked(null); 66 | }} 67 | /> 68 | } 69 | 70 | { 71 | clicked && eventForDate(clicked) && 72 | setClicked(null)} 75 | onDelete={() => { 76 | setEvents(events.filter(e => e.date !== clicked)); 77 | setClicked(null); 78 | }} 79 | /> 80 | } 81 | 82 | ); 83 | }; 84 | -------------------------------------------------------------------------------- /style.css: -------------------------------------------------------------------------------- 1 | body { 2 | display: flex; 3 | margin-top: 50px; 4 | justify-content: center; 5 | background-color: #FFFCFF; 6 | } 7 | button { 8 | margin-right: 2.5px; 9 | margin-left: 2.5px; 10 | width: 75px; 11 | cursor: pointer; 12 | box-shadow: 0px 0px 2px gray; 13 | border: none; 14 | outline: none; 15 | padding: 5px; 16 | border-radius: 5px; 17 | color: white; 18 | } 19 | 20 | #header { 21 | padding: 10px; 22 | color: #d36c6c; 23 | font-size: 26px; 24 | font-family: sans-serif; 25 | display: flex; 26 | justify-content: space-between; 27 | } 28 | #header button { 29 | background-color:#92a1d1; 30 | } 31 | #container { 32 | width: 770px; 33 | } 34 | #weekdays { 35 | width: 100%; 36 | display: flex; 37 | color: #247BA0; 38 | } 39 | #weekdays div { 40 | width: 100px; 41 | padding: 10px; 42 | } 43 | #calendar { 44 | width: 100%; 45 | margin: auto; 46 | display: flex; 47 | flex-wrap: wrap; 48 | } 49 | .day { 50 | width: 100px; 51 | padding: 10px; 52 | height: 100px; 53 | cursor: pointer; 54 | box-sizing: border-box; 55 | background-color: white; 56 | margin: 5px; 57 | box-shadow: 0px 0px 3px #CBD4C2; 58 | display: flex; 59 | flex-direction: column; 60 | justify-content: space-between; 61 | } 62 | .day:hover { 63 | background-color: #e8faed; 64 | } 65 | 66 | .day + .currentDay { 67 | background-color:#e8f4fa; 68 | } 69 | .event { 70 | font-size: 10px; 71 | padding: 3px; 72 | background-color: #58bae4; 73 | color: white; 74 | border-radius: 5px; 75 | max-height: 55px; 76 | overflow: hidden; 77 | } 78 | .padding { 79 | cursor: default !important; 80 | background-color: #FFFCFF !important; 81 | box-shadow: none !important; 82 | } 83 | #newEventModal, #deleteEventModal { 84 | z-index: 20; 85 | padding: 25px; 86 | background-color: #e8f4fa; 87 | box-shadow: 0px 0px 3px black; 88 | border-radius: 5px; 89 | width: 350px; 90 | top: 100px; 91 | left: calc(50% - 175px); 92 | position: absolute; 93 | font-family: sans-serif; 94 | } 95 | #eventTitleInput { 96 | padding: 10px; 97 | width: 100%; 98 | box-sizing: border-box; 99 | margin-bottom: 25px; 100 | border-radius: 3px; 101 | outline: none; 102 | border: none; 103 | box-shadow: 0px 0px 3px gray; 104 | } 105 | #eventTitleInput.error { 106 | border: 2px solid red; 107 | } 108 | #cancelButton, #deleteButton { 109 | background-color: #d36c6c; 110 | } 111 | #saveButton, #closeButton { 112 | background-color: #92a1d1; 113 | } 114 | #eventText { 115 | font-size: 14px; 116 | } 117 | #modalBackDrop { 118 | top: 0px; 119 | left: 0px; 120 | z-index: 10; 121 | width: 100vw; 122 | height: 100vh; 123 | position: absolute; 124 | background-color: rgba(0,0,0,0.8); 125 | } 126 | --------------------------------------------------------------------------------