@dreamstack-us/kaal 0.0.2 → 0.0.4

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 (50) hide show
  1. package/README.md +165 -0
  2. package/lib/module/components/CalendarGrid/CalendarGrid.js +80 -18
  3. package/lib/module/components/CalendarGrid/CalendarGrid.js.map +1 -1
  4. package/lib/module/components/CalendarGrid/CalendarGrid.web.js +80 -18
  5. package/lib/module/components/CalendarGrid/CalendarGrid.web.js.map +1 -1
  6. package/lib/module/components/CalendarGrid/DayCell.js +17 -5
  7. package/lib/module/components/CalendarGrid/DayCell.js.map +1 -1
  8. package/lib/module/components/CalendarGrid/DayCell.web.js +17 -5
  9. package/lib/module/components/CalendarGrid/DayCell.web.js.map +1 -1
  10. package/lib/module/components/DatePicker/DatePicker.android.js +42 -19
  11. package/lib/module/components/DatePicker/DatePicker.android.js.map +1 -1
  12. package/lib/module/components/DatePicker/DatePicker.ios.js +44 -21
  13. package/lib/module/components/DatePicker/DatePicker.ios.js.map +1 -1
  14. package/lib/module/components/DatePicker/DatePicker.js.map +1 -1
  15. package/lib/module/components/DatePicker/DatePicker.web.js +40 -16
  16. package/lib/module/components/DatePicker/DatePicker.web.js.map +1 -1
  17. package/lib/module/index.js.map +1 -1
  18. package/lib/module/utils/date.js +3 -1
  19. package/lib/module/utils/date.js.map +1 -1
  20. package/lib/typescript/components/CalendarGrid/CalendarGrid.d.ts +19 -7
  21. package/lib/typescript/components/CalendarGrid/CalendarGrid.d.ts.map +1 -1
  22. package/lib/typescript/components/CalendarGrid/CalendarGrid.web.d.ts +19 -7
  23. package/lib/typescript/components/CalendarGrid/CalendarGrid.web.d.ts.map +1 -1
  24. package/lib/typescript/components/CalendarGrid/DayCell.d.ts +3 -0
  25. package/lib/typescript/components/CalendarGrid/DayCell.d.ts.map +1 -1
  26. package/lib/typescript/components/CalendarGrid/DayCell.web.d.ts +3 -0
  27. package/lib/typescript/components/CalendarGrid/DayCell.web.d.ts.map +1 -1
  28. package/lib/typescript/components/DatePicker/DatePicker.android.d.ts.map +1 -1
  29. package/lib/typescript/components/DatePicker/DatePicker.d.ts +20 -8
  30. package/lib/typescript/components/DatePicker/DatePicker.d.ts.map +1 -1
  31. package/lib/typescript/components/DatePicker/DatePicker.ios.d.ts.map +1 -1
  32. package/lib/typescript/components/DatePicker/DatePicker.web.d.ts.map +1 -1
  33. package/lib/typescript/index.d.ts +1 -1
  34. package/lib/typescript/index.d.ts.map +1 -1
  35. package/lib/typescript/types/datepicker.d.ts +37 -3
  36. package/lib/typescript/types/datepicker.d.ts.map +1 -1
  37. package/lib/typescript/utils/date.d.ts +2 -1
  38. package/lib/typescript/utils/date.d.ts.map +1 -1
  39. package/package.json +1 -1
  40. package/src/components/CalendarGrid/CalendarGrid.tsx +216 -122
  41. package/src/components/CalendarGrid/CalendarGrid.web.tsx +227 -136
  42. package/src/components/CalendarGrid/DayCell.tsx +48 -6
  43. package/src/components/CalendarGrid/DayCell.web.tsx +48 -6
  44. package/src/components/DatePicker/DatePicker.android.tsx +39 -21
  45. package/src/components/DatePicker/DatePicker.ios.tsx +42 -24
  46. package/src/components/DatePicker/DatePicker.tsx +28 -8
  47. package/src/components/DatePicker/DatePicker.web.tsx +40 -16
  48. package/src/index.ts +2 -0
  49. package/src/types/datepicker.ts +43 -3
  50. package/src/utils/date.ts +4 -2
@@ -14,9 +14,9 @@ import {
14
14
  } from '../../utils/date';
