@dreamstack-us/kaal 0.0.1 → 0.0.3

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 (136) hide show
  1. package/README.md +165 -0
  2. package/lib/module/components/CalendarGrid/CalendarGrid.js +125 -29
  3. package/lib/module/components/CalendarGrid/CalendarGrid.js.map +1 -1
  4. package/lib/module/components/CalendarGrid/CalendarGrid.styles.js +22 -17
  5. package/lib/module/components/CalendarGrid/CalendarGrid.styles.js.map +1 -1
  6. package/lib/module/components/CalendarGrid/CalendarGrid.web.js +265 -0
  7. package/lib/module/components/CalendarGrid/CalendarGrid.web.js.map +1 -0
  8. package/lib/module/components/CalendarGrid/DayCell.js +77 -53
  9. package/lib/module/components/CalendarGrid/DayCell.js.map +1 -1
  10. package/lib/module/components/CalendarGrid/DayCell.web.js +124 -0
  11. package/lib/module/components/CalendarGrid/DayCell.web.js.map +1 -0
  12. package/lib/module/components/CalendarGrid/index.js +1 -1
  13. package/lib/module/components/CalendarGrid/index.js.map +1 -1
  14. package/lib/module/components/DatePicker/DatePicker.android.js +53 -21
  15. package/lib/module/components/DatePicker/DatePicker.android.js.map +1 -1
  16. package/lib/module/components/DatePicker/DatePicker.ios.js +55 -23
  17. package/lib/module/components/DatePicker/DatePicker.ios.js.map +1 -1
  18. package/lib/module/components/DatePicker/DatePicker.js.map +1 -1
  19. package/lib/module/components/DatePicker/DatePicker.styles.js +19 -20
  20. package/lib/module/components/DatePicker/DatePicker.styles.js.map +1 -1
  21. package/lib/module/components/DatePicker/DatePicker.web.js +59 -21
  22. package/lib/module/components/DatePicker/DatePicker.web.js.map +1 -1
  23. package/lib/module/components/TimePicker/ClockFace.js +27 -7
  24. package/lib/module/components/TimePicker/ClockFace.js.map +1 -1
  25. package/lib/module/components/TimePicker/ClockFace.web.js +253 -0
  26. package/lib/module/components/TimePicker/ClockFace.web.js.map +1 -0
  27. package/lib/module/components/TimePicker/MaterialTimePicker.js +68 -16
  28. package/lib/module/components/TimePicker/MaterialTimePicker.js.map +1 -1
  29. package/lib/module/components/TimePicker/MaterialTimePicker.web.js +231 -0
  30. package/lib/module/components/TimePicker/MaterialTimePicker.web.js.map +1 -0
  31. package/lib/module/components/TimePicker/TimePicker.android.js +13 -6
  32. package/lib/module/components/TimePicker/TimePicker.android.js.map +1 -1
  33. package/lib/module/components/TimePicker/TimePicker.ios.js +14 -7
  34. package/lib/module/components/TimePicker/TimePicker.ios.js.map +1 -1
  35. package/lib/module/components/TimePicker/TimePicker.styles.js +53 -45
  36. package/lib/module/components/TimePicker/TimePicker.styles.js.map +1 -1
  37. package/lib/module/components/TimePicker/TimePicker.web.js +24 -12
  38. package/lib/module/components/TimePicker/TimePicker.web.js.map +1 -1
  39. package/lib/module/components/TimePicker/TimeWheelPicker.js +45 -10
  40. package/lib/module/components/TimePicker/TimeWheelPicker.js.map +1 -1
  41. package/lib/module/components/TimePicker/TimeWheelPicker.web.js +339 -0
  42. package/lib/module/components/TimePicker/TimeWheelPicker.web.js.map +1 -0
  43. package/lib/module/components/TimePicker/index.js +3 -3
  44. package/lib/module/components/TimePicker/index.js.map +1 -1
  45. package/lib/module/components/WheelPicker/WheelPicker.js +21 -2
  46. package/lib/module/components/WheelPicker/WheelPicker.js.map +1 -1
  47. package/lib/module/components/WheelPicker/WheelPicker.styles.js +13 -8
  48. package/lib/module/components/WheelPicker/WheelPicker.styles.js.map +1 -1
  49. package/lib/module/components/WheelPicker/WheelPicker.web.js +146 -57
  50. package/lib/module/components/WheelPicker/WheelPicker.web.js.map +1 -1
  51. package/lib/module/context/ThemeOverrideContext.js +34 -0
  52. package/lib/module/context/ThemeOverrideContext.js.map +1 -0
  53. package/lib/module/index.js +3 -0
  54. package/lib/module/index.js.map +1 -1
  55. package/lib/module/utils/validation.js +74 -34
  56. package/lib/module/utils/validation.js.map +1 -1
  57. package/lib/typescript/components/CalendarGrid/CalendarGrid.d.ts +24 -3
  58. package/lib/typescript/components/CalendarGrid/CalendarGrid.d.ts.map +1 -1
  59. package/lib/typescript/components/CalendarGrid/CalendarGrid.styles.d.ts +12 -10
  60. package/lib/typescript/components/CalendarGrid/CalendarGrid.styles.d.ts.map +1 -1
  61. package/lib/typescript/components/CalendarGrid/CalendarGrid.web.d.ts +33 -0
  62. package/lib/typescript/components/CalendarGrid/CalendarGrid.web.d.ts.map +1 -0
  63. package/lib/typescript/components/CalendarGrid/DayCell.d.ts +3 -0
  64. package/lib/typescript/components/CalendarGrid/DayCell.d.ts.map +1 -1
  65. package/lib/typescript/components/CalendarGrid/DayCell.web.d.ts +15 -0
  66. package/lib/typescript/components/CalendarGrid/DayCell.web.d.ts.map +1 -0
  67. package/lib/typescript/components/DatePicker/DatePicker.android.d.ts.map +1 -1
  68. package/lib/typescript/components/DatePicker/DatePicker.d.ts +27 -4
  69. package/lib/typescript/components/DatePicker/DatePicker.d.ts.map +1 -1
  70. package/lib/typescript/components/DatePicker/DatePicker.ios.d.ts.map +1 -1
  71. package/lib/typescript/components/DatePicker/DatePicker.styles.d.ts +12 -13
  72. package/lib/typescript/components/DatePicker/DatePicker.styles.d.ts.map +1 -1
  73. package/lib/typescript/components/DatePicker/DatePicker.web.d.ts.map +1 -1
  74. package/lib/typescript/components/TimePicker/ClockFace.d.ts.map +1 -1
  75. package/lib/typescript/components/TimePicker/ClockFace.web.d.ts +12 -0
  76. package/lib/typescript/components/TimePicker/ClockFace.web.d.ts.map +1 -0
  77. package/lib/typescript/components/TimePicker/MaterialTimePicker.d.ts.map +1 -1
  78. package/lib/typescript/components/TimePicker/MaterialTimePicker.web.d.ts +12 -0
  79. package/lib/typescript/components/TimePicker/MaterialTimePicker.web.d.ts.map +1 -0
  80. package/lib/typescript/components/TimePicker/TimePicker.android.d.ts.map +1 -1
  81. package/lib/typescript/components/TimePicker/TimePicker.ios.d.ts.map +1 -1
  82. package/lib/typescript/components/TimePicker/TimePicker.styles.d.ts +29 -25
  83. package/lib/typescript/components/TimePicker/TimePicker.styles.d.ts.map +1 -1
  84. package/lib/typescript/components/TimePicker/TimePicker.web.d.ts.map +1 -1
  85. package/lib/typescript/components/TimePicker/TimeWheelPicker.d.ts.map +1 -1
  86. package/lib/typescript/components/TimePicker/TimeWheelPicker.web.d.ts +11 -0
  87. package/lib/typescript/components/TimePicker/TimeWheelPicker.web.d.ts.map +1 -0
  88. package/lib/typescript/components/WheelPicker/WheelPicker.d.ts +14 -1
  89. package/lib/typescript/components/WheelPicker/WheelPicker.d.ts.map +1 -1
  90. package/lib/typescript/components/WheelPicker/WheelPicker.styles.d.ts +9 -7
  91. package/lib/typescript/components/WheelPicker/WheelPicker.styles.d.ts.map +1 -1
  92. package/lib/typescript/components/WheelPicker/WheelPicker.web.d.ts.map +1 -1
  93. package/lib/typescript/context/ThemeOverrideContext.d.ts +23 -0
  94. package/lib/typescript/context/ThemeOverrideContext.d.ts.map +1 -0
  95. package/lib/typescript/index.d.ts +4 -2
  96. package/lib/typescript/index.d.ts.map +1 -1
  97. package/lib/typescript/types/datepicker.d.ts +78 -3
  98. package/lib/typescript/types/datepicker.d.ts.map +1 -1
  99. package/lib/typescript/types/timepicker.d.ts +62 -0
  100. package/lib/typescript/types/timepicker.d.ts.map +1 -1
  101. package/lib/typescript/utils/validation.d.ts +47 -27
  102. package/lib/typescript/utils/validation.d.ts.map +1 -1
  103. package/package.json +8 -8
  104. package/src/components/CalendarGrid/CalendarGrid.styles.ts +21 -17
  105. package/src/components/CalendarGrid/CalendarGrid.tsx +265 -85
  106. package/src/components/CalendarGrid/CalendarGrid.web.tsx +396 -0
  107. package/src/components/CalendarGrid/DayCell.tsx +122 -61
  108. package/src/components/CalendarGrid/DayCell.web.tsx +171 -0
  109. package/src/components/DatePicker/DatePicker.android.tsx +48 -24
  110. package/src/components/DatePicker/DatePicker.ios.tsx +51 -27
  111. package/src/components/DatePicker/DatePicker.styles.ts +18 -22
  112. package/src/components/DatePicker/DatePicker.tsx +35 -4
  113. package/src/components/DatePicker/DatePicker.web.tsx +55 -23
  114. package/src/components/TimePicker/ClockFace.tsx +34 -8
  115. package/src/components/TimePicker/ClockFace.web.tsx +303 -0
  116. package/src/components/TimePicker/MaterialTimePicker.tsx +144 -13
  117. package/src/components/TimePicker/MaterialTimePicker.web.tsx +271 -0
  118. package/src/components/TimePicker/TimePicker.android.tsx +9 -1
  119. package/src/components/TimePicker/TimePicker.ios.tsx +10 -6
  120. package/src/components/TimePicker/TimePicker.styles.ts +52 -45
  121. package/src/components/TimePicker/TimePicker.web.tsx +17 -7
  122. package/src/components/TimePicker/TimeWheelPicker.tsx +60 -6
  123. package/src/components/TimePicker/TimeWheelPicker.web.tsx +401 -0
  124. package/src/components/WheelPicker/WheelPicker.styles.ts +12 -8
  125. package/src/components/WheelPicker/WheelPicker.tsx +24 -2
  126. package/src/components/WheelPicker/WheelPicker.web.tsx +153 -57
  127. package/src/context/ThemeOverrideContext.tsx +38 -0
  128. package/src/index.ts +13 -0
  129. package/src/types/datepicker.ts +87 -3
  130. package/src/types/timepicker.ts +74 -0
  131. package/src/utils/validation.ts +111 -55
  132. package/lib/module/unistyles.js +0 -9
  133. package/lib/module/unistyles.js.map +0 -1
  134. package/lib/typescript/unistyles.d.ts +0 -3
  135. package/lib/typescript/unistyles.d.ts.map +0 -1
  136. package/src/unistyles.ts +0 -6
