@os-design/core 1.0.279 → 1.0.281

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 (198) hide show
  1. package/dist/Avatar/utils/nameToInitials.d.ts.map +1 -1
  2. package/dist/Avatar/utils/strToHue.d.ts.map +1 -1
  3. package/dist/Button/index.d.ts.map +1 -1
  4. package/dist/Button/index.js +1 -6
  5. package/dist/Button/utils/useButtonColors.d.ts.map +1 -1
  6. package/dist/ButtonLink/index.d.ts.map +1 -1
  7. package/dist/ButtonLink/index.js +0 -5
  8. package/dist/Checkbox/index.d.ts.map +1 -1
  9. package/dist/Checkbox/index.js +1 -6
  10. package/dist/DateCalendar/Calendar.d.ts +25 -0
  11. package/dist/DateCalendar/Calendar.d.ts.map +1 -0
  12. package/dist/DateCalendar/Calendar.js +271 -0
  13. package/dist/DateCalendar/MonthPicker.d.ts +12 -0
  14. package/dist/DateCalendar/MonthPicker.d.ts.map +1 -0
  15. package/dist/DateCalendar/MonthPicker.js +159 -0
  16. package/dist/DateCalendar/index.d.ts +41 -0
  17. package/dist/DateCalendar/index.d.ts.map +1 -0
  18. package/dist/DateCalendar/index.js +77 -0
  19. package/dist/DateCalendar/locale.d.ts +6 -0
  20. package/dist/DateCalendar/locale.d.ts.map +1 -0
  21. package/dist/DateCalendar/locale.js +4 -0
  22. package/dist/DateCalendar/utils/calendarDays.d.ts +10 -0
  23. package/dist/DateCalendar/utils/calendarDays.d.ts.map +1 -0
  24. package/dist/DateCalendar/utils/calendarDays.js +46 -0
  25. package/dist/DateCalendar/utils/dayOfWeek.d.ts +8 -0
  26. package/dist/DateCalendar/utils/dayOfWeek.d.ts.map +1 -0
  27. package/dist/DateCalendar/utils/dayOfWeek.js +6 -0
  28. package/dist/DateCalendar/utils/daysInMonth.d.ts +7 -0
  29. package/dist/DateCalendar/utils/daysInMonth.d.ts.map +1 -0
  30. package/dist/DateCalendar/utils/daysInMonth.js +14 -0
  31. package/dist/DateCalendar/utils/month.d.ts +14 -0
  32. package/dist/DateCalendar/utils/month.d.ts.map +1 -0
  33. package/dist/DateCalendar/utils/month.js +24 -0
  34. package/dist/DateCalendar/utils/shift.d.ts +3 -0
  35. package/dist/DateCalendar/utils/shift.d.ts.map +1 -0
  36. package/dist/DateCalendar/utils/shift.js +12 -0
  37. package/dist/DatePicker/index.d.ts +68 -62
  38. package/dist/DatePicker/index.d.ts.map +1 -1
  39. package/dist/DatePicker/index.js +359 -265
  40. package/dist/DatePicker/utils/createTimes.d.ts +7 -0
  41. package/dist/DatePicker/utils/createTimes.d.ts.map +1 -0
  42. package/dist/DatePicker/utils/createTimes.js +15 -0
  43. package/dist/GlobalStyles/resetStyles.d.ts.map +1 -1
  44. package/dist/GlobalStyles/typographyStyles.d.ts.map +1 -1
  45. package/dist/Input/index.d.ts +15 -0
  46. package/dist/Input/index.d.ts.map +1 -1
  47. package/dist/Input/index.js +5 -5
  48. package/dist/Input/utils/getFocusableElements.d.ts.map +1 -1
  49. package/dist/InputDateUnstyled/index.d.ts +94 -0
  50. package/dist/InputDateUnstyled/index.d.ts.map +1 -0
  51. package/dist/InputDateUnstyled/index.js +406 -0
  52. package/dist/InputDateUnstyled/utils/convertHours.d.ts +4 -0
  53. package/dist/InputDateUnstyled/utils/convertHours.d.ts.map +1 -0
  54. package/dist/InputDateUnstyled/utils/convertHours.js +12 -0
  55. package/dist/InputDateUnstyled/utils/convertToFullYear.d.ts +3 -0
  56. package/dist/InputDateUnstyled/utils/convertToFullYear.d.ts.map +1 -0
  57. package/dist/InputDateUnstyled/utils/convertToFullYear.js +10 -0
  58. package/dist/InputDateUnstyled/utils/dateToString.d.ts +3 -0
  59. package/dist/InputDateUnstyled/utils/dateToString.d.ts.map +1 -0
  60. package/dist/InputDateUnstyled/utils/dateToString.js +10 -0
  61. package/dist/InputDateUnstyled/utils/daysInMonth.d.ts +7 -0
  62. package/dist/InputDateUnstyled/utils/daysInMonth.d.ts.map +1 -0
  63. package/dist/InputDateUnstyled/utils/daysInMonth.js +14 -0
  64. package/dist/InputDateUnstyled/utils/ensureCaretVisible.d.ts +3 -0
  65. package/dist/InputDateUnstyled/utils/ensureCaretVisible.d.ts.map +1 -0
  66. package/dist/InputDateUnstyled/utils/ensureCaretVisible.js +32 -0
  67. package/dist/InputDateUnstyled/utils/eraseSelectedTokens.d.ts +10 -0
  68. package/dist/InputDateUnstyled/utils/eraseSelectedTokens.d.ts.map +1 -0
  69. package/dist/InputDateUnstyled/utils/eraseSelectedTokens.js +29 -0
  70. package/dist/InputDateUnstyled/utils/replaceSubstring.d.ts +3 -0
  71. package/dist/InputDateUnstyled/utils/replaceSubstring.d.ts.map +1 -0
  72. package/dist/InputDateUnstyled/utils/replaceSubstring.js +9 -0
  73. package/dist/InputDateUnstyled/utils/same.d.ts +6 -0
  74. package/dist/InputDateUnstyled/utils/same.d.ts.map +1 -0
  75. package/dist/InputDateUnstyled/utils/same.js +3 -0
  76. package/dist/InputDateUnstyled/utils/stringToDay.d.ts +12 -0
  77. package/dist/InputDateUnstyled/utils/stringToDay.d.ts.map +1 -0
  78. package/dist/InputDateUnstyled/utils/stringToDay.js +55 -0
  79. package/dist/InputDateUnstyled/utils/stringToTime.d.ts +7 -0
  80. package/dist/InputDateUnstyled/utils/stringToTime.d.ts.map +1 -0
  81. package/dist/InputDateUnstyled/utils/stringToTime.js +40 -0
  82. package/dist/InputDateUnstyled/utils/token.d.ts +9 -0
  83. package/dist/InputDateUnstyled/utils/token.d.ts.map +1 -0
  84. package/dist/InputDateUnstyled/utils/token.js +66 -0
  85. package/dist/Link/index.d.ts.map +1 -1
  86. package/dist/Link/index.js +3 -8
  87. package/dist/LinkButton/index.d.ts.map +1 -1
  88. package/dist/LinkButton/index.js +0 -5
  89. package/dist/List/utils/bodyPointerEvents.d.ts.map +1 -1
  90. package/dist/List/utils/frameTimeout.d.ts.map +1 -1
  91. package/dist/List/utils/useRWLoadNext.d.ts.map +1 -1
  92. package/dist/LogoLink/index.d.ts.map +1 -1
  93. package/dist/LogoLink/index.js +1 -6
  94. package/dist/Menu/utils/useFocusWithArrows.d.ts.map +1 -1
  95. package/dist/Modal/index.d.ts +5 -0
  96. package/dist/Modal/index.d.ts.map +1 -1
  97. package/dist/Modal/index.js +53 -48
  98. package/dist/Navigation/utils/useScrollFlags.d.ts.map +1 -1
  99. package/dist/NavigationItem/index.d.ts.map +1 -1
  100. package/dist/NavigationItem/index.js +1 -6
  101. package/dist/Popover/utils/usePopoverPosition.d.ts.map +1 -1
  102. package/dist/ScrollButton/utils/useContainerPosition.d.ts.map +1 -1
  103. package/dist/ScrollButton/utils/useVisibility.d.ts.map +1 -1
  104. package/dist/Select/index.d.ts.map +1 -1
  105. package/dist/Select/index.js +2 -3
  106. package/dist/Switch/index.d.ts.map +1 -1
  107. package/dist/Switch/index.js +1 -7
  108. package/dist/TagLink/index.d.ts.map +1 -1
  109. package/dist/TagLink/index.js +1 -6
  110. package/dist/TimeGrid/index.d.ts +63 -0
  111. package/dist/TimeGrid/index.d.ts.map +1 -0
  112. package/dist/TimeGrid/index.js +111 -0
  113. package/dist/TimeGrid/utils/convertHours.d.ts +4 -0
  114. package/dist/TimeGrid/utils/convertHours.d.ts.map +1 -0
  115. package/dist/TimeGrid/utils/convertHours.js +12 -0
  116. package/dist/TimeGrid/utils/createTimes.d.ts +7 -0
  117. package/dist/TimeGrid/utils/createTimes.d.ts.map +1 -0
  118. package/dist/TimeGrid/utils/createTimes.js +15 -0
  119. package/dist/TimeGrid/utils/timeToString.d.ts +4 -0
  120. package/dist/TimeGrid/utils/timeToString.d.ts.map +1 -0
  121. package/dist/TimeGrid/utils/timeToString.js +12 -0
  122. package/dist/TimeGridSkeleton/index.d.ts +18 -0
  123. package/dist/TimeGridSkeleton/index.d.ts.map +1 -0
  124. package/dist/TimeGridSkeleton/index.js +33 -0
  125. package/dist/TimeList/index.d.ts +45 -0
  126. package/dist/TimeList/index.d.ts.map +1 -0
  127. package/dist/TimeList/index.js +80 -0
  128. package/dist/TimeList/utils/convertHours.d.ts +4 -0
  129. package/dist/TimeList/utils/convertHours.d.ts.map +1 -0
  130. package/dist/TimeList/utils/convertHours.js +12 -0
  131. package/dist/TimeList/utils/createTimes.d.ts +7 -0
  132. package/dist/TimeList/utils/createTimes.d.ts.map +1 -0
  133. package/dist/TimeList/utils/createTimes.js +15 -0
  134. package/dist/TimeList/utils/timeToString.d.ts +4 -0
  135. package/dist/TimeList/utils/timeToString.d.ts.map +1 -0
  136. package/dist/TimeList/utils/timeToString.js +12 -0
  137. package/dist/TimeListSkeleton/index.d.ts +13 -0
  138. package/dist/TimeListSkeleton/index.d.ts.map +1 -0
  139. package/dist/TimeListSkeleton/index.js +30 -0
  140. package/dist/index.d.ts +12 -0
  141. package/dist/index.d.ts.map +1 -1
  142. package/dist/index.js +12 -0
  143. package/dist/message/styles.d.ts.map +1 -1
  144. package/package.json +8 -8
  145. package/src/Button/index.tsx +1 -6
  146. package/src/ButtonLink/index.tsx +0 -5
  147. package/src/Checkbox/index.tsx +1 -6
  148. package/src/DateCalendar/Calendar.tsx +400 -0
  149. package/src/DateCalendar/MonthPicker.tsx +212 -0
  150. package/src/DateCalendar/index.tsx +135 -0
  151. package/src/DateCalendar/locale.ts +22 -0
  152. package/src/DateCalendar/utils/calendarDays.ts +61 -0
  153. package/src/DateCalendar/utils/dayOfWeek.ts +14 -0
  154. package/src/DateCalendar/utils/daysInMonth.ts +22 -0
  155. package/src/DateCalendar/utils/month.ts +30 -0
  156. package/src/DateCalendar/utils/shift.ts +14 -0
  157. package/src/DatePicker/index.tsx +506 -417
  158. package/src/DatePicker/utils/createTimes.ts +20 -0
  159. package/src/Input/index.tsx +11 -8
  160. package/src/InputDateUnstyled/index.tsx +533 -0
  161. package/src/InputDateUnstyled/utils/convertHours.ts +15 -0
  162. package/src/InputDateUnstyled/utils/convertToFullYear.ts +11 -0
  163. package/src/InputDateUnstyled/utils/dateToString.ts +21 -0
  164. package/src/InputDateUnstyled/utils/daysInMonth.ts +22 -0
  165. package/src/InputDateUnstyled/utils/ensureCaretVisible.ts +37 -0
  166. package/src/InputDateUnstyled/utils/eraseSelectedTokens.ts +38 -0
  167. package/src/InputDateUnstyled/utils/replaceSubstring.ts +10 -0
  168. package/src/InputDateUnstyled/utils/same.ts +15 -0
  169. package/src/InputDateUnstyled/utils/stringToDay.ts +69 -0
  170. package/src/InputDateUnstyled/utils/stringToTime.ts +48 -0
  171. package/src/InputDateUnstyled/utils/token.ts +102 -0
  172. package/src/Link/index.tsx +5 -25
  173. package/src/LinkButton/index.tsx +2 -15
  174. package/src/LogoLink/index.tsx +2 -6
  175. package/src/Modal/index.tsx +71 -60
  176. package/src/NavigationItem/index.tsx +2 -16
  177. package/src/Select/index.tsx +2 -3
  178. package/src/Switch/index.tsx +1 -11
  179. package/src/TagLink/index.tsx +3 -11
  180. package/src/TimeGrid/index.tsx +189 -0
  181. package/src/TimeGrid/utils/convertHours.ts +15 -0
  182. package/src/TimeGrid/utils/createTimes.ts +20 -0
  183. package/src/TimeGrid/utils/timeToString.ts +17 -0
  184. package/src/TimeGridSkeleton/index.tsx +50 -0
  185. package/src/TimeList/index.tsx +135 -0
  186. package/src/TimeList/utils/convertHours.ts +15 -0
  187. package/src/TimeList/utils/createTimes.ts +20 -0
  188. package/src/TimeList/utils/timeToString.ts +17 -0
  189. package/src/TimeListSkeleton/index.tsx +44 -0
  190. package/src/index.ts +12 -0
  191. package/dist/DatePicker/DatePickerCalendar.d.ts +0 -11
  192. package/dist/DatePicker/DatePickerCalendar.d.ts.map +0 -1
  193. package/dist/DatePicker/DatePickerCalendar.js +0 -178
  194. package/dist/TimePicker/index.d.ts +0 -29
  195. package/dist/TimePicker/index.d.ts.map +0 -1
  196. package/dist/TimePicker/index.js +0 -100
  197. package/src/DatePicker/DatePickerCalendar.tsx +0 -230
  198. package/src/TimePicker/index.tsx +0 -144