15
15
  import { DayCell } from './DayCell';
16
16
 
17
- interface CalendarGridProps {
18
- value: Date;
19
- onChange: (date: Date) => void;
17
+ import type { DateRange } from '../../types/datepicker';
18
+
19
+ interface CalendarGridBaseProps {
20
20
  minDate?: Date;
21
21
  maxDate?: Date;
22
22
  disabledDates?: Date[];
@@ -24,14 +24,30 @@ interface CalendarGridProps {
24
24
  /**
25
25
  * First day of the week: 0 = Sunday, 1 = Monday
26
26
  * @default 0 (Sunday)
27
- *
28
- * TODO: This is a temporary solution. In the future, we need to add full
29
- * locale support to handle different calendar formats, layouts, and
30
- * localized day/month names across different regions.
31
27
  */
32
28
  weekStartsOn?: 0 | 1;
33
29
  }
34
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
+
35
51
  const CELL_SIZE = 44;
36
52
 
37
53
  // Week day labels starting from Sunday
@@ -154,152 +170,227 @@ const webStyles = StyleSheet.create({
154
170
  },
155
171
  });
156
172
 
157
- export const CalendarGrid: React.FC<CalendarGridProps> = memo(
158
- ({
159
- value,
160
- onChange,
173
+ export const CalendarGrid: React.FC<CalendarGridProps> = memo((props) => {
174
+ const {
161
175
  minDate,
162
176
  maxDate,
163
177
  disabledDates,
164
178
  themeMode,
165
179
  weekStartsOn = 0,
166
- }) => {
167
- const overrides = useDatePickerOverrides();
168
- const [currentMonth, setCurrentMonth] = React.useState(() =>
169
- getFirstDayOfMonth(value),
170
- );
171
-
172
- const days = useMemo(
173
- () => generateMonthDays(currentMonth, weekStartsOn),
174
- [currentMonth, weekStartsOn],
175
- );
176
-
177
- const weekDays =
178
- weekStartsOn === 0 ? WEEK_DAYS_SUNDAY_START : WEEK_DAYS_MONDAY_START;
179
-
180
- const todayDate = useMemo(() => today(), []);
181
-
182
- const isDisabled = useCallback(
183
- (date: Date | null): boolean => {
184
- if (!date) return true;
185
- if (minDate && compareDates(date, minDate) < 0) return true;
186
- if (maxDate && compareDates(date, maxDate) > 0) return true;
187
- if (disabledDates?.some((d) => isSameDay(date, d))) return true;
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 || today()),
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) {
188
254
  return false;
189
- },
190
- [minDate, maxDate, disabledDates],
191
- );
255
+ }
256
+ return (
257
+ compareDates(date, rangeStart) > 0 && compareDates(date, rangeEnd) < 0
258
+ );
259
+ },
260
+ [selectionMode, rangeStart, rangeEnd],
261
+ );
192
262
 
193
- const navigateMonth = useCallback((direction: 1 | -1) => {
194
- setCurrentMonth((prev) => addMonths(prev, direction));
195
- }, []);
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;
196
277
 
197
- const renderDay = useCallback(
198
- ({ item }: { item: Date | null }) => (
278
+ return (
199
279
  <DayCell
200
280
  date={item}
201
- isSelected={item ? isSameDay(item, value) : false}
281
+ isSelected={isSelected}
202
282
  isToday={item ? isSameDay(item, todayDate) : false}
203
283
  isDisabled={isDisabled(item)}
204
284
  isWeekend={
205
285
  item ? getDayOfWeek(item) === 0 || getDayOfWeek(item) === 6 : false
206
286
  }
207
- onPress={item && !isDisabled(item) ? () => onChange(item) : undefined}
287
+ isRangeStart={isRangeStart}
288
+ isRangeEnd={isRangeEnd}
289
+ isInRange={isDateInRange(item)}
290
+ onPress={
291
+ item && !isDisabled(item) ? () => handleDatePress(item) : undefined
292
+ }
208
293
  />
209
- ),
210
- [value, todayDate, isDisabled, onChange],
211
- );
212
-
213
- const keyExtractor = useCallback(
214
- (item: Date | null, index: number) =>
215
- item?.toISOString() ?? `empty-${index}`,
216
- [],
217
- );
218
-
219
- const getItemLayout = useCallback(
220
- (_data: ArrayLike<Date | null> | null | undefined, index: number) => ({
221
- length: CELL_SIZE,
222
- offset: CELL_SIZE * Math.floor(index / 7),
223
- index,
224
- }),
225
- [],
226
- );
227
-
228
- // Build override styles
229
- const containerStyle = useMemo(
230
- () => ({
231
- backgroundColor:
232
- overrides?.backgroundColor ?? DEFAULT_COLORS.backgroundColor,
233
- borderRadius: overrides?.borderRadius ?? DEFAULT_COLORS.borderRadius,
234
- padding: overrides?.padding ?? DEFAULT_COLORS.padding,
235
- }),
236
- [overrides],
237
- );
238
-
239
- const navTextStyle = useMemo(
240
- () => ({
241
- color: overrides?.primaryColor ?? DEFAULT_COLORS.primaryColor,
242
- }),
243
- [overrides],
244
- );
245
-
246
- const monthTitleStyle = useMemo(
247
- () => ({
248
- color: overrides?.textColor ?? DEFAULT_COLORS.textColor,
249
- }),
250
- [overrides],
251
- );
252
-
253
- const weekDayTextStyle = useMemo(
254
- () => ({
255
- color: overrides?.textWeekendColor ?? DEFAULT_COLORS.weekdayColor,
256
- }),
257
- [overrides],
258
- );
259
-
260
- return (
261
- <View style={[webStyles.container, containerStyle]}>
262
- <View style={webStyles.header}>
263
- <Pressable
264
- onPress={() => navigateMonth(-1)}
265
- style={webStyles.navButton}
266
- >
267
- <Text style={[webStyles.navText, navTextStyle]}>‹</Text>
268
- </Pressable>
269
- <Text style={[webStyles.monthTitle, monthTitleStyle]}>
270
- {formatYearMonth(currentMonth)}
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}
271
376
  </Text>
272
- <Pressable
273
- onPress={() => navigateMonth(1)}
274
- style={webStyles.navButton}
275
- >
276
- <Text style={[webStyles.navText, navTextStyle]}>›</Text>
277
- </Pressable>
278
- </View>
279
-
280
- <View style={webStyles.weekDays}>
281
- {weekDays.map((day) => (
282
- <Text key={day} style={[webStyles.weekDayText, weekDayTextStyle]}>
283
- {day}
284
- </Text>
285
- ))}
286
- </View>
287
-
288
- <FlatList
289
- data={days}
290
- renderItem={renderDay}
291
- keyExtractor={keyExtractor}
292
- getItemLayout={getItemLayout}
293
- numColumns={7}
294
- scrollEnabled={false}
295
- removeClippedSubviews={true}
296
- maxToRenderPerBatch={14}
297
- windowSize={3}
298
- initialNumToRender={42}
299
- />
377
+ ))}
300
378
  </View>
