├── .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 |
--------------------------------------------------------------------------------