@dreamstack-us/kaal 0.0.4 → 0.1.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 (34) hide show
  1. package/lib/module/components/CalendarGrid/CalendarGrid.js +1 -1
  2. package/lib/module/components/CalendarGrid/CalendarGrid.js.map +1 -1
  3. package/lib/module/components/CalendarGrid/CalendarGrid.web.js +1 -1
  4. package/lib/module/components/CalendarGrid/CalendarGrid.web.js.map +1 -1
  5. package/lib/module/components/CalendarGrid/DayCell.js +81 -36
  6. package/lib/module/components/CalendarGrid/DayCell.js.map +1 -1
  7. package/lib/module/components/CalendarGrid/DayCell.web.js +80 -36
  8. package/lib/module/components/CalendarGrid/DayCell.web.js.map +1 -1
  9. package/lib/module/components/TimePicker/MaterialTimePicker.web.js +67 -15
  10. package/lib/module/components/TimePicker/MaterialTimePicker.web.js.map +1 -1
  11. package/lib/module/components/WheelPicker/WheelPicker.web.js +14 -14
  12. package/lib/module/components/WheelPicker/WheelPicker.web.js.map +1 -1
  13. package/lib/module/hooks/useCalendar.js +1 -1
  14. package/lib/module/hooks/useCalendar.js.map +1 -1
  15. package/lib/module/utils/date.js +25 -21
  16. package/lib/module/utils/date.js.map +1 -1
  17. package/lib/module/utils/validation.js +3 -3
  18. package/lib/module/utils/validation.js.map +1 -1
  19. package/lib/typescript/components/CalendarGrid/DayCell.d.ts.map +1 -1
  20. package/lib/typescript/components/CalendarGrid/DayCell.web.d.ts.map +1 -1
  21. package/lib/typescript/components/TimePicker/MaterialTimePicker.web.d.ts.map +1 -1
  22. package/lib/typescript/components/WheelPicker/WheelPicker.web.d.ts.map +1 -1
  23. package/lib/typescript/utils/date.d.ts +7 -3
  24. package/lib/typescript/utils/date.d.ts.map +1 -1
  25. package/package.json +8 -9
  26. package/src/components/CalendarGrid/CalendarGrid.tsx +2 -2
  27. package/src/components/CalendarGrid/CalendarGrid.web.tsx +2 -2
  28. package/src/components/CalendarGrid/DayCell.tsx +98 -47
  29. package/src/components/CalendarGrid/DayCell.web.tsx +105 -47
  30. package/src/components/TimePicker/MaterialTimePicker.web.tsx +150 -13
  31. package/src/components/WheelPicker/WheelPicker.web.tsx +15 -25
  32. package/src/hooks/useCalendar.ts +2 -2
  33. package/src/utils/date.ts +34 -27
  34. package/src/utils/validation.ts +5 -5
@@ -1,9 +1,13 @@
1
1
  /**
2
2
  * Date utilities using native Date and Intl APIs
3
3
  * Replaces @js-temporal/polyfill for lighter bundle size
4
+ *
5
+ * All date-only values use LOCAL time (not UTC). This ensures that
6
+ * dates displayed in the calendar match what consumers read with
7
+ * standard .getDate(), .getMonth(), .getFullYear() methods.
4
8
  */
5
9
  /**
6
- * Converts a Date to ISO date string (YYYY-MM-DD)
10
+ * Converts a Date to ISO date string (YYYY-MM-DD) using local time
7
11
  */
8
12
  export declare const toISODateString: (date: Date) => string;
9
13
  /**
@@ -11,7 +15,7 @@ export declare const toISODateString: (date: Date) => string;
11
15
  */
12
16
  export declare const toISODateTimeString: (date: Date, timeZone?: string) => string;
13
17
  /**
14
- * Parses an ISO date string (YYYY-MM-DD) to Date
18
+ * Parses an ISO date string (YYYY-MM-DD) to a local Date
15
19
  */
16
20
  export declare const parseISODate: (iso: string) => Date;
17
21
  /**
@@ -89,7 +93,7 @@ export declare const formatWeekday: (date: Date, locale?: string, style?: "long"
89
93
  */
90
94
  export declare const formatYearMonth: (date: Date, locale?: string) => string;
91
95
  /**
92
- * Gets today's date at midnight UTC
96
+ * Gets today's date at local midnight
93
97
  */
94
98
  export declare const today: () => Date;
