@isma91/react-scheduler 4.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.
Files changed (134) hide show
  1. package/.github/workflows/publish.yml +29 -0
  2. package/.github/workflows/tests.yml +35 -0
  3. package/.gitignore +32 -0
  4. package/.husky/pre-commit +2 -0
  5. package/.prettierignore +1 -0
  6. package/.prettierrc.json +7 -0
  7. package/.yarnrc.yml +1 -0
  8. package/LICENSE +24 -0
  9. package/README.md +172 -0
  10. package/dist/LICENSE +24 -0
  11. package/dist/README.md +172 -0
  12. package/dist/SchedulerComponent.d.ts +3 -0
  13. package/dist/components/common/Cell.d.ts +13 -0
  14. package/dist/components/common/LocaleArrow.d.ts +8 -0
  15. package/dist/components/common/ResourceHeader.d.ts +6 -0
  16. package/dist/components/common/Tabs.d.ts +16 -0
  17. package/dist/components/common/TodayTypo.d.ts +8 -0
  18. package/dist/components/common/WithResources.d.ts +6 -0
  19. package/dist/components/events/Actions.d.ts +8 -0
  20. package/dist/components/events/AgendaEventsList.d.ts +7 -0
  21. package/dist/components/events/CurrentTimeBar.d.ts +11 -0
  22. package/dist/components/events/EmptyAgenda.d.ts +2 -0
  23. package/dist/components/events/EventItem.d.ts +10 -0
  24. package/dist/components/events/EventItemPopover.d.ts +9 -0
  25. package/dist/components/events/MonthEvents.d.ts +13 -0
  26. package/dist/components/events/TodayEvents.d.ts +16 -0
  27. package/dist/components/hoc/DateProvider.d.ts +5 -0
  28. package/dist/components/inputs/DatePicker.d.ts +14 -0
  29. package/dist/components/inputs/Input.d.ts +19 -0
  30. package/dist/components/inputs/SelectInput.d.ts +22 -0
  31. package/dist/components/month/MonthTable.d.ts +8 -0
  32. package/dist/components/nav/DayDateBtn.d.ts +6 -0
  33. package/dist/components/nav/MonthDateBtn.d.ts +6 -0
  34. package/dist/components/nav/Navigation.d.ts +3 -0
  35. package/dist/components/nav/WeekDateBtn.d.ts +8 -0
  36. package/dist/components/week/WeekTable.d.ts +11 -0
  37. package/dist/helpers/constants.d.ts +4 -0
  38. package/dist/helpers/generals.d.ts +78 -0
  39. package/dist/hooks/useArrowDisable.d.ts +5 -0
  40. package/dist/hooks/useCellAttributes.d.ts +18 -0
  41. package/dist/hooks/useDragAttributes.d.ts +10 -0
  42. package/dist/hooks/useEventPermissions.d.ts +7 -0
  43. package/dist/hooks/useStore.d.ts +2 -0
  44. package/dist/hooks/useSyncScroll.d.ts +8 -0
  45. package/dist/hooks/useWindowResize.d.ts +4 -0
  46. package/dist/index.d.ts +3 -0
  47. package/dist/index.js +2853 -0
  48. package/dist/package.json +65 -0
  49. package/dist/positionManger/context.d.ts +14 -0
  50. package/dist/positionManger/provider.d.ts +5 -0
  51. package/dist/positionManger/usePosition.d.ts +4 -0
  52. package/dist/store/context.d.ts +2 -0
  53. package/dist/store/default.d.ts +245 -0
  54. package/dist/store/provider.d.ts +7 -0
  55. package/dist/store/types.d.ts +27 -0
  56. package/dist/styles/styles.d.ts +30 -0
  57. package/dist/types.d.ts +372 -0
  58. package/dist/views/Day.d.ts +2 -0
  59. package/dist/views/DayAgenda.d.ts +7 -0
  60. package/dist/views/Editor.d.ts +11 -0
  61. package/dist/views/Month.d.ts +2 -0
  62. package/dist/views/MonthAgenda.d.ts +7 -0
  63. package/dist/views/Week.d.ts +2 -0
  64. package/dist/views/WeekAgenda.d.ts +8 -0
  65. package/eslint.config.js +79 -0
  66. package/index.html +41 -0
  67. package/jest.config.ts +194 -0
  68. package/package.json +137 -0
  69. package/public/favicon.ico +0 -0
  70. package/public/logo192.png +0 -0
  71. package/public/logo512.png +0 -0
  72. package/public/manifest.json +25 -0
  73. package/public/robots.txt +3 -0
  74. package/scripts/post-pack.js +34 -0
  75. package/src/App.tsx +25 -0
  76. package/src/Page1.tsx +67 -0
  77. package/src/events.tsx +227 -0
  78. package/src/index.tsx +21 -0
  79. package/src/lib/SchedulerComponent.tsx +78 -0
  80. package/src/lib/__tests__/index.test.tsx +24 -0
  81. package/src/lib/components/common/Cell.tsx +52 -0
  82. package/src/lib/components/common/LocaleArrow.tsx +38 -0
  83. package/src/lib/components/common/ResourceHeader.tsx +73 -0
  84. package/src/lib/components/common/Tabs.tsx +119 -0
  85. package/src/lib/components/common/TodayTypo.tsx +44 -0
  86. package/src/lib/components/common/WithResources.tsx +98 -0
  87. package/src/lib/components/events/Actions.tsx +65 -0
  88. package/src/lib/components/events/AgendaEventsList.tsx +115 -0
  89. package/src/lib/components/events/CurrentTimeBar.tsx +59 -0
  90. package/src/lib/components/events/EmptyAgenda.tsx +27 -0
  91. package/src/lib/components/events/EventItem.tsx +180 -0
  92. package/src/lib/components/events/EventItemPopover.tsx +179 -0
  93. package/src/lib/components/events/MonthEvents.tsx +141 -0
  94. package/src/lib/components/events/TodayEvents.tsx +99 -0
  95. package/src/lib/components/hoc/DateProvider.tsx +19 -0
  96. package/src/lib/components/inputs/DatePicker.tsx +95 -0
  97. package/src/lib/components/inputs/Input.tsx +113 -0
  98. package/src/lib/components/inputs/SelectInput.tsx +164 -0
  99. package/src/lib/components/month/MonthTable.tsx +207 -0
  100. package/src/lib/components/nav/DayDateBtn.tsx +77 -0
  101. package/src/lib/components/nav/MonthDateBtn.tsx +80 -0
  102. package/src/lib/components/nav/Navigation.tsx +201 -0
  103. package/src/lib/components/nav/WeekDateBtn.tsx +89 -0
  104. package/src/lib/components/week/WeekTable.tsx +229 -0
  105. package/src/lib/helpers/constants.ts +4 -0
  106. package/src/lib/helpers/generals.tsx +354 -0
  107. package/src/lib/hooks/useArrowDisable.ts +26 -0
  108. package/src/lib/hooks/useCellAttributes.ts +67 -0
  109. package/src/lib/hooks/useDragAttributes.ts +31 -0
  110. package/src/lib/hooks/useEventPermissions.ts +42 -0
  111. package/src/lib/hooks/useStore.ts +8 -0
  112. package/src/lib/hooks/useSyncScroll.ts +31 -0
  113. package/src/lib/hooks/useWindowResize.ts +37 -0
  114. package/src/lib/index.tsx +14 -0
  115. package/src/lib/positionManger/context.ts +14 -0
  116. package/src/lib/positionManger/provider.tsx +113 -0
  117. package/src/lib/positionManger/usePosition.ts +8 -0
  118. package/src/lib/store/context.ts +5 -0
  119. package/src/lib/store/default.ts +157 -0
  120. package/src/lib/store/provider.tsx +211 -0
  121. package/src/lib/store/types.ts +33 -0
  122. package/src/lib/styles/styles.ts +256 -0
  123. package/src/lib/types.ts +423 -0
  124. package/src/lib/views/Day.tsx +265 -0
  125. package/src/lib/views/DayAgenda.tsx +57 -0
  126. package/src/lib/views/Editor.tsx +258 -0
  127. package/src/lib/views/Month.tsx +82 -0
  128. package/src/lib/views/MonthAgenda.tsx +84 -0
  129. package/src/lib/views/Week.tsx +92 -0
  130. package/src/lib/views/WeekAgenda.tsx +81 -0
  131. package/src/vite-env.d.ts +3 -0
  132. package/tsconfig.build.json +5 -0
  133. package/tsconfig.json +27 -0
  134. package/vite.config.js +40 -0