301
- );
302
- },
303
- );
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
+ });
304
395
 
305
396
  CalendarGrid.displayName = 'CalendarGrid';
@@ -9,6 +9,9 @@ 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
 
@@ -17,10 +20,12 @@ const DEFAULT_COLORS = {
17
20
  cellBackground: 'transparent',
18
21
  cellSelected: '#4DA6FF',
19
22
  cellToday: '#1E3A5F',
23
+ cellInRange: 'rgba(77, 166, 255, 0.2)',
20
24
  textDefault: '#FFFFFF',
21
25
  textSelected: '#FFFFFF',
22
26
  textDisabled: '#555555',
23
27
  textWeekend: '#8E8E93',
28
+ textInRange: '#FFFFFF',
24
29
  primary: '#4DA6FF',
25
30
  cellBorderRadius: 22,
26
31
  };
@@ -39,7 +44,17 @@ const styles = StyleSheet.create({
39
44
  });
40
45
 
41
46
  export const DayCell: React.FC<DayCellProps> = memo(
42
- ({ 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
+ }) => {
43
58
  const overrides = useDatePickerOverrides();
44
59
 
45
60
  // Build cell style based on state and overrides
@@ -49,13 +64,18 @@ export const DayCell: React.FC<DayCellProps> = memo(
49
64
  backgroundColor: DEFAULT_COLORS.cellBackground,
50
65
  };
51
66
 
52
- if (isSelected) {
67
+ // Range start/end get selected styling
68
+ if (isRangeStart || isRangeEnd || isSelected) {
53
69
  style.backgroundColor =
54
70
  overrides?.cellSelectedColor ??
55
71
  overrides?.primaryColor ??
56
72
  DEFAULT_COLORS.cellSelected;
57
73
  style.borderRadius =
58
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;
59
79
  } else if (isToday) {
60
80
  style.backgroundColor =
61
81
  overrides?.cellTodayColor ?? DEFAULT_COLORS.cellToday;
@@ -70,7 +90,15 @@ export const DayCell: React.FC<DayCellProps> = memo(
70
90
  }
71
91
 
72
92
  return style;
73
- }, [overrides, isSelected, isToday, isDisabled]);
93
+ }, [
94
+ overrides,
95
+ isSelected,
96
+ isToday,
97
+ isDisabled,
98
+ isRangeStart,
99
+ isRangeEnd,
100
+ isInRange,
101
+ ]);
74
102
 