95
99
  //# sourceMappingURL=date.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"date.d.ts","sourceRoot":"","sources":["../../../src/utils/date.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;GAEG;AACH,eAAO,MAAM,eAAe,GAAI,MAAM,IAAI,KAAG,MAK5C,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,mBAAmB,GAAI,MAAM,IAAI,EAAE,WAAW,MAAM,KAAG,MAenE,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,YAAY,GAAI,KAAK,MAAM,KAAG,IAG1C,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,iBAAiB,QARI,MAAM,KAAG,IAQE,CAAC;AAE9C;;GAEG;AACH,eAAO,MAAM,gBAAgB,GAAI,KAAK,MAAM,KAAG,IAE9C,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,qBAAqB,QAPI,MAAM,KAAG,IAOM,CAAC;AAEtD;;GAEG;AACH,eAAO,MAAM,YAAY,GAAI,OAAO,IAAI,EAAE,KAAK,IAAI,KAAG,IAAI,EAUzD,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,aAAa,GACxB,MAAM,IAAI,EACV,UAAU,IAAI,EACd,UAAU,IAAI,KACb,OAKF,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,eAAe,QAAO,MAElC,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,OAAO,GAAI,MAAM,IAAI,EAAE,MAAM,MAAM,KAAG,IAIlD,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,SAAS,GAAI,MAAM,IAAI,EAAE,QAAQ,MAAM,KAAG,IAUtD,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,YAAY,GAAI,GAAG,IAAI,EAAE,GAAG,IAAI,KAAG,MAE/C,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,SAAS,GAAI,GAAG,IAAI,EAAE,GAAG,IAAI,KAAG,OAE5C,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,WAAW,GAAI,GAAG,IAAI,EAAE,GAAG,IAAI,KAAG,OAK9C,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,YAAY,GAAI,MAAM,MAAM,EAAE,OAAO,MAAM,KAAG,IAAI,EAO9D,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,kBAAkB,GAAI,MAAM,IAAI,GAAG,IAAI,GAAG,SAAS,KAAG,IAGlE,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,iBAAiB,GAAI,MAAM,IAAI,KAAG,IAE9C,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,YAAY,GAAI,MAAM,IAAI,KAAG,MAEzC,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,WAAW,GACtB,MAAM,IAAI,EACV,eAAgB,EAChB,QAAO,MAAM,GAAG,OAAO,GAAG,QAAiB,KAC1C,MAEF,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,aAAa,GACxB,MAAM,IAAI,EACV,eAAgB,EAChB,QAAO,MAAM,GAAG,OAAO,GAAG,QAAkB,KAC3C,MAEF,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,eAAe,GAAI,MAAM,IAAI,EAAE,eAAgB,KAAG,MAK9D,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,KAAK,QAAO,IAGxB,CAAC"}