@@ -0,0 +1,164 @@
1
+ import { useState, useEffect, useCallback } from "react";
2
+ import {
3
+ FormControl,
4
+ FormHelperText,
5
+ MenuItem,
6
+ Checkbox,
7
+ useTheme,
8
+ Chip,
9
+ Typography,
10
+ CircularProgress,
11
+ InputLabel,
12
+ Select,
13
+ } from "@mui/material";
14
+ import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
15
+ import useStore from "../../hooks/useStore";
16
+
17
+ export type SelectOption = {
18
+ id: string | number;
19
+ text: string;
20
+ value: any;
21
+ };
22
+ interface EditorSelectProps {
23
+ options: Array<SelectOption>;
24
+ value: string;
25
+ name: string;
26
+ onChange(name: string, value: string, isValid: boolean): void;
27
+ variant?: "standard" | "filled" | "outlined";
28
+ label?: string;
29
+ placeholder?: string;
30
+ required?: boolean;
31
+ disabled?: boolean;
32
+ touched?: boolean;
33
+ loading?: boolean;
34
+ multiple?: "default" | "chips";
35
+ errMsg?: string;
36
+ }
37
+
38
+ const EditorSelect = ({
39
+ options,
40
+ value,
41
+ name,
42
+ required,
43
+ onChange,
44
+ label,
45
+ disabled,
46
+ touched,
47
+ variant = "outlined",
48
+ loading,
49
+ multiple,
50
+ placeholder,
51
+ errMsg,
52
+ }: EditorSelectProps) => {
53
+ const theme = useTheme();
54
+ const { translations } = useStore();
55
+ const [state, setState] = useState({
56
+ touched: false,
57
+ valid: !!value,
58
+ errorMsg: errMsg
59
+ ? errMsg
60
+ : required
61
+ ? translations?.validation?.required || "Required"
62
+ : undefined,
63
+ });
64
+ const handleTouched = useCallback(() => {
65
+ if (!state.touched) {
66
+ setState((prev) => {
67
+ return { ...prev, touched: true, errorMsg: errMsg || prev.errorMsg };
68
+ });
69
+ }
70
+ }, [errMsg, state.touched]);
71
+ const handleChange = useCallback(
72
+ (value: string | any) => {
73
+ const val = value;
74
+ let isValid = true;
75
+ let errorMsg = errMsg;
76
+ if (required && (multiple ? !val.length : !val)) {
77
+ isValid = false;
78
+ errorMsg = errMsg || translations?.validation?.required || "Required";
79
+ }
80
+ setState((prev) => {
81
+ return { ...prev, touched: true, valid: isValid, errorMsg: errorMsg };
82
+ });
83
+ onChange(name, val, isValid);
84
+ },
85
+ [errMsg, multiple, name, onChange, required, translations?.validation?.required]
86
+ );
87
+
88
+ useEffect(() => {
89
+ if (touched) {
90
+ handleChange(value);
91
+ }
92
+ }, [handleChange, touched, value]);
93
+ return (
94
+ <>
95
+ <FormControl
96
+ variant={variant || "outlined"}
97
+ fullWidth
98
+ error={required && state.touched && !state.valid}
99
+ // style={{ minWidth: 230 }}
100
+ disabled={disabled}
101
+ >
102
+ {label && (
103
+ <InputLabel id={`input_${name}`}>
104
+ <Typography variant="body2">{`${label} ${required ? "*" : ""}`}</Typography>
105
+ </InputLabel>
106
+ )}
107
+ <Select
108
+ label={label}
109
+ labelId={`input_${name}`}
110
+ value={value}
111
+ onBlur={handleTouched}
112
+ onChange={(e) => handleChange(e.target.value)}
113
+ IconComponent={loading ? () => <CircularProgress size={5} /> : ExpandMoreIcon}
114
+ multiple={!!multiple}
115
+ classes={{
116
+ select: multiple === "chips" ? "flex__wrap" : undefined,
117
+ }}
118
+ renderValue={(selected: string | Array<any> | any) => {
119
+ if (!selected || selected.length === 0) {
120
+ return <em>{label}</em>;
121
+ }
122
+ const text = [];
123
+ if (multiple) {
124
+ for (const opt of options) {
125
+ if (selected.includes(opt.value)) {
126
+ text.push([opt.text]);
127
+ }
128
+ }
129
+ if (multiple === "chips") {
130
+ return text.map((t, i) => (
131
+ <Chip key={`${t}_${i}`} label={t} style={{ margin: "0 2px" }} color="primary" />
132
+ ));
133
+ } else {
134
+ return text.join(",");
135
+ }
136
+ } else {
137
+ for (const opt of options) {
138
+ if (selected === opt.value) text.push([opt.text]);
139
+ }
140
+ return text.join(",");
141
+ }
142
+ }}
143
+ >
144
+ {placeholder && (
145
+ <MenuItem value="">
146
+ <em>{placeholder}</em>
147
+ </MenuItem>
148
+ )}
149
+ {options.map((op) => (
150
+ <MenuItem value={op.value} key={op.id || op.value}>
151
+ {multiple && <Checkbox checked={value.indexOf(op.value) > -1} color="primary" />}
152
+ {op.text}
153
+ </MenuItem>
154
+ ))}
155
+ </Select>
156
+ </FormControl>
157
+ <FormHelperText style={{ color: theme.palette.error.main }}>
158
+ {state.touched && !state.valid && state.errorMsg}
159
+ </FormHelperText>
160
+ </>
161
+ );
162
+ };
163
+
164
+ export { EditorSelect };
@@ -0,0 +1,207 @@
1
+ import { Avatar, Typography, useTheme } from "@mui/material";
2
+ import {
3
+ addDays,
4
+ endOfDay,
5
+ format,
6
+ isSameDay,
7
+ isSameMonth,
8
+ isWithinInterval,
9
+ setHours,
10
+ startOfDay,
11
+ startOfMonth,
12
+ } from "date-fns";
13
+ import { Fragment, useCallback } from "react";
14
+ import {
15
+ getHourFormat,
16
+ getRecurrencesForDate,
17
+ getResourcedEvents,
18
+ isTimeZonedToday,
19
+ sortEventsByTheEarliest,
20
+ } from "../../helpers/generals";
21
+ import useStore from "../../hooks/useStore";
22
+ import useSyncScroll from "../../hooks/useSyncScroll";
23
+ import { TableGrid } from "../../styles/styles";
24
+ import { DefaultResource } from "../../types";
25
+ import Cell from "../common/Cell";
26
+ import MonthEvents from "../events/MonthEvents";
27
+
28
+ type Props = {
29
+ daysList: Date[];
30
+ resource?: DefaultResource;
31
+ eachWeekStart: Date[];
32
+ };
33
+
34
+ const MonthTable = ({ daysList, resource, eachWeekStart }: Props) => {
35
+ const {
36
+ height,
37
+ month,
38
+ selectedDate,
39
+ events,
40
+ handleGotoDay,
41
+ resourceFields,
42
+ fields,
43
+ locale,
44
+ hourFormat,
45
+ stickyNavigation,
46
+ timeZone,
47
+ onClickMore,
48
+ stickyNavigationOffset,
49
+ stickyNavigationHeight,
50
+ } = useStore();
51
+ const { weekDays, startHour, endHour, cellRenderer, headRenderer, disableGoToDay } = month!;
52
+ const { headersRef, bodyRef } = useSyncScroll();
53
+
54
+ const theme = useTheme();
55
+ const monthStart = startOfMonth(selectedDate);
56
+ const hFormat = getHourFormat(hourFormat);
57
+ const CELL_HEIGHT = height / eachWeekStart.length;
58
+
59
+ const renderCells = useCallback(
60
+ (resource?: DefaultResource) => {
61
+ let resourcedEvents = sortEventsByTheEarliest(events);
62
+ if (resource) {
63
+ resourcedEvents = getResourcedEvents(events, resource, resourceFields, fields);
64
+ }
65
+ const rows: React.ReactNode[] = [];
66
+
67
+ for (const startDay of eachWeekStart) {
68
+ const cells = weekDays.map((d) => {
69
+ const today = addDays(startDay, d);
70
+ const start = new Date(`${format(setHours(today, startHour), `yyyy/MM/dd ${hFormat}`)}`);
71
+ const end = new Date(`${format(setHours(today, endHour), `yyyy/MM/dd ${hFormat}`)}`);
72
+ const field = resourceFields.idField;
73
+ const eachFirstDayInCalcRow = isSameDay(startDay, today) ? today : null;
74
+ const todayEvents = resourcedEvents
75
+ .flatMap((e) => getRecurrencesForDate(e, today))
76
+ .filter((e) => {
77
+ if (isSameDay(e.start, today)) return true;
78
+ const dayInterval = { start: startOfDay(e.start), end: endOfDay(e.end) };
79
+ if (eachFirstDayInCalcRow && isWithinInterval(eachFirstDayInCalcRow, dayInterval))
80
+ return true;
81
+ return false;
82
+ });
83
+ const isToday = isTimeZonedToday({ dateLeft: today, timeZone });
84
+ return (
85
+ <span style={{ height: CELL_HEIGHT }} key={d.toString()} className="rs__cell">
86
+ <Cell
87
+ start={start}
88
+ end={end}
89
+ day={selectedDate}
90
+ height={CELL_HEIGHT}
91
+ resourceKey={field}
92
+ resourceVal={resource ? resource[field] : null}
93
+ cellRenderer={cellRenderer}
94
+ />
95
+ <Fragment>
96
+ {typeof headRenderer === "function" ? (
97
+ <div style={{ position: "absolute", top: 0 }}>
98
+ {headRenderer({ day: today, events: resourcedEvents, resource })}
99
+ </div>
100
+ ) : (
101
+ <Avatar
102
+ style={{
103
+ width: 27,
104
+ height: 27,
105
+ position: "absolute",
106
+ top: 0,
107
+ background: isToday ? theme.palette.secondary.main : "transparent",
108
+ color: isToday ? theme.palette.secondary.contrastText : "",
109
+ marginBottom: 2,
110
+ }}
111
+ >
112
+ <Typography
113
+ color={!isSameMonth(today, monthStart) ? "#ccc" : "textPrimary"}
114
+ className={!disableGoToDay ? "rs__hover__op" : ""}
115
+ onClick={(e) => {
116
+ e.stopPropagation();
117
+ if (!disableGoToDay) {
118
+ handleGotoDay(today);
119
+ }
120
+ }}
121
+ >
122
+ {format(today, "dd")}
123
+ </Typography>
124
+ </Avatar>
125
+ )}
126
+
127
+ <MonthEvents
128
+ events={todayEvents}
129
+ resourceId={resource?.[field]}
130
+ today={today}
131
+ eachWeekStart={eachWeekStart}
132
+ eachFirstDayInCalcRow={eachFirstDayInCalcRow}
133
+ daysList={daysList}
134
+ onViewMore={(e) => {
135
+ if (onClickMore && typeof onClickMore === "function") {
136
+ onClickMore(e, handleGotoDay);
137
+ } else {
138
+ handleGotoDay(e);
139
+ }
140
+ }}
141
+ cellHeight={CELL_HEIGHT}
142
+ />
143
+ </Fragment>
144
+ </span>
145
+ );
146
+ });
147
+
148
+ rows.push(<Fragment key={startDay.toString()}>{cells}</Fragment>);
149
+ }
150
+ return rows;
151
+ },
152
+ [
153
+ CELL_HEIGHT,
154
+ cellRenderer,
155
+ daysList,
156
+ disableGoToDay,
157
+ eachWeekStart,
158
+ endHour,
159
+ events,
160
+ fields,
161
+ hFormat,
162
+ handleGotoDay,
163
+ headRenderer,
164
+ monthStart,
165
+ onClickMore,
166
+ resourceFields,
167
+ selectedDate,
168
+ startHour,
169
+ theme.palette.secondary.contrastText,
170
+ theme.palette.secondary.main,
171
+ timeZone,
172
+ weekDays,
173
+ ]
174
+ );
175
+
176
+ return (
177
+ <>
178
+ {/* Header Days */}
179
+ <TableGrid
180
+ days={daysList.length}
181
+ ref={headersRef}
182
+ indent="0"
183
+ sticky="1"
184
+ stickyNavigation={stickyNavigation}
185
+ stickyOffset={stickyNavigationOffset}
186
+ stickyHeight={stickyNavigationHeight}
187
+ >
188
+ {daysList.map((date, i) => (
189
+ <Typography
190
+ key={i}
191
+ className="rs__cell rs__header rs__header__center"
192
+ align="center"
193
+ variant="body2"
194
+ >
195
+ {format(date, "EE", { locale })}
196
+ </Typography>
197
+ ))}
198
+ </TableGrid>
199
+ {/* Time Cells */}
200
+ <TableGrid days={daysList.length} ref={bodyRef} indent="0">
201
+ {renderCells(resource)}
202
+ </TableGrid>
203
+ </>
204
+ );
205
+ };
206
+
207
+ export default MonthTable;
@@ -0,0 +1,77 @@
1
+ import { useState } from "react";
2
+ import DateProvider from "../hoc/DateProvider";
3
+ import { DateCalendar } from "@mui/x-date-pickers";
4
+ import { Button, Popover } from "@mui/material";
5
+ import { format, addDays } from "date-fns";
6
+ import { LocaleArrow } from "../common/LocaleArrow";
7
+ import useStore from "../../hooks/useStore";
8
+ import useArrowDisable from "../../hooks/useArrowDisable";
9
+
10
+ interface DayDateBtnProps {
11
+ selectedDate: Date;
12
+ onChange(value: Date): void;
13
+ }
14
+
15
+ const DayDateBtn = ({ selectedDate, onChange }: DayDateBtnProps) => {
16
+ const { locale, navigationPickerProps } = useStore();
17
+ const [anchorEl, setAnchorEl] = useState<HTMLButtonElement | null>(null);
18
+ const { prevDisabled, nextDisabled } = useArrowDisable();
19
+
20
+ const handleOpen = (event: React.MouseEvent<HTMLButtonElement>) => {
21
+ setAnchorEl(event.currentTarget);
22
+ };
23
+ const handleClose = () => {
24
+ setAnchorEl(null);
25
+ };
26
+
27
+ const handleChange = (e: Date | null) => {
28
+ onChange(e || new Date());
29
+ handleClose();
30
+ };
31
+
32
+ const handlePrev = () => {
33
+ const prevDay = addDays(selectedDate, -1);
34
+ onChange(prevDay);
35
+ };
36
+
37
+ const handleNext = () => {
38
+ const nexDay = addDays(selectedDate, 1);
39
+ onChange(nexDay);
40
+ };
41
+
42
+ return (
43
+ <>
44
+ <LocaleArrow
45
+ type="prev"
46
+ onClick={handlePrev}
47
+ disabled={prevDisabled}
48
+ aria-label="previous day"
49
+ />
50
+ <Button style={{ padding: 4 }} onClick={handleOpen} aria-label="selected date">
51
+ {format(selectedDate, "dd MMMM yyyy", { locale })}
52
+ </Button>
53
+ <Popover
54
+ open={Boolean(anchorEl)}
55
+ anchorEl={anchorEl}
56
+ onClose={handleClose}
57
+ anchorOrigin={{
58
+ vertical: "bottom",
59
+ horizontal: "left",
60
+ }}
61
+ >
62
+ <DateProvider>
63
+ <DateCalendar
64
+ {...navigationPickerProps}
65
+ openTo="day"
66
+ views={["month", "day"]}
67
+ value={selectedDate}
68
+ onChange={handleChange}
69
+ />
70
+ </DateProvider>
71
+ </Popover>
72
+ <LocaleArrow type="next" onClick={handleNext} disabled={nextDisabled} aria-label="next day" />
73
+ </>
74
+ );
75
+ };
76
+
77
+ export { DayDateBtn };
@@ -0,0 +1,80 @@
1
+ import { useState } from "react";
2
+ import DateProvider from "../hoc/DateProvider";
3
+ import { DateCalendar } from "@mui/x-date-pickers";
4
+ import { Button, Popover } from "@mui/material";
5
+ import { format, getMonth, setMonth } from "date-fns";
6
+ import { LocaleArrow } from "../common/LocaleArrow";
7
+ import useStore from "../../hooks/useStore";
8
+ import useArrowDisable from "../../hooks/useArrowDisable";
9
+
10
+ interface MonthDateBtnProps {
11
+ selectedDate: Date;
12
+ onChange(value: Date): void;
13
+ }
14
+
15
+ const MonthDateBtn = ({ selectedDate, onChange }: MonthDateBtnProps) => {
16
+ const { locale, navigationPickerProps } = useStore();
17
+ const currentMonth = getMonth(selectedDate);
18
+ const [anchorEl, setAnchorEl] = useState<HTMLButtonElement | null>(null);
19
+ const { prevDisabled, nextDisabled } = useArrowDisable();
20
+
21
+ const handleOpen = (event: React.MouseEvent<HTMLButtonElement>) => {
22
+ setAnchorEl(event.currentTarget);
23
+ };
24
+ const handleClose = () => {
25
+ setAnchorEl(null);
26
+ };
27
+
28
+ const handleChange = (e: Date | null) => {
29
+ onChange(e || new Date());
30
+ handleClose();
31
+ };
32
+ const handlePrev = () => {
33
+ const prevMonth = currentMonth - 1;
34
+ onChange(setMonth(selectedDate, prevMonth));
35
+ };
36
+ const handleNext = () => {
37
+ const nextMonth = currentMonth + 1;
38
+ onChange(setMonth(selectedDate, nextMonth));
39
+ };
40
+ return (
41
+ <>
42
+ <LocaleArrow
43
+ type="prev"
44
+ onClick={handlePrev}
45
+ disabled={prevDisabled}
46
+ aria-label="previous month"
47
+ />
48
+ <Button style={{ padding: 4 }} onClick={handleOpen} aria-label="selected month">
49
+ {format(selectedDate, "MMMM yyyy", { locale })}
50
+ </Button>
51
+ <Popover
52
+ open={Boolean(anchorEl)}
53
+ anchorEl={anchorEl}
54
+ onClose={handleClose}
55
+ anchorOrigin={{
56
+ vertical: "bottom",
57
+ horizontal: "left",
58
+ }}
59
+ >
60
+ <DateProvider>
61
+ <DateCalendar
62
+ {...navigationPickerProps}
63
+ openTo="month"
64
+ views={["year", "month"]}
65
+ value={selectedDate}
66
+ onChange={handleChange}
67
+ />
68
+ </DateProvider>
69
+ </Popover>
70
+ <LocaleArrow
71
+ type="next"
72
+ onClick={handleNext}
73
+ disabled={nextDisabled}
74
+ aria-label="next month"
75
+ />
76
+ </>
77
+ );
78
+ };
79
+
80
+ export { MonthDateBtn };