75
103
  // Build text style based on state and overrides
76
104
  const textStyle = useMemo(() => {
@@ -79,10 +107,12 @@ export const DayCell: React.FC<DayCellProps> = memo(
79
107
  fontWeight: '400' as const,
80
108
  };
81
109
 
82
- if (isSelected) {
110
+ if (isRangeStart || isRangeEnd || isSelected) {
83
111
  style.color =
84
112
  overrides?.textSelectedColor ?? DEFAULT_COLORS.textSelected;
85
113
  style.fontWeight = '600';
114
+ } else if (isInRange) {
115
+ style.color = overrides?.textInRangeColor ?? DEFAULT_COLORS.textInRange;
86
116
  } else if (isToday) {
87
117
  style.color = overrides?.primaryColor ?? DEFAULT_COLORS.primary;
88
118
  style.fontWeight = '600';
@@ -94,7 +124,16 @@ export const DayCell: React.FC<DayCellProps> = memo(
94
124
  }
95
125
 
96
126
  return style;
97
- }, [overrides, isSelected, isToday, isDisabled, isWeekend]);
127
+ }, [
128
+ overrides,
129
+ isSelected,
130
+ isToday,
131
+ isDisabled,
132
+ isWeekend,
133
+ isRangeStart,
134
+ isRangeEnd,
135
+ isInRange,
136
+ ]);
98
137
 
