Massive functional upgrade: separated localization, full day viewer, month switch, first npm publish, code refactors
This commit is contained in:
parent
b37fe9644a
commit
0d66f3c4f6
4
.prettierrc
Normal file
4
.prettierrc
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
"semi": false,
|
||||
"singleQuote": true
|
||||
}
|
@ -1,14 +1,19 @@
|
||||
import React from "react";
|
||||
import Calendar from "react-eventful-calendar";
|
||||
import React from 'react'
|
||||
import Calendar, { Viewers } from 'react-eventful-calendar'
|
||||
|
||||
import events from './events.json'
|
||||
import locale from './locale.json'
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<div className="App">
|
||||
<Calendar eventList={events} />
|
||||
<Calendar
|
||||
eventList={events}
|
||||
locale={locale}
|
||||
initialViewer={Viewers.MonthViewer}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
export default App;
|
||||
export default App
|
||||
|
@ -2,7 +2,36 @@
|
||||
{
|
||||
"id": 1,
|
||||
"title": "Test",
|
||||
"startDate": "Wed Oct 28 2020 17:00:00 GMT+0500 (Yekaterinburg Standard Time)",
|
||||
"endDate": "Wed Oct 28 2020 20:00:00 GMT+0500 (Yekaterinburg Standard Time)"
|
||||
"startDate": "Wed Oct 22 2020 17:00:00 GMT+0500 (Yekaterinburg Standard Time)",
|
||||
"endDate": "Wed Oct 22 2020 20:00:00 GMT+0500 (Yekaterinburg Standard Time)",
|
||||
"color": "#e57373"
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"title": "AnotherTest",
|
||||
"startDate": "Wed Oct 22 2020 17:00:00 GMT+0500 (Yekaterinburg Standard Time)",
|
||||
"endDate": "Wed Oct 22 2020 20:00:00 GMT+0500 (Yekaterinburg Standard Time)",
|
||||
"color": "#64b5f6"
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"title": "And finally: third test",
|
||||
"startDate": "Wed Oct 22 2020 17:00:00 GMT+0500 (Yekaterinburg Standard Time)",
|
||||
"endDate": "Wed Oct 22 2020 20:00:00 GMT+0500 (Yekaterinburg Standard Time)",
|
||||
"color": "#81c784"
|
||||
},
|
||||
{
|
||||
"id": 666,
|
||||
"title": "Halloween",
|
||||
"startDate": "Wed Oct 31 2020 00:00:00 GMT+0500 (Yekaterinburg Standard Time)",
|
||||
"endDate": "Wed Nov 1 2020 00:00:00 GMT+0500 (Yekaterinburg Standard Time)",
|
||||
"color": "#ffb74d"
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"title": "Watching film",
|
||||
"startDate": "Wed Oct 31 2020 23:00:00 GMT+0500 (Yekaterinburg Standard Time)",
|
||||
"endDate": "Wed Nov 1 2020 01:00:00 GMT+0500 (Yekaterinburg Standard Time)",
|
||||
"color": "#ffb74d"
|
||||
}
|
||||
]
|
||||
|
25
example/src/locale.json
Normal file
25
example/src/locale.json
Normal file
@ -0,0 +1,25 @@
|
||||
{
|
||||
"monthNames": [
|
||||
"январь",
|
||||
"февраль",
|
||||
"март",
|
||||
"апрель",
|
||||
"май",
|
||||
"июнь",
|
||||
"июль",
|
||||
"август",
|
||||
"сентябрь",
|
||||
"октябрь",
|
||||
"ноябрь",
|
||||
"декабрь"
|
||||
],
|
||||
"weekdaysNames": [
|
||||
"понедельник",
|
||||
"вторник",
|
||||
"среда",
|
||||
"четверг",
|
||||
"пятница",
|
||||
"субота",
|
||||
"воскресенье"
|
||||
]
|
||||
}
|
@ -8847,7 +8847,7 @@ react-error-overlay@^6.0.8:
|
||||
resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-6.0.8.tgz#474ed11d04fc6bda3af643447d85e9127ed6b5de"
|
||||
integrity sha512-HvPuUQnLp5H7TouGq3kzBeioJmXms1wHy9EGjz2OURWBp4qZO6AfGEcnxts1D/CbwPLRAgTMPCEgYhA3sEM4vw==
|
||||
|
||||
react-eventful-calendar@../.:
|
||||
react-eventful-calendar@../:
|
||||
version "1.0.0"
|
||||
|
||||
react-is@^16.8.1:
|
||||
|
@ -1,69 +1,18 @@
|
||||
import React, { useContext, useEffect, useState } from 'react'
|
||||
import React from 'react'
|
||||
|
||||
import Day from './Day'
|
||||
import { CalendarContext } from './context'
|
||||
import { DayT, ICalendarProps } from './types'
|
||||
import { useCalendarContext } from './hooks'
|
||||
import { Viewers } from './types'
|
||||
import { DayView, MonthView } from './views'
|
||||
import styles from './views/styles'
|
||||
|
||||
const CalendarInContext: React.FC<ICalendarProps> = ({ eventList }) => {
|
||||
const context = useContext(CalendarContext)
|
||||
|
||||
const { month } = context.state
|
||||
|
||||
const [monthDaysState, setMonthDaysState] = useState<DayT[]>([])
|
||||
|
||||
useEffect(() => {
|
||||
const daysNumbersArray = [
|
||||
Array.from(
|
||||
Array(month.firstDayOfWeek - 1 > 0 ? month.firstDayOfWeek - 1 : 0)
|
||||
).map(
|
||||
(_, index) =>
|
||||
month.previousMonthLength - month.firstDayOfWeek + index + 2
|
||||
),
|
||||
Array.from(Array(month.numberOfDays)).map((_, index) => index + 1),
|
||||
Array.from(Array(7 - month.lastDayOfWeek)).map((_, index) => index + 1),
|
||||
]
|
||||
|
||||
setMonthDaysState(
|
||||
daysNumbersArray.flatMap((month, index) =>
|
||||
month.map<DayT>((dayNumber) => ({
|
||||
number: dayNumber,
|
||||
events: eventList.filter((event) => {
|
||||
if (index != 1) return false
|
||||
|
||||
const eventDate = new Date(event.startDate)
|
||||
|
||||
return (
|
||||
eventDate.getDate() == dayNumber &&
|
||||
eventDate.getMonth() == context.state.month.number
|
||||
)
|
||||
}),
|
||||
}))
|
||||
)
|
||||
)
|
||||
}, [context.state])
|
||||
const CalendarInContext: React.FC = () => {
|
||||
const { state: calendar } = useCalendarContext()
|
||||
const { currentViewer } = calendar
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
height: '100%',
|
||||
width: '100%',
|
||||
display: 'grid',
|
||||
gridTemplateColumns: 'repeat(7, 1fr)',
|
||||
gridTemplateRows: 'repeat(5, 1fr)',
|
||||
gap: '5px'
|
||||
}}
|
||||
>
|
||||
{monthDaysState.map((day, dayIndex) => (
|
||||
<Day
|
||||
key={dayIndex}
|
||||
dayNumber={day.number}
|
||||
events={day.events}
|
||||
shaded={
|
||||
dayIndex < month.firstDayOfWeek - 1 ||
|
||||
dayIndex > month.numberOfDays + month.firstDayOfWeek - 2
|
||||
}
|
||||
/>
|
||||
))}
|
||||
<div style={styles.calendar}>
|
||||
{currentViewer == Viewers.MonthViewer && <MonthView />}
|
||||
{currentViewer == Viewers.DayViewer && <DayView />}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
48
src/Day.tsx
48
src/Day.tsx
@ -1,48 +0,0 @@
|
||||
import React from 'react'
|
||||
|
||||
import { IDayProps } from './types'
|
||||
|
||||
const Day: React.FC<IDayProps> = ({ dayNumber, events, shaded }) => {
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
background: '#EEEEEE',
|
||||
borderRadius: '5px',
|
||||
maxHeight: '100px',
|
||||
overflow: 'hidden',
|
||||
padding: '5px',
|
||||
position: 'relative',
|
||||
opacity: shaded ? '0.5' : '1',
|
||||
}}
|
||||
>
|
||||
<h5
|
||||
style={{
|
||||
textAlign: 'center',
|
||||
margin: '0px',
|
||||
}}
|
||||
>
|
||||
{dayNumber}
|
||||
</h5>
|
||||
<ul style={{ paddingLeft: '20px', margin: '0' }}>
|
||||
{events.map((event, key) => (
|
||||
<li key={key} style={{}}>
|
||||
{event.title}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<div
|
||||
style={{
|
||||
position: 'absolute',
|
||||
bottom: '0px',
|
||||
marginLeft: '-5px',
|
||||
width: '100%',
|
||||
height: '50%',
|
||||
display: 'block',
|
||||
backgroundImage: 'linear-gradient(transparent, #EEEEEE)',
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Day
|
@ -1,47 +0,0 @@
|
||||
import React, { createContext, useEffect, useState } from 'react'
|
||||
|
||||
import { CalendarContextStateT, CalendarContextT } from './types'
|
||||
import { getCurrentMonth } from './utils'
|
||||
|
||||
const initialCalendarContextState: CalendarContextStateT = {
|
||||
month: {
|
||||
number: 1,
|
||||
firstDayOfWeek: 1,
|
||||
lastDayOfWeek: 1,
|
||||
numberOfDays: 1,
|
||||
previousMonthLength: 1,
|
||||
},
|
||||
year: 1,
|
||||
day: 1,
|
||||
}
|
||||
|
||||
const CalendarContext = createContext<CalendarContextT>({
|
||||
state: initialCalendarContextState,
|
||||
})
|
||||
|
||||
const CalendarProvider: React.FC = ({ children }) => {
|
||||
const [state, setState] = useState<CalendarContextStateT>(
|
||||
initialCalendarContextState
|
||||
)
|
||||
|
||||
const [now] = useState<Date>(new Date())
|
||||
|
||||
useEffect(
|
||||
() =>
|
||||
setState((prev) => ({
|
||||
...prev,
|
||||
month: getCurrentMonth(now),
|
||||
year: now.getFullYear(),
|
||||
day: now.getDate(),
|
||||
})),
|
||||
[]
|
||||
)
|
||||
|
||||
return (
|
||||
<CalendarContext.Provider value={{ state, setState }}>
|
||||
{children}
|
||||
</CalendarContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
export { CalendarContext, CalendarProvider }
|
74
src/context/calendar.tsx
Normal file
74
src/context/calendar.tsx
Normal file
@ -0,0 +1,74 @@
|
||||
import React, { createContext, useEffect, useMemo, useState } from 'react'
|
||||
|
||||
import {
|
||||
CalendarStateT,
|
||||
CalendarContextT,
|
||||
ICalendarProviderProps,
|
||||
} from './types'
|
||||
import { Viewers } from '../types'
|
||||
import { getMonth } from '../utils'
|
||||
|
||||
const initialCalendarContextState: CalendarStateT = {
|
||||
month: {
|
||||
number: 1,
|
||||
firstDayOfWeek: 0,
|
||||
lastDayOfWeek: 1,
|
||||
numberOfDays: 1,
|
||||
previousMonthLength: 1,
|
||||
},
|
||||
year: 1,
|
||||
day: {
|
||||
number: 1,
|
||||
},
|
||||
today: {
|
||||
day: 1,
|
||||
month: 1,
|
||||
year: 1,
|
||||
},
|
||||
eventList: [],
|
||||
currentViewer: Viewers.MonthViewer,
|
||||
}
|
||||
|
||||
const CalendarContext = createContext<CalendarContextT | null>(null)
|
||||
|
||||
const CalendarProvider: React.FC<ICalendarProviderProps> = ({
|
||||
children,
|
||||
eventList,
|
||||
initialViewer,
|
||||
}) => {
|
||||
const [state, setState] = useState<CalendarStateT>(
|
||||
initialCalendarContextState
|
||||
)
|
||||
|
||||
const [now] = useState<Date>(new Date())
|
||||
|
||||
useEffect(
|
||||
() =>
|
||||
setState((prev) => ({
|
||||
...prev,
|
||||
year: now.getFullYear(),
|
||||
month: getMonth(now),
|
||||
day: {
|
||||
number: now.getDate(),
|
||||
},
|
||||
today: {
|
||||
day: now.getDate(),
|
||||
month: now.getMonth(),
|
||||
year: now.getFullYear(),
|
||||
},
|
||||
currentViewer: initialViewer,
|
||||
eventList,
|
||||
})),
|
||||
[]
|
||||
)
|
||||
|
||||
const value = useMemo(() => ({ state, setState }), [state])
|
||||
|
||||
return (
|
||||
<CalendarContext.Provider value={value}>
|
||||
{children}
|
||||
</CalendarContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
export { CalendarContext, CalendarProvider }
|
32
src/context/locale.tsx
Normal file
32
src/context/locale.tsx
Normal file
@ -0,0 +1,32 @@
|
||||
import React, { createContext, useEffect, useMemo, useState } from 'react'
|
||||
import { ILocaleProviderProps, LocaleContextT, LocaleStateT } from './types'
|
||||
|
||||
const initialLocaleState: LocaleStateT = {
|
||||
monthNames: [],
|
||||
weekdaysNames: [],
|
||||
}
|
||||
|
||||
const LocaleContext = createContext<LocaleContextT | null>(null)
|
||||
|
||||
const LocaleProvider: React.FC<ILocaleProviderProps> = ({
|
||||
children,
|
||||
monthList,
|
||||
weekList,
|
||||
}) => {
|
||||
const [state, setState] = useState<LocaleStateT>(initialLocaleState)
|
||||
|
||||
useEffect(() => {
|
||||
setState({
|
||||
monthNames: monthList,
|
||||
weekdaysNames: weekList,
|
||||
})
|
||||
}, [monthList, weekList])
|
||||
|
||||
const value = useMemo(() => ({ state, setState }), [state])
|
||||
|
||||
return (
|
||||
<LocaleContext.Provider value={value}>{children}</LocaleContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
export { LocaleContext, LocaleProvider }
|
59
src/context/types.ts
Normal file
59
src/context/types.ts
Normal file
@ -0,0 +1,59 @@
|
||||
import { Dispatch, SetStateAction } from 'react'
|
||||
import { EventT, Viewers } from '../types'
|
||||
|
||||
// Calendar
|
||||
|
||||
export type CalendarStateT = {
|
||||
month: MonthContextT
|
||||
year: number
|
||||
day: {
|
||||
number: number
|
||||
events?: EventT[]
|
||||
}
|
||||
today: {
|
||||
day: number
|
||||
month: number
|
||||
year: number
|
||||
}
|
||||
eventList: EventT[]
|
||||
currentViewer: Viewers
|
||||
}
|
||||
|
||||
export type MonthContextT = {
|
||||
number: number
|
||||
firstDayOfWeek: number
|
||||
lastDayOfWeek: number
|
||||
numberOfDays: number
|
||||
previousMonthLength: number
|
||||
}
|
||||
|
||||
export type CalendarContextSetterT = Dispatch<SetStateAction<CalendarStateT>>
|
||||
|
||||
export type CalendarContextT = {
|
||||
state: CalendarStateT
|
||||
setState?: CalendarContextSetterT
|
||||
}
|
||||
|
||||
export interface ICalendarProviderProps {
|
||||
eventList: EventT[]
|
||||
initialViewer: Viewers
|
||||
}
|
||||
|
||||
// localization
|
||||
|
||||
export type LocaleStateT = {
|
||||
weekdaysNames: string[]
|
||||
monthNames: string[]
|
||||
}
|
||||
|
||||
export type LocaleContextSetterT = Dispatch<SetStateAction<LocaleStateT>>
|
||||
|
||||
export type LocaleContextT = {
|
||||
state: LocaleStateT
|
||||
setState?: LocaleContextSetterT
|
||||
}
|
||||
|
||||
export interface ILocaleProviderProps {
|
||||
weekList: string[]
|
||||
monthList: string[]
|
||||
}
|
23
src/hooks.ts
Normal file
23
src/hooks.ts
Normal file
@ -0,0 +1,23 @@
|
||||
import { useContext } from 'react'
|
||||
import { CalendarContext } from './context/calendar'
|
||||
import { LocaleContext } from './context/locale'
|
||||
|
||||
const useCalendarContext = () => {
|
||||
const context = useContext(CalendarContext)
|
||||
|
||||
if (!context)
|
||||
throw new Error("Can't use calendar context outside of CalendarProvider")
|
||||
|
||||
return context
|
||||
}
|
||||
|
||||
const useLocaleContext = () => {
|
||||
const context = useContext(LocaleContext)
|
||||
|
||||
if (!context)
|
||||
throw new Error("Can't use locale context outside of LocaleProvider")
|
||||
|
||||
return context
|
||||
}
|
||||
|
||||
export { useCalendarContext, useLocaleContext }
|
@ -1,14 +1,26 @@
|
||||
import React from 'react'
|
||||
import CalendarInContext from './CalendarInContext'
|
||||
import { CalendarProvider as CalendarContextProvider } from './context'
|
||||
import { CalendarProvider as CalendarContextProvider } from './context/calendar'
|
||||
import { ICalendarProps } from './types'
|
||||
import { LocaleProvider as LocaleContextProvider } from './context/locale'
|
||||
|
||||
const Calendar: React.FC<ICalendarProps> = ({ eventList }) => {
|
||||
const Calendar: React.FC<ICalendarProps> = ({
|
||||
eventList,
|
||||
initialViewer,
|
||||
locale: { monthNames, weekdaysNames },
|
||||
}) => {
|
||||
return (
|
||||
<CalendarContextProvider>
|
||||
<CalendarInContext eventList={eventList} />
|
||||
<CalendarContextProvider
|
||||
eventList={eventList}
|
||||
initialViewer={initialViewer}
|
||||
>
|
||||
<LocaleContextProvider monthList={monthNames} weekList={weekdaysNames}>
|
||||
<CalendarInContext />
|
||||
</LocaleContextProvider>
|
||||
</CalendarContextProvider>
|
||||
)
|
||||
}
|
||||
|
||||
export { Viewers } from './types'
|
||||
|
||||
export default Calendar
|
||||
|
57
src/types.ts
57
src/types.ts
@ -1,30 +1,26 @@
|
||||
import { Dispatch, SetStateAction } from 'react'
|
||||
import { CalendarStateT, LocaleStateT, MonthContextT } from './context/types'
|
||||
|
||||
// Context
|
||||
// Experimental
|
||||
|
||||
export type CalendarContextStateT = {
|
||||
month: MonthContextT
|
||||
year: number
|
||||
day: number
|
||||
}
|
||||
export type switchMonthInContextFT = (
|
||||
setContext: Dispatch<SetStateAction<CalendarStateT>> | undefined,
|
||||
increase: boolean
|
||||
) => void
|
||||
|
||||
export type MonthContextT = {
|
||||
number: number
|
||||
firstDayOfWeek: number
|
||||
lastDayOfWeek: number
|
||||
numberOfDays: number
|
||||
previousMonthLength: number
|
||||
}
|
||||
|
||||
export type CalendarContextT = {
|
||||
state: CalendarContextStateT
|
||||
setState?: Dispatch<SetStateAction<CalendarContextStateT>>
|
||||
}
|
||||
export type UseCalendarSwitchFT = () => (increase: boolean) => void
|
||||
|
||||
// Calendar component
|
||||
|
||||
export enum Viewers {
|
||||
MonthViewer,
|
||||
DayViewer,
|
||||
}
|
||||
|
||||
export interface ICalendarProps {
|
||||
eventList: EventT[]
|
||||
initialViewer: Viewers
|
||||
locale: LocaleStateT
|
||||
}
|
||||
|
||||
// Day
|
||||
@ -34,11 +30,23 @@ export type DayT = {
|
||||
events: EventT[]
|
||||
}
|
||||
|
||||
export interface IDayProps {
|
||||
dayNumber: number
|
||||
events: EventT[]
|
||||
shaded: boolean
|
||||
}
|
||||
export type ShouldDayBeShadedFT = (
|
||||
month: MonthContextT,
|
||||
dayIndex: number
|
||||
) => boolean
|
||||
|
||||
export type FilterEventsByDayFT = (
|
||||
eventList: EventT[],
|
||||
dayNumber: number,
|
||||
monthNumber: number,
|
||||
yearNumber: number
|
||||
) => EventT[]
|
||||
|
||||
export type SetDayFT = (
|
||||
dayNumber: number,
|
||||
dayEvents: EventT[],
|
||||
setState: Dispatch<SetStateAction<CalendarStateT>> | undefined
|
||||
) => void
|
||||
|
||||
// Event
|
||||
|
||||
@ -47,4 +55,7 @@ export type EventT = {
|
||||
title: string
|
||||
startDate: string
|
||||
endDate: string
|
||||
color: string
|
||||
}
|
||||
|
||||
export type GetEventLengthFT = (start: string, end: string) => string
|
||||
|
107
src/utils.ts
107
src/utils.ts
@ -1,13 +1,17 @@
|
||||
import { MonthContextT } from './types'
|
||||
import { MonthContextT } from './context/types'
|
||||
import {
|
||||
FilterEventsByDayFT,
|
||||
GetEventLengthFT,
|
||||
SetDayFT,
|
||||
ShouldDayBeShadedFT,
|
||||
switchMonthInContextFT,
|
||||
} from './types'
|
||||
|
||||
const getCurrentMonth = (currentDay: Date): MonthContextT => {
|
||||
const monthInfo = getMonthInfo(
|
||||
currentDay.getFullYear(),
|
||||
currentDay.getMonth()
|
||||
)
|
||||
const getMonth = (date: Date): MonthContextT => {
|
||||
const monthInfo = getMonthInfo(date.getFullYear(), date.getMonth())
|
||||
|
||||
return {
|
||||
number: currentDay.getMonth(),
|
||||
number: date.getMonth(),
|
||||
...monthInfo,
|
||||
}
|
||||
}
|
||||
@ -20,12 +24,97 @@ const getMonthInfo = (
|
||||
const lastDayOfMonth = new Date(year, month + 1, 0)
|
||||
const prevMonth = new Date(year, month, 0)
|
||||
|
||||
const firstDayOfWeek = firstDayOfMonth.getDay()
|
||||
|
||||
const convertedFirstDayOfWeek =
|
||||
firstDayOfWeek - 1 >= 0 ? firstDayOfWeek - 1 : 6
|
||||
|
||||
return {
|
||||
firstDayOfWeek: firstDayOfMonth.getDay(),
|
||||
firstDayOfWeek: convertedFirstDayOfWeek,
|
||||
lastDayOfWeek: lastDayOfMonth.getDay(),
|
||||
numberOfDays: lastDayOfMonth.getDate(),
|
||||
previousMonthLength: prevMonth.getDate(),
|
||||
}
|
||||
}
|
||||
|
||||
export { getCurrentMonth, getMonthInfo }
|
||||
const getMonthName = (number: number, monthNames: string[]) =>
|
||||
monthNames[number]
|
||||
|
||||
const switchMonthInContext: switchMonthInContextFT = (setContext, increase) => {
|
||||
if (!setContext) return
|
||||
|
||||
setContext((previousContext) => {
|
||||
const newDate = new Date(
|
||||
previousContext.year,
|
||||
previousContext.month.number,
|
||||
1
|
||||
)
|
||||
|
||||
if (increase) newDate.setMonth(newDate.getMonth() + 1)
|
||||
else newDate.setMonth(newDate.getMonth() - 1)
|
||||
|
||||
return {
|
||||
...previousContext,
|
||||
year: newDate.getFullYear(),
|
||||
month: getMonth(newDate),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const shouldDayBeShaded: ShouldDayBeShadedFT = (month, dayIndex) =>
|
||||
dayIndex < month.firstDayOfWeek ||
|
||||
dayIndex > month.numberOfDays + month.firstDayOfWeek - 1
|
||||
|
||||
const filterEventsByDay: FilterEventsByDayFT = (
|
||||
eventList,
|
||||
dayNumber,
|
||||
monthNumber,
|
||||
yearnumber
|
||||
) =>
|
||||
eventList.filter((event) => {
|
||||
const eventDate = new Date(event.startDate)
|
||||
|
||||
return (
|
||||
eventDate.getDate() == dayNumber &&
|
||||
eventDate.getMonth() == monthNumber &&
|
||||
eventDate.getFullYear() == yearnumber
|
||||
)
|
||||
})
|
||||
|
||||
const setDay: SetDayFT = (dayNumber, dayEvents, setState) => {
|
||||
if (setState)
|
||||
setState((prevState) => ({
|
||||
...prevState,
|
||||
day: {
|
||||
...prevState.day,
|
||||
number: dayNumber,
|
||||
events: dayEvents,
|
||||
},
|
||||
}))
|
||||
}
|
||||
|
||||
const getEventLength: GetEventLengthFT = (start, end) => {
|
||||
const startDate = new Date(start)
|
||||
const endDate = new Date(end)
|
||||
|
||||
if ((endDate.getTime() - startDate.getTime()) / (1000 * 3600 * 24) >= 1)
|
||||
return 'Whole day'
|
||||
const startMinutes = startDate.getMinutes().toString()
|
||||
const endMinutes = endDate.getMinutes().toString()
|
||||
|
||||
return `${startDate.getHours()}:${
|
||||
startMinutes.length < 2 ? '0' + startMinutes : startMinutes
|
||||
}-${endDate.getHours()}:${
|
||||
endMinutes.length < 2 ? '0' + endMinutes : endMinutes
|
||||
}`
|
||||
}
|
||||
|
||||
export {
|
||||
getMonth,
|
||||
getMonthName,
|
||||
switchMonthInContext,
|
||||
shouldDayBeShaded,
|
||||
filterEventsByDay,
|
||||
setDay,
|
||||
getEventLength,
|
||||
}
|
||||
|
77
src/views/Day.tsx
Normal file
77
src/views/Day.tsx
Normal file
@ -0,0 +1,77 @@
|
||||
import React, { useEffect } from 'react'
|
||||
import { useCalendarContext, useLocaleContext } from '../hooks'
|
||||
import { Viewers } from '../types'
|
||||
import { getEventLength, getMonthName } from '../utils'
|
||||
|
||||
import { IDayViewer } from './types'
|
||||
|
||||
const HomeIcon = () => (
|
||||
<svg
|
||||
width="23"
|
||||
height="22"
|
||||
viewBox="0 0 23 22"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<g fill="#000000">
|
||||
<path d="M11.5 5.21591L20.125 13.2268V22H14.375V16.2609H8.625V22H2.875V13.2268L11.5 5.21591ZM23 10.6633L11.5 0L0 10.649L1.30429 12.0503L11.5 2.6113L21.6957 12.0646L23 10.6633Z"></path>
|
||||
</g>
|
||||
</svg>
|
||||
)
|
||||
|
||||
const DayViewer: React.FC<IDayViewer> = () => {
|
||||
const {
|
||||
state: { day, month, year },
|
||||
setState,
|
||||
} = useCalendarContext()
|
||||
const {
|
||||
state: { monthNames },
|
||||
} = useLocaleContext()
|
||||
|
||||
useEffect(() => {
|
||||
if (setState)
|
||||
if (day.events === undefined) {
|
||||
setState((prevState) => ({
|
||||
...prevState,
|
||||
day: {
|
||||
...prevState.day,
|
||||
events: prevState.eventList.filter(() => false),
|
||||
},
|
||||
}))
|
||||
}
|
||||
}, [day.number])
|
||||
|
||||
const handle = () => {
|
||||
if (setState)
|
||||
setState((prevSate) => ({
|
||||
...prevSate,
|
||||
currentViewer: Viewers.MonthViewer,
|
||||
}))
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<button
|
||||
title="Return to main"
|
||||
onClick={handle}
|
||||
style={{ background: 'none', border: 'none', cursor: 'pointer' }}
|
||||
>
|
||||
<HomeIcon />
|
||||
</button>
|
||||
<div>
|
||||
<h1>{day.number}</h1>
|
||||
<h3>{getMonthName(month.number, monthNames) + ' ' + year}</h3>
|
||||
</div>
|
||||
<ul>
|
||||
{day.events?.map((event, eventIndex) => (
|
||||
<li key={eventIndex}>
|
||||
<h2>{event.title}</h2>
|
||||
<h3>{getEventLength(event.startDate, event.endDate)}</h3>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default DayViewer
|
79
src/views/DayOfMonth.tsx
Normal file
79
src/views/DayOfMonth.tsx
Normal file
@ -0,0 +1,79 @@
|
||||
import React, { useState } from 'react'
|
||||
import { useCalendarContext } from '../hooks'
|
||||
import { Viewers } from '../types'
|
||||
import { setDay } from '../utils'
|
||||
|
||||
import { IDayOfMonthProps } from './types'
|
||||
import styles from './styles'
|
||||
|
||||
const ArrowDown: React.FC = () => (
|
||||
<svg
|
||||
width="11"
|
||||
height="20"
|
||||
viewBox="0 0 11 6"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<g fill="#000000">
|
||||
<path d="M0 1.132L1.29663 0L5.50183 3.7356L9.70337 0L11 1.132L5.50183 6L0 1.132Z" />
|
||||
</g>
|
||||
</svg>
|
||||
)
|
||||
|
||||
const DayOfMonth: React.FC<IDayOfMonthProps> = ({
|
||||
dayNumber,
|
||||
events,
|
||||
shaded,
|
||||
}) => {
|
||||
const { setState } = useCalendarContext()
|
||||
|
||||
const [expanded, setExpanded] = useState(false)
|
||||
|
||||
const handleClick = () => {
|
||||
if (shaded) return
|
||||
if (events.length > 2 && !expanded) return setExpanded(true)
|
||||
setDay(dayNumber, events, setState)
|
||||
if (setState)
|
||||
setState((prevState) => ({
|
||||
...prevState,
|
||||
currentViewer: Viewers.DayViewer,
|
||||
}))
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
...styles.dayOfMonth,
|
||||
opacity: shaded ? '0.5' : '1',
|
||||
maxHeight: expanded ? '100vh' : '5rem',
|
||||
}}
|
||||
onClick={handleClick}
|
||||
>
|
||||
<h5 style={styles.monthDayNumber}>{dayNumber}</h5>
|
||||
<ul style={styles.monthDayEventList}>
|
||||
{events.map((event, key) => (
|
||||
<li
|
||||
key={key}
|
||||
style={{ ...styles.monthDayEvent, backgroundColor: event.color }}
|
||||
>
|
||||
{event.title}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
{events.length > 2 ? (
|
||||
<div
|
||||
style={{
|
||||
...styles.monthDayBottomShade,
|
||||
height: expanded ? '0px' : '50%',
|
||||
}}
|
||||
>
|
||||
<ArrowDown />
|
||||
</div>
|
||||
) : (
|
||||
''
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default DayOfMonth
|
61
src/views/Month.tsx
Normal file
61
src/views/Month.tsx
Normal file
@ -0,0 +1,61 @@
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { useCalendarContext } from '../hooks'
|
||||
import { DayT } from '../types'
|
||||
import { filterEventsByDay, shouldDayBeShaded } from '../utils'
|
||||
import DayOfMonth from './DayOfMonth'
|
||||
import MonthHeader from './MonthHeader'
|
||||
import styles from './styles'
|
||||
|
||||
const MonthView: React.FC = () => {
|
||||
const {
|
||||
state: { month, eventList, year: yearNumber },
|
||||
} = useCalendarContext()
|
||||
|
||||
const [monthDays, setMonthDaysState] = useState<DayT[]>([])
|
||||
|
||||
useEffect(() => {
|
||||
const daysNumbersArray = [
|
||||
Array.from(
|
||||
// Array(month.firstDayOfWeek - 1 > 0 ? month.firstDayOfWeek - 1 : 0)
|
||||
Array(month.firstDayOfWeek)
|
||||
).map(
|
||||
(_, index) =>
|
||||
month.previousMonthLength - month.firstDayOfWeek + index + 1
|
||||
),
|
||||
Array.from(Array(month.numberOfDays)).map((_, index) => index + 1),
|
||||
Array.from(Array(7 - month.lastDayOfWeek)).map((_, index) => index + 1),
|
||||
]
|
||||
|
||||
const monthNumber = month.number
|
||||
|
||||
setMonthDaysState(
|
||||
daysNumbersArray.flatMap((month, index) =>
|
||||
month.map<DayT>((dayNumber) => ({
|
||||
number: dayNumber,
|
||||
events:
|
||||
index == 1
|
||||
? filterEventsByDay(eventList, dayNumber, monthNumber, yearNumber)
|
||||
: [],
|
||||
}))
|
||||
)
|
||||
)
|
||||
}, [month, eventList])
|
||||
|
||||
return (
|
||||
<>
|
||||
<MonthHeader />
|
||||
<div style={styles.monthGrid}>
|
||||
{monthDays.map((day, dayIndex) => (
|
||||
<DayOfMonth
|
||||
key={dayIndex}
|
||||
dayNumber={day.number}
|
||||
events={day.events}
|
||||
shaded={shouldDayBeShaded(month, dayIndex)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default MonthView
|
52
src/views/MonthHeader.tsx
Normal file
52
src/views/MonthHeader.tsx
Normal file
@ -0,0 +1,52 @@
|
||||
import React from 'react'
|
||||
import { useCalendarContext, useLocaleContext } from '../hooks'
|
||||
import { getMonthName, switchMonthInContext } from '../utils'
|
||||
import styles from './styles'
|
||||
|
||||
const MonthHeader: React.FC = () => {
|
||||
const {
|
||||
state: { weekdaysNames, monthNames },
|
||||
} = useLocaleContext()
|
||||
|
||||
const {
|
||||
state: {
|
||||
year,
|
||||
month: { number },
|
||||
},
|
||||
setState,
|
||||
} = useCalendarContext()
|
||||
|
||||
return (
|
||||
<header>
|
||||
<div style={styles.monthSwitchHeader}>
|
||||
<button
|
||||
title={getMonthName(number - 1, monthNames)}
|
||||
onClick={() => switchMonthInContext(setState, false)}
|
||||
style={styles.switchMonthButton}
|
||||
>
|
||||
<
|
||||
</button>
|
||||
<h4 style={styles.monthSwitchHeaderTitle}>
|
||||
{getMonthName(number, monthNames) + ' ' + year}
|
||||
</h4>
|
||||
<button
|
||||
title={getMonthName(number + 1, monthNames)}
|
||||
onClick={() => switchMonthInContext(setState, true)}
|
||||
style={styles.switchMonthButton}
|
||||
>
|
||||
>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div style={styles.weekdaysListHeader}>
|
||||
{weekdaysNames.map((weekDay, weekDayIndex) => (
|
||||
<p key={weekDayIndex} style={styles.weekDay}>
|
||||
{weekDay}
|
||||
</p>
|
||||
))}
|
||||
</div>
|
||||
</header>
|
||||
)
|
||||
}
|
||||
|
||||
export default MonthHeader
|
4
src/views/index.ts
Normal file
4
src/views/index.ts
Normal file
@ -0,0 +1,4 @@
|
||||
import MonthView from './Month'
|
||||
import DayView from './Day'
|
||||
|
||||
export { MonthView, DayView }
|
117
src/views/styles.ts
Normal file
117
src/views/styles.ts
Normal file
@ -0,0 +1,117 @@
|
||||
type Style = React.CSSProperties
|
||||
|
||||
const calendar: Style = {
|
||||
height: '100%',
|
||||
width: '100%',
|
||||
border: '1px black solid',
|
||||
borderRadius: '5px',
|
||||
overflow: 'hidden',
|
||||
}
|
||||
|
||||
// Month header
|
||||
|
||||
const weekdaysListHeader: Style = {
|
||||
display: 'grid',
|
||||
gridTemplateColumns: 'repeat(7, 1fr)',
|
||||
width: '100%',
|
||||
gap: '5px',
|
||||
}
|
||||
|
||||
const monthSwitchHeader: Style = {
|
||||
display: 'grid',
|
||||
gridTemplateColumns: 'repeat(7, 1fr)',
|
||||
width: '100%',
|
||||
gap: '5px',
|
||||
backgroundColor: 'black',
|
||||
color: 'white',
|
||||
padding: '5px',
|
||||
}
|
||||
|
||||
const switchMonthButton: Style = {
|
||||
backgroundColor: 'white',
|
||||
border: 'none',
|
||||
borderRadius: '5px',
|
||||
}
|
||||
|
||||
const monthSwitchHeaderTitle: Style = {
|
||||
margin: '0px',
|
||||
textAlign: 'center',
|
||||
textTransform: 'capitalize',
|
||||
gridColumn: 'span 5',
|
||||
}
|
||||
|
||||
const weekDay: Style = {
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
textAlign: 'center',
|
||||
padding: '5px 0',
|
||||
margin: '0px',
|
||||
textTransform: 'capitalize',
|
||||
}
|
||||
|
||||
// Month viewer
|
||||
|
||||
const monthGrid: Style = {
|
||||
display: 'grid',
|
||||
gridTemplateColumns: 'repeat(7, 1fr)',
|
||||
gap: '5px',
|
||||
padding: '5px',
|
||||
}
|
||||
|
||||
// Month day
|
||||
|
||||
const dayOfMonth: Style = {
|
||||
background: '#EEEEEE',
|
||||
borderRadius: '5px',
|
||||
overflow: 'hidden',
|
||||
minHeight: '5rem',
|
||||
padding: '5px',
|
||||
position: 'relative',
|
||||
transition: 'all 3s',
|
||||
}
|
||||
|
||||
const monthDayEventList: Style = {
|
||||
padding: '0px',
|
||||
margin: '0px',
|
||||
listStyle: 'none',
|
||||
overflow: 'hidden',
|
||||
borderRadius: '4px',
|
||||
}
|
||||
|
||||
const monthDayEvent: Style = {
|
||||
padding: '2px',
|
||||
}
|
||||
|
||||
const monthDayBottomShade: Style = {
|
||||
position: 'absolute',
|
||||
bottom: '0px',
|
||||
marginLeft: '-5px',
|
||||
width: '100%',
|
||||
overflow: 'hidden',
|
||||
display: 'flex',
|
||||
alignItems: 'flex-end',
|
||||
justifyContent: 'center',
|
||||
backgroundImage: 'linear-gradient(transparent, #EEEEEE)',
|
||||
}
|
||||
|
||||
const monthDayNumber: Style = {
|
||||
textAlign: 'center',
|
||||
margin: '0px',
|
||||
}
|
||||
|
||||
const styles = {
|
||||
switchMonthButton,
|
||||
calendar,
|
||||
weekdaysListHeader,
|
||||
monthSwitchHeader,
|
||||
monthSwitchHeaderTitle,
|
||||
weekDay,
|
||||
monthGrid,
|
||||
dayOfMonth,
|
||||
monthDayEventList,
|
||||
monthDayEvent,
|
||||
monthDayBottomShade,
|
||||
monthDayNumber,
|
||||
}
|
||||
|
||||
export default styles
|
9
src/views/types.ts
Normal file
9
src/views/types.ts
Normal file
@ -0,0 +1,9 @@
|
||||
import { EventT } from '../types'
|
||||
|
||||
export interface IDayOfMonthProps {
|
||||
dayNumber: number
|
||||
events: EventT[]
|
||||
shaded: boolean
|
||||
}
|
||||
|
||||
export interface IDayViewer {}
|
Loading…
x
Reference in New Issue
Block a user