@daryasidorovich/modsen-datepicker 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.
Files changed (183) hide show
  1. package/.babelrc.json +7 -0
  2. package/.github/workflows/chromatic.yml +25 -0
  3. package/.github/workflows/ci.yml +56 -0
  4. package/.husky/commit-msg +1 -0
  5. package/.husky/pre-commit +1 -0
  6. package/.prettierignore +16 -0
  7. package/.prettierrc +6 -0
  8. package/.storybook/main.js +93 -0
  9. package/.storybook/preview-head.html +6 -0
  10. package/.storybook/preview.js +26 -0
  11. package/.storybook/preview.tsx +18 -0
  12. package/README.md +302 -0
  13. package/commitlint.config.js +23 -0
  14. package/dist/components/BaseCalendar/index.d.ts +2 -0
  15. package/dist/components/CalendarGrid/CalendarGridWithTasks/Tooltip/index.d.ts +8 -0
  16. package/dist/components/CalendarGrid/CalendarGridWithTasks/index.d.ts +2 -0
  17. package/dist/components/CalendarGrid/CalendarGridWithoutTasks/CalendarGridWithoutTasks.test.d.ts +1 -0
  18. package/dist/components/CalendarGrid/CalendarGridWithoutTasks/index.d.ts +4 -0
  19. package/dist/components/CalendarGrid/index.d.ts +2 -0
  20. package/dist/components/CalendarGrid/types.d.ts +21 -0
  21. package/dist/components/Clear/index.d.ts +5 -0
  22. package/dist/components/DatePicker/index.d.ts +5 -0
  23. package/dist/components/Day/index.d.ts +16 -0
  24. package/dist/components/Input/index.d.ts +10 -0
  25. package/dist/components/Modal/CrudModal/index.d.ts +2 -0
  26. package/dist/components/Modal/index.d.ts +12 -0
  27. package/dist/components/Modal/types.d.ts +12 -0
  28. package/dist/components/Month/index.d.ts +7 -0
  29. package/dist/components/RangePicker/index.d.ts +6 -0
  30. package/dist/components/TasksManager/AddModal/index.d.ts +6 -0
  31. package/dist/components/TasksManager/DeleteModal/index.d.ts +6 -0
  32. package/dist/components/TasksManager/EditModal/index.d.ts +7 -0
  33. package/dist/components/TasksManager/index.d.ts +9 -0
  34. package/dist/components/Week/index.d.ts +17 -0
  35. package/dist/components/WeekDay/index.d.ts +7 -0
  36. package/dist/constants/dateMask.d.ts +21 -0
  37. package/dist/constants/firstDayOfWeek.d.ts +5 -0
  38. package/dist/constants/holidays.d.ts +1 -0
  39. package/dist/constants/modes.d.ts +5 -0
  40. package/dist/constants/monthsNames.d.ts +1 -0
  41. package/dist/constants/weekdaysNames.d.ts +1 -0
  42. package/dist/hooks/useCalendar.d.ts +7 -0
  43. package/dist/hooks/useLockBodyScroll.d.ts +2 -0
  44. package/dist/hooks/useModal.d.ts +13 -0
  45. package/dist/hooks/useTooltip.d.ts +9 -0
  46. package/dist/hooks/useValidate.d.ts +11 -0
  47. package/dist/index.cjs +17250 -0
  48. package/dist/index.cjs.map +1 -0
  49. package/dist/index.d.ts +11 -0
  50. package/dist/index.js +17233 -0
  51. package/dist/index.js.map +1 -0
  52. package/dist/services/calendarService.d.ts +19 -0
  53. package/dist/services/decorators/withHolidays.d.ts +2 -0
  54. package/dist/services/decorators/withHolidays.test.d.ts +1 -0
  55. package/dist/services/decorators/withMinMax.d.ts +2 -0
  56. package/dist/services/decorators/withMinMax.test.d.ts +1 -0
  57. package/dist/services/holidayService.d.ts +5 -0
  58. package/dist/setupTests.d.ts +1 -0
  59. package/dist/store/localStorage.d.ts +3 -0
  60. package/dist/store/tasksStore.d.ts +8 -0
  61. package/dist/store/tasksStore.test.d.ts +1 -0
  62. package/dist/store/types.d.ts +18 -0
  63. package/dist/utils/calendar.d.ts +7 -0
  64. package/dist/utils/calendar.test.d.ts +1 -0
  65. package/dist/utils/controlDate.d.ts +1 -0
  66. package/dist/utils/dateChecks.d.ts +10 -0
  67. package/dist/utils/dateChecks.test.d.ts +1 -0
  68. package/dist/utils/formatDate.d.ts +3 -0
  69. package/dist/utils/formatDate.test.d.ts +1 -0
  70. package/dist/utils/schema.d.ts +6 -0
  71. package/dist/utils/schema.test.d.ts +1 -0
  72. package/dist/utils/weekdayOrder.d.ts +1 -0
  73. package/dist/utils/weekdayOrder.test.d.ts +1 -0
  74. package/docs/base-calendar.png +0 -0
  75. package/docs/dark-theme.png +0 -0
  76. package/docs/datepicker.png +0 -0
  77. package/docs/rangepicker-with-error.png +0 -0
  78. package/docs/rangepicker.png +0 -0
  79. package/eslint.config.ts +80 -0
  80. package/jest.config.json +21 -0
  81. package/package.json +106 -0
  82. package/rollup.config.js +65 -0
  83. package/src/assets/actions/Delete.svg +4 -0
  84. package/src/assets/actions/Edit.svg +3 -0
  85. package/src/assets/arrows/Next.svg +4 -0
  86. package/src/assets/arrows/Prev.svg +4 -0
  87. package/src/assets/input/Calendar.svg +3 -0
  88. package/src/assets/input/Clear.svg +3 -0
  89. package/src/components/BaseCalendar/BaseCalendar.stories.tsx +77 -0
  90. package/src/components/BaseCalendar/index.tsx +63 -0
  91. package/src/components/BaseCalendar/styles.module.scss +15 -0
  92. package/src/components/CalendarGrid/CalendarGrid.stories.tsx +136 -0
  93. package/src/components/CalendarGrid/CalendarGridWithTasks/Tooltip/index.tsx +27 -0
  94. package/src/components/CalendarGrid/CalendarGridWithTasks/Tooltip/styles.module.scss +9 -0
  95. package/src/components/CalendarGrid/CalendarGridWithTasks/index.tsx +51 -0
  96. package/src/components/CalendarGrid/CalendarGridWithoutTasks/CalendarGridWithoutTasks.test.tsx +223 -0
  97. package/src/components/CalendarGrid/CalendarGridWithoutTasks/index.tsx +36 -0
  98. package/src/components/CalendarGrid/CalendarGridWithoutTasks/styles.module.scss +11 -0
  99. package/src/components/CalendarGrid/index.tsx +19 -0
  100. package/src/components/CalendarGrid/types.ts +26 -0
  101. package/src/components/Clear/Clear.stories.tsx +16 -0
  102. package/src/components/Clear/index.tsx +12 -0
  103. package/src/components/Clear/styles.module.scss +34 -0
  104. package/src/components/DatePicker/DatePicker.stories.tsx +127 -0
  105. package/src/components/DatePicker/index.tsx +72 -0
  106. package/src/components/DatePicker/styles.module.scss +10 -0
  107. package/src/components/Day/Day.stories.tsx +75 -0
  108. package/src/components/Day/index.tsx +95 -0
  109. package/src/components/Day/styles.module.scss +97 -0
  110. package/src/components/Input/Input.stories.tsx +37 -0
  111. package/src/components/Input/index.tsx +64 -0
  112. package/src/components/Input/styles.module.scss +79 -0
  113. package/src/components/Modal/CrudModal/index.tsx +43 -0
  114. package/src/components/Modal/CrudModal/styles.module.scss +44 -0
  115. package/src/components/Modal/index.tsx +66 -0
  116. package/src/components/Modal/styles.module.scss +40 -0
  117. package/src/components/Modal/types.ts +13 -0
  118. package/src/components/Month/Month.stories.tsx +19 -0
  119. package/src/components/Month/index.tsx +31 -0
  120. package/src/components/Month/styles.module.scss +30 -0
  121. package/src/components/RangePicker/RangePicker.stories.tsx +134 -0
  122. package/src/components/RangePicker/index.tsx +144 -0
  123. package/src/components/RangePicker/styles.module.scss +19 -0
  124. package/src/components/TasksManager/AddModal/index.tsx +62 -0
  125. package/src/components/TasksManager/DeleteModal/index.tsx +22 -0
  126. package/src/components/TasksManager/EditModal/index.tsx +96 -0
  127. package/src/components/TasksManager/index.tsx +112 -0
  128. package/src/components/TasksManager/styles.module.scss +62 -0
  129. package/src/components/Week/Week.stories.tsx +49 -0
  130. package/src/components/Week/index.tsx +68 -0
  131. package/src/components/Week/styles.module.scss +7 -0
  132. package/src/components/WeekDay/WeekDay.stories.tsx +38 -0
  133. package/src/components/WeekDay/index.tsx +35 -0
  134. package/src/components/WeekDay/styles.module.scss +19 -0
  135. package/src/constants/dateMask.ts +23 -0
  136. package/src/constants/firstDayOfWeek.ts +7 -0
  137. package/src/constants/holidays.ts +32 -0
  138. package/src/constants/modes.ts +6 -0
  139. package/src/constants/monthsNames.ts +14 -0
  140. package/src/constants/weekdaysNames.ts +1 -0
  141. package/src/hooks/useCalendar.ts +62 -0
  142. package/src/hooks/useLockBodyScroll.ts +19 -0
  143. package/src/hooks/useModal.ts +45 -0
  144. package/src/hooks/useTooltip.ts +41 -0
  145. package/src/hooks/useValidate.ts +80 -0
  146. package/src/index.ts +15 -0
  147. package/src/services/calendarService.ts +62 -0
  148. package/src/services/decorators/withHolidays.test.ts +64 -0
  149. package/src/services/decorators/withHolidays.ts +12 -0
  150. package/src/services/decorators/withMinMax.test.ts +77 -0
  151. package/src/services/decorators/withMinMax.ts +19 -0
  152. package/src/services/holidayService.ts +56 -0
  153. package/src/setupTests.ts +1 -0
  154. package/src/store/localStorage.ts +33 -0
  155. package/src/store/tasksStore.test.tsx +208 -0
  156. package/src/store/tasksStore.tsx +146 -0
  157. package/src/store/types.ts +22 -0
  158. package/src/styles/_border.scss +9 -0
  159. package/src/styles/_colors.scss +53 -0
  160. package/src/styles/_gap.scss +4 -0
  161. package/src/styles/_globals.scss +61 -0
  162. package/src/styles/_grid.scss +9 -0
  163. package/src/styles/_mixins.scss +6 -0
  164. package/src/styles/_reset.scss +98 -0
  165. package/src/styles/_theme-mixins.scss +51 -0
  166. package/src/styles/_typography.scss +10 -0
  167. package/src/styles/_variables.scss +8 -0
  168. package/src/styles/main.scss +3 -0
  169. package/src/types/types.d.ts +15 -0
  170. package/src/utils/calendar.test.ts +154 -0
  171. package/src/utils/calendar.ts +82 -0
  172. package/src/utils/controlDate.ts +3 -0
  173. package/src/utils/dateChecks.test.ts +216 -0
  174. package/src/utils/dateChecks.ts +75 -0
  175. package/src/utils/formatDate.test.ts +57 -0
  176. package/src/utils/formatDate.ts +27 -0
  177. package/src/utils/schema.test.ts +40 -0
  178. package/src/utils/schema.ts +8 -0
  179. package/src/utils/weekdayOrder.test.ts +11 -0
  180. package/src/utils/weekdayOrder.ts +7 -0
  181. package/tsconfig.json +31 -0
  182. package/vitest.config.ts +38 -0
  183. package/vitest.shims.d.ts +1 -0
