@utilitywarehouse/hearth-react-native 0.3.1 → 0.4.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 (193) hide show
  1. package/.storybook/preview.tsx +3 -0
  2. package/.turbo/turbo-build.log +1 -1
  3. package/.turbo/turbo-lint.log +1 -1
  4. package/CHANGELOG.md +10 -0
  5. package/build/components/CurrencyInput/CurrencyInput.js +1 -1
  6. package/build/components/DatePicker/DatePicker.context.d.ts +19 -0
  7. package/build/components/DatePicker/DatePicker.context.js +3 -0
  8. package/build/components/DatePicker/DatePicker.d.ts +19 -0
  9. package/build/components/DatePicker/DatePicker.js +479 -0
  10. package/build/components/DatePicker/DatePicker.props.d.ts +125 -0
  11. package/build/components/DatePicker/DatePicker.props.js +1 -0
  12. package/build/components/DatePicker/DatePickerCalendar.d.ts +2 -0
  13. package/build/components/DatePicker/DatePickerCalendar.js +28 -0
  14. package/build/components/DatePicker/DatePickerDay.d.ts +11 -0
  15. package/build/components/DatePicker/DatePickerDay.js +242 -0
  16. package/build/components/DatePicker/DatePickerDays.d.ts +2 -0
  17. package/build/components/DatePicker/DatePickerDays.js +157 -0
  18. package/build/components/DatePicker/DatePickerFooter.d.ts +2 -0
  19. package/build/components/DatePicker/DatePickerFooter.js +23 -0
  20. package/build/components/DatePicker/DatePickerHeader/DatePickerHeader.props.d.ts +8 -0
  21. package/build/components/DatePicker/DatePickerHeader/DatePickerHeader.props.js +1 -0
  22. package/build/components/DatePicker/DatePickerHeader/DatePickerMonthButton.d.ts +2 -0
  23. package/build/components/DatePicker/DatePickerHeader/DatePickerMonthButton.js +14 -0
  24. package/build/components/DatePicker/DatePickerHeader/DatePickerNextButton.d.ts +2 -0
  25. package/build/components/DatePicker/DatePickerHeader/DatePickerNextButton.js +32 -0
  26. package/build/components/DatePicker/DatePickerHeader/DatePickerPrevButton.d.ts +2 -0
  27. package/build/components/DatePicker/DatePickerHeader/DatePickerPrevButton.js +32 -0
  28. package/build/components/DatePicker/DatePickerHeader/DatePickerSelectors.d.ts +6 -0
  29. package/build/components/DatePicker/DatePickerHeader/DatePickerSelectors.js +64 -0
  30. package/build/components/DatePicker/DatePickerHeader/DatePickerTimeButton.d.ts +1 -0
  31. package/build/components/DatePicker/DatePickerHeader/DatePickerTimeButton.js +22 -0
  32. package/build/components/DatePicker/DatePickerHeader/DatePickerYearButton.d.ts +2 -0
  33. package/build/components/DatePicker/DatePickerHeader/DatePickerYearButton.js +25 -0
  34. package/build/components/DatePicker/DatePickerHeader/index.d.ts +3 -0
  35. package/build/components/DatePicker/DatePickerHeader/index.js +30 -0
  36. package/build/components/DatePicker/DatePickerMonths.d.ts +2 -0
  37. package/build/components/DatePicker/DatePickerMonths.js +69 -0
  38. package/build/components/DatePicker/DatePickerWeekdays.d.ts +8 -0
  39. package/build/components/DatePicker/DatePickerWeekdays.js +26 -0
  40. package/build/components/DatePicker/DatePickerYears.d.ts +2 -0
  41. package/build/components/DatePicker/DatePickerYears.js +83 -0
  42. package/build/components/DatePicker/TimePicker.d.ts +3 -0
  43. package/build/components/DatePicker/TimePicker.js +84 -0
  44. package/build/components/DatePicker/enums.d.ts +12 -0
  45. package/build/components/DatePicker/enums.js +12 -0
  46. package/build/components/DatePicker/index.d.ts +4 -0
  47. package/build/components/DatePicker/index.js +3 -0
  48. package/build/components/DatePicker/polyfill.d.ts +0 -0
  49. package/build/components/DatePicker/polyfill.js +22 -0
  50. package/build/components/DatePicker/time-picker/animated-math.d.ts +4 -0
  51. package/build/components/DatePicker/time-picker/animated-math.js +19 -0
  52. package/build/components/DatePicker/time-picker/period-native.d.ts +6 -0
  53. package/build/components/DatePicker/time-picker/period-native.js +17 -0
  54. package/build/components/DatePicker/time-picker/period-picker.d.ts +6 -0
  55. package/build/components/DatePicker/time-picker/period-picker.js +10 -0
  56. package/build/components/DatePicker/time-picker/period-web.d.ts +6 -0
  57. package/build/components/DatePicker/time-picker/period-web.js +21 -0
  58. package/build/components/DatePicker/time-picker/wheel-native.d.ts +8 -0
  59. package/build/components/DatePicker/time-picker/wheel-native.js +19 -0
  60. package/build/components/DatePicker/time-picker/wheel-picker/index.d.ts +2 -0
  61. package/build/components/DatePicker/time-picker/wheel-picker/index.js +2 -0
  62. package/build/components/DatePicker/time-picker/wheel-picker/wheel-picker-item.d.ts +16 -0
  63. package/build/components/DatePicker/time-picker/wheel-picker/wheel-picker-item.js +97 -0
  64. package/build/components/DatePicker/time-picker/wheel-picker/wheel-picker.d.ts +21 -0
  65. package/build/components/DatePicker/time-picker/wheel-picker/wheel-picker.js +88 -0
  66. package/build/components/DatePicker/time-picker/wheel-picker/wheel-picker.style.d.ts +23 -0
  67. package/build/components/DatePicker/time-picker/wheel-picker/wheel-picker.style.js +21 -0
  68. package/build/components/DatePicker/time-picker/wheel-web.d.ts +8 -0
  69. package/build/components/DatePicker/time-picker/wheel-web.js +148 -0
  70. package/build/components/DatePicker/time-picker/wheel.d.ts +8 -0
  71. package/build/components/DatePicker/time-picker/wheel.js +10 -0
  72. package/build/components/DatePicker/utils.d.ts +212 -0
  73. package/build/components/DatePicker/utils.js +391 -0
  74. package/build/components/DatePickerInput/DatePickerInput.d.ts +6 -0
  75. package/build/components/DatePickerInput/DatePickerInput.js +126 -0
  76. package/build/components/DatePickerInput/DatePickerInput.props.d.ts +47 -0
  77. package/build/components/DatePickerInput/DatePickerInput.props.js +1 -0
  78. package/build/components/DatePickerInput/DatePickerInputDoneButton.d.ts +8 -0
  79. package/build/components/DatePickerInput/DatePickerInputDoneButton.js +19 -0
  80. package/build/components/DatePickerInput/DatePickerInputDoneButton.web.d.ts +5 -0
  81. package/build/components/DatePickerInput/DatePickerInputDoneButton.web.js +5 -0
  82. package/build/components/DatePickerInput/index.d.ts +2 -0
  83. package/build/components/DatePickerInput/index.js +1 -0
  84. package/build/components/Input/InputField.d.ts +1 -1
  85. package/build/components/Input/InputField.js +1 -1
  86. package/build/components/Input/InputSlot.d.ts +1 -1
  87. package/build/components/Input/InputSlot.js +3 -3
  88. package/build/components/UnstyledIconButton/UnstyledIconButton.js +2 -2
  89. package/build/components/UnstyledIconButton/UnstyledIconButtonRoot.js +1 -1
  90. package/build/components/index.d.ts +2 -0
  91. package/build/components/index.js +2 -0
  92. package/build/hooks/index.d.ts +4 -3
  93. package/build/hooks/index.js +4 -3
  94. package/build/hooks/usePrevious.d.ts +1 -0
  95. package/build/hooks/usePrevious.js +8 -0
  96. package/build/hooks/useTheme.d.ts +2 -1
  97. package/build/hooks/useTheme.js +2 -2
  98. package/build/tokens/components/dark/date-picker.d.ts +1 -0
  99. package/build/tokens/components/dark/date-picker.js +1 -0
  100. package/build/tokens/components/dark/illustrations.d.ts +7 -0
  101. package/build/tokens/components/dark/illustrations.js +6 -0
  102. package/build/tokens/components/dark/index.d.ts +1 -0
  103. package/build/tokens/components/dark/index.js +1 -0
  104. package/build/tokens/components/dark/segmented-control.d.ts +2 -2
  105. package/build/tokens/components/dark/segmented-control.js +2 -2
  106. package/build/tokens/components/dark/table.d.ts +3 -0
  107. package/build/tokens/components/dark/table.js +3 -0
  108. package/build/tokens/components/light/date-picker.d.ts +1 -0
  109. package/build/tokens/components/light/date-picker.js +1 -0
  110. package/build/tokens/components/light/illustrations.d.ts +7 -0
  111. package/build/tokens/components/light/illustrations.js +6 -0
  112. package/build/tokens/components/light/index.d.ts +1 -0
  113. package/build/tokens/components/light/index.js +1 -0
  114. package/build/tokens/components/light/segmented-control.d.ts +2 -2
  115. package/build/tokens/components/light/segmented-control.js +2 -2
  116. package/build/tokens/components/light/table.d.ts +3 -0
  117. package/build/tokens/components/light/table.js +3 -0
  118. package/build/utils/index.d.ts +1 -0
  119. package/build/utils/index.js +1 -0
  120. package/build/utils/isEqual.d.ts +2 -0
  121. package/build/utils/isEqual.js +29 -0
  122. package/chromatic.config.json +6 -0
  123. package/docs/components/AllComponents.web.tsx +43 -1
  124. package/docs/components/ViewWrap.tsx +32 -0
  125. package/docs/components/index.ts +1 -0
  126. package/docs/getting-started.mdx +6 -6
  127. package/package.json +10 -7
  128. package/src/components/CurrencyInput/CurrencyInput.tsx +2 -1
  129. package/src/components/DatePicker/DatePicker.context.ts +23 -0
  130. package/src/components/DatePicker/DatePicker.docs.mdx +239 -0
  131. package/src/components/DatePicker/DatePicker.props.ts +139 -0
  132. package/src/components/DatePicker/DatePicker.stories.tsx +98 -0
  133. package/src/components/DatePicker/DatePicker.tsx +669 -0
  134. package/src/components/DatePicker/DatePickerCalendar.tsx +41 -0
  135. package/src/components/DatePicker/DatePickerDay.tsx +302 -0
  136. package/src/components/DatePicker/DatePickerDays.tsx +241 -0
  137. package/src/components/DatePicker/DatePickerFooter.tsx +35 -0
  138. package/src/components/DatePicker/DatePickerHeader/DatePickerHeader.props.ts +10 -0
  139. package/src/components/DatePicker/DatePickerHeader/DatePickerMonthButton.tsx +29 -0
  140. package/src/components/DatePicker/DatePickerHeader/DatePickerNextButton.tsx +46 -0
  141. package/src/components/DatePicker/DatePickerHeader/DatePickerPrevButton.tsx +46 -0
  142. package/src/components/DatePicker/DatePickerHeader/DatePickerSelectors.tsx +96 -0
  143. package/src/components/DatePicker/DatePickerHeader/DatePickerTimeButton.tsx +48 -0
  144. package/src/components/DatePicker/DatePickerHeader/DatePickerYearButton.tsx +50 -0
  145. package/src/components/DatePicker/DatePickerHeader/index.tsx +64 -0
  146. package/src/components/DatePicker/DatePickerMonths.tsx +101 -0
  147. package/src/components/DatePicker/DatePickerWeekdays.tsx +49 -0
  148. package/src/components/DatePicker/DatePickerYears.tsx +119 -0
  149. package/src/components/DatePicker/TimePicker.tsx +141 -0
  150. package/src/components/DatePicker/enums.ts +14 -0
  151. package/src/components/DatePicker/index.ts +13 -0
  152. package/src/components/DatePicker/polyfill.ts +21 -0
  153. package/src/components/DatePicker/time-picker/animated-math.ts +33 -0
  154. package/src/components/DatePicker/time-picker/period-native.tsx +34 -0
  155. package/src/components/DatePicker/time-picker/period-picker.tsx +16 -0
  156. package/src/components/DatePicker/time-picker/period-web.tsx +36 -0
  157. package/src/components/DatePicker/time-picker/wheel-native.tsx +37 -0
  158. package/src/components/DatePicker/time-picker/wheel-picker/index.ts +3 -0
  159. package/src/components/DatePicker/time-picker/wheel-picker/wheel-picker-item.tsx +132 -0
  160. package/src/components/DatePicker/time-picker/wheel-picker/wheel-picker.style.ts +22 -0
  161. package/src/components/DatePicker/time-picker/wheel-picker/wheel-picker.tsx +200 -0
  162. package/src/components/DatePicker/time-picker/wheel-web.tsx +181 -0
  163. package/src/components/DatePicker/time-picker/wheel.tsx +18 -0
  164. package/src/components/DatePicker/utils.ts +549 -0
  165. package/src/components/DatePickerInput/DatePickerInput.docs.mdx +237 -0
  166. package/src/components/DatePickerInput/DatePickerInput.props.ts +50 -0
  167. package/src/components/DatePickerInput/DatePickerInput.stories.tsx +178 -0
  168. package/src/components/DatePickerInput/DatePickerInput.tsx +265 -0
  169. package/src/components/DatePickerInput/DatePickerInputDoneButton.tsx +42 -0
  170. package/src/components/DatePickerInput/DatePickerInputDoneButton.web.tsx +7 -0
  171. package/src/components/DatePickerInput/index.ts +2 -0
  172. package/src/components/Icon/Icon.stories.tsx +4 -3
  173. package/src/components/Input/InputField.tsx +0 -2
  174. package/src/components/Input/InputSlot.tsx +14 -3
  175. package/src/components/Modal/Modal.stories.tsx +2 -30
  176. package/src/components/UnstyledIconButton/UnstyledIconButton.tsx +14 -2
  177. package/src/components/UnstyledIconButton/UnstyledIconButtonRoot.tsx +7 -3
  178. package/src/components/index.ts +2 -0
  179. package/src/hooks/index.ts +4 -3
  180. package/src/hooks/usePrevious.ts +9 -0
  181. package/src/hooks/useTheme.ts +4 -3
  182. package/src/tokens/components/dark/date-picker.ts +1 -0
  183. package/src/tokens/components/dark/illustrations.ts +7 -0
  184. package/src/tokens/components/dark/index.ts +1 -0
  185. package/src/tokens/components/dark/segmented-control.ts +2 -2
  186. package/src/tokens/components/dark/table.ts +3 -0
  187. package/src/tokens/components/light/date-picker.ts +1 -0
  188. package/src/tokens/components/light/illustrations.ts +7 -0
  189. package/src/tokens/components/light/index.ts +1 -0
  190. package/src/tokens/components/light/segmented-control.ts +2 -2
  191. package/src/tokens/components/light/table.ts +3 -0
  192. package/src/utils/index.ts +1 -0
  193. package/src/utils/isEqual.ts +30 -0