@@ -0,0 +1,396 @@
1
+ /// <reference lib="dom" />
2
+ import React, { memo, useCallback, useMemo } from 'react';
3
+ import { FlatList, Pressable, StyleSheet, Text, View } from 'react-native';
4
+ import { useDatePickerOverrides } from '../../context/ThemeOverrideContext';
5
+ import {
6
+ addMonths,
7
+ compareDates,
8
+ formatYearMonth,
9
+ getDayOfWeek,
10
+ getFirstDayOfMonth,
11
+ getMonthDays,
12
+ isSameDay,
13
+ today,
14
+ } from '../../utils/date';
15
+ import { DayCell } from './DayCell';
16
+
17
+ import type { DateRange } from '../../types/datepicker';
18
+
19
+ interface CalendarGridBaseProps {
20
+ minDate?: Date;
21
+ maxDate?: Date;
22
+ disabledDates?: Date[];
23
+ themeMode: 'ios' | 'android' | 'custom';
24
+ /**
25
+ * First day of the week: 0 = Sunday, 1 = Monday
26
+ * @default 0 (Sunday)
27
+ */
28
+ weekStartsOn?: 0 | 1;
29
+ }
30
+
31
+ interface CalendarGridSingleProps extends CalendarGridBaseProps {
32
+ selectionMode?: 'single';
33
+ value: Date;
34
+ onChange: (date: Date) => void;
35
+ startDate?: never;
36
+ endDate?: never;
37
+ onRangeChange?: never;
38
+ }
39
+
40
+ interface CalendarGridRangeProps extends CalendarGridBaseProps {
41
+ selectionMode: 'range';
42
+ startDate: Date | null;
43
+ endDate: Date | null;
44
+ onRangeChange: (range: DateRange) => void;
45
+ value?: never;
46
+ onChange?: never;
47
+ }
48
+
49
+ type CalendarGridProps = CalendarGridSingleProps | CalendarGridRangeProps;
50
+
51
+ const CELL_SIZE = 44;
52
+
53
+ // Week day labels starting from Sunday
54
+ const WEEK_DAYS_SUNDAY_START = [
55
+ 'Sun',
56
+ 'Mon',
57
+ 'Tue',
58
+ 'Wed',
59
+ 'Thu',
60
+ 'Fri',
61
+ 'Sat',
62
+ ];
63
+ const WEEK_DAYS_MONDAY_START = [
64
+ 'Mon',
65
+ 'Tue',
66
+ 'Wed',
67
+ 'Thu',
68
+ 'Fri',
69
+ 'Sat',
70
+ 'Sun',
71
+ ];
72
+
73
+ // Default colors (light theme for web)
74
+ const DEFAULT_COLORS = {
75
+ backgroundColor: '#FFFFFF',
76
+ primaryColor: '#007AFF',
77
+ textColor: '#1C1C1E',
78
+ weekdayColor: '#8E8E93',
79
+ borderRadius: 14,
80
+ padding: 16,
81
+ };
82
+
83
+ /**
84
+ * Generate padding days for the month grid based on week start day
85
+ * TODO: This logic should be refactored when adding locale support
86
+ */
87
+ const generateMonthDays = (
88
+ currentMonth: Date,
89
+ weekStartsOn: 0 | 1,
90
+ ): (Date | null)[] => {
91
+ const firstDay = getFirstDayOfMonth(currentMonth);
92
+ // getDayOfWeek returns 0 for Sunday, 1 for Monday, etc.
93
+ const dayOfWeek = getDayOfWeek(firstDay);
94
+
95
+ // Calculate padding days based on week start
96
+ let paddingDays: number;
97
+ if (weekStartsOn === 0) {
98
+ // Sunday start: Sunday = 0 padding, Monday = 1, etc.
99
+ paddingDays = dayOfWeek;
100
+ } else {
101
+ // Monday start: Monday = 0 padding, Sunday = 6 padding
102
+ paddingDays = dayOfWeek === 0 ? 6 : dayOfWeek - 1;
103
+ }
104
+
105
+ const days: (Date | null)[] = [];
106
+
107
+ for (let i = 0; i < paddingDays; i++) {
108
+ days.push(null);
109
+ }
110
+
111
+ const monthDays = getMonthDays(
112
+ currentMonth.getUTCFullYear(),
113
+ currentMonth.getUTCMonth(),
114
+ );
115
+ for (const day of monthDays) {
116
+ days.push(day);
117
+ }
118
+
119
+ const remaining = 7 - (days.length % 7);
120
+ if (remaining < 7) {
121
+ for (let i = 0; i < remaining; i++) {
122
+ days.push(null);
123
+ }
124
+ }
125
+
126
+ return days;
127
+ };
128
+
129
+ // Web-compatible styles (no unistyles dependency)
130
+ const webStyles = StyleSheet.create({
131
+ container: {
132
+ backgroundColor: '#FFFFFF',
133
+ borderRadius: 14,
134
+ padding: 16,
135
+ },
136
+ header: {
137
+ flexDirection: 'row',
138
+ justifyContent: 'space-between',
139
+ alignItems: 'center',
140
+ marginBottom: 16,
141
+ paddingHorizontal: 8,
142
+ },
143
+ navButton: {
144
+ width: 40,
145
+ height: 40,
146
+ justifyContent: 'center',
147
+ alignItems: 'center',
148
+ borderRadius: 8,
149
+ },
150
+ navText: {
151
+ fontSize: 24,
152
+ color: '#007AFF',
153
+ fontWeight: '600',
154
+ },
155
+ monthTitle: {
156
+ fontSize: 17,
157
+ fontWeight: '600',
158
+ color: '#1C1C1E',
159
+ },
160
+ weekDays: {
161
+ flexDirection: 'row',
162
+ marginBottom: 8,
163
+ },
164
+ weekDayText: {
165
+ flex: 1,
166
+ textAlign: 'center',
167
+ fontSize: 13,
168
+ fontWeight: '600',
169
+ color: '#8E8E93',
170
+ },
171
+ });
172
+
173
+ export const CalendarGrid: React.FC<CalendarGridProps> = memo((props) => {
174
+ const {
175
+ minDate,
176
+ maxDate,
177
+ disabledDates,
178
+ themeMode,
179
+ weekStartsOn = 0,
180
+ selectionMode = 'single',
181
+ } = props;
182
+
183
+ // Extract mode-specific props
184
+ const singleValue = selectionMode === 'single' ? props.value : null;
185
+ const singleOnChange = selectionMode === 'single' ? props.onChange : null;
186
+ const rangeStart = selectionMode === 'range' ? props.startDate : null;
187
+ const rangeEnd = selectionMode === 'range' ? props.endDate : null;
188
+ const rangeOnChange = selectionMode === 'range' ? props.onRangeChange : null;
189
+
190
+ const overrides = useDatePickerOverrides();
191
+ const [currentMonth, setCurrentMonth] = React.useState(() =>
192
+ getFirstDayOfMonth(singleValue ?? rangeStart ?? new Date()),
193
+ );
194
+
195
+ const days = useMemo(
196
+ () => generateMonthDays(currentMonth, weekStartsOn),
197
+ [currentMonth, weekStartsOn],
198
+ );
199
+
200
+ const weekDays =
201
+ weekStartsOn === 0 ? WEEK_DAYS_SUNDAY_START : WEEK_DAYS_MONDAY_START;
202
+
203
+ const todayDate = useMemo(() => today(), []);
204
+
205
+ const isDisabled = useCallback(
206
+ (date: Date | null): boolean => {
207
+ if (!date) return true;
208
+ if (minDate && compareDates(date, minDate) < 0) return true;
209
+ if (maxDate && compareDates(date, maxDate) > 0) return true;
210
+ if (disabledDates?.some((d) => isSameDay(date, d))) return true;
211
+ return false;
212
+ },
213
+ [minDate, maxDate, disabledDates],
214
+ );
215
+
216
+ const navigateMonth = useCallback((direction: 1 | -1) => {
217
+ setCurrentMonth((prev) => addMonths(prev, direction));
218
+ }, []);
219
+
220
+ // Handle date press for both single and range modes
221
+ const handleDatePress = useCallback(
222
+ (date: Date) => {
223
+ if (selectionMode === 'single' && singleOnChange) {
224
+ singleOnChange(date);
225
+ } else if (selectionMode === 'range' && rangeOnChange) {
226
+ // Range selection logic:
227
+ // 1. If no start date, set start date
228
+ // 2. If start date but no end date, set end date (if after start)
229
+ // 3. If both dates exist, reset to new start date
230
+ if (!rangeStart || (rangeStart && rangeEnd)) {
231
+ rangeOnChange({ startDate: date, endDate: null });
232
+ } else {
233
+ // Have start but no end
234
+ if (compareDates(date, rangeStart) < 0) {
235
+ // Clicked before start - make this the new start
236
+ rangeOnChange({ startDate: date, endDate: null });
237
+ } else if (isSameDay(date, rangeStart)) {
238
+ // Clicked same day - clear selection
239
+ rangeOnChange({ startDate: date, endDate: null });
240
+ } else {
241
+ // Clicked after start - set as end date
242
+ rangeOnChange({ startDate: rangeStart, endDate: date });
243
+ }
244
+ }
245
+ }
246
+ },
247
+ [selectionMode, singleOnChange, rangeOnChange, rangeStart, rangeEnd],
248
+ );
249
+
250
+ // Check if date is in range (between start and end)
251
+ const isDateInRange = useCallback(
252
+ (date: Date | null): boolean => {
253
+ if (!date || selectionMode !== 'range' || !rangeStart || !rangeEnd) {
254
+ return false;
255
+ }
256
+ return (
257
+ compareDates(date, rangeStart) > 0 && compareDates(date, rangeEnd) < 0
258
+ );
259
+ },
260
+ [selectionMode, rangeStart, rangeEnd],
261
+ );
262
+
263
+ const renderDay = useCallback(
264
+ ({ item }: { item: Date | null }) => {
265
+ const isRangeStart =
266
+ selectionMode === 'range' && item && rangeStart
267
+ ? isSameDay(item, rangeStart)
268
+ : false;
269
+ const isRangeEnd =
270
+ selectionMode === 'range' && item && rangeEnd
271
+ ? isSameDay(item, rangeEnd)
272
+ : false;
273
+ const isSelected =
274
+ selectionMode === 'single' && item && singleValue
275
+ ? isSameDay(item, singleValue)
276
+ : false;
277
+
278
+ return (
279
+ <DayCell
280
+ date={item}
281
+ isSelected={isSelected}
282
+ isToday={item ? isSameDay(item, todayDate) : false}
283
+ isDisabled={isDisabled(item)}
284
+ isWeekend={
285
+ item ? getDayOfWeek(item) === 0 || getDayOfWeek(item) === 6 : false
286
+ }
287
+ isRangeStart={isRangeStart}
288
+ isRangeEnd={isRangeEnd}
289
+ isInRange={isDateInRange(item)}
290
+ onPress={
291
+ item && !isDisabled(item) ? () => handleDatePress(item) : undefined
292
+ }
293
+ />
294
+ );
295
+ },
296
+ [
297
+ selectionMode,
298
+ singleValue,
299
+ rangeStart,
300
+ rangeEnd,
301
+ todayDate,
302
+ isDisabled,
303
+ isDateInRange,
304
+ handleDatePress,
305
+ ],
306
+ );
307
+
308
+ const keyExtractor = useCallback(
309
+ (item: Date | null, index: number) =>
310
+ item?.toISOString() ?? `empty-${index}`,
311
+ [],
312
+ );
313
+
314
+ const getItemLayout = useCallback(
315
+ (_data: ArrayLike<Date | null> | null | undefined, index: number) => ({
316
+ length: CELL_SIZE,
317
+ offset: CELL_SIZE * Math.floor(index / 7),
318
+ index,
319
+ }),
320
+ [],
321
+ );
322
+
323
+ // Build override styles
324
+ const containerStyle = useMemo(
325
+ () => ({
326
+ backgroundColor:
327
+ overrides?.backgroundColor ?? DEFAULT_COLORS.backgroundColor,
328
+ borderRadius: overrides?.borderRadius ?? DEFAULT_COLORS.borderRadius,
329
+ padding: overrides?.padding ?? DEFAULT_COLORS.padding,
330
+ }),
331
+ [overrides],
332
+ );
333
+
334
+ const navTextStyle = useMemo(
335
+ () => ({
336
+ color: overrides?.primaryColor ?? DEFAULT_COLORS.primaryColor,
337
+ }),
338
+ [overrides],
339
+ );
340
+
341
+ const monthTitleStyle = useMemo(
342
+ () => ({
343
+ color: overrides?.textColor ?? DEFAULT_COLORS.textColor,
344
+ }),
345
+ [overrides],
346
+ );
347
+
348
+ const weekDayTextStyle = useMemo(
349
+ () => ({
350
+ color: overrides?.textWeekendColor ?? DEFAULT_COLORS.weekdayColor,
351
+ }),
352
+ [overrides],
353
+ );
354
+
355
+ return (
356
+ <View style={[webStyles.container, containerStyle]}>
357
+ <View style={webStyles.header}>
358
+ <Pressable
359
+ onPress={() => navigateMonth(-1)}
360
+ style={webStyles.navButton}
361
+ >
362
+ <Text style={[webStyles.navText, navTextStyle]}>‹</Text>
363
+ </Pressable>
364
+ <Text style={[webStyles.monthTitle, monthTitleStyle]}>
365
+ {formatYearMonth(currentMonth)}
366
+ </Text>
367
+ <Pressable onPress={() => navigateMonth(1)} style={webStyles.navButton}>
368
+ <Text style={[webStyles.navText, navTextStyle]}>›</Text>
369
+ </Pressable>
370
+ </View>
371
+
372
+ <View style={webStyles.weekDays}>
373
+ {weekDays.map((day) => (
374
+ <Text key={day} style={[webStyles.weekDayText, weekDayTextStyle]}>
375
+ {day}
376
+ </Text>
377
+ ))}
378
+ </View>
379
+
380
+ <FlatList
381
+ data={days}
382
+ renderItem={renderDay}
383
+ keyExtractor={keyExtractor}
384
+ getItemLayout={getItemLayout}
385
+ numColumns={7}
386
+ scrollEnabled={false}
387
+ removeClippedSubviews={true}
388
+ maxToRenderPerBatch={14}
389
+ windowSize={3}
390
+ initialNumToRender={42}
391
+ />
392
+ </View>
393
+ );
394
+ });
395
+
396
+ CalendarGrid.displayName = 'CalendarGrid';
@@ -1,7 +1,7 @@
1
1
  import type React from 'react';