99
138
  if (!date) {
100
139
  return <Pressable style={styles.cell} disabled />;
@@ -121,7 +160,10 @@ export const DayCell: React.FC<DayCellProps> = memo(
121
160
  prev.date?.getTime() === next.date?.getTime() &&
122
161
  prev.isSelected === next.isSelected &&
123
162
  prev.isToday === next.isToday &&
124
- prev.isDisabled === next.isDisabled,
163
+ prev.isDisabled === next.isDisabled &&
164
+ prev.isRangeStart === next.isRangeStart &&
165
+ prev.isRangeEnd === next.isRangeEnd &&
166
+ prev.isInRange === next.isInRange,
125
167
  );
126
168
 
127
169
  DayCell.displayName = 'DayCell';
@@ -10,6 +10,9 @@ interface DayCellProps {
10
10
  isToday: boolean;
11
11
  isDisabled: boolean;
12
12
  isWeekend: boolean;
13
+ isRangeStart?: boolean;
14
+ isRangeEnd?: boolean;
15
+ isInRange?: boolean;
13
16
  onPress?: () => void;
14
17
  }
15
18
 
@@ -18,10 +21,12 @@ const DEFAULT_COLORS = {
18
21
  cellBackground: 'transparent',
19
22
  cellSelected: '#007AFF',
20
23
  cellToday: 'rgba(0, 122, 255, 0.1)',
24
+ cellInRange: 'rgba(0, 122, 255, 0.15)',
21
25
  textDefault: '#1C1C1E',
22
26
  textSelected: '#FFFFFF',
23
27
  textDisabled: '#8E8E93',
24
28
  textWeekend: '#8E8E93',
29
+ textInRange: '#1C1C1E',
25
30
  primary: '#007AFF',
26
31
  cellBorderRadius: 22,
27
32
  };
@@ -41,7 +46,17 @@ const webStyles = StyleSheet.create({
41
46
  });
42
47
 
43
48
  export const DayCell: React.FC<DayCellProps> = memo(
44
- ({ date, isSelected, isToday, isDisabled, isWeekend, onPress }) => {
49
+ ({
50
+ date,
51
+ isSelected,
52
+ isToday,
53
+ isDisabled,
54
+ isWeekend,
55
+ isRangeStart,
56
+ isRangeEnd,
57
+ isInRange,
58
+ onPress,
59
+ }) => {
45
60
  const overrides = useDatePickerOverrides();
46
61
 
47
62
  // Build cell style based on state and overrides
@@ -51,13 +66,18 @@ export const DayCell: React.FC<DayCellProps> = memo(
51
66
  backgroundColor: DEFAULT_COLORS.cellBackground,
52
67
  };
53
68
 
54
- if (isSelected) {
69
+ // Range start/end get selected styling
70
+ if (isRangeStart || isRangeEnd || isSelected) {
55
71
  style.backgroundColor =
56
72
  overrides?.cellSelectedColor ??
57
73
  overrides?.primaryColor ??
58
74
  DEFAULT_COLORS.cellSelected;
59
75
  style.borderRadius =
60
76
  overrides?.cellBorderRadius ?? DEFAULT_COLORS.cellBorderRadius;
77
+ } else if (isInRange) {
78
+ // Dates in range get lighter background
79
+ style.backgroundColor =
80
+ overrides?.cellInRangeColor ?? DEFAULT_COLORS.cellInRange;
61
81
  } else if (isToday) {
62
82
  style.backgroundColor =
63
83
  overrides?.cellTodayColor ?? DEFAULT_COLORS.cellToday;
@@ -72,7 +92,15 @@ export const DayCell: React.FC<DayCellProps> = memo(
72
92
  }
73
93
 
74
94
  return style;
75
- }, [overrides, isSelected, isToday, isDisabled]);
95
+ }, [
96
+ overrides,
97
+ isSelected,
98
+ isToday,
99
+ isDisabled,
100
+ isRangeStart,
101
+ isRangeEnd,
102
+ isInRange,
103
+ ]);
76
104
 
77
105
  // Build text style based on state and overrides
78
106
  const textStyle = useMemo(() => {
@@ -81,10 +109,12 @@ export const DayCell: React.FC<DayCellProps> = memo(
81
109
  fontWeight: '400' as const,
82
110
  };
83
111
 
84
- if (isSelected) {
112
+ if (isRangeStart || isRangeEnd || isSelected) {
85
113
  style.color =
86
114
  overrides?.textSelectedColor ?? DEFAULT_COLORS.textSelected;
87
115
  style.fontWeight = '600';
116
+ } else if (isInRange) {
117
+ style.color = overrides?.textInRangeColor ?? DEFAULT_COLORS.textInRange;
88
118
  } else if (isToday) {
89
119
  style.color = overrides?.primaryColor ?? DEFAULT_COLORS.primary;
90
120
  style.fontWeight = '600';
@@ -96,7 +126,16 @@ export const DayCell: React.FC<DayCellProps> = memo(
96
126
  }
97
127
 
98
128
  return style;
99
- }, [overrides, isSelected, isToday, isDisabled, isWeekend]);
129
+ }, [
130
+ overrides,
131
+ isSelected,
132
+ isToday,
133
+ isDisabled,
134
+ isWeekend,
135
+ isRangeStart,
136
+ isRangeEnd,
137
+ isInRange,
138
+ ]);
100
139
 
101
140
  if (!date) {
102
141
  return <Pressable style={webStyles.cell} disabled />;
@@ -123,7 +162,10 @@ export const DayCell: React.FC<DayCellProps> = memo(
123
162
  prev.date?.getTime() === next.date?.getTime() &&
124
163
  prev.isSelected === next.isSelected &&
125
164
  prev.isToday === next.isToday &&
126
- prev.isDisabled === next.isDisabled,
165
+ prev.isDisabled === next.isDisabled &&
166
+ prev.isRangeStart === next.isRangeStart &&
167
+ prev.isRangeEnd === next.isRangeEnd &&
168
+ prev.isInRange === next.isInRange,
127
169
  );
128
170
 
129
171
  DayCell.displayName = 'DayCell';