@@ -0,0 +1,96 @@
1
+ import { zodResolver } from '@hookform/resolvers/zod';
2
+ import { useEffect } from 'react';
3
+ import { useForm } from 'react-hook-form';
4
+
5
+ import { Input } from '@/components/Input';
6
+ import { ModalProps } from '@/components/Modal';
7
+ import { CrudModal } from '@/components/Modal/CrudModal';
8
+ import { useTasks } from '@/store/tasksStore';
9
+ import { formatDate } from '@/utils/formatDate';
10
+ import { FormData } from '@/utils/schema';
11
+ import { formSchema } from '@/utils/schema';
12
+
13
+ interface EditModalProps extends Pick<ModalProps, 'isOpen' | 'onClose'> {
14
+ id: string;
15
+ date: Date;
16
+ }
17
+
18
+ export const EditModal = ({ isOpen, onClose, id, date }: EditModalProps) => {
19
+ const { editTask, tasks } = useTasks();
20
+ const {
21
+ register,
22
+ handleSubmit,
23
+ reset,
24
+ formState: { errors },
25
+ } = useForm<FormData>({
26
+ resolver: zodResolver(formSchema),
27
+ defaultValues: {
28
+ title: '',
29
+ date: formatDate(date),
30
+ },
31
+ });
32
+
33
+ const onSubmit = (data: FormData) => {
34
+ const taskData = {
35
+ title: data.title,
36
+ date: new Date(data.date),
37
+ };
38
+ editTask(id, taskData);
39
+ reset({
40
+ title: '',
41
+ date: formatDate(date),
42
+ });
43
+ onClose();
44
+ };
45
+
46
+ useEffect(() => {
47
+ if (isOpen && id) {
48
+ const task = tasks.find((task) => task.id === id);
49
+ if (task) {
50
+ reset({
51
+ title: task.title,
52
+ date: formatDate(task.date),
53
+ });
54
+ } else {
55
+ reset({
56
+ title: '',
57
+ date: formatDate(date),
58
+ });
59
+ }
60
+ }
61
+ }, [isOpen, id, tasks, reset, date]);
62
+
63
+ const handleClose = () => {
64
+ reset({
65
+ title: '',
66
+ date: formatDate(date),
67
+ });
68
+ onClose();
69
+ };
70
+
71
+ return (
72
+ <CrudModal
73
+ isOpen={isOpen}
74
+ onClose={handleClose}
75
+ onConfirm={handleSubmit(onSubmit)}
76
+ showCloseButton={true}
77
+ confirmText="Edit"
78
+ cancelText="Cancel"
79
+ title="Edit task"
80
+ >
81
+ <Input
82
+ {...register('title')}
83
+ label="Title"
84
+ placeholder="Training name"
85
+ error={errors.title?.message}
86
+ />
87
+ <Input
88
+ {...register('date')}
89
+ label="Date"
90
+ type="date"
91
+ placeholder="Training date"
92
+ error={errors.date?.message}
93
+ />
94
+ </CrudModal>
95
+ );
96
+ };
@@ -0,0 +1,112 @@
1
+ import Delete from '@assets/actions/Delete.svg?react';
2
+ import Edit from '@assets/actions/Edit.svg?react';
3
+ import classNames from 'classnames';
4
+ import { useState } from 'react';
5
+
6
+ import { useModal } from '@/hooks/useModal';
7
+ import { useTasks } from '@/store/tasksStore';
8
+ import { Task } from '@/store/types';
9
+
10
+ import { Modal } from '../Modal';
11
+ import { AddModal } from './AddModal';
12
+ import { DeleteModal } from './DeleteModal';
13
+ import { EditModal } from './EditModal';
14
+ import styles from './styles.module.scss';
15
+
16
+ interface TasksManager {
17
+ tasks: Task[];
18
+ onClose: () => void;
19
+ isOpen: boolean;
20
+ selectedDate: Date;
21
+ }
22
+
23
+ export const TasksManager = ({
24
+ onClose,
25
+ isOpen,
26
+ tasks,
27
+ selectedDate,
28
+ }: TasksManager) => {
29
+ const addModal = useModal();
30
+ const editModal = useModal();
31
+ const deleteModal = useModal();
32
+ const { toggleTask } = useTasks();
33
+
34
+ const [currentTaskId, setCurrentTaskId] = useState<string>('');
35
+
36
+ const handleAddTask = () => {
37
+ addModal.open();
38
+ };
39
+ const handleEditTask = (id: string) => () => {
40
+ setCurrentTaskId(id);
41
+ editModal.open();
42
+ };
43
+
44
+ const handleDeleteTask = (id: string) => () => {
45
+ setCurrentTaskId(id);
46
+ deleteModal.open();
47
+ };
48
+
49
+ const handleToggle = (id: string) => () => {
50
+ toggleTask(id);
51
+ };
52
+ return (
53
+ <>
54
+ <Modal isOpen={isOpen} onClose={onClose}>
55
+ <div className={styles.tasksManager}>
56
+ <h1 className={styles.header}>Tasks for the day:</h1>
57
+ <button className={styles.add} onClick={handleAddTask}>
58
+ Add task
59
+ </button>
60
+ <ul className={styles.tasks}>
61
+ {tasks.length < 1 && (
62
+ <li>
63
+ <p>No tasks added</p>
64
+ </li>
65
+ )}
66
+ {tasks.map(({ id, title, completed }) => (
67
+ <li
68
+ key={id}
69
+ className={classNames(styles.task, {
70
+ [styles.checked]: completed,
71
+ })}
72
+ >
73
+ <input
74
+ type="checkbox"
75
+ checked={completed}
76
+ onChange={handleToggle(id)}
77
+ />
78
+ <p className={styles.title}>{title}</p>
79
+ <button onClick={handleEditTask(id)} className={styles.action}>
80
+ <Edit />
81
+ </button>
82
+ <button
83
+ onClick={handleDeleteTask(id)}
84
+ className={classNames(styles.action, styles.delete)}
85
+ >
86
+ <Delete />
87
+ </button>
88
+ </li>
89
+ ))}
90
+ </ul>
91
+ </div>
92
+ </Modal>
93
+ <AddModal
94
+ isOpen={addModal.isOpen}
95
+ onClose={addModal.close}
96
+ date={selectedDate}
97
+ />
98
+
99
+ <EditModal
100
+ isOpen={editModal.isOpen}
101
+ onClose={editModal.close}
102
+ id={currentTaskId}
103
+ date={selectedDate}
104
+ />
105
+ <DeleteModal
106
+ isOpen={deleteModal.isOpen}
107
+ onClose={deleteModal.close}
108
+ id={currentTaskId}
109
+ />
110
+ </>
111
+ );
112
+ };
@@ -0,0 +1,62 @@
1
+ .tasksManager {
2
+ min-width: 300px;
3
+ width: fit-content;
4
+ display: flex;
5
+ flex-direction: column;
6
+ gap: $gap-24;
7
+ max-height: 70%;
8
+ }
9
+
10
+ .tasks {
11
+ display: flex;
12
+ flex-direction: column;
13
+ gap: $gap-8;
14
+ }
15
+
16
+ .task {
17
+ display: flex;
18
+ gap: $gap-8;
19
+ justify-content: space-between;
20
+ align-items: center;
21
+ }
22
+
23
+ .checked {
24
+ @include themify-modules($themes) {
25
+ color: themed('disabled');
26
+ }
27
+ }
28
+
29
+ .title {
30
+ margin-right: auto;
31
+ }
32
+
33
+ .action {
34
+ border: none;
35
+ background-color: transparent;
36
+
37
+ @include themify-modules($themes) {
38
+ color: themed('text');
39
+ }
40
+ }
41
+
42
+ .delete {
43
+ @include themify-modules($themes) {
44
+ color: themed('accent');
45
+ }
46
+ }
47
+
48
+ .add {
49
+ @include themify-modules($themes) {
50
+ background-color: themed('middle-blue');
51
+ border-color: themed('border');
52
+ color: themed('white-color');
53
+ }
54
+
55
+ @include border;
56
+
57
+ border-radius: $radius-8;
58
+ }
59
+
60
+ .header {
61
+ font-size: $font-size-18;
62
+ }
@@ -0,0 +1,49 @@
1
+ import type { Meta, StoryObj } from '@storybook/react';
2
+ import { Week } from '.';
3
+
4
+ const meta: Meta<typeof Week> = {
5
+ component: Week,
6
+ parameters: {
7
+ layout: 'centered',
8
+ },
9
+ };
10
+
11
+ export default meta;
12
+
13
+ const weekDaysArr = [
14
+ new Date(2026, 7, 25),
15
+ new Date(2026, 7, 26),
16
+ new Date(2026, 7, 27),
17
+ new Date(2026, 7, 28),
18
+ new Date(2026, 7, 29),
19
+ new Date(2026, 7, 30),
20
+ new Date(2026, 7, 31),
21
+ ];
22
+
23
+ const baseArgs = {
24
+ weekDays: weekDaysArr,
25
+ getIsCurrentMonth: () => {
26
+ return true;
27
+ },
28
+ };
29
+
30
+ type Story = StoryObj<typeof Week>;
31
+
32
+ export const Default: Story = {
33
+ args: { ...baseArgs },
34
+ };
35
+
36
+ export const WithSelectedDate: Story = {
37
+ args: {
38
+ ...baseArgs,
39
+ selectedDate: new Date(2026, 7, 28),
40
+ },
41
+ };
42
+
43
+ export const WithRange: Story = {
44
+ args: {
45
+ ...baseArgs,
46
+ rangeStart: new Date(2026, 7, 26),
47
+ rangeEnd: new Date(2026, 7, 30),
48
+ },
49
+ };
@@ -0,0 +1,68 @@
1
+ import {
2
+ isInRange,
3
+ isRangeEnd,
4
+ isRangeStart,
5
+ isSelected,
6
+ } from '@/utils/dateChecks';
7
+
8
+ import { Day } from '../Day';
9
+ import styles from './styles.module.scss';
10
+
11
+ export interface WeekProps {
12
+ weekDays: Date[];
13
+ selectedDate?: Date;
14
+ rangeStart?: Date;
15
+ rangeEnd?: Date;
16
+ getIsHoliday?: (date: Date) => boolean;
17
+ getHasTask?: (date: Date) => boolean;
18
+ getIsDisabled?: (date: Date) => boolean;
19
+ getIsCurrentMonth: (date: Date) => boolean;
20
+ getIsWeekend?: (date: Date) => boolean;
21
+ hasTasks?: (date: Date) => boolean;
22
+ onDateClick?: (e: React.MouseEvent<HTMLButtonElement>, date: Date) => void;
23
+ onDateDoubleClick?: (date: Date) => void;
24
+ onMouseEnter?: (e: React.MouseEvent<HTMLButtonElement>) => void;
25
+ onMouseLeave?: () => void;
26
+ }
27
+
28
+ export const Week = ({
29
+ weekDays,
30
+ selectedDate,
31
+ getIsHoliday,
32
+ getIsDisabled,
33
+ getIsCurrentMonth,
34
+ rangeStart,
35
+ rangeEnd,
36
+ hasTasks,
37
+ onDateClick,
38
+ onDateDoubleClick,
39
+ onMouseEnter,
40
+ onMouseLeave,
41
+ }: WeekProps) => {
42
+ return (
43
+ <div className={styles.week}>
44
+ {weekDays.map((date) => {
45
+ return (
46
+ <Day
47
+ key={date.toISOString()}
48
+ date={date}
49
+ isSelected={isSelected(date, selectedDate)}
50
+ isDisabled={getIsDisabled?.(date) || false}
51
+ isCurrentMonth={getIsCurrentMonth(date)}
52
+ isHoliday={getIsHoliday?.(date) || false}
53
+ hasTasks={hasTasks?.(date) || false}
54
+ isRangeStart={isRangeStart(date, rangeStart)}
55
+ isRangeEnd={isRangeEnd(date, rangeEnd)}
56
+ isRangeMiddle={isInRange(date, rangeStart, rangeEnd)}
57
+ onClick={getIsDisabled?.(date) ? undefined : onDateClick}
58
+ onDoubleClick={
59
+ getIsDisabled?.(date) ? undefined : onDateDoubleClick
60
+ }
61
+ onMouseEnter={getIsDisabled?.(date) ? undefined : onMouseEnter}
62
+ onMouseLeave={getIsDisabled?.(date) ? undefined : onMouseLeave}
63
+ />
64
+ );
65
+ })}
66
+ </div>
67
+ );
68
+ };
@@ -0,0 +1,7 @@
1
+ .week {
2
+ display: flex;
3
+
4
+ @include themify-modules($themes) {
5
+ background-color: themed('background');
6
+ }
7
+ }
@@ -0,0 +1,38 @@
1
+ import type { Meta, StoryObj } from '@storybook/react';
2
+ import { WeekDay } from '.';
3
+
4
+ const meta: Meta<typeof WeekDay> = {
5
+ component: WeekDay,
6
+ parameters: {
7
+ layout: 'centered',
8
+ },
9
+ argTypes: {
10
+ firstDayOfWeek: {
11
+ control: 'select',
12
+ options: [0, 1],
13
+ },
14
+ },
15
+ };
16
+
17
+ export default meta;
18
+
19
+ type Story = StoryObj<typeof WeekDay>;
20
+
21
+ export const MondayFirst: Story = {
22
+ args: {
23
+ firstDayOfWeek: 1,
24
+ },
25
+ };
26
+
27
+ export const SundayFirst: Story = {
28
+ args: {
29
+ firstDayOfWeek: 0,
30
+ },
31
+ };
32
+
33
+ export const HideWeekends: Story = {
34
+ args: {
35
+ firstDayOfWeek: 0,
36
+ hideWeekends: true,
37
+ },
38
+ };
@@ -0,0 +1,35 @@
1
+ import classNames from 'classnames';
2
+
3
+ import { WEEKDAYS_NAMES } from '@/constants/weekdaysNames';
4
+ import { getOrderedDays } from '@/utils/weekdayOrder';
5
+
6
+ import styles from './styles.module.scss';
7
+
8
+ interface WeekDayProps {
9
+ firstDayOfWeek?: 0 | 1;
10
+ className?: string;
11
+ hideWeekends?: boolean;
12
+ }
13
+
14
+ export const WeekDay = ({
15
+ firstDayOfWeek = 1,
16
+ className = '',
17
+ hideWeekends = false,
18
+ }: WeekDayProps) => {
19
+ let days = getOrderedDays(firstDayOfWeek);
20
+
21
+ if (hideWeekends) {
22
+ days = days.filter((dayIndex) => dayIndex !== 0 && dayIndex !== 6);
23
+ }
24
+ return (
25
+ <div className={classNames(styles.weekDay, className)}>
26
+ {days.map((index) => {
27
+ return (
28
+ <span key={WEEKDAYS_NAMES[index]} className={styles.dayName}>
29
+ {WEEKDAYS_NAMES[index]}
30
+ </span>
31
+ );
32
+ })}
33
+ </div>
34
+ );
35
+ };
@@ -0,0 +1,19 @@
1
+ .weekDay {
2
+ display: flex;
3
+ }
4
+
5
+ .dayName {
6
+ @include square(32px);
7
+
8
+ @include themify-modules($themes) {
9
+ background-color: themed('background');
10
+ }
11
+
12
+ font-size: $font-size-13;
13
+ padding: $gap-10;
14
+ border-radius: $radius-8;
15
+ display: flex;
16
+ align-items: center;
17
+ justify-content: center;
18
+ font-weight: $font-weight-semibold;
19
+ }
@@ -0,0 +1,23 @@
1
+ import { IMask } from 'react-imask';
2
+
3
+ export const DATE_MASK = {
4
+ mask: 'dd/mm/yyyy',
5
+ blocks: {
6
+ dd: {
7
+ mask: IMask.MaskedRange,
8
+ from: 1,
9
+ to: 31,
10
+ },
11
+ mm: {
12
+ mask: IMask.MaskedRange,
13
+ from: 1,
14
+ to: 12,
15
+ },
16
+ yyyy: {
17
+ mask: IMask.MaskedRange,
18
+ from: 1900,
19
+ to: 2100,
20
+ },
21
+ },
22
+ autofix: true,
23
+ } as const;
@@ -0,0 +1,7 @@
1
+ export const FIRST_DAY_OF_WEEK = {
2
+ MONDAY: 1,
3
+ SUNDAY: 0,
4
+ } as const;
5
+
6
+ export type FIRST_DAY_OF_WEEK_TYPES =
7
+ (typeof FIRST_DAY_OF_WEEK)[keyof typeof FIRST_DAY_OF_WEEK];
@@ -0,0 +1,32 @@
1
+ const HOLIDAYS_RULES = [
2
+ { name: 'New Year', getDate: (year: number) => new Date(year, 0, 1) },
3
+ { name: 'Christmas', getDate: (year: number) => new Date(year, 11, 25) },
4
+ {
5
+ name: 'Chinese New Year',
6
+ getDate: (year: number) => new Date(year, 1, 17),
7
+ },
8
+ {
9
+ name: "International Women's Day",
10
+ getDate: (year: number) => new Date(year, 2, 8),
11
+ },
12
+ {
13
+ name: 'Defender of the Fatherland Day',
14
+ getDate: (year: number) => new Date(year, 1, 23),
15
+ },
16
+ { name: "Children's Day", getDate: (year: number) => new Date(year, 5, 1) },
17
+ { name: 'Labor Day', getDate: (year: number) => new Date(year, 4, 1) },
18
+ {
19
+ name: 'Thanksgiving Day',
20
+ getDate: (year: number) => new Date(year, 10, 26),
21
+ },
22
+ { name: 'Easter', getDate: (year: number) => new Date(year, 3, 5) },
23
+ { name: 'Boxing Day', getDate: (year: number) => new Date(year, 11, 26) },
24
+ { name: 'Eid al-Fitr', getDate: (year: number) => new Date(year, 2, 20) },
25
+ { name: 'Eid al-Adha', getDate: (year: number) => new Date(year, 4, 27) },
26
+ { name: 'Nowruz', getDate: (year: number) => new Date(year, 2, 21) },
27
+ { name: 'Bastille Day', getDate: (year: number) => new Date(year, 6, 14) },
28
+ ];
29
+
30
+ export const getDefaultHolidays = (year: number): Date[] => {
31
+ return HOLIDAYS_RULES.map((rule) => rule.getDate(year));
32
+ };
@@ -0,0 +1,6 @@
1
+ export const MODES = {
2
+ WEEK: 'week',
3
+ MONTH: 'month',
4
+ } as const;
5
+
6
+ export type MODES_TYPES = (typeof MODES)[keyof typeof MODES];
@@ -0,0 +1,14 @@
1
+ export const MONTHS_NAMES = [
2
+ 'January',
3
+ 'February',
4
+ 'March',
5
+ 'April',
6
+ 'May',
7
+ 'June',
8
+ 'July',
9
+ 'August',
10
+ 'September',
11
+ 'October',
12
+ 'November',
13
+ 'December',
14
+ ];
@@ -0,0 +1 @@
1
+ export const WEEKDAYS_NAMES = ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa'];
@@ -0,0 +1,62 @@
1
+ import { useEffect, useState } from 'react';
2
+
3
+ import { CalendarProps } from '@/components/Calendar/types';
4
+ import { getDefaultHolidays } from '@/constants/holidays';
5
+ import { CalendarService } from '@/services/calendarService';
6
+ import { withHolidays } from '@/services/decorators/withHolidays';
7
+ import { withMinMax } from '@/services/decorators/withMinMax';
8
+ import { getHolidays } from '@/services/holidayService';
9
+
10
+ export function useCalendar(props: CalendarProps) {
11
+ const year = props.monthDate.getFullYear();
12
+ const [holidays, setHolidays] = useState<Date[]>([]);
13
+ const [loading, setLoading] = useState(false);
14
+ const [error, setError] = useState('');
15
+
16
+ useEffect(() => {
17
+ const abortController = new AbortController();
18
+
19
+ if (props.showHolidays) {
20
+ setLoading(true);
21
+ setError('');
22
+ getHolidays(year, abortController.signal)
23
+ .then((result) => {
24
+ setHolidays(result);
25
+ })
26
+ .catch((err) => {
27
+ if (err.name === 'AbortError') {
28
+ return;
29
+ }
30
+ setHolidays(getDefaultHolidays(year));
31
+ setError(err.message);
32
+ })
33
+ .finally(() => setLoading(false));
34
+ }
35
+ return () => {
36
+ abortController.abort();
37
+ };
38
+ }, [props.showHolidays, year]);
39
+
40
+ const calendar: CalendarService = new CalendarService(
41
+ props.monthDate,
42
+ props.firstDayOfWeek,
43
+ props.selectedDate,
44
+ props.mode,
45
+ props.rangeStart,
46
+ props.rangeEnd
47
+ );
48
+
49
+ if (props.hideWeekends) {
50
+ calendar.setHideWeekends(true);
51
+ }
52
+
53
+ if (props.showHolidays && holidays.length > 0) {
54
+ withHolidays(calendar, holidays);
55
+ }
56
+
57
+ if (props.minDate || props.maxDate) {
58
+ withMinMax(calendar, props.minDate, props.maxDate);
59
+ }
60
+
61
+ return { calendar, loading, error };
62
+ }
@@ -0,0 +1,19 @@
1
+ import { useEffect } from 'react';
2
+
3
+ export const useLockBodyScroll = (isLocked: boolean) => {
4
+ useEffect(() => {
5
+ const originalOverflow = document.body.style.overflow;
6
+
7
+ if (isLocked) {
8
+ document.body.style.overflow = 'hidden';
9
+ } else {
10
+ document.body.style.overflow = 'unset';
11
+ }
12
+
13
+ return () => {
14
+ document.body.style.overflow = originalOverflow;
15
+ };
16
+ }, [isLocked]);
17
+ };
18
+
19
+ export default useLockBodyScroll;