Skip to content

Commit 41264e2

Browse files
authored
Refactor to make the Monthly Day component composable (#4)
* Refactor to make the Monthly Day component composable * Fix a few more missing pieces * remove key * Export handleOmittedDays * bump version
1 parent 67d4be5 commit 41264e2

9 files changed

Lines changed: 223 additions & 176 deletions

File tree

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,5 @@
33
node_modules
44
.cache
55
dist
6-
.parcel-cache
6+
.parcel-cache
7+
coverage

README.md

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,10 @@ npm install @zach.codes/react-calendar date-fns
2121
[See this code in action](https://calendar.zach.codes/?path=/story/monthly-calendar--basic-monthly-calendar)
2222

2323
```tsx
24-
import {format, subHours, startOfMonth} from 'date-fns'
24+
import { format, subHours, startOfMonth } from 'date-fns';
2525
import {
2626
MonthlyBody,
27+
MonthlyDay,
2728
MonthlyCalendar,
2829
MonthlyNav,
2930
DefaultMonthlyEventItem,
@@ -46,17 +47,20 @@ export const MyMonthlyCalendar = () => {
4647
{ title: 'Call John', date: subHours(new Date(), 1) },
4748
{ title: 'Meeting with Bob', date: new Date() },
4849
]}
49-
renderDay={data =>
50-
data.map((item, index) => (
51-
<DefaultMonthlyEventItem
52-
key={index}
53-
title={item.title}
54-
// Format the date here to be in the format you prefer
55-
date={format(item.date, 'k:mm')}
56-
/>
57-
))
58-
}
59-
/>
50+
>
51+
<MonthlyDay<EventType>
52+
renderDay={data =>
53+
data.map((item, index) => (
54+
<DefaultMonthlyEventItem
55+
key={index}
56+
title={item.title}
57+
// Format the date here to be in the format you prefer
58+
date={format(item.date, 'k:mm')}
59+
/>
60+
))
61+
}
62+
/>
63+
</MonthlyBody>
6064
</MonthlyCalendar>
6165
);
6266
};
@@ -105,6 +109,9 @@ No props at this time
105109

106110
- `omitDays` lets you hide certain days from the calendar, for instance, hide Saturday and Sunday. Days are represented as 0-6, as seen in the [date-fns](https://date-fns.org/v2.19.0/docs/getDay#returns) documentation. Hiding Monday would be `omitDays={[1]}` Hiding the weekend would be `omitDays={[0, 6]}`
107111
- `events` this is an array of events, the only thing required inside each object is a `date` field with a Date object representing the exact time of the event
112+
113+
`MonthlyDay`
114+
108115
- `renderDay` callback function that is passed an array of events for each day displayed, letting you render the events for the day
109116

110117
## WeeklyCalendar

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"version": "0.0.3",
2+
"version": "0.1.0",
33
"license": "MIT",
44
"name": "@zach.codes/react-calendar",
55
"main": "dist/index.js",

src/Monthly/MonthlyBody.tsx

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
import React, { ReactNode, useContext } from 'react';
2+
import { useMonthlyCalendar } from './MonthlyCalendar';
3+
import { daysInWeek } from '../shared';
4+
import { format, getDay, isSameDay } from 'date-fns';
5+
6+
const MonthlyBodyContext = React.createContext({} as any);
7+
type BodyState<DayData> = {
8+
day: Date;
9+
events: DayData[];
10+
};
11+
12+
export function useMonthlyBody<DayData>() {
13+
return useContext<BodyState<DayData>>(MonthlyBodyContext);
14+
}
15+
16+
type OmittedDaysProps = {
17+
days: Date[];
18+
omitDays?: number[];
19+
};
20+
21+
export const handleOmittedDays = ({ days, omitDays }: OmittedDaysProps) => {
22+
let headings = daysInWeek;
23+
let daysToRender = days;
24+
25+
//omit the headings and days of the week that were passed in
26+
if (omitDays) {
27+
headings = daysInWeek.filter(day => !omitDays.includes(day.day));
28+
daysToRender = days.filter(day => !omitDays.includes(getDay(day)));
29+
}
30+
31+
// omit the padding if an omitted day was before the start of the month
32+
let firstDayOfMonth = getDay(daysToRender[0]) as number;
33+
if (omitDays) {
34+
let subtractOmittedDays = omitDays.filter(day => day < firstDayOfMonth)
35+
.length;
36+
firstDayOfMonth = firstDayOfMonth - subtractOmittedDays;
37+
}
38+
let padding = new Array(firstDayOfMonth).fill(0);
39+
40+
return { headings, daysToRender, padding };
41+
};
42+
43+
//to prevent these from being purged in production, we make a lookup object
44+
const headingClasses = {
45+
l3: 'lg:grid-cols-3',
46+
l4: 'lg:grid-cols-4',
47+
l5: 'lg:grid-cols-5',
48+
l6: 'lg:grid-cols-6',
49+
l7: 'lg:grid-cols-7',
50+
};
51+
52+
type MonthlyBodyProps<DayData> = {
53+
/*
54+
skip days, an array of days, starts at sunday (0), saturday is 6
55+
ex: [0,6] would remove sunday and saturday from rendering
56+
*/
57+
omitDays?: number[];
58+
events: (DayData & { date: Date })[];
59+
children: ReactNode;
60+
};
61+
62+
export function MonthlyBody<DayData>({
63+
omitDays,
64+
events,
65+
children,
66+
}: MonthlyBodyProps<DayData>) {
67+
let { days } = useMonthlyCalendar();
68+
let { headings, daysToRender, padding } = handleOmittedDays({
69+
days,
70+
omitDays,
71+
});
72+
73+
let headingClassName = 'border-b-2 p-2 border-r-2 lg:block hidden';
74+
return (
75+
<div className="bg-white border-l-2 border-t-2">
76+
<div
77+
className={`grid grid-cols-1 sm:grid-cols-2 md:grid-cols-4 ${
78+
//@ts-ignore
79+
headingClasses[`l${headings.length}`]
80+
}`}
81+
>
82+
{headings.map(day => (
83+
<div
84+
key={day.day}
85+
className={headingClassName}
86+
aria-label="Day of Week"
87+
>
88+
{day.label}
89+
</div>
90+
))}
91+
{padding.map((_, index) => (
92+
<div
93+
key={index}
94+
className={headingClassName}
95+
aria-label="Empty Day"
96+
/>
97+
))}
98+
{daysToRender.map(day => (
99+
<MonthlyBodyContext.Provider
100+
key={day.toISOString()}
101+
value={{
102+
day,
103+
events: events.filter(data => isSameDay(data.date, day)),
104+
}}
105+
>
106+
{children}
107+
</MonthlyBodyContext.Provider>
108+
))}
109+
</div>
110+
</div>
111+
);
112+
}
113+
114+
type MonthlyDayProps<DayData> = {
115+
renderDay: (events: DayData[]) => ReactNode;
116+
};
117+
export function MonthlyDay<DayData>({ renderDay }: MonthlyDayProps<DayData>) {
118+
let { day, events } = useMonthlyBody<DayData>();
119+
let dayNumber = format(day, 'd');
120+
121+
return (
122+
<div
123+
aria-label={`Events for day ${dayNumber}`}
124+
className="h-48 p-2 border-b-2 border-r-2"
125+
>
126+
<div className="flex justify-between">
127+
<div className="font-bold">{dayNumber}</div>
128+
<div className="lg:hidden block">{format(day, 'EEEE')}</div>
129+
</div>
130+
<ul className="divide-gray-200 divide-y overflow-hidden max-h-36 overflow-y-auto">
131+
{renderDay(events)}
132+
</ul>
133+
</div>
134+
);
135+
}

src/Monthly/MonthlyCalendar.tsx

Lines changed: 0 additions & 111 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,8 @@ import {
66
subMonths,
77
addMonths,
88
getYear,
9-
getDay,
10-
isSameDay,
119
} from 'date-fns';
1210
import React, { ReactNode, useContext } from 'react';
13-
import { daysInWeek } from '../shared';
1411

1512
type CalendarState = {
1613
days: Date[];
@@ -80,111 +77,3 @@ export const MonthlyNav = () => {
8077
</div>
8178
);
8279
};
83-
type ExtraMonthData = { date: Date };
84-
85-
type MonthlyBodyProps<MonthData> = {
86-
/*
87-
skip days, an array of days, starts at sunday (0), saturday is 6
88-
ex: [0,6] would remove sunday and saturday from rendering
89-
*/
90-
omitDays?: number[];
91-
events: (MonthData & ExtraMonthData)[];
92-
93-
renderDay: (data: (MonthData & ExtraMonthData)[]) => ReactNode;
94-
};
95-
96-
type OmittedDaysProps = {
97-
days: Date[];
98-
omitDays?: number[];
99-
};
100-
101-
const handleOmittedDays = ({ days, omitDays }: OmittedDaysProps) => {
102-
let headings = daysInWeek;
103-
let daysToRender = days;
104-
105-
//omit the headings and days of the week that were passed in
106-
if (omitDays) {
107-
headings = daysInWeek.filter(day => !omitDays.includes(day.day));
108-
daysToRender = days.filter(day => !omitDays.includes(getDay(day)));
109-
}
110-
111-
// omit the padding if an omitted day was before the start of the month
112-
let firstDayOfMonth = getDay(daysToRender[0]) as number;
113-
if (omitDays) {
114-
let subtractOmittedDays = omitDays.filter(day => day < firstDayOfMonth)
115-
.length;
116-
firstDayOfMonth = firstDayOfMonth - subtractOmittedDays;
117-
}
118-
let padding = new Array(firstDayOfMonth).fill(0);
119-
120-
return { headings, daysToRender, padding };
121-
};
122-
123-
//to prevent these from being purged in production, we make a lookup object
124-
const headingClasses = {
125-
l3: 'lg:grid-cols-3',
126-
l4: 'lg:grid-cols-4',
127-
l5: 'lg:grid-cols-5',
128-
l6: 'lg:grid-cols-6',
129-
l7: 'lg:grid-cols-7',
130-
};
131-
132-
export function MonthlyBody<MonthData>({
133-
omitDays,
134-
events,
135-
renderDay,
136-
}: MonthlyBodyProps<MonthData>) {
137-
let { days } = useMonthlyCalendar();
138-
let { headings, daysToRender, padding } = handleOmittedDays({
139-
days,
140-
omitDays,
141-
});
142-
143-
let headingClassName = 'border-b-2 p-2 border-r-2 lg:block hidden';
144-
return (
145-
<div className="bg-white border-l-2 border-t-2">
146-
<div
147-
className={`grid grid-cols-1 sm:grid-cols-2 md:grid-cols-4 ${
148-
//@ts-ignore
149-
headingClasses[`l${headings.length}`]
150-
}`}
151-
>
152-
{headings.map(day => (
153-
<div
154-
key={day.day}
155-
className={headingClassName}
156-
aria-label="Day of Week"
157-
>
158-
{day.label}
159-
</div>
160-
))}
161-
{padding.map((_, index) => (
162-
<div
163-
key={index}
164-
className={headingClassName}
165-
aria-label="Empty Day"
166-
/>
167-
))}
168-
{daysToRender.map(day => {
169-
let dayData = events.filter(data => isSameDay(data.date, day));
170-
let dayNumber = format(day, 'd');
171-
return (
172-
<div
173-
key={day.toISOString()}
174-
aria-label={`Events for day ${dayNumber}`}
175-
className="h-48 p-2 border-b-2 border-r-2"
176-
>
177-
<div className="flex justify-between">
178-
<div className="font-bold">{dayNumber}</div>
179-
<div className="lg:hidden block">{format(day, 'EEEE')}</div>
180-
</div>
181-
<ul className="divide-gray-200 divide-y overflow-hidden max-h-36 overflow-y-auto">
182-
{renderDay(dayData)}
183-
</ul>
184-
</div>
185-
);
186-
})}
187-
</div>
188-
</div>
189-
);
190-
}

src/index.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
export * from './Monthly/MonthlyCalendar';
2+
export * from './Monthly/MonthlyBody';
23
export * from './Monthly/MonthlyEventItems';
34

45
export * from './Weekly/WeeklyCalendar';

0 commit comments

Comments
 (0)