2
- import { memo } from 'react';
3
- import { Pressable, Text } from 'react-native';
4
- import { StyleSheet } from 'react-native-unistyles';
2
+ import { memo, useMemo } from 'react';
3
+ import { Pressable, StyleSheet, Text } from 'react-native';
4
+ import { useDatePickerOverrides } from '../../context/ThemeOverrideContext';
5
5
 
6
6
  interface DayCellProps {
7
7
  date: Date | null;
@@ -9,81 +9,139 @@ interface DayCellProps {
9
9
  isToday: boolean;
10
10
  isDisabled: boolean;
11
11
  isWeekend: boolean;
12
+ isRangeStart?: boolean;
13
+ isRangeEnd?: boolean;
14
+ isInRange?: boolean;
12
15
  onPress?: () => void;
13
16
  }
14
17
 
15
- const styles = StyleSheet.create((theme) => ({
18
+ // Default colors (dark theme)
19
+ const DEFAULT_COLORS = {
20
+ cellBackground: 'transparent',
21
+ cellSelected: '#4DA6FF',
22
+ cellToday: '#1E3A5F',
23
+ cellInRange: 'rgba(77, 166, 255, 0.2)',
24
+ textDefault: '#FFFFFF',
25
+ textSelected: '#FFFFFF',
26
+ textDisabled: '#555555',
27
+ textWeekend: '#8E8E93',
28
+ textInRange: '#FFFFFF',
29
+ primary: '#4DA6FF',
30
+ cellBorderRadius: 22,
31
+ };
32
+
33
+ const styles = StyleSheet.create({
16
34
  cell: {
17
35
  width: 44,
18
36
  height: 44,
19
37
  justifyContent: 'center',
20
38
  alignItems: 'center',
21
- backgroundColor: theme.colors.datepicker.cellBackground,
22
- variants: {
23
- state: {
24
- selected: {
25
- backgroundColor: theme.colors.datepicker.cellSelected,
26
- borderRadius: theme.radii.cell,
27
- },
28
- today: {
29
- backgroundColor: theme.colors.datepicker.cellToday,
30
- borderRadius: theme.radii.cell,
31
- borderWidth: 1,
32
- borderColor: theme.colors.primary.default,
33
- },
34
- disabled: {
35
- opacity: 0.4,
36
- },
37
- weekend: {},
38
- },
39
- },
40
39
  },
41
40
  text: {
42
- fontSize: theme.typography.dayCell.fontSize,
43
- fontWeight: theme.typography.dayCell.fontWeight,
44
- color: theme.colors.datepicker.textDefault,
45
- variants: {
46
- state: {
47
- selected: {
48
- color: theme.colors.datepicker.textSelected,
49
- fontWeight: '600',
50
- },
51
- today: {
52
- color: theme.colors.primary.default,
53
- fontWeight: '600',
54
- },
55
- disabled: {
56
- color: theme.colors.datepicker.textDisabled,
57
- },
58
- weekend: {
59
- color: theme.colors.datepicker.textWeekend,
60
- },
61
- },
62
- },
41
+ fontSize: 17,
42
+ fontWeight: '400',
63
43
  },
64
- }));
44
+ });
65
45
 
66
46
  export const DayCell: React.FC<DayCellProps> = memo(
67
- ({ date, isSelected, isToday, isDisabled, isWeekend, onPress }) => {
47
+ ({
48
+ date,
49
+ isSelected,
50
+ isToday,
51
+ isDisabled,
52
+ isWeekend,
53
+ isRangeStart,
54
+ isRangeEnd,
55
+ isInRange,
56
+ onPress,
57
+ }) => {
58
+ const overrides = useDatePickerOverrides();
59
+
60
+ // Build cell style based on state and overrides
61
+ // Use primaryColor as fallback for cellSelectedColor (consumer expectation)
62
+ const cellStyle = useMemo(() => {
63
+ const style: Record<string, unknown> = {
64
+ backgroundColor: DEFAULT_COLORS.cellBackground,
65
+ };
66
+
67
+ // Range start/end get selected styling
68
+ if (isRangeStart || isRangeEnd || isSelected) {
69
+ style.backgroundColor =
70
+ overrides?.cellSelectedColor ??
71
+ overrides?.primaryColor ??
72
+ DEFAULT_COLORS.cellSelected;
73
+ style.borderRadius =
74
+ overrides?.cellBorderRadius ?? DEFAULT_COLORS.cellBorderRadius;
75
+ } else if (isInRange) {
76
+ // Dates in range get lighter background
77
+ style.backgroundColor =
78
+ overrides?.cellInRangeColor ?? DEFAULT_COLORS.cellInRange;
79
+ } else if (isToday) {
80
+ style.backgroundColor =
81
+ overrides?.cellTodayColor ?? DEFAULT_COLORS.cellToday;
82
+ style.borderRadius =
83
+ overrides?.cellBorderRadius ?? DEFAULT_COLORS.cellBorderRadius;
84
+ style.borderWidth = 1;
85
+ style.borderColor = overrides?.primaryColor ?? DEFAULT_COLORS.primary;
86
+ }
87
+
88
+ if (isDisabled) {
89
+ style.opacity = 0.4;
90
+ }
91
+
92
+ return style;
93
+ }, [
94
+ overrides,
95
+ isSelected,
96
+ isToday,
97
+ isDisabled,
98
+ isRangeStart,
99
+ isRangeEnd,
100
+ isInRange,
101
+ ]);
102
+
103
+ // Build text style based on state and overrides
104
+ const textStyle = useMemo(() => {
105
+ const style: Record<string, unknown> = {
106
+ color: overrides?.textColor ?? DEFAULT_COLORS.textDefault,
107
+ fontWeight: '400' as const,
108
+ };
109
+
110
+ if (isRangeStart || isRangeEnd || isSelected) {
111
+ style.color =
112
+ overrides?.textSelectedColor ?? DEFAULT_COLORS.textSelected;
113
+ style.fontWeight = '600';
114
+ } else if (isInRange) {
115
+ style.color = overrides?.textInRangeColor ?? DEFAULT_COLORS.textInRange;
116
+ } else if (isToday) {
117
+ style.color = overrides?.primaryColor ?? DEFAULT_COLORS.primary;
118
+ style.fontWeight = '600';
119
+ } else if (isDisabled) {
120
+ style.color =
121
+ overrides?.textDisabledColor ?? DEFAULT_COLORS.textDisabled;
122
+ } else if (isWeekend) {
123
+ style.color = overrides?.textWeekendColor ?? DEFAULT_COLORS.textWeekend;
124
+ }
125
+
126
+ return style;
127
+ }, [
128
+ overrides,
129
+ isSelected,
130
+ isToday,
131
+ isDisabled,
132
+ isWeekend,
133
+ isRangeStart,
134
+ isRangeEnd,
135
+ isInRange,
136
+ ]);
137
+
68
138
  if (!date) {
69
139
  return <Pressable style={styles.cell} disabled />;
70
140
  }
71
141
 
72
- const state = isDisabled
73
- ? 'disabled'
74
- : isSelected
75
- ? 'selected'
76
- : isToday
77
- ? 'today'
78
- : isWeekend
79
- ? 'weekend'
80
- : undefined;
81
-
82
- styles.useVariants({ state });
83
-
84
142
  return (
85
143
  <Pressable
86
- style={styles.cell}
144
+ style={[styles.cell, cellStyle]}
87
145
  onPress={onPress}
88
146
  disabled={isDisabled}
89
147
  accessibilityRole="button"
@@ -94,7 +152,7 @@ export const DayCell: React.FC<DayCellProps> = memo(
94
152
  }).format(date)}
95
153
  accessibilityState={{ selected: isSelected, disabled: isDisabled }}
96
154
  >
97
- <Text style={styles.text}>{date.getUTCDate()}</Text>
155
+ <Text style={[styles.text, textStyle]}>{date.getUTCDate()}</Text>
98
156
  </Pressable>
99
157
  );
100
158
  },
@@ -102,7 +160,10 @@ export const DayCell: React.FC<DayCellProps> = memo(
102
160
  prev.date?.getTime() === next.date?.getTime() &&
103
161
  prev.isSelected === next.isSelected &&
104
162
  prev.isToday === next.isToday &&
105
- prev.isDisabled === next.isDisabled,
163
+ prev.isDisabled === next.isDisabled &&
164
+ prev.isRangeStart === next.isRangeStart &&
165
+ prev.isRangeEnd === next.isRangeEnd &&
166
+ prev.isInRange === next.isInRange,
106
167
  );
107
168
 
108
169
  DayCell.displayName = 'DayCell';