@@ -0,0 +1,400 @@
1
+ import styled from '@emotion/styled';
2
+ import React, {
3
+ useCallback,
4
+ useEffect,
5
+ useMemo,
6
+ useState,
7
+ type HTMLAttributes,
8
+ } from 'react';
9
+ import Button from '../Button/index.js';
10
+ import { clr, type Color } from '@os-design/theming';
11
+ import { resetFocusStyles, transitionStyles } from '@os-design/styles';
12
+ import { omitEmotionProps } from '@os-design/utils';
13
+ import { css } from '@emotion/react';
14
+ import shift from './utils/shift.js';
15
+ import calendarDays, { type Day, type Month } from './utils/calendarDays.js';
16
+ import { nextMonth, prevMonth } from './utils/month.js';
17
+ import { Left, Loading, Right } from '@os-design/icons';
18
+ import dayOfWeek from './utils/dayOfWeek.js';
19
+ import type { DateCalendarLocale } from './locale.js';
20
+
21
+ const Header = styled.div`
22
+ display: flex;
23
+ align-items: center;
24
+ margin-bottom: 0.5em;
25
+ `;
26
+
27
+ const MonthContainer = styled.div`
28
+ flex-grow: 1;
29
+ overflow: hidden;
30
+ `;
31
+
32
+ const MonthButton = styled(Button)`
33
+ color: ${(p) => clr(p.theme.colorText)};
34
+ padding: 0 0.75em;
35
+
36
+ & > span {
37
+ font-size: 1.2em;
38
+ }
39
+
40
+ @media (hover: hover) {
41
+ &:hover,
42
+ &:focus-visible {
43
+ color: ${(p) => clr(p.theme.colorPrimary)};
44
+ }
45
+ }
46
+ `;
47
+
48
+ const SwitchMonthButton = styled(Button)`
49
+ flex-shrink: 0;
50
+ padding: 0 0.75em;
51
+ `;
52
+
53
+ const DayGridContainer = styled.div`
54
+ position: relative;
55
+ `;
56
+
57
+ const dayGridLoadingStyles = (p) =>
58
+ p.loading &&
59
+ css`
60
+ filter: blur(0.15em);
61
+ `;
62
+
63
+ interface DayGridProps {
64
+ loading: boolean;
65
+ }
66
+ const DayGrid = styled('div', omitEmotionProps('loading'))<DayGridProps>`
67
+ display: grid;
68
+ grid-template-columns: repeat(7, ${(p) => p.theme.dateCalendarCellSize}em);
69
+ grid-auto-rows: ${(p) => p.theme.dateCalendarCellSize}em;
70
+ justify-content: space-between;
71
+
72
+ overflow-x: auto;
73
+
74
+ ${dayGridLoadingStyles};
75
+ `;
76
+
77
+ const dayOfWeekWeekendStyles = (p) =>
78
+ p.weekend &&
79
+ css`
80
+ color: ${clr(p.theme.dateCalendarDayOfWeekWeekendColorText)};
81
+ `;
82
+
83
+ interface DayOfWeekProps {
84
+ weekend: boolean;
85
+ }
86
+ const DayOfWeek = styled('div', omitEmotionProps('weekend'))<DayOfWeekProps>`
87
+ justify-self: center;
88
+ align-self: center;
89
+ color: ${(p) => clr(p.theme.dateCalendarDayOfWeekColorText)};
90
+ font-size: ${(p) => p.theme.sizes.small}em;
91
+ ${dayOfWeekWeekendStyles};
92
+ `;
93
+
94
+ const dayWeekendStyles = (p) =>
95
+ p.weekend &&
96
+ css`
97
+ color: ${clr(p.theme.dateCalendarDayWeekendColorText)};
98
+ `;
99
+
100
+ const dayHoverStyles = (p) =>
101
+ !p.disabled &&
102
+ css`
103
+ cursor: pointer;
104
+
105
+ @media (hover: hover) {
106
+ &:hover,
107
+ &:focus-visible {
108
+ background-color: ${clr(p.theme.dateCalendarDayColorBgHover)};
109
+ color: ${clr(p.theme.dateCalendarDayColorTextHover)};
110
+ }
111
+ }
112
+ `;
113
+
114
+ const disabledStyles = (p) =>
115
+ p.disabled &&
116
+ css`
117
+ cursor: not-allowed;
118
+ color: ${p.weekend
119
+ ? clr(p.theme.dateCalendarDisabledDayWeekendColorText)
120
+ : clr(p.theme.dateCalendarDisabledDayColorText)};
121
+ `;
122
+
123
+ interface DayProps {
124
+ weekend: boolean;
125
+ disabled: boolean;
126
+ }
127
+ const Day = styled('div', omitEmotionProps('weekend', 'disabled'))<DayProps>`
128
+ ${resetFocusStyles};
129
+ justify-self: center;
130
+ align-self: center;
131
+ user-select: none;
132
+ -webkit-user-select: none;
133
+
134
+ width: ${(p) => p.theme.dateCalendarDaySize}em;
135
+ height: ${(p) => p.theme.dateCalendarDaySize}em;
136
+
137
+ display: flex;
138
+ justify-content: center;
139
+ align-items: center;
140
+
141
+ border-radius: ${(p) => p.theme.borderRadius}em;
142
+
143
+ ${dayHoverStyles};
144
+ ${dayWeekendStyles};
145
+ ${disabledStyles};
146
+ ${transitionStyles('background-color', 'color')};
147
+ `;
148
+
149
+ const dayAnotherMonthWeekendStyles = (p) =>
150
+ p.weekend &&
151
+ css`
152
+ color: ${clr(p.theme.dateCalendarDayAnotherMonthWeekendColorText)};
153
+ `;
154
+
155
+ const DayAnotherMonth = styled(Day)`
156
+ ${(p) =>
157
+ !p.disabled &&
158
+ css`
159
+ color: ${clr(p.theme.dateCalendarDayAnotherMonthColorText)};
160
+ ${dayAnotherMonthWeekendStyles(p)};
161
+ `};
162
+ `;
163
+
164
+ const Today = styled(Day)`
165
+ border: 2px dashed ${(p) => clr(p.theme.dateCalendarTodayColorBorder)};
166
+ box-sizing: border-box;
167
+ `;
168
+
169
+ const selectedDayHoverStyles = (p) =>
170
+ !p.disabled &&
171
+ css`
172
+ @media (hover: hover) {
173
+ &:hover,
174
+ &:focus-visible {
175
+ background-color: ${clr(p.theme.dateCalendarSelectedDayColorBgHover)};
176
+ color: ${clr(p.theme.dateCalendarSelectedDayColorTextHover)};
177
+ }
178
+ }
179
+ `;
180
+
181
+ const SelectedDay = styled(Day)`
182
+ background-color: ${(p) => clr(p.theme.dateCalendarSelectedDayColorBg)};
183
+ color: ${(p) => clr(p.theme.dateCalendarSelectedDayColorText)};
184
+ ${selectedDayHoverStyles};
185
+ `;
186
+
187
+ const LoadingContainer = styled.div`
188
+ position: absolute;
189
+ top: 0;
190
+ bottom: 0;
191
+ left: 0;
192
+ right: 0;
193
+
194
+ display: flex;
195
+ justify-content: center;
196
+ align-items: center;
197
+
198
+ background-color: ${(p) =>
199
+ clr([...p.theme.colorBg.slice(0, 3), 0.6] as Color)};
200
+ `;
201
+
202
+ const StyledLoading = styled(Loading)`
203
+ color: ${(p) => clr(p.theme.colorPrimary)};
204
+ font-size: 1.8em;
205
+ `;
206
+
207
+ const daysOfWeek = [0, 1, 2, 3, 4, 5, 6];
208
+
209
+ interface CalendarProps {
210
+ firstDayOfWeek: number;
211
+ disabledDays?: (month: Month) => number[] | Promise<number[]>;
212
+ locale: DateCalendarLocale;
213
+ value?: Day | null;
214
+ onChange: (value: Day | null) => void;
215
+ month: Month;
216
+ onChangeMonth: (month: Month) => void;
217
+ onSelectMonth: () => void;
218
+ today: Date;
219
+ }
220
+
221
+ type DayPropsGetter = (
222
+ day: Day,
223
+ disabled: boolean
224
+ ) => HTMLAttributes<HTMLDivElement>;
225
+
226
+ const Calendar: React.FC<CalendarProps> = ({
227
+ firstDayOfWeek,
228
+ disabledDays: disabledDaysFn,
229
+ locale,
230
+ value,
231
+ onChange,
232
+ month,
233
+ onChangeMonth,
234
+ onSelectMonth,
235
+ today,
236
+ }) => {
237
+ const [loading, setLoading] = useState(false);
238
+ const [disabledDays, setDisabledDays] = useState<number[]>([]);
239
+
240
+ useEffect(() => {
241
+ if (!disabledDaysFn) return;
242
+ let mounted = true;
243
+ const fn = async () => {
244
+ setLoading(true);
245
+ const disabledDays = await disabledDaysFn(month);
246
+ if (mounted) {
247
+ setDisabledDays(disabledDays);
248
+ setLoading(false);
249
+ }
250
+ };
251
+ fn();
252
+ return () => {
253
+ mounted = false;
254
+ };
255
+ }, [disabledDaysFn, month]);
256
+
257
+ const shiftedDaysOfWeek = useMemo(
258
+ () => shift(daysOfWeek, firstDayOfWeek),
259
+ [firstDayOfWeek]
260
+ );
261
+
262
+ const days = useMemo(
263
+ () => calendarDays(month, firstDayOfWeek),
264
+ [month, firstDayOfWeek]
265
+ );
266
+
267
+ const getDayProps = useCallback<DayPropsGetter>(
268
+ (day, disabled) => {
269
+ if (disabled) {
270
+ return {};
271
+ }
272
+ return {
273
+ tabIndex: 0,
274
+ role: 'button',
275
+ onClick: () => onChange(day),
276
+ onKeyDown: (e) => {
277
+ if (e.key === 'Enter') {
278
+ onChange(day);
279
+ }
280
+ },
281
+ };
282
+ },
283
+ [onChange]
284
+ );
285
+ return (
286
+ <>
287
+ <Header>
288
+ <MonthContainer>
289
+ <MonthButton
290
+ type='ghost'
291
+ wide='never'
292
+ size='small'
293
+ onClick={onSelectMonth}
294
+ >
295
+ {locale.months[month.month]} {month.year}
296
+ </MonthButton>
297
+ </MonthContainer>
298
+
299
+ <SwitchMonthButton
300
+ type='ghost'
301
+ wide='never'
302
+ size='small'
303
+ onClick={() => onChangeMonth(prevMonth(month))}
304
+ >
305
+ <Left />
306
+ </SwitchMonthButton>
307
+ <SwitchMonthButton
308
+ type='ghost'
309
+ wide='never'
310
+ size='small'
311
+ onClick={() => onChangeMonth(nextMonth(month))}
312
+ >
313
+ <Right />
314
+ </SwitchMonthButton>
315
+ </Header>
316
+
317
+ <DayGridContainer>
318
+ <DayGrid loading={loading}>
319
+ {shiftedDaysOfWeek.map((i) => {
320
+ return (
321
+ <DayOfWeek key={i} weekend={i === 6 || i === 0}>
322
+ {locale.shortDaysOfWeek[i]}
323
+ </DayOfWeek>
324
+ );
325
+ })}
326
+
327
+ {days.map((day) => {
328
+ const key = `${day.year}/${day.month}/${day.day}`;
329
+ const dow = dayOfWeek(day);
330
+ const weekend = dow === 6 || dow === 0;
331
+ if (day.month !== month.month) {
332
+ return (
333
+ <DayAnotherMonth
334
+ key={key}
335
+ weekend={weekend}
336
+ disabled={!!disabledDaysFn}
337
+ {...getDayProps(day, !!disabledDaysFn)}
338
+ >
339
+ {day.day}
340
+ </DayAnotherMonth>
341
+ );
342
+ }
343
+ const disabled = disabledDays.includes(day.day);
344
+ if (
345
+ value &&
346
+ value.year === day.year &&
347
+ value.month === day.month &&
348
+ value.day === day.day
349
+ ) {
350
+ return (
351
+ <SelectedDay
352
+ key={key}
353
+ weekend={weekend}
354
+ disabled={disabled}
355
+ {...getDayProps(day, disabled)}
356
+ >
357
+ {day.day}
358
+ </SelectedDay>
359
+ );
360
+ }
361
+ if (
362
+ today.getFullYear() === day.year &&
363
+ today.getMonth() === day.month &&
364
+ today.getDate() === day.day
365
+ ) {
366
+ return (
367
+ <Today
368
+ key={key}
369
+ weekend={weekend}
370
+ disabled={disabled}
371
+ {...getDayProps(day, disabled)}
372
+ >
373
+ {day.day}
374
+ </Today>
375
+ );
376
+ }
377
+ return (
378
+ <Day
379
+ key={key}
380
+ weekend={weekend}
381
+ disabled={disabled}
382
+ {...getDayProps(day, disabled)}
383
+ >
384
+ {day.day}
385
+ </Day>
386
+ );
387
+ })}
388
+ </DayGrid>
389
+
390
+ {loading && (
391
+ <LoadingContainer>
392
+ <StyledLoading />
393
+ </LoadingContainer>
394
+ )}
395
+ </DayGridContainer>
396
+ </>
397
+ );
398
+ };
399
+
400
+ export default Calendar;
@@ -0,0 +1,212 @@
1
+ import React, {
2
+ useEffect,
3
+ useLayoutEffect,
4
+ useMemo,
5
+ useRef,
6
+ useState,
7
+ } from 'react';
8
+ import type { Month } from './utils/calendarDays.js';
9
+ import type { DateCalendarLocale } from './locale.js';
10
+ import styled from '@emotion/styled';
11
+ import Button from '../Button/index.js';
12
+ import { clr } from '@os-design/theming';
13
+ import { css } from '@emotion/react';
14
+ import { omitEmotionProps } from '@os-design/utils';
15
+ import { enableScrollingStyles } from '@os-design/styles';
16
+
17
+ const Header = styled.div`
18
+ display: flex;
19
+ align-items: center;
20
+ margin-bottom: 0.5em;
21
+ `;
22
+
23
+ const BackButtonContainer = styled.div`
24
+ flex-grow: 1;
25
+ `;
26
+
27
+ const DateButton = styled(Button)`
28
+ padding: 0 0.75em;
29
+ & > span {
30
+ font-size: ${(p) => p.theme.sizes.large}em;
31
+ }
32
+ `;
33
+
34
+ const Container = styled.div`
35
+ position: relative;
36
+ height: ${(p) => p.theme.dateCalendarCellSize * 7}em;
37
+
38
+ display: flex;
39
+ justify-content: center;
40
+ column-gap: 0.5em;
41
+ `;
42
+
43
+ const List = styled.div`
44
+ ${enableScrollingStyles('y', false)};
45
+ `;
46
+
47
+ const selectedStyles = (p) =>
48
+ p.selected &&
49
+ css`
50
+ & > span {
51
+ color: ${clr(p.theme.buttonGhostColorText)};
52
+ }
53
+ `;
54
+
55
+ interface ListItemButtonProps {
56
+ selected: boolean;
57
+ }
58
+ const ListItemButton = styled(
59
+ Button,
60
+ omitEmotionProps('selected')
61
+ )<ListItemButtonProps>`
62
+ & > span {
63
+ color: ${(p) => clr(p.theme.colorText)};
64
+ }
65
+ ${selectedStyles};
66
+ `;
67
+
68
+ interface MonthPickerProps {
69
+ locale: DateCalendarLocale;
70
+ month: Month;
71
+ onChangeMonth: (month: Month) => void;
72
+ today: Date;
73
+ }
74
+
75
+ const MonthPicker: React.FC<MonthPickerProps> = ({
76
+ locale,
77
+ month,
78
+ onChangeMonth,
79
+ today,
80
+ }) => {
81
+ const [selectedMonth, setSelectedMonth] = useState(month);
82
+ const [selectedParts, setSelectedParts] = useState({
83
+ year: false,
84
+ month: false,
85
+ });
86
+
87
+ const yearListRef = useRef<HTMLDivElement>(null);
88
+ const monthListRef = useRef<HTMLDivElement>(null);
89
+ const selectedYearButtonRef = useRef<HTMLButtonElement>(null);
90
+ const selectedMonthButtonRef = useRef<HTMLButtonElement>(null);
91
+
92
+ // Scroll the lists to the selected year and month
93
+ useLayoutEffect(() => {
94
+ if (monthListRef.current && selectedMonthButtonRef.current) {
95
+ monthListRef.current.scrollTo({
96
+ top: selectedMonthButtonRef.current.offsetTop,
97
+ });
98
+ }
99
+ if (yearListRef.current && selectedYearButtonRef.current) {
100
+ yearListRef.current.scrollTo({
101
+ top: selectedYearButtonRef.current.offsetTop,
102
+ });
103
+ }
104
+ }, []);
105
+
106
+ // Call the onChangeMonth callback when all the parts are selected
107
+ useEffect(() => {
108
+ if (selectedParts.year && selectedParts.month) {
109
+ onChangeMonth(selectedMonth);
110
+ }
111
+ }, [onChangeMonth, selectedMonth, selectedParts.month, selectedParts.year]);
112
+
113
+ const years = useMemo(() => {
114
+ const res: number[] = [];
115
+ for (let i = -100; i <= 10; i++) {
116
+ res.push(today.getFullYear() + i);
117
+ }
118
+ return res;
119
+ }, [today]);
120
+
121
+ return (
122
+ <>
123
+ <Header>
124
+ <BackButtonContainer>
125
+ <DateButton
126
+ type='ghost'
127
+ wide='never'
128
+ size='small'
129
+ onClick={() => {
130
+ onChangeMonth(selectedMonth);
131
+ }}
132
+ >
133
+ {locale.months[selectedMonth.month]} {selectedMonth.year}
134
+ </DateButton>
135
+ </BackButtonContainer>
136
+
137
+ {(selectedMonth.year !== today.getFullYear() ||
138
+ selectedMonth.month !== today.getMonth()) && (
139
+ <DateButton
140
+ type='ghost'
141
+ wide='never'
142
+ size='small'
143
+ onClick={() => {
144
+ onChangeMonth({
145
+ year: today.getFullYear(),
146
+ month: today.getMonth(),
147
+ });
148
+ }}
149
+ >
150
+ {locale.months[today.getMonth()]} {today.getFullYear()}
151
+ </DateButton>
152
+ )}
153
+ </Header>
154
+
155
+ <Container>
156
+ <List ref={monthListRef}>
157
+ {locale.months.map((item, index) => {
158
+ const selected = index === selectedMonth.month;
159
+ return (
160
+ <ListItemButton
161
+ ref={selected ? selectedMonthButtonRef : undefined}
162
+ key={item}
163
+ type='ghost'
164
+ selected={selected}
165
+ onClick={() => {
166
+ setSelectedMonth((prev) => ({
167
+ year: prev.year,
168
+ month: index,
169
+ }));
170
+ setSelectedParts((prev) => ({
171
+ year: selected || prev.year,
172
+ month: true,
173
+ }));
174
+ }}
175
+ >
176
+ {item}
177
+ </ListItemButton>
178
+ );
179
+ })}
180
+ </List>
181
+
182
+ <List ref={yearListRef}>
183
+ {years.map((item) => {
184
+ const selected = item === selectedMonth.year;
185
+ return (
186
+ <ListItemButton
187
+ ref={selected ? selectedYearButtonRef : undefined}
188
+ selected={selected}
189
+ key={item}
190
+ type='ghost'
191
+ onClick={() => {
192
+ setSelectedMonth((prev) => ({
193
+ year: item,
194
+ month: prev.month,
195
+ }));
196
+ setSelectedParts((prev) => ({
197
+ year: true,
198
+ month: selected || prev.month,
199
+ }));
200
+ }}
201
+ >
202
+ {item}
203
+ </ListItemButton>
204
+ );
205
+ })}
206
+ </List>
207
+ </Container>
208
+ </>
209
+ );
210
+ };
211
+
212
+ export default MonthPicker;