@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,127 @@
1
+ import type { Meta, StoryObj } from '@storybook/react';
2
+ import { DatePicker } from './index';
3
+ import { setControlDate } from '../../utils/controlDate';
4
+
5
+ const meta: Meta<typeof DatePicker> = {
6
+ component: DatePicker,
7
+ parameters: {
8
+ layout: 'centered',
9
+ },
10
+ argTypes: {
11
+ monthDate: { control: 'date' },
12
+ selectedDate: { control: 'date' },
13
+ minDate: { control: 'date' },
14
+ maxDate: { control: 'date' },
15
+ firstDayOfWeek: {
16
+ control: 'select',
17
+ options: [0, 1],
18
+ },
19
+ },
20
+ };
21
+
22
+ export default meta;
23
+ type Story = StoryObj<typeof DatePicker>;
24
+
25
+ export const Default: Story = {
26
+ args: {
27
+ monthDate: new Date(2026, 7, 1),
28
+ firstDayOfWeek: 1,
29
+ selectedDate: new Date(2026, 7, 15),
30
+ },
31
+ render: (args) => (
32
+ <DatePicker
33
+ {...args}
34
+ monthDate={new Date(args.monthDate)}
35
+ selectedDate={setControlDate(args.selectedDate)}
36
+ minDate={setControlDate(args.minDate)}
37
+ maxDate={setControlDate(args.maxDate)}
38
+ />
39
+ ),
40
+ };
41
+
42
+ export const WithTasks: Story = {
43
+ args: {
44
+ monthDate: new Date(2026, 7, 25),
45
+ firstDayOfWeek: 1,
46
+ enableTasks: true,
47
+ },
48
+ render: (args) => (
49
+ <DatePicker
50
+ {...args}
51
+ monthDate={new Date(args.monthDate)}
52
+ selectedDate={setControlDate(args.selectedDate)}
53
+ minDate={setControlDate(args.minDate)}
54
+ maxDate={setControlDate(args.maxDate)}
55
+ />
56
+ ),
57
+ };
58
+
59
+ export const HideWeekends: Story = {
60
+ args: {
61
+ monthDate: new Date(2026, 8, 7),
62
+ firstDayOfWeek: 1,
63
+ hideWeekends: true,
64
+ },
65
+ render: (args) => (
66
+ <DatePicker
67
+ {...args}
68
+ monthDate={new Date(args.monthDate)}
69
+ selectedDate={setControlDate(args.selectedDate)}
70
+ minDate={setControlDate(args.minDate)}
71
+ maxDate={setControlDate(args.maxDate)}
72
+ />
73
+ ),
74
+ };
75
+
76
+ export const ShowHolidays: Story = {
77
+ args: {
78
+ monthDate: new Date(2025, 0, 3),
79
+ firstDayOfWeek: 1,
80
+ showHolidays: true,
81
+ },
82
+ render: (args) => (
83
+ <DatePicker
84
+ {...args}
85
+ monthDate={new Date(args.monthDate)}
86
+ selectedDate={setControlDate(args.selectedDate)}
87
+ minDate={setControlDate(args.minDate)}
88
+ maxDate={setControlDate(args.maxDate)}
89
+ />
90
+ ),
91
+ };
92
+
93
+ export const WeekMode: Story = {
94
+ args: {
95
+ monthDate: new Date(2025, 0, 31),
96
+ firstDayOfWeek: 1,
97
+ showHolidays: true,
98
+ mode: 'week',
99
+ },
100
+ render: (args) => (
101
+ <DatePicker
102
+ {...args}
103
+ monthDate={new Date(args.monthDate)}
104
+ selectedDate={setControlDate(args.selectedDate)}
105
+ minDate={setControlDate(args.minDate)}
106
+ maxDate={setControlDate(args.maxDate)}
107
+ />
108
+ ),
109
+ };
110
+
111
+ export const WithMinMaxDates: Story = {
112
+ args: {
113
+ monthDate: new Date(2026, 7, 1),
114
+ firstDayOfWeek: 1,
115
+ minDate: new Date(2026, 7, 10),
116
+ maxDate: new Date(2026, 7, 20),
117
+ },
118
+ render: (args) => (
119
+ <DatePicker
120
+ {...args}
121
+ monthDate={new Date(args.monthDate)}
122
+ selectedDate={setControlDate(args.selectedDate)}
123
+ minDate={setControlDate(args.minDate)}
124
+ maxDate={setControlDate(args.maxDate)}
125
+ />
126
+ ),
127
+ };
@@ -0,0 +1,72 @@
1
+ import { ChangeEvent, KeyboardEvent, Ref } from 'react';
2
+
3
+ import { useValidate } from '@/hooks/useValidate';
4
+
5
+ import { BaseCalendar } from '../BaseCalendar';
6
+ import { BaseCalendarProps, CalendarProps } from '../CalendarGrid/types';
7
+ import { Input } from '../Input';
8
+ import styles from './styles.module.scss';
9
+
10
+ export interface DatePickerProps
11
+ extends BaseCalendarProps, Pick<CalendarProps, 'selectedDate'> {
12
+ onChange?: (date: Date | undefined) => void;
13
+ }
14
+
15
+ export const DatePicker = ({
16
+ selectedDate,
17
+ monthDate = new Date(),
18
+ onChange,
19
+ ...props
20
+ }: DatePickerProps) => {
21
+ const {
22
+ currentDate,
23
+ value,
24
+ handleDateSelect,
25
+ error,
26
+ ref,
27
+ handleDateValidation,
28
+ handleClearInput,
29
+ } = useValidate(selectedDate);
30
+
31
+ const handleDateSelectAndExternalUpdate = (
32
+ e: React.MouseEvent<HTMLButtonElement>,
33
+ date: Date
34
+ ) => {
35
+ handleDateSelect(date);
36
+ if (onChange) {
37
+ onChange(date);
38
+ }
39
+ };
40
+
41
+ const handleDateChange = (e: ChangeEvent<HTMLInputElement>) => {
42
+ handleDateValidation(e.target.value);
43
+ };
44
+
45
+ const handleKeyDown = (e: KeyboardEvent<HTMLInputElement>) => {
46
+ if (e.key === 'Enter') {
47
+ handleDateValidation(e.currentTarget.value);
48
+ }
49
+ };
50
+
51
+ return (
52
+ <div className={styles.datepicker}>
53
+ <Input
54
+ isDateInput
55
+ label="Date"
56
+ value={value}
57
+ ref={ref as Ref<HTMLInputElement>}
58
+ placeholder="dd/mm/yyyy"
59
+ onBlur={handleDateChange}
60
+ onReset={handleClearInput}
61
+ onKeyDown={handleKeyDown}
62
+ error={error}
63
+ />
64
+ <BaseCalendar
65
+ selectedDate={currentDate}
66
+ onDateClick={handleDateSelectAndExternalUpdate}
67
+ monthDate={currentDate ?? monthDate}
68
+ {...props}
69
+ />
70
+ </div>
71
+ );
72
+ };
@@ -0,0 +1,10 @@
1
+ .datepicker {
2
+ display: flex;
3
+ flex-direction: column;
4
+ gap: $gap-8;
5
+ padding: $gap-8;
6
+
7
+ @include themify-modules($themes) {
8
+ background-color: themed('background');
9
+ }
10
+ }
@@ -0,0 +1,75 @@
1
+ import type { Meta, StoryObj } from '@storybook/react';
2
+ import { Day } from '.';
3
+
4
+ const meta: Meta<typeof Day> = {
5
+ component: Day,
6
+ parameters: {
7
+ layout: 'centered',
8
+ },
9
+ };
10
+
11
+ export default meta;
12
+
13
+ type Story = StoryObj<typeof Day>;
14
+
15
+ export const Today: Story = {
16
+ args: {
17
+ date: new Date(),
18
+ },
19
+ };
20
+
21
+ export const Selected: Story = {
22
+ args: {
23
+ date: new Date(2026, 7, 25),
24
+ isSelected: true,
25
+ },
26
+ };
27
+
28
+ export const Disabled: Story = {
29
+ args: {
30
+ date: new Date(2026, 7, 25),
31
+ isDisabled: true,
32
+ },
33
+ };
34
+
35
+ export const Holiday: Story = {
36
+ args: {
37
+ date: new Date(2026, 7, 25),
38
+ isHoliday: true,
39
+ },
40
+ };
41
+
42
+ export const HasTasks: Story = {
43
+ args: {
44
+ date: new Date(2026, 7, 25),
45
+ hasTasks: true,
46
+ },
47
+ };
48
+
49
+ export const RangeStart: Story = {
50
+ args: {
51
+ date: new Date(2026, 7, 25),
52
+ isRangeStart: true,
53
+ },
54
+ };
55
+
56
+ export const RangeMiddle: Story = {
57
+ args: {
58
+ date: new Date(2026, 7, 25),
59
+ isRangeMiddle: true,
60
+ },
61
+ };
62
+
63
+ export const RangeEnd: Story = {
64
+ args: {
65
+ date: new Date(2026, 7, 25),
66
+ isRangeEnd: true,
67
+ },
68
+ };
69
+
70
+ export const TodayAndHasTasks: Story = {
71
+ args: {
72
+ date: new Date(),
73
+ hasTasks: true,
74
+ },
75
+ };
@@ -0,0 +1,95 @@
1
+ import classNames from 'classnames';
2
+
3
+ import { isToday } from '@/utils/dateChecks';
4
+
5
+ import styles from './styles.module.scss';
6
+
7
+ export interface DayProps {
8
+ date: Date;
9
+ isSelected?: boolean;
10
+ isDisabled?: boolean;
11
+ isHoliday?: boolean;
12
+ hasTasks?: boolean;
13
+ isRangeStart?: boolean;
14
+ isRangeEnd?: boolean;
15
+ isRangeMiddle?: boolean;
16
+ isCurrentMonth: boolean;
17
+ onClick?: (e: React.MouseEvent<HTMLButtonElement>, date: Date) => void;
18
+ onDoubleClick?: (date: Date) => void;
19
+ onMouseEnter?: (e: React.MouseEvent<HTMLButtonElement>) => void;
20
+ onMouseLeave?: () => void;
21
+ }
22
+
23
+ export const Day = ({
24
+ date,
25
+ isSelected = false,
26
+ isCurrentMonth,
27
+ isDisabled = false,
28
+ isHoliday = false,
29
+ hasTasks = false,
30
+ isRangeStart = false,
31
+ isRangeEnd = false,
32
+ isRangeMiddle = false,
33
+ onClick,
34
+ onDoubleClick,
35
+ onMouseEnter,
36
+ onMouseLeave,
37
+ }: DayProps) => {
38
+ const dayNumber = date.getDate();
39
+ let clickTimeout: ReturnType<typeof setTimeout> | null = null;
40
+
41
+ const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {
42
+ if (clickTimeout) {
43
+ clearTimeout(clickTimeout);
44
+ }
45
+ clickTimeout = setTimeout(() => {
46
+ if (!isDisabled && onClick) {
47
+ onClick(e, date);
48
+ }
49
+ clickTimeout = null;
50
+ }, 250);
51
+ };
52
+ const handleDoubleClick = () => {
53
+ if (clickTimeout) {
54
+ clearTimeout(clickTimeout);
55
+ }
56
+ if (!isDisabled && onDoubleClick) {
57
+ onDoubleClick(date);
58
+ }
59
+ };
60
+
61
+ const handleMouseEnter = (e: React.MouseEvent<HTMLButtonElement>) => {
62
+ if (!isDisabled && onMouseEnter) {
63
+ onMouseEnter(e);
64
+ }
65
+ };
66
+
67
+ const handleMouseLeave = () => {
68
+ if (!isDisabled && onMouseLeave) {
69
+ onMouseLeave();
70
+ }
71
+ };
72
+
73
+ return (
74
+ <button
75
+ className={classNames(styles.day, {
76
+ [styles.today]: isToday(date),
77
+ [styles.selected]: isSelected,
78
+ [styles.disabled]: isDisabled,
79
+ [styles.holiday]: isHoliday,
80
+ [styles.hasTasks]: hasTasks,
81
+ [styles.rangeStart]: isRangeStart,
82
+ [styles.rangeEnd]: isRangeEnd,
83
+ [styles.rangeMiddle]: isRangeMiddle,
84
+ [styles.otherMonth]: !isCurrentMonth,
85
+ })}
86
+ onClick={handleClick}
87
+ onDoubleClick={handleDoubleClick}
88
+ onMouseEnter={handleMouseEnter}
89
+ onMouseLeave={handleMouseLeave}
90
+ disabled={isDisabled}
91
+ >
92
+ <span className={styles.number}>{dayNumber}</span>
93
+ </button>
94
+ );
95
+ };
@@ -0,0 +1,97 @@
1
+ .day {
2
+ @include square(32px);
3
+
4
+ @include themify-modules($themes) {
5
+ background-color: themed('background');
6
+ color: themed('text');
7
+ }
8
+
9
+ font-size: $font-size-13;
10
+ padding: $gap-10;
11
+ border-radius: $radius-8;
12
+ display: flex;
13
+ align-items: center;
14
+ justify-content: center;
15
+ font-weight: $font-weight-semibold;
16
+ border: none;
17
+ position: relative;
18
+ }
19
+
20
+ .holiday {
21
+ @include themify-modules($themes) {
22
+ color: themed('accent');
23
+ }
24
+ }
25
+
26
+ .today {
27
+ @include themify-modules($themes) {
28
+ color: themed('selected');
29
+ }
30
+ }
31
+
32
+ .selected {
33
+ @include themify-modules($themes) {
34
+ background-color: themed('selected');
35
+ color: themed('white-color');
36
+ }
37
+ }
38
+
39
+ .disabled {
40
+ @include themify-modules($themes) {
41
+ color: themed('disabled');
42
+ }
43
+
44
+ cursor: not-allowed;
45
+ }
46
+
47
+ .otherMonth {
48
+ @include themify-modules($themes) {
49
+ color: themed('disabled');
50
+ }
51
+ }
52
+
53
+ .rangeStart {
54
+ @include themify-modules($themes) {
55
+ color: themed('white-color');
56
+ background-color: themed('middle-blue');
57
+ }
58
+
59
+ border-top-right-radius: 0;
60
+ border-bottom-right-radius: 0;
61
+ }
62
+
63
+ .rangeMiddle {
64
+ @include themify-modules($themes) {
65
+ color: themed('blue');
66
+ background-color: themed('light-blue');
67
+ }
68
+
69
+ border-radius: 0;
70
+ }
71
+
72
+ .rangeEnd {
73
+ @include themify-modules($themes) {
74
+ color: themed('white-color');
75
+ background-color: themed('selected');
76
+ }
77
+
78
+ border-top-left-radius: 0;
79
+ border-bottom-left-radius: 0;
80
+ }
81
+
82
+ .hasTasks {
83
+ position: relative;
84
+ &::after {
85
+ @include square(4px);
86
+
87
+ @include themify-modules($themes) {
88
+ background-color: themed('marker');
89
+ }
90
+
91
+ content: '';
92
+ position: absolute;
93
+ top: 12%;
94
+ right: 12%;
95
+ border-radius: 100%;
96
+ }
97
+ }
@@ -0,0 +1,37 @@
1
+ import type { Meta, StoryObj } from '@storybook/react';
2
+ import { Input } from '.';
3
+
4
+ const meta: Meta<typeof Input> = {
5
+ component: Input,
6
+ parameters: {
7
+ layout: 'centered',
8
+ },
9
+ };
10
+
11
+ export default meta;
12
+
13
+ type Story = StoryObj<typeof Input>;
14
+
15
+ export const Default: Story = {
16
+ args: {
17
+ label: 'Title',
18
+ placeholder: 'Enter title',
19
+ },
20
+ };
21
+
22
+ export const DefaultDate: Story = {
23
+ args: {
24
+ label: 'Date',
25
+ placeholder: 'Date input...',
26
+ isDateInput: true,
27
+ },
28
+ };
29
+
30
+ export const WithError: Story = {
31
+ args: {
32
+ label: 'Date',
33
+ value: '45',
34
+ error: 'Invalid date',
35
+ isDateInput: true,
36
+ },
37
+ };
@@ -0,0 +1,64 @@
1
+ import Calendar from '@assets/input/Calendar.svg?react';
2
+ import Clear from '@assets/input/Clear.svg?react';
3
+ import classNames from 'classnames';
4
+ import { InputHTMLAttributes, Ref, useId } from 'react';
5
+
6
+ import styles from './styles.module.scss';
7
+
8
+ interface InputProps extends InputHTMLAttributes<HTMLInputElement> {
9
+ label?: string;
10
+ error?: string;
11
+ onReset?: () => void;
12
+ ref?: Ref<HTMLInputElement>;
13
+ isDateInput?: boolean;
14
+ }
15
+
16
+ export const Input = ({
17
+ label,
18
+ error,
19
+ className = '',
20
+ id,
21
+ onReset,
22
+ placeholder,
23
+ type = 'text',
24
+ isDateInput = false,
25
+ ref = null,
26
+ ...restProps
27
+ }: InputProps) => {
28
+ const generatedId = useId();
29
+ const inputId = id || `input-${generatedId}`;
30
+
31
+ return (
32
+ <div className={styles.wrapper}>
33
+ {label && (
34
+ <label htmlFor={inputId} className={styles.label}>
35
+ {label}
36
+ </label>
37
+ )}
38
+
39
+ <div
40
+ className={classNames(styles.inputWrapper, {
41
+ [styles.errorWrapper]: error,
42
+ })}
43
+ >
44
+ {isDateInput && <Calendar className={styles.icon} />}
45
+ <input
46
+ id={inputId}
47
+ type={type}
48
+ ref={ref}
49
+ placeholder={placeholder}
50
+ className={classNames(styles.input, className)}
51
+ {...restProps}
52
+ />
53
+
54
+ {isDateInput && onReset && (
55
+ <button className={styles.clear} onClick={onReset} type="button">
56
+ <Clear className={styles.icon} />
57
+ </button>
58
+ )}
59
+ </div>
60
+
61
+ <span className={styles.error}>{error ?? ''}</span>
62
+ </div>
63
+ );
64
+ };
@@ -0,0 +1,79 @@
1
+ .wrapper {
2
+ @include themify-modules($themes) {
3
+ color: themed('text');
4
+ background-color: themed('background');
5
+ }
6
+
7
+ display: flex;
8
+ flex-direction: column;
9
+ align-items: start;
10
+ gap: 2px;
11
+ width: 100%;
12
+ }
13
+
14
+ .label {
15
+ font-size: $font-size-15;
16
+ font-weight: $font-weight-semibold;
17
+ margin-bottom: $gap-8;
18
+ }
19
+
20
+ .inputWrapper {
21
+ @include themify-modules($themes) {
22
+ color: themed('text');
23
+ background-color: themed('background');
24
+ border-color: themed('border');
25
+ }
26
+
27
+ @include border;
28
+
29
+ padding: $gap-8 15px;
30
+ border-radius: $radius-8;
31
+ display: flex;
32
+ gap: $gap-8;
33
+ justify-content: space-between;
34
+ align-items: center;
35
+ width: 100%;
36
+ }
37
+
38
+ .errorWrapper {
39
+ @include themify-modules($themes) {
40
+ border-color: themed('accent');
41
+ }
42
+ }
43
+
44
+ .input {
45
+ border: none;
46
+ outline: none;
47
+ background-color: transparent;
48
+ width: 100%;
49
+
50
+ @include themify-modules($themes) {
51
+ color: themed('text');
52
+ }
53
+
54
+ &::placeholder {
55
+ @include themify-modules($themes) {
56
+ color: themed('border');
57
+ }
58
+ }
59
+ }
60
+
61
+ .clear {
62
+ border: none;
63
+ background-color: transparent;
64
+ width: max-content;
65
+ padding: 0;
66
+ }
67
+
68
+ .icon {
69
+ @include themify-modules($themes) {
70
+ color: themed('disabled');
71
+ }
72
+ }
73
+
74
+ .error {
75
+ @include themify-modules($themes) {
76
+ color: themed('accent');
77
+ }
78
+ height: 15px;
79
+ }
@@ -0,0 +1,43 @@
1
+ import classNames from 'classnames';
2
+
3
+ import { Modal } from '..';
4
+ import { CrudModalProps } from '../types';
5
+ import styles from './styles.module.scss';
6
+
7
+ export const CrudModal = ({
8
+ isOpen,
9
+ onClose,
10
+ onConfirm,
11
+ title,
12
+ cancelText = 'No',
13
+ confirmText = 'Yes',
14
+ isLoading = false,
15
+ children,
16
+ showCloseButton = false,
17
+ }: CrudModalProps) => {
18
+ return (
19
+ <Modal isOpen={isOpen} onClose={onClose} showCloseButton={showCloseButton}>
20
+ <div className={styles.header}>
21
+ <h1 className={styles.title}>{title}</h1>
22
+ </div>
23
+
24
+ {children && <div className={styles.children}>{children}</div>}
25
+
26
+ <div className={styles.footer}>
27
+ <button
28
+ className={styles.modalButtons}
29
+ onClick={onClose}
30
+ disabled={isLoading}
31
+ >
32
+ {cancelText}
33
+ </button>
34
+ <button
35
+ className={classNames(styles.confirm, styles.modalButtons)}
36
+ onClick={onConfirm}
37
+ >
38
+ {confirmText}
39
+ </button>
40
+ </div>
41
+ </Modal>
42
+ );
43
+ };