@@ -0,0 +1,669 @@
1
+ import dayjs from 'dayjs';
2
+ import duration from 'dayjs/plugin/duration';
3
+ import localeData from 'dayjs/plugin/localeData';
4
+ import localizedFormat from 'dayjs/plugin/localizedFormat';
5
+ import relativeTime from 'dayjs/plugin/relativeTime';
6
+ import timezone from 'dayjs/plugin/timezone';
7
+ import utc from 'dayjs/plugin/utc';
8
+ import { useCallback, useEffect, useImperativeHandle, useMemo, useReducer, useRef } from 'react';
9
+ import { AccessibilityInfo, findNodeHandle, Platform, View as RNView } from 'react-native';
10
+ import { usePrevious } from '../../hooks/usePrevious';
11
+ import { BottomSheetModal, BottomSheetScrollView } from '../BottomSheet';
12
+ import { DatePickerContext } from './DatePicker.context';
13
+ import type {
14
+ CalendarAction,
15
+ DatePickerBaseProps,
16
+ DateType,
17
+ LocalState,
18
+ MultiChange,
19
+ RangeChange,
20
+ SingleChange,
21
+ } from './DatePicker.props';
22
+ import Calendar from './DatePickerCalendar';
23
+ import { CalendarActionKind, CalendarViews, CONTAINER_HEIGHT, WEEKDAYS_HEIGHT } from './enums';
24
+ import { areDatesOnSameDay, dateToUnix, getEndOfDay, getStartOfDay, removeTime } from './utils';
25
+
26
+ dayjs.extend(localeData);
27
+ dayjs.extend(relativeTime);
28
+ dayjs.extend(localizedFormat);
29
+ dayjs.extend(utc);
30
+ dayjs.extend(timezone);
31
+ dayjs.extend(duration);
32
+
33
+ export interface DatePickerSingleProps extends DatePickerBaseProps {
34
+ mode: 'single';
35
+ date?: DateType;
36
+ onChange?: SingleChange;
37
+ }
38
+
39
+ export interface DatePickerRangeProps extends DatePickerBaseProps {
40
+ mode: 'range';
41
+ startDate?: DateType;
42
+ endDate?: DateType;
43
+ onChange?: RangeChange;
44
+ }
45
+
46
+ export interface DatePickerMultipleProps extends DatePickerBaseProps {
47
+ mode: 'multiple';
48
+ dates?: DateType[];
49
+ onChange?: MultiChange;
50
+ }
51
+
52
+ const DateTimePicker = (
53
+ props: DatePickerSingleProps | DatePickerRangeProps | DatePickerMultipleProps
54
+ ) => {
55
+ const numerals: 'latn' = 'latn';
56
+ const {
57
+ mode = 'single',
58
+ timeZone,
59
+ showOutsideDays = true,
60
+ timePicker = false,
61
+ firstDayOfWeek = 1,
62
+ // startYear,
63
+ // endYear,
64
+ minDate,
65
+ maxDate,
66
+ enabledDates,
67
+ disabledDates,
68
+ date,
69
+ startDate,
70
+ endDate,
71
+ dates,
72
+ min,
73
+ max,
74
+ onChange,
75
+ initialView = 'day',
76
+ containerHeight = CONTAINER_HEIGHT,
77
+ weekdaysHeight = WEEKDAYS_HEIGHT,
78
+ navigationPosition = 'right',
79
+ weekdaysFormat = 'min',
80
+ monthsFormat = 'short',
81
+ monthCaptionFormat = 'full',
82
+ multiRangeMode,
83
+ hideHeader,
84
+ hideFooter,
85
+ hideWeekdays,
86
+ disableMonthPicker,
87
+ disableYearPicker,
88
+ month,
89
+ year,
90
+ onMonthChange = () => {},
91
+ onYearChange = () => {},
92
+ use12Hours,
93
+ ref,
94
+ onCancel = () => {},
95
+ } = props;
96
+
97
+ dayjs.tz.setDefault(timeZone);
98
+ dayjs.locale('en');
99
+
100
+ const modalRef = useRef<BottomSheetModal>(null);
101
+ const calendarViewRef = useRef<RNView>(null);
102
+ const scrollViewRef = useRef<any>(null);
103
+ // Forward ref methods to parent component
104
+ useImperativeHandle(ref, () => modalRef.current as BottomSheetModal);
105
+
106
+ const closeDatePicker = useCallback(() => {
107
+ modalRef.current?.close();
108
+ }, [modalRef.current]);
109
+
110
+ const prevTimezone = usePrevious(timeZone);
111
+
112
+ const initialCalendarView: CalendarViews = useMemo(
113
+ () => (mode !== 'single' && initialView === 'time' ? 'day' : initialView),
114
+ [mode, initialView]
115
+ );
116
+
117
+ const firstDay = useMemo(
118
+ () => (firstDayOfWeek && firstDayOfWeek > 0 && firstDayOfWeek <= 6 ? firstDayOfWeek : 0),
119
+ [firstDayOfWeek]
120
+ );
121
+
122
+ const initialState: LocalState = useMemo(() => {
123
+ let initialDate = dayjs().tz(timeZone);
124
+
125
+ if (mode === 'single' && date) {
126
+ initialDate = dayjs(date);
127
+ }
128
+
129
+ if (mode === 'range' && startDate) {
130
+ initialDate = dayjs(startDate);
131
+ }
132
+
133
+ if (mode === 'multiple' && dates && dates.length > 0) {
134
+ initialDate = dayjs(dates[0]);
135
+ }
136
+
137
+ if (minDate && initialDate.isBefore(minDate)) {
138
+ initialDate = dayjs(minDate);
139
+ }
140
+
141
+ if (month !== undefined && month && month >= 0 && month <= 11) {
142
+ initialDate = initialDate.month(month);
143
+ }
144
+
145
+ if (year !== undefined && year >= 0) {
146
+ initialDate = initialDate.year(year);
147
+ }
148
+
149
+ let _date = (date ? dayjs(date) : date) as DateType;
150
+
151
+ if (_date && maxDate && dayjs(_date).isAfter(maxDate)) {
152
+ _date = dayjs(maxDate);
153
+ }
154
+
155
+ if (_date && minDate && dayjs(_date).isBefore(minDate)) {
156
+ _date = dayjs(minDate);
157
+ }
158
+
159
+ let start = (startDate ? dayjs(startDate) : startDate) as DateType;
160
+
161
+ if (start && maxDate && dayjs(start).isAfter(maxDate)) {
162
+ start = dayjs(maxDate);
163
+ }
164
+
165
+ if (start && minDate && dayjs(start).isBefore(minDate)) {
166
+ start = dayjs(minDate);
167
+ }
168
+
169
+ let end = (endDate ? dayjs(endDate) : endDate) as DateType;
170
+
171
+ if (end && maxDate && dayjs(end).isAfter(maxDate)) {
172
+ end = dayjs(maxDate);
173
+ }
174
+
175
+ if (end && minDate && dayjs(end).isBefore(minDate)) {
176
+ end = dayjs(minDate);
177
+ }
178
+
179
+ return {
180
+ date: _date,
181
+ startDate: start,
182
+ endDate: end,
183
+ dates,
184
+ calendarView: initialCalendarView,
185
+ currentDate: initialDate,
186
+ currentYear: initialDate.year(),
187
+ };
188
+ }, [
189
+ mode,
190
+ date,
191
+ startDate,
192
+ endDate,
193
+ dates,
194
+ minDate,
195
+ maxDate,
196
+ month,
197
+ year,
198
+ timeZone,
199
+ initialCalendarView,
200
+ ]);
201
+
202
+ const [state, dispatch] = useReducer((prevState: LocalState, action: CalendarAction) => {
203
+ switch (action.type) {
204
+ case CalendarActionKind.SET_CALENDAR_VIEW:
205
+ return {
206
+ ...prevState,
207
+ calendarView: action.payload,
208
+ };
209
+ case CalendarActionKind.CHANGE_CURRENT_DATE:
210
+ return {
211
+ ...prevState,
212
+ currentDate: action.payload,
213
+ };
214
+ case CalendarActionKind.CHANGE_CURRENT_YEAR:
215
+ return {
216
+ ...prevState,
217
+ currentYear: action.payload,
218
+ };
219
+ case CalendarActionKind.CHANGE_SELECTED_DATE:
220
+ const { date: selectedDate } = action.payload;
221
+ return {
222
+ ...prevState,
223
+ date: selectedDate,
224
+ currentDate: selectedDate,
225
+ };
226
+ case CalendarActionKind.CHANGE_SELECTED_RANGE:
227
+ const { startDate: start, endDate: end } = action.payload;
228
+ return {
229
+ ...prevState,
230
+ startDate: start,
231
+ endDate: end,
232
+ };
233
+ case CalendarActionKind.CHANGE_SELECTED_MULTIPLE:
234
+ const { dates: selectedDates } = action.payload;
235
+ return {
236
+ ...prevState,
237
+ dates: selectedDates,
238
+ };
239
+ case CalendarActionKind.RESET_STATE:
240
+ return action.payload;
241
+ default:
242
+ return prevState;
243
+ }
244
+ }, initialState);
245
+
246
+ const stateRef = useRef(state);
247
+ stateRef.current = state;
248
+
249
+ useEffect(() => {
250
+ const newState = {
251
+ ...initialState,
252
+ };
253
+
254
+ dispatch({ type: CalendarActionKind.RESET_STATE, payload: newState });
255
+ }, []);
256
+
257
+ useEffect(() => {
258
+ if (prevTimezone !== timeZone) {
259
+ const newDate = dayjs().tz(timeZone);
260
+ dispatch({
261
+ type: CalendarActionKind.CHANGE_CURRENT_DATE,
262
+ payload: newDate,
263
+ });
264
+ }
265
+ }, [timeZone, prevTimezone]);
266
+
267
+ useEffect(() => {
268
+ if (mode === 'single') {
269
+ let _date =
270
+ (date &&
271
+ (timePicker ? dayjs.tz(date, timeZone) : getStartOfDay(dayjs.tz(date, timeZone)))) ??
272
+ date;
273
+
274
+ if (_date && maxDate && dayjs.tz(_date, timeZone).isAfter(maxDate)) {
275
+ _date = dayjs.tz(maxDate, timeZone);
276
+ }
277
+
278
+ if (_date && minDate && dayjs.tz(_date, timeZone).isBefore(minDate)) {
279
+ _date = dayjs.tz(minDate, timeZone);
280
+ }
281
+
282
+ dispatch({
283
+ type: CalendarActionKind.CHANGE_SELECTED_DATE,
284
+ payload: { date: _date },
285
+ });
286
+
287
+ if (prevTimezone !== timeZone) {
288
+ (onChange as SingleChange)({
289
+ date: _date ? dayjs(_date).toDate() : _date,
290
+ });
291
+ }
292
+ } else if (mode === 'range') {
293
+ let start = (startDate ? dayjs.tz(startDate, timeZone) : startDate) as DateType;
294
+
295
+ if (start && maxDate && dayjs.tz(start, timeZone).isAfter(maxDate)) {
296
+ start = dayjs.tz(maxDate, timeZone);
297
+ }
298
+
299
+ if (start && minDate && dayjs.tz(start, timeZone).isBefore(minDate)) {
300
+ start = dayjs.tz(minDate, timeZone);
301
+ }
302
+
303
+ let end = (endDate ? dayjs.tz(endDate, timeZone) : endDate) as DateType;
304
+
305
+ if (end && maxDate && dayjs.tz(end, timeZone).isAfter(maxDate)) {
306
+ end = dayjs.tz(maxDate, timeZone);
307
+ }
308
+
309
+ if (end && minDate && dayjs.tz(end, timeZone).isBefore(minDate)) {
310
+ end = dayjs.tz(minDate, timeZone);
311
+ }
312
+
313
+ dispatch({
314
+ type: CalendarActionKind.CHANGE_SELECTED_RANGE,
315
+ payload: {
316
+ startDate: start,
317
+ endDate: end,
318
+ },
319
+ });
320
+
321
+ if (prevTimezone !== timeZone) {
322
+ (onChange as RangeChange)({
323
+ startDate: start ? dayjs(start).toDate() : start,
324
+ endDate: end ? dayjs(end).toDate() : end,
325
+ });
326
+ }
327
+ } else if (mode === 'multiple') {
328
+ const _dates = dates?.map(date => dayjs(date).tz(timeZone)) as DateType[];
329
+
330
+ dispatch({
331
+ type: CalendarActionKind.CHANGE_SELECTED_MULTIPLE,
332
+ payload: { dates: _dates },
333
+ });
334
+
335
+ if (prevTimezone !== timeZone) {
336
+ (onChange as MultiChange)({
337
+ dates: _dates.map(item => dayjs(item).toDate()),
338
+ change: 'updated',
339
+ });
340
+ }
341
+ }
342
+ }, [mode, date, startDate, endDate, dates, minDate, maxDate, timePicker, prevTimezone, timeZone]);
343
+
344
+ const setCalendarView = useCallback((view: CalendarViews) => {
345
+ dispatch({ type: CalendarActionKind.SET_CALENDAR_VIEW, payload: view });
346
+ }, []);
347
+
348
+ const onSelectDate = useCallback(
349
+ (selectedDate: DateType) => {
350
+ if (onChange) {
351
+ if (mode === 'single') {
352
+ const newDate = timePicker
353
+ ? dayjs.tz(selectedDate, timeZone)
354
+ : dayjs.tz(getStartOfDay(selectedDate), timeZone);
355
+
356
+ dispatch({
357
+ type: CalendarActionKind.CHANGE_CURRENT_DATE,
358
+ payload: newDate,
359
+ });
360
+
361
+ (onChange as SingleChange)({
362
+ date: newDate ? dayjs(newDate).toDate() : newDate,
363
+ });
364
+ } else if (mode === 'range') {
365
+ // set time to 00:00:00
366
+ let start = removeTime(stateRef.current.startDate, timeZone);
367
+ let end = removeTime(stateRef.current.endDate, timeZone);
368
+ const selected = removeTime(selectedDate, timeZone);
369
+ let isStart: boolean = true;
370
+ let isReset: boolean = false;
371
+
372
+ if (
373
+ dateToUnix(selected) !== dateToUnix(end) &&
374
+ dateToUnix(selected) >= dateToUnix(start) &&
375
+ dateToUnix(start) !== dateToUnix(end)
376
+ ) {
377
+ isStart = false;
378
+ } else if (start && dateToUnix(selected) === dateToUnix(start)) {
379
+ isReset = true;
380
+ }
381
+
382
+ if (start && end) {
383
+ if (dateToUnix(start) === dateToUnix(end) && dateToUnix(selected) > dateToUnix(start)) {
384
+ isStart = false;
385
+ }
386
+
387
+ if (
388
+ dateToUnix(selected) > dateToUnix(start) &&
389
+ dateToUnix(selected) === dateToUnix(end)
390
+ ) {
391
+ end = undefined;
392
+ }
393
+ }
394
+
395
+ if (start && !end && dateToUnix(selected) < dateToUnix(start)) {
396
+ end = start;
397
+ }
398
+
399
+ if (isStart && end && (min || max)) {
400
+ const numberOfDays = dayjs(end).diff(selected, 'day');
401
+
402
+ if ((max && numberOfDays > max) || (min && numberOfDays < min)) {
403
+ isStart = true;
404
+ end = undefined;
405
+ }
406
+ }
407
+
408
+ if (!isStart && start && (min || max)) {
409
+ const numberOfDays = dayjs(selected).diff(start, 'day');
410
+
411
+ if (dateToUnix(selected) === dateToUnix(start)) {
412
+ isReset = true;
413
+ } else if ((max && numberOfDays > max) || (min && numberOfDays < min)) {
414
+ isStart = true;
415
+ end = undefined;
416
+ }
417
+ }
418
+
419
+ if (isReset) {
420
+ (onChange as RangeChange)({
421
+ startDate: undefined,
422
+ endDate: undefined,
423
+ });
424
+ } else {
425
+ (onChange as RangeChange)({
426
+ startDate: isStart
427
+ ? dayjs(selected).toDate()
428
+ : start
429
+ ? dayjs.tz(start).toDate()
430
+ : start,
431
+ endDate: !isStart
432
+ ? dayjs.tz(getEndOfDay(selected), timeZone).toDate()
433
+ : end
434
+ ? dayjs.tz(getEndOfDay(end), timeZone).toDate()
435
+ : end,
436
+ });
437
+ }
438
+ } else if (mode === 'multiple') {
439
+ const safeDates = (stateRef.current.dates as DateType[]) || [];
440
+ const newDate = dayjs(selectedDate, timeZone).startOf('day');
441
+
442
+ const exists = safeDates.some(ed => areDatesOnSameDay(ed, newDate));
443
+
444
+ const newDates = exists
445
+ ? safeDates.filter(ed => !areDatesOnSameDay(ed, newDate))
446
+ : [...safeDates, newDate];
447
+
448
+ if (max && newDates.length > max) {
449
+ return;
450
+ }
451
+
452
+ newDates.sort((a, b) => (dayjs(a).isAfter(dayjs(b)) ? 1 : -1));
453
+
454
+ const _dates = newDates.map(date => dayjs(date).tz(timeZone)) as DateType[];
455
+
456
+ (onChange as MultiChange)({
457
+ dates: _dates.map(item => dayjs(item).toDate()),
458
+ datePressed: newDate ? dayjs(newDate).toDate() : newDate,
459
+ change: exists ? 'removed' : 'added',
460
+ });
461
+ }
462
+ }
463
+ },
464
+ [mode, timePicker, min, max, timeZone]
465
+ );
466
+
467
+ // set the active displayed month
468
+ const onSelectMonth = useCallback(
469
+ (value: number) => {
470
+ const currentMonth = dayjs(stateRef.current.currentDate).month();
471
+ const newDate = dayjs(stateRef.current.currentDate).month(value);
472
+
473
+ // Only call onMonthChange if the month actually changed
474
+ if (value !== currentMonth) {
475
+ onMonthChange(value);
476
+ }
477
+
478
+ dispatch({
479
+ type: CalendarActionKind.CHANGE_CURRENT_DATE,
480
+ payload: newDate,
481
+ });
482
+ setCalendarView('day');
483
+ },
484
+ [setCalendarView, onMonthChange]
485
+ );
486
+
487
+ // set the active displayed year
488
+ const onSelectYear = useCallback(
489
+ (value: number) => {
490
+ const currentYear = dayjs(stateRef.current.currentDate).year();
491
+ const newDate = dayjs(stateRef.current.currentDate).year(value);
492
+
493
+ // Only call onYearChange if the year actually changed
494
+ if (value !== currentYear) {
495
+ onYearChange(value);
496
+ }
497
+
498
+ dispatch({
499
+ type: CalendarActionKind.CHANGE_CURRENT_DATE,
500
+ payload: newDate,
501
+ });
502
+ setCalendarView('day');
503
+ },
504
+ [setCalendarView, onYearChange]
505
+ );
506
+
507
+ const onChangeMonth = useCallback(
508
+ (value: number) => {
509
+ const newDate = dayjs(stateRef.current.currentDate).add(value, 'month');
510
+ dispatch({
511
+ type: CalendarActionKind.CHANGE_CURRENT_DATE,
512
+ payload: dayjs(newDate),
513
+ });
514
+ },
515
+ [stateRef, dispatch]
516
+ );
517
+
518
+ const onChangeYear = useCallback(
519
+ (value: number) => {
520
+ dispatch({
521
+ type: CalendarActionKind.CHANGE_CURRENT_YEAR,
522
+ payload: value,
523
+ });
524
+ },
525
+ [dispatch]
526
+ );
527
+
528
+ useEffect(() => {
529
+ if (month !== undefined && month >= 0 && month <= 11) {
530
+ onSelectMonth(month);
531
+ }
532
+ }, [month]);
533
+
534
+ useEffect(() => {
535
+ if (year !== undefined && year >= 0) {
536
+ onSelectYear(year);
537
+ }
538
+ }, [year]);
539
+
540
+ const baseContextValue = useMemo(
541
+ () => ({
542
+ mode,
543
+ numerals,
544
+ timeZone,
545
+ showOutsideDays,
546
+ timePicker,
547
+ minDate,
548
+ maxDate,
549
+ min,
550
+ max,
551
+ enabledDates,
552
+ disabledDates,
553
+ firstDayOfWeek: firstDay,
554
+ containerHeight,
555
+ weekdaysHeight,
556
+ navigationPosition,
557
+ weekdaysFormat,
558
+ monthsFormat,
559
+ monthCaptionFormat,
560
+ multiRangeMode,
561
+ hideHeader,
562
+ hideFooter,
563
+ hideWeekdays,
564
+ disableMonthPicker,
565
+ disableYearPicker,
566
+ use12Hours,
567
+ closeDatePicker,
568
+ }),
569
+ [
570
+ mode,
571
+ numerals,
572
+ timeZone,
573
+ showOutsideDays,
574
+ timePicker,
575
+ minDate,
576
+ maxDate,
577
+ min,
578
+ max,
579
+ enabledDates,
580
+ disabledDates,
581
+ firstDay,
582
+ containerHeight,
583
+ weekdaysHeight,
584
+ navigationPosition,
585
+ weekdaysFormat,
586
+ monthsFormat,
587
+ monthCaptionFormat,
588
+ multiRangeMode,
589
+ hideHeader,
590
+ hideFooter,
591
+ hideWeekdays,
592
+ disableMonthPicker,
593
+ disableYearPicker,
594
+ use12Hours,
595
+ closeDatePicker,
596
+ ]
597
+ );
598
+
599
+ const handlerContextValue = useMemo(
600
+ () => ({
601
+ setCalendarView,
602
+ onSelectDate,
603
+ onSelectMonth,
604
+ onSelectYear,
605
+ onChangeMonth,
606
+ onChangeYear,
607
+ onCancel,
608
+ }),
609
+ [
610
+ setCalendarView,
611
+ onSelectDate,
612
+ onSelectMonth,
613
+ onSelectYear,
614
+ onChangeMonth,
615
+ onChangeYear,
616
+ onCancel,
617
+ ]
618
+ );
619
+
620
+ const memoizedValue = useMemo(
621
+ () => ({
622
+ ...state,
623
+ ...baseContextValue,
624
+ ...handlerContextValue,
625
+ }),
626
+ [state, baseContextValue, handlerContextValue]
627
+ );
628
+
629
+ const handleChange = useCallback((index: number) => {
630
+ if (index > -1) {
631
+ // Add a small delay to ensure the bottom sheet is fully rendered
632
+ setTimeout(() => {
633
+ // Announce to screen readers
634
+ AccessibilityInfo.announceForAccessibility('Date picker opened.');
635
+
636
+ // Set focus for screen readers - different refs for iOS vs Android
637
+ const scrollViewTargetRef = scrollViewRef.current?.getInnerViewNode();
638
+ const targetRef = calendarViewRef.current;
639
+ if ((Platform.OS === 'android' && targetRef) || scrollViewTargetRef) {
640
+ const nodeHandle = findNodeHandle(
641
+ Platform.OS === 'android' ? targetRef : scrollViewTargetRef
642
+ );
643
+ if (nodeHandle) {
644
+ AccessibilityInfo.setAccessibilityFocus(nodeHandle);
645
+ }
646
+ }
647
+ }, 50);
648
+ }
649
+ }, []);
650
+
651
+ return (
652
+ <BottomSheetModal ref={modalRef} onChange={handleChange} accessible={false}>
653
+ <BottomSheetScrollView ref={scrollViewRef}>
654
+ <RNView
655
+ ref={calendarViewRef}
656
+ accessible={Platform.OS === 'android' ? true : undefined}
657
+ accessibilityLabel={Platform.OS === 'android' ? 'Date picker calendar' : undefined}
658
+ importantForAccessibility={Platform.OS === 'android' ? 'yes' : 'auto'}
659
+ >
660
+ <DatePickerContext.Provider value={memoizedValue}>
661
+ <Calendar />
662
+ </DatePickerContext.Provider>
663
+ </RNView>
664
+ </BottomSheetScrollView>
665
+ </BottomSheetModal>
666
+ );
667
+ };
668
+
669
+ export default DateTimePicker;
@@ -0,0 +1,41 @@
1
+ import { ReactNode } from 'react';
2
+ import { View } from 'react-native';
3
+ import { StyleSheet } from 'react-native-unistyles';
4
+ import { useDatePickerContext } from './DatePicker.context';
5
+ import Days from './DatePickerDays';
6
+ import Footer from './DatePickerFooter';
7
+ import Header from './DatePickerHeader';
8
+ import Months from './DatePickerMonths';
9
+ import Years from './DatePickerYears';
10
+ import type { CalendarViews } from './enums';
11
+ import TimePicker from './TimePicker';
12
+
13
+ const CalendarView: Record<CalendarViews, ReactNode> = {
14
+ year: <Years />,
15
+ month: <Months />,
16
+ day: <Days />,
17
+ time: <TimePicker />,
18
+ };
19
+
20
+ const Calendar = () => {
21
+ const { hideHeader, hideFooter, calendarView, containerHeight, navigationPosition } =
22
+ useDatePickerContext();
23
+
24
+ return (
25
+ <View style={[styles.container]} testID="calendar">
26
+ {!hideHeader ? <Header navigationPosition={navigationPosition} /> : null}
27
+ <View style={styles.containerInner(containerHeight)}>{CalendarView[calendarView]}</View>
28
+ {!hideFooter ? <Footer /> : null}
29
+ </View>
30
+ );
31
+ };
32
+
33
+ const styles = StyleSheet.create(theme => ({
34
+ container: {
35
+ backgroundColor: theme.color.background.secondary,
36
+ gap: theme.components.datePicker.calendar.gap,
37
+ },
38
+ containerInner: (height?: number) => ({ height }),
39
+ }));
40
+
41
+ export default Calendar;