@monolith-forensics/monolith-ui 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/.gitattributes ADDED
@@ -0,0 +1,2 @@
1
+ # Auto detect text files and perform LF normalization
2
+ * text=auto
package/package.json ADDED
@@ -0,0 +1,19 @@
1
+ {
2
+ "name": "@monolith-forensics/monolith-ui",
3
+ "version": "1.0.0",
4
+ "description": "UI used for Monolith Apps",
5
+ "main": "index.js",
6
+ "scripts": {
7
+ "test": "echo \"Error: no test specified\" && exit 1"
8
+ },
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "git+https://github.com/Monolith-Forensics/monolith-ui.git"
12
+ },
13
+ "author": "Matt Danner",
14
+ "license": "ISC",
15
+ "bugs": {
16
+ "url": "https://github.com/Monolith-Forensics/monolith-ui/issues"
17
+ },
18
+ "homepage": "https://github.com/Monolith-Forensics/monolith-ui#readme"
19
+ }
@@ -0,0 +1,329 @@
1
+ import React, { Fragment, useEffect, useState } from "react";
2
+ import PropTypes from "prop-types";
3
+ import ArrowLeftIcon from "@mui/icons-material/ArrowLeft";
4
+ import ArrowRightIcon from "@mui/icons-material/ArrowRight";
5
+ import {
6
+ CalendarContainer,
7
+ CalendarHeader,
8
+ CalendarGrid,
9
+ CalendarMonth,
10
+ CalendarDay,
11
+ CalendarDate,
12
+ HighlightedCalendarDate,
13
+ TodayCalendarDate,
14
+ TimeContainer,
15
+ MainContainer,
16
+ TimePickerContainer,
17
+ TimeHourSelect,
18
+ TimeHeader,
19
+ TimeMinuteSelect,
20
+ TimeItem,
21
+ TimeItemActive,
22
+ } from "./CalendarStyles";
23
+ import calendar, {
24
+ isDate,
25
+ isSameDay,
26
+ isSameMonth,
27
+ getDateISO,
28
+ getNextMonth,
29
+ getPreviousMonth,
30
+ WEEK_DAYS,
31
+ CALENDAR_MONTHS,
32
+ HOURS24,
33
+ HOURS12,
34
+ MINUTES,
35
+ } from "./calendarHelpers";
36
+ import moment from "moment";
37
+
38
+ export default function Calendar({
39
+ date = new Date(),
40
+ onDateChanged = () => {},
41
+ hourFormat = "24",
42
+ }) {
43
+ const [dateState, setDateState] = useState({
44
+ current: new Date(),
45
+ month: 0,
46
+ year: 0,
47
+ hours: 0,
48
+ minutes: 0,
49
+ });
50
+ const [today, setToday] = useState(new Date());
51
+ let pressureTimer, pressureTimeout, dayTimeout;
52
+
53
+ useEffect(() => {
54
+ addDateToState(date);
55
+ }, []);
56
+
57
+ const addDateToState = (date) => {
58
+ const isDateObject = isDate(date);
59
+ const _date = isDateObject ? date : new Date();
60
+ setDateState({
61
+ current: isDateObject ? date : null,
62
+ month: +_date.getMonth() + 1,
63
+ year: _date.getFullYear(),
64
+ hours: _date.getHours(),
65
+ minutes: _date.getMinutes(),
66
+ });
67
+ };
68
+
69
+ const getCalendarDates = () => {
70
+ const { current, month, year } = dateState;
71
+ const calendarMonth = month || +current?.getMonth() + 1;
72
+ const calendarYear = year || current?.getFullYear();
73
+ return calendar(calendarMonth, calendarYear);
74
+ };
75
+
76
+ //new start
77
+
78
+ const renderMonthAndYear = () => {
79
+ const { month, year } = dateState;
80
+
81
+ // Resolve the month name from the CALENDAR_MONTHS object map
82
+ const monthname =
83
+ Object.keys(CALENDAR_MONTHS)[Math.max(0, Math.min(month - 1, 11))];
84
+ return (
85
+ <CalendarHeader>
86
+ <ArrowLeftIcon
87
+ onMouseDown={handlePrevious}
88
+ onMouseUp={clearPressureTimer}
89
+ title="Previous Month"
90
+ />
91
+
92
+ <CalendarMonth>
93
+ {monthname} {year}
94
+ </CalendarMonth>
95
+
96
+ <ArrowRightIcon
97
+ onMouseDown={handleNext}
98
+ onMouseUp={clearPressureTimer}
99
+ title="Next Month"
100
+ />
101
+ </CalendarHeader>
102
+ );
103
+ };
104
+ // Render the label for day of the week
105
+ // This method is used as a map callback as seen in render()
106
+ const renderDayLabel = (day, index) => {
107
+ // Resolve the day of the week label from the WEEK_DAYS object map
108
+ const daylabel = WEEK_DAYS[day];
109
+
110
+ return (
111
+ <CalendarDay key={daylabel} index={index}>
112
+ {daylabel}
113
+ </CalendarDay>
114
+ );
115
+ };
116
+ // Render a calendar date as returned from the calendar builder function
117
+ // This method is used as a map callback as seen in render()
118
+ const renderCalendarDate = (date, index) => {
119
+ const { current, month, year } = dateState;
120
+ const _date = new Date(date.join("-"));
121
+ // Check if calendar date is same day as today
122
+ const isToday = isSameDay(_date, today);
123
+
124
+ // Check if calendar date is same day as currently selected date
125
+ const isCurrent = current && isSameDay(_date, current);
126
+
127
+ // Check if calendar date is in the same month as the state month and year
128
+ const inMonth =
129
+ month && year && isSameMonth(_date, new Date([year, month, 1].join("-")));
130
+ // The click handler
131
+ const onClick = gotoDate(_date);
132
+ const props = { index, inMonth, onClick, title: _date.toDateString() };
133
+ // Conditionally render a styled date component
134
+ const DateComponent = isCurrent
135
+ ? HighlightedCalendarDate
136
+ : isToday
137
+ ? TodayCalendarDate
138
+ : CalendarDate;
139
+ return (
140
+ <DateComponent key={getDateISO(_date)} {...props}>
141
+ {_date.getDate()}
142
+ </DateComponent>
143
+ );
144
+ };
145
+
146
+ const renderHours = () => {
147
+ const { hours } = dateState;
148
+ const hoursArray = hourFormat === "24" ? HOURS24 : HOURS12;
149
+ return hoursArray.map((hour, index) => {
150
+ const HourComponent =
151
+ parseInt(hour.value) === hours ? TimeItemActive : TimeItem;
152
+ return (
153
+ <HourComponent
154
+ key={hour.value}
155
+ value={hour.value}
156
+ onClick={(e) => {
157
+ const newTime = moment(dateState.current)
158
+ .hours(hour.value)
159
+ .toDate();
160
+ setDateState({
161
+ ...dateState,
162
+ hours: parseInt(hour.value),
163
+ current: newTime,
164
+ });
165
+ onDateChanged(newTime);
166
+ }}
167
+ >
168
+ {/* {`${hour.label}${
169
+ hourFormat === "24" ? "" : index < 11 ? " AM" : " PM"
170
+ }`} */}
171
+ {hour.label}
172
+ </HourComponent>
173
+ );
174
+ });
175
+ };
176
+
177
+ const renderMinutes = () => {
178
+ const { minutes } = dateState;
179
+ return MINUTES.map((minute) => {
180
+ const MinuteComponent =
181
+ parseInt(minute) === minutes ? TimeItemActive : TimeItem;
182
+ return (
183
+ <MinuteComponent
184
+ key={minute}
185
+ value={minute}
186
+ onClick={(e) => {
187
+ const newTime = moment(dateState.current).minutes(minute).toDate();
188
+ setDateState({
189
+ ...dateState,
190
+ minutes: parseInt(minute),
191
+ current: newTime,
192
+ });
193
+ onDateChanged(newTime);
194
+ }}
195
+ >
196
+ {minute}
197
+ </MinuteComponent>
198
+ );
199
+ });
200
+ };
201
+
202
+ // new 2
203
+ const gotoDate = (date) => (evt) => {
204
+ evt && evt.preventDefault();
205
+ const { current } = dateState;
206
+
207
+ // Set Hours and Minutes
208
+ const newTime = moment(date)
209
+ .hours(dateState.hours)
210
+ .minutes(dateState.minutes)
211
+ .toDate();
212
+
213
+ !(current && isSameDay(newTime, current)) && addDateToState(newTime);
214
+ onDateChanged(newTime);
215
+ };
216
+
217
+ const gotoPreviousMonth = () => {
218
+ const { month, year } = dateState;
219
+ // this.setState(getPreviousMonth(month, year));
220
+ const previousMonth = getPreviousMonth(month, year);
221
+ setDateState({
222
+ month: previousMonth.month,
223
+ year: previousMonth.year,
224
+ current: dateState.current,
225
+ hours: dateState.hours,
226
+ minutes: dateState.minutes,
227
+ });
228
+ };
229
+
230
+ const gotoNextMonth = () => {
231
+ const { month, year } = dateState;
232
+ //this.setState(getNextMonth(month, year));
233
+ const nextMonth = getNextMonth(month, year);
234
+ setDateState({
235
+ month: nextMonth.month,
236
+ year: nextMonth.year,
237
+ current: dateState.current,
238
+ hours: dateState.hours,
239
+ minutes: dateState.minutes,
240
+ });
241
+ };
242
+ const gotoPreviousYear = () => {
243
+ const { year } = dateState;
244
+ setDateState({
245
+ month: dateState.month,
246
+ year: year - 1,
247
+ current: dateState.current,
248
+ hours: dateState.hours,
249
+ minutes: dateState.minutes,
250
+ });
251
+ };
252
+ const gotoNextYear = () => {
253
+ const { year } = dateState;
254
+ setDateState({
255
+ month: dateState.month,
256
+ year: year + 1,
257
+ current: dateState.current,
258
+ hours: dateState.hours,
259
+ minutes: dateState.minutes,
260
+ });
261
+ };
262
+ const handlePressure = (fn) => {
263
+ if (typeof fn === "function") {
264
+ fn();
265
+ pressureTimeout = setTimeout(() => {
266
+ pressureTimer = setInterval(fn, 100);
267
+ }, 500);
268
+ }
269
+ };
270
+ const clearPressureTimer = () => {
271
+ pressureTimer && clearInterval(pressureTimer);
272
+ pressureTimeout && clearTimeout(pressureTimeout);
273
+ };
274
+ const handlePrevious = (evt) => {
275
+ evt && evt.preventDefault();
276
+ const fn = evt.shiftKey ? gotoPreviousYear : gotoPreviousMonth;
277
+ fn();
278
+ // handlePressure(fn);
279
+ };
280
+ const handleNext = (evt) => {
281
+ evt && evt.preventDefault();
282
+ const fn = evt.shiftKey ? gotoNextYear : gotoNextMonth;
283
+ fn();
284
+ // handlePressure(fn);
285
+ };
286
+
287
+ // lifecycle methods
288
+ useEffect(() => {
289
+ const now = new Date();
290
+ const tomorrow = new Date().setHours(0, 0, 0, 0) + 24 * 60 * 60 * 1000;
291
+ const ms = tomorrow - now;
292
+ dayTimeout = setTimeout(() => {
293
+ setToday(new Date());
294
+ clearDayTimeout();
295
+ }, ms);
296
+ return () => {
297
+ clearPressureTimer();
298
+ clearDayTimeout();
299
+ };
300
+ }, []);
301
+
302
+ const clearDayTimeout = () => {
303
+ dayTimeout && clearTimeout(dayTimeout);
304
+ };
305
+
306
+ return (
307
+ <MainContainer>
308
+ <CalendarContainer>
309
+ {renderMonthAndYear()}
310
+ <CalendarGrid>
311
+ <Fragment>{Object.keys(WEEK_DAYS).map(renderDayLabel)}</Fragment>
312
+ <Fragment>{getCalendarDates().map(renderCalendarDate)}</Fragment>
313
+ </CalendarGrid>
314
+ </CalendarContainer>
315
+ <TimeContainer>
316
+ <TimeHeader>Select Time</TimeHeader>
317
+ <TimePickerContainer>
318
+ <TimeHourSelect>{renderHours()}</TimeHourSelect>
319
+ <TimeMinuteSelect>{renderMinutes()}</TimeMinuteSelect>
320
+ </TimePickerContainer>
321
+ </TimeContainer>
322
+ </MainContainer>
323
+ );
324
+ }
325
+
326
+ Calendar.propTypes = {
327
+ date: PropTypes.instanceOf(Date),
328
+ onDateChanged: PropTypes.func,
329
+ };
@@ -0,0 +1,168 @@
1
+ import styled from "@emotion/styled";
2
+
3
+ export const MainContainer = styled.div`
4
+ display: flex;
5
+ `;
6
+ export const CalendarContainer = styled.div`
7
+ font-size: 5px;
8
+ width: 275px;
9
+ border: 1px solid ${(props) => props.theme.palette.divider};
10
+ border-radius: 4px;
11
+ overflow: hidden;
12
+ `;
13
+ export const CalendarHeader = styled.div`
14
+ display: flex;
15
+ align-items: center;
16
+ justify-content: space-between;
17
+ `;
18
+ export const CalendarGrid = styled.div`
19
+ display: grid;
20
+ grid-template: repeat(7, auto) / repeat(7, auto);
21
+ `;
22
+ export const CalendarMonth = styled.div`
23
+ font-weight: 500;
24
+ font-size: 12px;
25
+ color: ${(props) => props.theme.palette.text.primary};
26
+ text-align: center;
27
+ padding: 0.5em 0.25em;
28
+ word-spacing: 5px;
29
+ user-select: none;
30
+ `;
31
+ export const CalendarCell = styled.div`
32
+ text-align: center;
33
+ align-self: center;
34
+ letter-spacing: 0.1rem;
35
+ padding: 10px;
36
+ user-select: none;
37
+ border-radius: 5px;
38
+ border: 1px solid transparent;
39
+ grid-column: ${(props) => (props.index % 7) + 1} / span 1;
40
+ `;
41
+ export const CalendarDay = styled(CalendarCell)`
42
+ font-weight: bold;
43
+ font-size: 10px;
44
+ border-radius: 0px;
45
+ color: ${(props) => props.theme.palette.text.secondary};
46
+ // border-top: 1px solid ${(props) => props.theme.palette.divider};
47
+ // border-bottom: 1px solid ${(props) => props.theme.palette.divider};
48
+ border-right: ${(props) =>
49
+ (props.index % 7) + 1 === 7
50
+ ? `none`
51
+ : `2px solid ${(props) => props.theme.palette.divider}`};
52
+ `;
53
+ export const CalendarDate = styled(CalendarCell)`
54
+ font-weight: ${(props) => (props.inMonth ? 500 : 300)};
55
+ font-size: 10px;
56
+ cursor: pointer;
57
+ border-bottom: ${(props) =>
58
+ (props.index + 1) / 7 <= 5
59
+ ? `1px solid ${(props) => props.theme.palette.divider}`
60
+ : `1px solid transparent`};
61
+ border-right: ${(props) =>
62
+ (props.index % 7) + 1 === 7
63
+ ? `1px solid transparent`
64
+ : `1px solid ${(props) => props.theme.palette.divider}`};
65
+ border-left: 1px solid transparent;
66
+ border-top: 1px solid transparent;
67
+ color: ${(props) =>
68
+ props.inMonth
69
+ ? props.theme.palette.text.primary
70
+ : props.theme.palette.text.secondary};
71
+ grid-row: ${(props) => Math.floor(props.index / 7) + 2} / span 1;
72
+ transition: all 0.2s ease-out;
73
+ border-radius: 5px;
74
+ :hover {
75
+ // color: ${(props) => props.theme.palette.primary.main};
76
+ background: ${(props) => props.theme.palette.action.hover};
77
+ }
78
+ `;
79
+ export const HighlightedCalendarDate = styled(CalendarDate)`
80
+ // color: ${(props) => props.theme.palette.primary.main} !important;
81
+ color: white !important;
82
+ background: ${(props) => props.theme.palette.primary.main} !important;
83
+ border: 1px solid ${(props) => props.theme.palette.primary.main} !important;
84
+ position: relative;
85
+ ::before {
86
+ content: "";
87
+ position: absolute;
88
+ top: -1px;
89
+ left: -1px;
90
+ width: calc(100% + 2px);
91
+ height: calc(100% + 2px);
92
+ // border: 2px solid #06c;
93
+ }
94
+ `;
95
+ export const TodayCalendarDate = styled(HighlightedCalendarDate)`
96
+ color: ${(props) => props.theme.palette.text.primary} !important;
97
+ background: transparent !important;
98
+ ::after {
99
+ content: "";
100
+ position: absolute;
101
+ right: 0;
102
+ bottom: 0;
103
+ border-bottom: 0.75em solid #06c;
104
+ border-left: 0.75em solid transparent;
105
+ border-top: 0.75em solid transparent;
106
+ }
107
+ :hover {
108
+ color: ${(props) => props.theme.palette.text.primary} !important;
109
+ background: ${(props) => props.theme.palette.action.hover} !important;
110
+ }
111
+ `;
112
+ export const TimeContainer = styled.div`
113
+ font-size: 12px;
114
+ width: 150px;
115
+ height: 100%;
116
+ border: 1px solid ${(props) => props.theme.palette.divider};
117
+ border-left: none;
118
+ border-radius: 4px;
119
+ overflow: hidden;
120
+ `;
121
+ export const TimePickerContainer = styled.div`
122
+ display: flex;
123
+ height: 100%;
124
+ `;
125
+ export const TimeHeader = styled.div`
126
+ font-weight: 500;
127
+ font-size: 12px;
128
+ color: ${(props) => props.theme.palette.text.primary};
129
+ border-bottom: 1px solid ${(props) => props.theme.palette.divider};
130
+ text-align: center;
131
+ padding: 5px;
132
+ line-height: 2.3;
133
+ height: 40px;
134
+ // word-spacing: 5px;
135
+ user-select: none;
136
+ `;
137
+ export const TimeHourSelect = styled.div`
138
+ border-right: 1px solid ${(props) => props.theme.palette.divider};
139
+ width: 50%;
140
+ height: 250px;
141
+ overflow-y: auto;
142
+ `;
143
+ export const TimeMinuteSelect = styled.div`
144
+ width: 50%;
145
+ height: 250px;
146
+ overflow-y: auto;
147
+ `;
148
+ export const TimeItem = styled.div`
149
+ font-weight: 500;
150
+ font-size: 10px;
151
+ color: ${(props) => props.theme.palette.text.primary};
152
+ text-align: center;
153
+ padding: 5px;
154
+ // word-spacing: 5px;
155
+ user-select: none;
156
+ cursor: pointer;
157
+ transition: all 0.2s ease-out;
158
+ :hover {
159
+ background: ${(props) => props.theme.palette.action.hover};
160
+ }
161
+ `;
162
+ export const TimeItemActive = styled(TimeItem)`
163
+ background: ${(props) => props.theme.palette.primary.main};
164
+ color: white;
165
+ &:hover {
166
+ background: ${(props) => props.theme.palette.primary.main};
167
+ }
168
+ `;
@@ -0,0 +1,194 @@
1
+ // (int) The current year
2
+ export const THIS_YEAR = +new Date().getFullYear();
3
+
4
+ // (int) The current month starting from 1 - 12
5
+ // 1 => January, 12 => December
6
+ export const THIS_MONTH = +new Date().getMonth() + 1;
7
+ // Week days names and shortnames
8
+
9
+ export const HOURS24 = [...new Array(24)].map((n, index) => {
10
+ return {
11
+ value: index,
12
+ label: index < 10 ? `0${index}` : `${index}`,
13
+ };
14
+ // return index < 10 ? `0${index}` : `${index}`;
15
+ });
16
+
17
+ export const HOURS12 = [
18
+ ...[...new Array(12)].map((n, index) => {
19
+ return {
20
+ value: index,
21
+ label: index < 10 ? `0${index} AM` : `${index} AM`,
22
+ };
23
+ }),
24
+ ...[...new Array(12)].map((n, index) => {
25
+ return {
26
+ value: index + 12,
27
+ label: index < 10 ? `0${index} PM` : `${index} PM`,
28
+ };
29
+ }),
30
+ ];
31
+
32
+ export const MINUTES = new Array(60).fill(0).map((n, index) => {
33
+ return index < 10 ? `0${index}` : index;
34
+ });
35
+
36
+ export const WEEK_DAYS = {
37
+ Sunday: "Su",
38
+ Monday: "Mo",
39
+ Tuesday: "Tu",
40
+ Wednesday: "We",
41
+ Thursday: "Th",
42
+ Friday: "Fr",
43
+ Saturday: "Sa",
44
+ };
45
+
46
+ // Calendar months names and short names
47
+ export const CALENDAR_MONTHS = {
48
+ January: "Jan",
49
+ February: "Feb",
50
+ March: "Mar",
51
+ April: "Apr",
52
+ May: "May",
53
+ June: "Jun",
54
+ July: "Jul",
55
+ August: "Aug",
56
+ September: "Sep",
57
+ October: "Oct",
58
+ November: "Nov",
59
+ December: "Dec",
60
+ };
61
+
62
+ // Weeks displayed on calendar
63
+ export const CALENDAR_WEEKS = 6;
64
+
65
+ // Pads a string value with leading zeroes(0) until length is reached
66
+ // For example: zeroPad(5, 2) => "05"
67
+ export const zeroPad = (value, length) => {
68
+ return `${value}`.padStart(length, "0");
69
+ };
70
+
71
+ // (int) Number days in a month for a given year from 28 - 31
72
+ export const getMonthDays = (month = THIS_MONTH, year = THIS_YEAR) => {
73
+ const months30 = [4, 6, 9, 11];
74
+ const leapYear = year % 4 === 0;
75
+ return month === 2
76
+ ? leapYear
77
+ ? 29
78
+ : 28
79
+ : months30.includes(month)
80
+ ? 30
81
+ : 31;
82
+ };
83
+
84
+ // (int) First day of the month for a given year from 1 - 7
85
+ // 1 => Sunday, 7 => Saturday
86
+ export const getMonthFirstDay = (month = THIS_MONTH, year = THIS_YEAR) => {
87
+ return +new Date(`${year}-${zeroPad(month, 2)}-01`).getDay() + 1;
88
+ };
89
+
90
+ // (bool) Checks if a value is a date - this is just a simple check
91
+ export const isDate = (date) => {
92
+ const isDate = Object.prototype.toString.call(date) === "[object Date]";
93
+ const isValidDate = date && !Number.isNaN(date.valueOf());
94
+
95
+ return isDate && isValidDate;
96
+ };
97
+
98
+ // (bool) Checks if two date values are of the same month and year
99
+ export const isSameMonth = (date, basedate = new Date()) => {
100
+ if (!(isDate(date) && isDate(basedate))) return false;
101
+ const basedateMonth = +basedate.getMonth() + 1;
102
+ const basedateYear = basedate.getFullYear();
103
+ const dateMonth = +date.getMonth() + 1;
104
+ const dateYear = date.getFullYear();
105
+ return +basedateMonth === +dateMonth && +basedateYear === +dateYear;
106
+ };
107
+ // (bool) Checks if two date values are the same day
108
+ export const isSameDay = (date, basedate = new Date()) => {
109
+ if (!(isDate(date) && isDate(basedate))) return false;
110
+ const basedateDate = basedate.getDate();
111
+ const basedateMonth = +basedate.getMonth() + 1;
112
+ const basedateYear = basedate.getFullYear();
113
+ const dateDate = date.getDate();
114
+ const dateMonth = +date.getMonth() + 1;
115
+ const dateYear = date.getFullYear();
116
+ return (
117
+ +basedateDate === +dateDate &&
118
+ +basedateMonth === +dateMonth &&
119
+ +basedateYear === +dateYear
120
+ );
121
+ };
122
+ // (string) Formats the given date as YYYY-MM-DD
123
+ // Months and Days are zero padded
124
+ export const getDateISO = (date = new Date()) => {
125
+ if (!isDate(date)) return null;
126
+ return [
127
+ date.getFullYear(),
128
+ zeroPad(+date.getMonth() + 1, 2),
129
+ zeroPad(+date.getDate(), 2),
130
+ ].join("-");
131
+ };
132
+ // ({month, year}) Gets the month and year before the given month and year
133
+ // For example: getPreviousMonth(1, 2000) => {month: 12, year: 1999}
134
+ // while: getPreviousMonth(12, 2000) => {month: 11, year: 2000}
135
+ export const getPreviousMonth = (month, year) => {
136
+ const prevMonth = month > 1 ? month - 1 : 12;
137
+ const prevMonthYear = month > 1 ? year : year - 1;
138
+ return { month: prevMonth, year: prevMonthYear };
139
+ };
140
+ // ({month, year}) Gets the month and year after the given month and year
141
+ // For example: getNextMonth(1, 2000) => {month: 2, year: 2000}
142
+ // while: getNextMonth(12, 2000) => {month: 1, year: 2001}
143
+ export const getNextMonth = (month, year) => {
144
+ const nextMonth = month < 12 ? month + 1 : 1;
145
+ const nextMonthYear = month < 12 ? year : year + 1;
146
+ return { month: nextMonth, year: nextMonthYear };
147
+ };
148
+
149
+ // Calendar builder for a month in the specified year
150
+ // Returns an array of the calendar dates.
151
+ // Each calendar date is represented as an array => [YYYY, MM, DD]
152
+ const calendarBuilder = (month = THIS_MONTH, year = THIS_YEAR) => {
153
+ // Get number of days in the month and the month's first day
154
+
155
+ const monthDays = getMonthDays(month, year);
156
+ const monthFirstDay = getMonthFirstDay(month, year);
157
+ // Get number of days to be displayed from previous and next months
158
+ // These ensure a total of 42 days (6 weeks) displayed on the calendar
159
+
160
+ const daysFromPrevMonth = monthFirstDay - 1;
161
+ const daysFromNextMonth =
162
+ CALENDAR_WEEKS * 7 - (daysFromPrevMonth + monthDays);
163
+ // Get the previous and next months and years
164
+
165
+ const { month: prevMonth, year: prevMonthYear } = getPreviousMonth(
166
+ month,
167
+ year
168
+ );
169
+ const { month: nextMonth, year: nextMonthYear } = getNextMonth(month, year);
170
+ // Get number of days in previous month
171
+ const prevMonthDays = getMonthDays(prevMonth, prevMonthYear);
172
+ // Builds dates to be displayed from previous month
173
+
174
+ const prevMonthDates = [...new Array(daysFromPrevMonth)].map((n, index) => {
175
+ const day = index + 1 + (prevMonthDays - daysFromPrevMonth);
176
+ return [prevMonthYear, zeroPad(prevMonth, 2), zeroPad(day, 2)];
177
+ });
178
+ // Builds dates to be displayed from current month
179
+
180
+ const thisMonthDates = [...new Array(monthDays)].map((n, index) => {
181
+ const day = index + 1;
182
+ return [year, zeroPad(month, 2), zeroPad(day, 2)];
183
+ });
184
+ // Builds dates to be displayed from next month
185
+
186
+ const nextMonthDates = [...new Array(daysFromNextMonth)].map((n, index) => {
187
+ const day = index + 1;
188
+ return [nextMonthYear, zeroPad(nextMonth, 2), zeroPad(day, 2)];
189
+ });
190
+ // Combines all dates from previous, current and next months
191
+ return [...prevMonthDates, ...thisMonthDates, ...nextMonthDates];
192
+ };
193
+
194
+ export default calendarBuilder;