1
+ {"version":3,"file":"date.d.ts","sourceRoot":"","sources":["../../../src/utils/date.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH;;GAEG;AACH,eAAO,MAAM,eAAe,GAAI,MAAM,IAAI,KAAG,MAK5C,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,mBAAmB,GAAI,MAAM,IAAI,EAAE,WAAW,MAAM,KAAG,MAenE,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,YAAY,GAAI,KAAK,MAAM,KAAG,IAG1C,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,iBAAiB,QARI,MAAM,KAAG,IAQE,CAAC;AAE9C;;GAEG;AACH,eAAO,MAAM,gBAAgB,GAAI,KAAK,MAAM,KAAG,IAE9C,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,qBAAqB,QAPI,MAAM,KAAG,IAOM,CAAC;AAEtD;;GAEG;AACH,eAAO,MAAM,YAAY,GAAI,OAAO,IAAI,EAAE,KAAK,IAAI,KAAG,IAAI,EAUzD,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,aAAa,GACxB,MAAM,IAAI,EACV,UAAU,IAAI,EACd,UAAU,IAAI,KACb,OAKF,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,eAAe,QAAO,MAElC,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,OAAO,GAAI,MAAM,IAAI,EAAE,MAAM,MAAM,KAAG,IAIlD,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,SAAS,GAAI,MAAM,IAAI,EAAE,QAAQ,MAAM,KAAG,IAYtD,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,YAAY,GAAI,GAAG,IAAI,EAAE,GAAG,IAAI,KAAG,MAE/C,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,SAAS,GAAI,GAAG,IAAI,EAAE,GAAG,IAAI,KAAG,OAE5C,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,WAAW,GAAI,GAAG,IAAI,EAAE,GAAG,IAAI,KAAG,OAE9C,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,YAAY,GAAI,MAAM,MAAM,EAAE,OAAO,MAAM,KAAG,IAAI,EAO9D,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,kBAAkB,GAAI,MAAM,IAAI,GAAG,IAAI,GAAG,SAAS,KAAG,IAGlE,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,iBAAiB,GAAI,MAAM,IAAI,KAAG,IAE9C,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,YAAY,GAAI,MAAM,IAAI,KAAG,MAEzC,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,WAAW,GACtB,MAAM,IAAI,EACV,eAAgB,EAChB,QAAO,MAAM,GAAG,OAAO,GAAG,QAAiB,KAC1C,MAIF,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,aAAa,GACxB,MAAM,IAAI,EACV,eAAgB,EAChB,QAAO,MAAM,GAAG,OAAO,GAAG,QAAkB,KAC3C,MAIF,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,eAAe,GAAI,MAAM,IAAI,EAAE,eAAgB,KAAG,MAK9D,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,KAAK,QAAO,IAGxB,CAAC"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@dreamstack-us/kaal",
3
- "version": "0.0.4",
4
- "description": "High-performance React Native DatePicker with Unistyles v3",
3
+ "version": "0.1.0",
4
+ "description": "High-performance React Native DatePicker and TimePicker",
5
5
  "type": "module",
6
6
  "main": "./lib/module/index.js",
7
7
  "types": "./lib/typescript/index.d.ts",
@@ -33,12 +33,9 @@
33
33
  "peerDependencies": {
34
34
  "react": ">=18.2.0",
35
35
  "react-native": ">=0.78.0",
36
- "react-native-unistyles": "^3.0.0",
37
- "react-native-nitro-modules": "0.31.4",
38
- "react-native-reanimated": ">=3.17.0",
39
- "react-native-gesture-handler": ">=2.20.0",
40
36
  "react-native-svg": ">=13.0.0",
41
- "@dreamstack-us/kaal-themes": ">=1.0.0"
37
+ "react-native-reanimated": ">=3.17.0",
38
+ "react-native-gesture-handler": ">=2.20.0"
42
39
  },
43
40
  "peerDependenciesMeta": {
44
41
  "@expo/ui": {
@@ -46,6 +43,9 @@
46
43
  },
47
44
  "@dreamstack-us/kaal-themes": {
48
45
  "optional": true
46
+ },
47
+ "react-native-nitro-modules": {
48
+ "optional": true
49
49
  }
50
50
  },
51
51
  "devDependencies": {
@@ -57,7 +57,6 @@
57
57
  "react-native-nitro-modules": "0.31.4",
58
58
  "react-native-reanimated": "^3.17.0",
59
59
  "react-native-svg": "^15.15.1",
60
- "react-native-unistyles": "^3.0.0",
61
60
  "typescript": "^5.7.0"
62
61
  },
63
62
  "react-native-builder-bob": {
@@ -94,7 +93,7 @@
94
93
  "datepicker",
95
94
  "timepicker",
96
95
  "calendar",
97
- "unistyles",
96
+ "expo",
98
97
  "new-architecture"
99
98
  ],
100
99
  "license": "MIT"
@@ -99,8 +99,8 @@ const generateMonthDays = (
99
99
  }
100
100
 
101
101
  const monthDays = getMonthDays(
102
- currentMonth.getUTCFullYear(),
103
- currentMonth.getUTCMonth(),
102
+ currentMonth.getFullYear(),
103
+ currentMonth.getMonth(),
104
104
  );
105
105
  for (const day of monthDays) {
106
106
  days.push(day);
@@ -109,8 +109,8 @@ const generateMonthDays = (
109
109
  }
110
110
 
111
111
  const monthDays = getMonthDays(
112
- currentMonth.getUTCFullYear(),
113
- currentMonth.getUTCMonth(),
112
+ currentMonth.getFullYear(),
113
+ currentMonth.getMonth(),
114
114
  );
115
115
  for (const day of monthDays) {
116
116
  days.push(day);
@@ -1,6 +1,6 @@
1
1
  import type React from 'react';
2
2
  import { memo, useMemo } from 'react';
3
- import { Pressable, StyleSheet, Text } from 'react-native';
3
+ import { Pressable, StyleSheet, Text, View } from 'react-native';
4
4
  import { useDatePickerOverrides } from '../../context/ThemeOverrideContext';
5
5
 
6
6
  interface DayCellProps {
@@ -30,16 +30,51 @@ const DEFAULT_COLORS = {
30
30
  cellBorderRadius: 22,
31
31
  };
32
32
 
33
+ const CELL_SIZE = 44;
34
+ const BAND_HEIGHT = 28; // Narrower band for thermometer effect
35
+
33
36
  const styles = StyleSheet.create({
34
37
  cell: {
35
- width: 44,
36
- height: 44,
38
+ width: CELL_SIZE,
39
+ height: CELL_SIZE,
37
40
  justifyContent: 'center',
38
41
  alignItems: 'center',
39
42
  },
43
+ // Thermometer band for in-range dates
44
+ rangeBand: {
45
+ position: 'absolute',
46
+ top: (CELL_SIZE - BAND_HEIGHT) / 2,
47
+ height: BAND_HEIGHT,
48
+ left: 0,
49
+ right: 0,
50
+ },
51
+ // Half band extending right from range start
52
+ rangeBandRight: {
53
+ position: 'absolute',
54
+ top: (CELL_SIZE - BAND_HEIGHT) / 2,
55
+ height: BAND_HEIGHT,
56
+ left: CELL_SIZE / 2,
57
+ right: 0,
58
+ },
59
+ // Half band extending left to range end
60
+ rangeBandLeft: {
61
+ position: 'absolute',
62
+ top: (CELL_SIZE - BAND_HEIGHT) / 2,
63
+ height: BAND_HEIGHT,
64
+ left: 0,
65
+ right: CELL_SIZE / 2,
66
+ },
67
+ // Circle overlay for selected dates
68
+ circleOverlay: {
69
+ position: 'absolute',
70
+ width: CELL_SIZE,
71
+ height: CELL_SIZE,
72
+ borderRadius: CELL_SIZE / 2,
73
+ },
40
74
  text: {
41
75
  fontSize: 17,
42
76
  fontWeight: '400',
77
+ zIndex: 1,
43
78
  },
44
79
  });
45
80
 
@@ -57,48 +92,12 @@ export const DayCell: React.FC<DayCellProps> = memo(
57
92
  }) => {
58
93
  const overrides = useDatePickerOverrides();
59
94
 
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
- ]);
95
+ const rangeColor =
96
+ overrides?.cellInRangeColor ?? DEFAULT_COLORS.cellInRange;
97
+ const selectedColor =
98
+ overrides?.cellSelectedColor ??
99
+ overrides?.primaryColor ??
100
+ DEFAULT_COLORS.cellSelected;
102
101
 
103
102
  // Build text style based on state and overrides
104
103
  const textStyle = useMemo(() => {
@@ -135,13 +134,36 @@ export const DayCell: React.FC<DayCellProps> = memo(
135
134
  isInRange,
136
135
  ]);
137
136
 
137
+ // Today style (non-range)
138
+ const todayStyle = useMemo(() => {
139
+ if (
140
+ isToday &&
141
+ !isSelected &&
142
+ !isRangeStart &&
143
+ !isRangeEnd &&
144
+ !isInRange
145
+ ) {
146
+ return {
147
+ backgroundColor:
148
+ overrides?.cellTodayColor ?? DEFAULT_COLORS.cellToday,
149
+ borderRadius:
150
+ overrides?.cellBorderRadius ?? DEFAULT_COLORS.cellBorderRadius,
151
+ borderWidth: 1,
152
+ borderColor: overrides?.primaryColor ?? DEFAULT_COLORS.primary,
153
+ };
154
+ }
155
+ return null;
156
+ }, [isToday, isSelected, isRangeStart, isRangeEnd, isInRange, overrides]);
157
+
138
158
  if (!date) {
139
159
  return <Pressable style={styles.cell} disabled />;
140
160
  }
141
161
 
162
+ const cellOpacity = isDisabled ? 0.4 : 1;
163
+
142
164
  return (
143
165
  <Pressable
144
- style={[styles.cell, cellStyle]}
166
+ style={[styles.cell, { opacity: cellOpacity }]}
145
167
  onPress={onPress}
146
168
  disabled={isDisabled}
147
169
  accessibilityRole="button"
@@ -152,7 +174,36 @@ export const DayCell: React.FC<DayCellProps> = memo(
152
174
  }).format(date)}
153
175
  accessibilityState={{ selected: isSelected, disabled: isDisabled }}
154
176
  >
155
- <Text style={[styles.text, textStyle]}>{date.getUTCDate()}</Text>
177
+ {/* Thermometer band for range start (extends right) */}
178
+ {isRangeStart && !isRangeEnd && (
179
+ <View
180
+ style={[styles.rangeBandRight, { backgroundColor: rangeColor }]}
181
+ />
182
+ )}
183
+
184
+ {/* Thermometer band for range end (extends left) */}
185
+ {isRangeEnd && !isRangeStart && (
186
+ <View
187
+ style={[styles.rangeBandLeft, { backgroundColor: rangeColor }]}
188
+ />
189
+ )}
190
+
191
+ {/* Thermometer band for in-range dates (full width) */}
192
+ {isInRange && (
193
+ <View style={[styles.rangeBand, { backgroundColor: rangeColor }]} />
194
+ )}
195
+
196
+ {/* Circle for selected/range start/end */}
197
+ {(isRangeStart || isRangeEnd || isSelected) && (
198
+ <View
199
+ style={[styles.circleOverlay, { backgroundColor: selectedColor }]}
200
+ />
201
+ )}
202
+
203
+ {/* Today indicator (when not in range) */}
204
+ {todayStyle && <View style={[styles.circleOverlay, todayStyle]} />}
205
+
206
+ <Text style={[styles.text, textStyle]}>{date.getDate()}</Text>
156
207
  </Pressable>
157
208
  );
158
209
  },
@@ -1,7 +1,7 @@
1
1
  /// <reference lib="dom" />
2
2
  import type React from 'react';
3
3
  import { memo, useMemo } from 'react';
4
- import { Pressable, StyleSheet, Text } from 'react-native';
4
+ import { Pressable, StyleSheet, Text, View } from 'react-native';
5
5
  import { useDatePickerOverrides } from '../../context/ThemeOverrideContext';
6
6
 
7
7
  interface DayCellProps {
@@ -31,17 +31,52 @@ const DEFAULT_COLORS = {
31
31
  cellBorderRadius: 22,
32
32
  };
33
33
 
34
+ const CELL_SIZE = 44;
35
+ const BAND_HEIGHT = 28; // Narrower band for thermometer effect
36
+
34
37
  // Web-compatible styles (no unistyles dependency)
35
38
  const webStyles = StyleSheet.create({
36
39
  cell: {
37
- width: 44,
38
- height: 44,
40
+ width: CELL_SIZE,
41
+ height: CELL_SIZE,
39
42
  justifyContent: 'center',
40
43
  alignItems: 'center',
41
44
  },
45
+ // Thermometer band for in-range dates
46
+ rangeBand: {
47
+ position: 'absolute',
48
+ top: (CELL_SIZE - BAND_HEIGHT) / 2,
49
+ height: BAND_HEIGHT,
50
+ left: 0,
51
+ right: 0,
52
+ },
53
+ // Half band extending right from range start
54
+ rangeBandRight: {
55
+ position: 'absolute',
56
+ top: (CELL_SIZE - BAND_HEIGHT) / 2,
57
+ height: BAND_HEIGHT,
58
+ left: CELL_SIZE / 2,
59
+ right: 0,
60
+ },
61
+ // Half band extending left to range end
62
+ rangeBandLeft: {
63
+ position: 'absolute',
64
+ top: (CELL_SIZE - BAND_HEIGHT) / 2,
65
+ height: BAND_HEIGHT,
66
+ left: 0,
67
+ right: CELL_SIZE / 2,
68
+ },
69
+ // Circle overlay for selected dates
70
+ circleOverlay: {
71
+ position: 'absolute',
72
+ width: CELL_SIZE,
73
+ height: CELL_SIZE,
74
+ borderRadius: CELL_SIZE / 2,
75
+ },
42
76
  text: {
43
77
  fontSize: 17,
44
78
  fontWeight: '400',
79
+ zIndex: 1,
45
80
  },
46
81
  });
47
82
 
@@ -59,48 +94,12 @@ export const DayCell: React.FC<DayCellProps> = memo(
59
94
  }) => {
60
95
  const overrides = useDatePickerOverrides();
61
96
 
62
- // Build cell style based on state and overrides
63
- // Use primaryColor as fallback for cellSelectedColor (consumer expectation)
64
- const cellStyle = useMemo(() => {
65
- const style: Record<string, unknown> = {
66
- backgroundColor: DEFAULT_COLORS.cellBackground,
67
- };
68
-
69
- // Range start/end get selected styling
70
- if (isRangeStart || isRangeEnd || isSelected) {
71
- style.backgroundColor =
72
- overrides?.cellSelectedColor ??
73
- overrides?.primaryColor ??
74
- DEFAULT_COLORS.cellSelected;
75
- style.borderRadius =
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;
81
- } else if (isToday) {
82
- style.backgroundColor =
83
- overrides?.cellTodayColor ?? DEFAULT_COLORS.cellToday;
84
- style.borderRadius =
85
- overrides?.cellBorderRadius ?? DEFAULT_COLORS.cellBorderRadius;
86
- style.borderWidth = 1;
87
- style.borderColor = overrides?.primaryColor ?? DEFAULT_COLORS.primary;
88
- }
89
-
90
- if (isDisabled) {
91
- style.opacity = 0.4;
92
- }
93
-
94
- return style;
95
- }, [
96
- overrides,
97
- isSelected,
98
- isToday,
99
- isDisabled,
100
- isRangeStart,
101
- isRangeEnd,
102
- isInRange,
103
- ]);
97
+ const rangeColor =
98
+ overrides?.cellInRangeColor ?? DEFAULT_COLORS.cellInRange;
99
+ const selectedColor =
100
+ overrides?.cellSelectedColor ??
101
+ overrides?.primaryColor ??
102
+ DEFAULT_COLORS.cellSelected;
104
103
 
105
104
  // Build text style based on state and overrides
106
105
  const textStyle = useMemo(() => {
@@ -137,13 +136,36 @@ export const DayCell: React.FC<DayCellProps> = memo(
137
136
  isInRange,
138
137
  ]);
139
138
 
139
+ // Today style (non-range)
140
+ const todayStyle = useMemo(() => {
141
+ if (
142
+ isToday &&
143
+ !isSelected &&
144
+ !isRangeStart &&
145
+ !isRangeEnd &&
146
+ !isInRange
147
+ ) {
148
+ return {
149
+ backgroundColor:
150
+ overrides?.cellTodayColor ?? DEFAULT_COLORS.cellToday,
151
+ borderRadius:
152
+ overrides?.cellBorderRadius ?? DEFAULT_COLORS.cellBorderRadius,
153
+ borderWidth: 1,
154
+ borderColor: overrides?.primaryColor ?? DEFAULT_COLORS.primary,
155
+ };
156
+ }
157
+ return null;
158
+ }, [isToday, isSelected, isRangeStart, isRangeEnd, isInRange, overrides]);
159
+
140
160
  if (!date) {
141
161
  return <Pressable style={webStyles.cell} disabled />;
142
162
  }
143
163
 
164
+ const cellOpacity = isDisabled ? 0.4 : 1;
165
+
144
166
  return (
145
167
  <Pressable
146
- style={[webStyles.cell, cellStyle]}
168
+ style={[webStyles.cell, { opacity: cellOpacity }]}
147
169
  onPress={onPress}
148
170
  disabled={isDisabled}
149
171
  accessibilityRole="button"
@@ -154,7 +176,43 @@ export const DayCell: React.FC<DayCellProps> = memo(
154
176
  }).format(date)}
155
177
  accessibilityState={{ selected: isSelected, disabled: isDisabled }}
156
178
  >
157
- <Text style={[webStyles.text, textStyle]}>{date.getUTCDate()}</Text>
179
+ {/* Thermometer band for range start (extends right) */}
180
+ {isRangeStart && !isRangeEnd && (
181
+ <View
182
+ style={[webStyles.rangeBandRight, { backgroundColor: rangeColor }]}
183
+ />
184
+ )}
185
+
186
+ {/* Thermometer band for range end (extends left) */}
187
+ {isRangeEnd && !isRangeStart && (
188
+ <View
189
+ style={[webStyles.rangeBandLeft, { backgroundColor: rangeColor }]}
190
+ />
191
+ )}
192
+
193
+ {/* Thermometer band for in-range dates (full width) */}
194
+ {isInRange && (
195
+ <View
196
+ style={[webStyles.rangeBand, { backgroundColor: rangeColor }]}
197
+ />
198
+ )}
199
+
200
+ {/* Circle for selected/range start/end */}
201
+ {(isRangeStart || isRangeEnd || isSelected) && (
202
+ <View
203
+ style={[
204
+ webStyles.circleOverlay,
205
+ { backgroundColor: selectedColor },
206
+ ]}
207
+ />
208
+ )}
209
+
210
+ {/* Today indicator (when not in range) */}
211
+ {todayStyle ? (
212
+ <View style={[webStyles.circleOverlay, todayStyle]} />
213
+ ) : null}
214
+
215
+ <Text style={[webStyles.text, textStyle]}>{date.getDate()}</Text>
158
216
  </Pressable>
159
217
  );
160
218
  },