@dreamstack-us/kaal 0.0.1

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 (181) hide show
  1. package/lib/module/components/CalendarGrid/CalendarGrid.js +112 -0
  2. package/lib/module/components/CalendarGrid/CalendarGrid.js.map +1 -0
  3. package/lib/module/components/CalendarGrid/CalendarGrid.styles.js +46 -0
  4. package/lib/module/components/CalendarGrid/CalendarGrid.styles.js.map +1 -0
  5. package/lib/module/components/CalendarGrid/DayCell.js +96 -0
  6. package/lib/module/components/CalendarGrid/DayCell.js.map +1 -0
  7. package/lib/module/components/CalendarGrid/index.js +4 -0
  8. package/lib/module/components/CalendarGrid/index.js.map +1 -0
  9. package/lib/module/components/DatePicker/DatePicker.android.js +66 -0
  10. package/lib/module/components/DatePicker/DatePicker.android.js.map +1 -0
  11. package/lib/module/components/DatePicker/DatePicker.ios.js +74 -0
  12. package/lib/module/components/DatePicker/DatePicker.ios.js.map +1 -0
  13. package/lib/module/components/DatePicker/DatePicker.js +9 -0
  14. package/lib/module/components/DatePicker/DatePicker.js.map +1 -0
  15. package/lib/module/components/DatePicker/DatePicker.styles.js +35 -0
  16. package/lib/module/components/DatePicker/DatePicker.styles.js.map +1 -0
  17. package/lib/module/components/DatePicker/DatePicker.web.js +32 -0
  18. package/lib/module/components/DatePicker/DatePicker.web.js.map +1 -0
  19. package/lib/module/components/DatePicker/index.js +4 -0
  20. package/lib/module/components/DatePicker/index.js.map +1 -0
  21. package/lib/module/components/TimePicker/ClockFace.js +194 -0
  22. package/lib/module/components/TimePicker/ClockFace.js.map +1 -0
  23. package/lib/module/components/TimePicker/MaterialTimePicker.js +122 -0
  24. package/lib/module/components/TimePicker/MaterialTimePicker.js.map +1 -0
  25. package/lib/module/components/TimePicker/TimePicker.android.js +77 -0
  26. package/lib/module/components/TimePicker/TimePicker.android.js.map +1 -0
  27. package/lib/module/components/TimePicker/TimePicker.ios.js +83 -0
  28. package/lib/module/components/TimePicker/TimePicker.ios.js.map +1 -0
  29. package/lib/module/components/TimePicker/TimePicker.js +34 -0
  30. package/lib/module/components/TimePicker/TimePicker.js.map +1 -0
  31. package/lib/module/components/TimePicker/TimePicker.styles.js +180 -0
  32. package/lib/module/components/TimePicker/TimePicker.styles.js.map +1 -0
  33. package/lib/module/components/TimePicker/TimePicker.web.js +37 -0
  34. package/lib/module/components/TimePicker/TimePicker.web.js.map +1 -0
  35. package/lib/module/components/TimePicker/TimeWheelPicker.js +178 -0
  36. package/lib/module/components/TimePicker/TimeWheelPicker.js.map +1 -0
  37. package/lib/module/components/TimePicker/index.js +7 -0
  38. package/lib/module/components/TimePicker/index.js.map +1 -0
  39. package/lib/module/components/WheelPicker/WheelPicker.js +5 -0
  40. package/lib/module/components/WheelPicker/WheelPicker.js.map +1 -0
  41. package/lib/module/components/WheelPicker/WheelPicker.styles.js +41 -0
  42. package/lib/module/components/WheelPicker/WheelPicker.styles.js.map +1 -0
  43. package/lib/module/components/WheelPicker/WheelPicker.web.js +190 -0
  44. package/lib/module/components/WheelPicker/WheelPicker.web.js.map +1 -0
  45. package/lib/module/components/WheelPicker/index.js +4 -0
  46. package/lib/module/components/WheelPicker/index.js.map +1 -0
  47. package/lib/module/components/index.js +7 -0
  48. package/lib/module/components/index.js.map +1 -0
  49. package/lib/module/hooks/index.js +6 -0
  50. package/lib/module/hooks/index.js.map +1 -0
  51. package/lib/module/hooks/useCalendar.js +44 -0
  52. package/lib/module/hooks/useCalendar.js.map +1 -0
  53. package/lib/module/hooks/useDatePicker.js +30 -0
  54. package/lib/module/hooks/useDatePicker.js.map +1 -0
  55. package/lib/module/hooks/useTimePicker.js +125 -0
  56. package/lib/module/hooks/useTimePicker.js.map +1 -0
  57. package/lib/module/index.js +22 -0
  58. package/lib/module/index.js.map +1 -0
  59. package/lib/module/types/datepicker.js +2 -0
  60. package/lib/module/types/datepicker.js.map +1 -0
  61. package/lib/module/types/index.js +5 -0
  62. package/lib/module/types/index.js.map +1 -0
  63. package/lib/module/types/timepicker.js +2 -0
  64. package/lib/module/types/timepicker.js.map +1 -0
  65. package/lib/module/unistyles.js +9 -0
  66. package/lib/module/unistyles.js.map +1 -0
  67. package/lib/module/utils/date.js +205 -0
  68. package/lib/module/utils/date.js.map +1 -0
  69. package/lib/module/utils/index.js +5 -0
  70. package/lib/module/utils/index.js.map +1 -0
  71. package/lib/module/utils/validation.js +61 -0
  72. package/lib/module/utils/validation.js.map +1 -0
  73. package/lib/typescript/components/CalendarGrid/CalendarGrid.d.ts +12 -0
  74. package/lib/typescript/components/CalendarGrid/CalendarGrid.d.ts.map +1 -0
  75. package/lib/typescript/components/CalendarGrid/CalendarGrid.styles.d.ts +45 -0
  76. package/lib/typescript/components/CalendarGrid/CalendarGrid.styles.d.ts.map +1 -0
  77. package/lib/typescript/components/CalendarGrid/DayCell.d.ts +12 -0
  78. package/lib/typescript/components/CalendarGrid/DayCell.d.ts.map +1 -0
  79. package/lib/typescript/components/CalendarGrid/index.d.ts +2 -0
  80. package/lib/typescript/components/CalendarGrid/index.d.ts.map +1 -0
  81. package/lib/typescript/components/DatePicker/DatePicker.android.d.ts +4 -0
  82. package/lib/typescript/components/DatePicker/DatePicker.android.d.ts.map +1 -0
  83. package/lib/typescript/components/DatePicker/DatePicker.d.ts +15 -0
  84. package/lib/typescript/components/DatePicker/DatePicker.d.ts.map +1 -0
  85. package/lib/typescript/components/DatePicker/DatePicker.ios.d.ts +4 -0
  86. package/lib/typescript/components/DatePicker/DatePicker.ios.d.ts.map +1 -0
  87. package/lib/typescript/components/DatePicker/DatePicker.styles.d.ts +29 -0
  88. package/lib/typescript/components/DatePicker/DatePicker.styles.d.ts.map +1 -0
  89. package/lib/typescript/components/DatePicker/DatePicker.web.d.ts +4 -0
  90. package/lib/typescript/components/DatePicker/DatePicker.web.d.ts.map +1 -0
  91. package/lib/typescript/components/DatePicker/index.d.ts +3 -0
  92. package/lib/typescript/components/DatePicker/index.d.ts.map +1 -0
  93. package/lib/typescript/components/TimePicker/ClockFace.d.ts +12 -0
  94. package/lib/typescript/components/TimePicker/ClockFace.d.ts.map +1 -0
  95. package/lib/typescript/components/TimePicker/MaterialTimePicker.d.ts +12 -0
  96. package/lib/typescript/components/TimePicker/MaterialTimePicker.d.ts.map +1 -0
  97. package/lib/typescript/components/TimePicker/TimePicker.android.d.ts +4 -0
  98. package/lib/typescript/components/TimePicker/TimePicker.android.d.ts.map +1 -0
  99. package/lib/typescript/components/TimePicker/TimePicker.d.ts +29 -0
  100. package/lib/typescript/components/TimePicker/TimePicker.d.ts.map +1 -0
  101. package/lib/typescript/components/TimePicker/TimePicker.ios.d.ts +4 -0
  102. package/lib/typescript/components/TimePicker/TimePicker.ios.d.ts.map +1 -0
  103. package/lib/typescript/components/TimePicker/TimePicker.styles.d.ts +168 -0
  104. package/lib/typescript/components/TimePicker/TimePicker.styles.d.ts.map +1 -0
  105. package/lib/typescript/components/TimePicker/TimePicker.web.d.ts +10 -0
  106. package/lib/typescript/components/TimePicker/TimePicker.web.d.ts.map +1 -0
  107. package/lib/typescript/components/TimePicker/TimeWheelPicker.d.ts +11 -0
  108. package/lib/typescript/components/TimePicker/TimeWheelPicker.d.ts.map +1 -0
  109. package/lib/typescript/components/TimePicker/index.d.ts +6 -0
  110. package/lib/typescript/components/TimePicker/index.d.ts.map +1 -0
  111. package/lib/typescript/components/WheelPicker/WheelPicker.d.ts +2 -0
  112. package/lib/typescript/components/WheelPicker/WheelPicker.d.ts.map +1 -0
  113. package/lib/typescript/components/WheelPicker/WheelPicker.styles.d.ts +40 -0
  114. package/lib/typescript/components/WheelPicker/WheelPicker.styles.d.ts.map +1 -0
  115. package/lib/typescript/components/WheelPicker/WheelPicker.web.d.ts +10 -0
  116. package/lib/typescript/components/WheelPicker/WheelPicker.web.d.ts.map +1 -0
  117. package/lib/typescript/components/WheelPicker/index.d.ts +2 -0
  118. package/lib/typescript/components/WheelPicker/index.d.ts.map +1 -0
  119. package/lib/typescript/components/index.d.ts +5 -0
  120. package/lib/typescript/components/index.d.ts.map +1 -0
  121. package/lib/typescript/hooks/index.d.ts +4 -0
  122. package/lib/typescript/hooks/index.d.ts.map +1 -0
  123. package/lib/typescript/hooks/useCalendar.d.ts +10 -0
  124. package/lib/typescript/hooks/useCalendar.d.ts.map +1 -0
  125. package/lib/typescript/hooks/useDatePicker.d.ts +15 -0
  126. package/lib/typescript/hooks/useDatePicker.d.ts.map +1 -0
  127. package/lib/typescript/hooks/useTimePicker.d.ts +52 -0
  128. package/lib/typescript/hooks/useTimePicker.d.ts.map +1 -0
  129. package/lib/typescript/index.d.ts +12 -0
  130. package/lib/typescript/index.d.ts.map +1 -0
  131. package/lib/typescript/types/datepicker.d.ts +15 -0
  132. package/lib/typescript/types/datepicker.d.ts.map +1 -0
  133. package/lib/typescript/types/index.d.ts +3 -0
  134. package/lib/typescript/types/index.d.ts.map +1 -0
  135. package/lib/typescript/types/timepicker.d.ts +54 -0
  136. package/lib/typescript/types/timepicker.d.ts.map +1 -0
  137. package/lib/typescript/unistyles.d.ts +3 -0
  138. package/lib/typescript/unistyles.d.ts.map +1 -0
  139. package/lib/typescript/utils/date.d.ts +94 -0
  140. package/lib/typescript/utils/date.d.ts.map +1 -0
  141. package/lib/typescript/utils/index.d.ts +3 -0
  142. package/lib/typescript/utils/index.d.ts.map +1 -0
  143. package/lib/typescript/utils/validation.d.ts +40 -0
  144. package/lib/typescript/utils/validation.d.ts.map +1 -0
  145. package/package.json +101 -0
  146. package/src/components/CalendarGrid/CalendarGrid.styles.ts +44 -0
  147. package/src/components/CalendarGrid/CalendarGrid.tsx +151 -0
  148. package/src/components/CalendarGrid/DayCell.tsx +108 -0
  149. package/src/components/CalendarGrid/index.ts +1 -0
  150. package/src/components/DatePicker/DatePicker.android.tsx +69 -0
  151. package/src/components/DatePicker/DatePicker.ios.tsx +78 -0
  152. package/src/components/DatePicker/DatePicker.styles.ts +35 -0
  153. package/src/components/DatePicker/DatePicker.tsx +21 -0
  154. package/src/components/DatePicker/DatePicker.web.tsx +36 -0
  155. package/src/components/DatePicker/index.ts +2 -0
  156. package/src/components/TimePicker/ClockFace.tsx +233 -0
  157. package/src/components/TimePicker/MaterialTimePicker.tsx +169 -0
  158. package/src/components/TimePicker/TimePicker.android.tsx +80 -0
  159. package/src/components/TimePicker/TimePicker.ios.tsx +88 -0
  160. package/src/components/TimePicker/TimePicker.styles.ts +209 -0
  161. package/src/components/TimePicker/TimePicker.tsx +35 -0
  162. package/src/components/TimePicker/TimePicker.web.tsx +35 -0
  163. package/src/components/TimePicker/TimeWheelPicker.tsx +211 -0
  164. package/src/components/TimePicker/index.ts +5 -0
  165. package/src/components/WheelPicker/WheelPicker.styles.ts +39 -0
  166. package/src/components/WheelPicker/WheelPicker.tsx +2 -0
  167. package/src/components/WheelPicker/WheelPicker.web.tsx +237 -0
  168. package/src/components/WheelPicker/index.ts +1 -0
  169. package/src/components/index.ts +11 -0
  170. package/src/hooks/index.ts +9 -0
  171. package/src/hooks/useCalendar.ts +59 -0
  172. package/src/hooks/useDatePicker.ts +40 -0
  173. package/src/hooks/useTimePicker.ts +152 -0
  174. package/src/index.ts +77 -0
  175. package/src/types/datepicker.ts +17 -0
  176. package/src/types/index.ts +2 -0
  177. package/src/types/timepicker.ts +59 -0
  178. package/src/unistyles.ts +6 -0
  179. package/src/utils/date.ts +217 -0
  180. package/src/utils/index.ts +2 -0
  181. package/src/utils/validation.ts +76 -0
@@ -0,0 +1,2 @@
1
+ export { DatePicker } from './DatePicker';
2
+ export type { KaalDatePickerProps } from './DatePicker';
@@ -0,0 +1,233 @@
1
+ import type React from 'react';
2
+ import { memo, useCallback, useMemo } from 'react';
3
+ import { View } from 'react-native';
4
+ import { Gesture, GestureDetector } from 'react-native-gesture-handler';
5
+ import { runOnJS } from 'react-native-reanimated';
6
+ import Svg, { Circle, G, Line, Text as SvgText } from 'react-native-svg';
7
+ import { to12Hour, to24Hour } from '../../hooks/useTimePicker';
8
+ import type { ClockMode, TimePeriod, TimeValue } from '../../types/timepicker';
9
+ import { styles } from './TimePicker.styles';
10
+
11
+ // Clock dimensions
12
+ const CLOCK_SIZE = 256;
13
+ const CLOCK_CENTER = CLOCK_SIZE / 2;
14
+ const OUTER_RADIUS = 96;
15
+ const SELECTION_DOT_RADIUS = 20;
16
+ const CENTER_DOT_RADIUS = 4;
17
+
18
+ // Hour positions (12 at top, clockwise)
19
+ const HOURS_12 = [12, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11];
20
+ const MINUTE_LABELS = [0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55];
21
+
22
+ /**
23
+ * Converts clock position (0-11 for hours, 0-59 for minutes) to angle in degrees
24
+ * 12 o'clock is at -90 degrees (top)
25
+ */
26
+ const positionToAngle = (position: number, isMinutes = false): number => {
27
+ if (isMinutes) {
28
+ return position * 6 - 90; // 360/60 = 6 degrees per minute
29
+ }
30
+ return position * 30 - 90; // 360/12 = 30 degrees per position
31
+ };
32
+
33
+ /**
34
+ * Gets x,y coordinates on circle from angle and radius
35
+ */
36
+ const getPointOnCircle = (
37
+ angleDegrees: number,
38
+ radius: number,
39
+ ): { x: number; y: number } => {
40
+ const rad = (angleDegrees * Math.PI) / 180;
41
+ return {
42
+ x: CLOCK_CENTER + radius * Math.cos(rad),
43
+ y: CLOCK_CENTER + radius * Math.sin(rad),
44
+ };
45
+ };
46
+
47
+ interface ClockFaceProps {
48
+ value: TimeValue;
49
+ onChange: (time: TimeValue) => void;
50
+ mode: ClockMode;
51
+ onModeChange?: (mode: ClockMode) => void;
52
+ is24Hour?: boolean;
53
+ }
54
+
55
+ export const ClockFace: React.FC<ClockFaceProps> = memo(
56
+ ({ value, onChange, mode, onModeChange, is24Hour = false }) => {
57
+ const { hour: hour12, period } = to12Hour(value.hours);
58
+
59
+ // Calculate hand end position based on current value
60
+ const handAngle = useMemo(() => {
61
+ if (mode === 'hours') {
62
+ const hourIndex = HOURS_12.indexOf(hour12);
63
+ return positionToAngle(hourIndex >= 0 ? hourIndex : 0);
64
+ }
65
+ return positionToAngle(value.minutes, true);
66
+ }, [mode, hour12, value.minutes]);
67
+
68
+ const handEndPos = getPointOnCircle(handAngle, OUTER_RADIUS);
69
+
70
+ const handleInteraction = useCallback(
71
+ (x: number, y: number) => {
72
+ // Convert touch coordinates to angle
73
+ const dx = x - CLOCK_CENTER;
74
+ const dy = y - CLOCK_CENTER;
75
+ const angle = Math.atan2(dy, dx) * (180 / Math.PI);
76
+
77
+ // Normalize angle to 0-360, with 0 at top (12 o'clock)
78
+ const normalized = (((angle + 90) % 360) + 360) % 360;
79
+
80
+ if (mode === 'hours') {
81
+ // Round to nearest hour position
82
+ const position = Math.round(normalized / 30) % 12;
83
+ const hour = HOURS_12[position];
84
+ if (hour !== undefined) {
85
+ const newHours = to24Hour(hour, period);
86
+ onChange({ hours: newHours, minutes: value.minutes });
87
+ }
88
+ } else {
89
+ // Round to nearest minute
90
+ const minutes = Math.round(normalized / 6) % 60;
91
+ onChange({ hours: value.hours, minutes });
92
+ }
93
+ },
94
+ [mode, period, value, onChange],
95
+ );
96
+
97
+ const handleInteractionEnd = useCallback(() => {
98
+ // Switch to minutes mode after selecting hours
99
+ if (mode === 'hours' && onModeChange) {
100
+ // Small delay for visual feedback
101
+ setTimeout(() => onModeChange('minutes'), 200);
102
+ }
103
+ }, [mode, onModeChange]);
104
+
105
+ const panGesture = useMemo(
106
+ () =>
107
+ Gesture.Pan()
108
+ .onStart((e) => {
109
+ 'worklet';
110
+ runOnJS(handleInteraction)(e.x, e.y);
111
+ })
112
+ .onUpdate((e) => {
113
+ 'worklet';
114
+ runOnJS(handleInteraction)(e.x, e.y);
115
+ })
116
+ .onEnd(() => {
117
+ 'worklet';
118
+ runOnJS(handleInteractionEnd)();
119
+ }),
120
+ [handleInteraction, handleInteractionEnd],
121
+ );
122
+
123
+ const tapGesture = useMemo(
124
+ () =>
125
+ Gesture.Tap().onEnd((e) => {
126
+ 'worklet';
127
+ runOnJS(handleInteraction)(e.x, e.y);
128
+ runOnJS(handleInteractionEnd)();
129
+ }),
130
+ [handleInteraction, handleInteractionEnd],
131
+ );
132
+
133
+ const combinedGesture = Gesture.Race(panGesture, tapGesture);
134
+
135
+ // Render clock numbers
136
+ const numbers = useMemo(() => {
137
+ if (mode === 'hours') {
138
+ return HOURS_12.map((hour, index) => {
139
+ const angle = positionToAngle(index);
140
+ const pos = getPointOnCircle(angle, OUTER_RADIUS);
141
+ const isSelected = hour12 === hour;
142
+
143
+ return (
144
+ <SvgText
145
+ key={hour}
146
+ x={pos.x}
147
+ y={pos.y}
148
+ textAnchor="middle"
149
+ alignmentBaseline="central"
150
+ fontSize={14}
151
+ fontWeight={isSelected ? '500' : '400'}
152
+ fill={isSelected ? '#FFFFFF' : '#E6E1E5'}
153
+ >
154
+ {hour}
155
+ </SvgText>
156
+ );
157
+ });
158
+ }
159
+
160
+ return MINUTE_LABELS.map((minute, index) => {
161
+ const angle = positionToAngle(index);
162
+ const pos = getPointOnCircle(angle, OUTER_RADIUS);
163
+ const isSelected = value.minutes === minute;
164
+
165
+ return (
166
+ <SvgText
167
+ key={minute}
168
+ x={pos.x}
169
+ y={pos.y}
170
+ textAnchor="middle"
171
+ alignmentBaseline="central"
172
+ fontSize={14}
173
+ fontWeight={isSelected ? '500' : '400'}
174
+ fill={isSelected ? '#FFFFFF' : '#E6E1E5'}
175
+ >
176
+ {minute.toString().padStart(2, '0')}
177
+ </SvgText>
178
+ );
179
+ });
180
+ }, [mode, hour12, value.minutes]);
181
+
182
+ return (
183
+ <View style={styles.clockContainer}>
184
+ <GestureDetector gesture={combinedGesture}>
185
+ <Svg
186
+ width={CLOCK_SIZE}
187
+ height={CLOCK_SIZE}
188
+ viewBox={`0 0 ${CLOCK_SIZE} ${CLOCK_SIZE}`}
189
+ >
190
+ {/* Background circle */}
191
+ <Circle
192
+ cx={CLOCK_CENTER}
193
+ cy={CLOCK_CENTER}
194
+ r={CLOCK_SIZE / 2 - 4}
195
+ fill="#3F384C"
196
+ />
197
+
198
+ {/* Selection dot (behind numbers) */}
199
+ <Circle
200
+ cx={handEndPos.x}
201
+ cy={handEndPos.y}
202
+ r={SELECTION_DOT_RADIUS}
203
+ fill="#4DA6FF"
204
+ />
205
+
206
+ {/* Clock hand */}
207
+ <Line
208
+ x1={CLOCK_CENTER}
209
+ y1={CLOCK_CENTER}
210
+ x2={handEndPos.x}
211
+ y2={handEndPos.y}
212
+ stroke="#4DA6FF"
213
+ strokeWidth={2}
214
+ />
215
+
216
+ {/* Center dot */}
217
+ <Circle
218
+ cx={CLOCK_CENTER}
219
+ cy={CLOCK_CENTER}
220
+ r={CENTER_DOT_RADIUS}
221
+ fill="#4DA6FF"
222
+ />
223
+
224
+ {/* Numbers */}
225
+ <G>{numbers}</G>
226
+ </Svg>
227
+ </GestureDetector>
228
+ </View>
229
+ );
230
+ },
231
+ );
232
+
233
+ ClockFace.displayName = 'ClockFace';
@@ -0,0 +1,169 @@
1
+ import type React from 'react';
2
+ import { memo, useCallback, useState } from 'react';
3
+ import { Pressable, Text, View } from 'react-native';
4
+ import { to12Hour, to24Hour } from '../../hooks/useTimePicker';
5
+ import type { ClockMode, TimePeriod, TimeValue } from '../../types/timepicker';
6
+ import { ClockFace } from './ClockFace';
7
+ import { styles } from './TimePicker.styles';
8
+
9
+ interface MaterialTimePickerProps {
10
+ value: TimeValue;
11
+ onChange: (time: TimeValue) => void;
12
+ is24Hour?: boolean;
13
+ onCancel?: () => void;
14
+ onConfirm?: () => void;
15
+ }
16
+
17
+ export const MaterialTimePicker: React.FC<MaterialTimePickerProps> = memo(
18
+ ({ value, onChange, is24Hour = false, onCancel, onConfirm }) => {
19
+ const [mode, setMode] = useState<ClockMode>('hours');
20
+ const { hour: hour12, period } = to12Hour(value.hours);
21
+
22
+ const handleHourPress = useCallback(() => {
23
+ setMode('hours');
24
+ }, []);
25
+
26
+ const handleMinutePress = useCallback(() => {
27
+ setMode('minutes');
28
+ }, []);
29
+
30
+ const handlePeriodChange = useCallback(
31
+ (newPeriod: TimePeriod) => {
32
+ const newHours = to24Hour(hour12, newPeriod);
33
+ onChange({ hours: newHours, minutes: value.minutes });
34
+ },
35
+ [hour12, value.minutes, onChange],
36
+ );
37
+
38
+ const handleModeChange = useCallback((newMode: ClockMode) => {
39
+ setMode(newMode);
40
+ }, []);
41
+
42
+ // Format display values
43
+ const displayHour = is24Hour
44
+ ? value.hours.toString().padStart(2, '0')
45
+ : hour12.toString().padStart(2, '0');
46
+ const displayMinute = value.minutes.toString().padStart(2, '0');
47
+
48
+ return (
49
+ <View style={styles.materialContainer}>
50
+ {/* Header */}
51
+ <Text style={styles.materialHeader}>Select time</Text>
52
+
53
+ {/* Time Input Display */}
54
+ <View style={styles.timeInputContainer}>
55
+ <View style={styles.timeFieldsContainer}>
56
+ {/* Hour Field */}
57
+ <Pressable
58
+ onPress={handleHourPress}
59
+ style={[
60
+ styles.timeField,
61
+ mode === 'hours' && styles.timeFieldActive,
62
+ ]}
63
+ >
64
+ <Text
65
+ style={[
66
+ styles.timeFieldText,
67
+ mode === 'hours' && styles.timeFieldTextActive,
68
+ ]}
69
+ >
70
+ {displayHour}
71
+ </Text>
72
+ </Pressable>
73
+
74
+ {/* Separator */}
75
+ <Text style={styles.timeSeparator}>:</Text>
76
+
77
+ {/* Minute Field */}
78
+ <Pressable
79
+ onPress={handleMinutePress}
80
+ style={[
81
+ styles.timeField,
82
+ mode === 'minutes' && styles.timeFieldActive,
83
+ ]}
84
+ >
85
+ <Text
86
+ style={[
87
+ styles.timeFieldText,
88
+ mode === 'minutes' && styles.timeFieldTextActive,
89
+ ]}
90
+ >
91
+ {displayMinute}
92
+ </Text>
93
+ </Pressable>
94
+ </View>
95
+
96
+ {/* AM/PM Toggle (only for 12-hour format) */}
97
+ {!is24Hour && (
98
+ <View style={styles.periodToggleContainer}>
99
+ <Pressable
100
+ onPress={() => handlePeriodChange('AM')}
101
+ style={[
102
+ styles.periodButton,
103
+ styles.periodButtonTop,
104
+ period === 'AM' && styles.periodButtonActive,
105
+ ]}
106
+ >
107
+ <Text
108
+ style={[
109
+ styles.periodButtonText,
110
+ period === 'AM' && styles.periodButtonTextActive,
111
+ ]}
112
+ >
113
+ AM
114
+ </Text>
115
+ </Pressable>
116
+ <Pressable
117
+ onPress={() => handlePeriodChange('PM')}
118
+ style={[
119
+ styles.periodButton,
120
+ period === 'PM' && styles.periodButtonActive,
121
+ ]}
122
+ >
123
+ <Text
124
+ style={[
125
+ styles.periodButtonText,
126
+ period === 'PM' && styles.periodButtonTextActive,
127
+ ]}
128
+ >
129
+ PM
130
+ </Text>
131
+ </Pressable>
132
+ </View>
133
+ )}
134
+ </View>
135
+
136
+ {/* Clock Face */}
137
+ <ClockFace
138
+ value={value}
139
+ onChange={onChange}
140
+ mode={mode}
141
+ onModeChange={handleModeChange}
142
+ is24Hour={is24Hour}
143
+ />
144
+
145
+ {/* Actions */}
146
+ <View style={styles.actionsContainer}>
147
+ <Pressable style={styles.keyboardButton}>
148
+ <Text style={styles.keyboardButtonText}>⌨️</Text>
149
+ </Pressable>
150
+
151
+ <View style={styles.actionButtonsContainer}>
152
+ {onCancel && (
153
+ <Pressable style={styles.actionButton} onPress={onCancel}>
154
+ <Text style={styles.actionButtonText}>Cancel</Text>
155
+ </Pressable>
156
+ )}
157
+ {onConfirm && (
158
+ <Pressable style={styles.actionButton} onPress={onConfirm}>
159
+ <Text style={styles.actionButtonText}>OK</Text>
160
+ </Pressable>
161
+ )}
162
+ </View>
163
+ </View>
164
+ </View>
165
+ );
166
+ },
167
+ );
168
+
169
+ MaterialTimePicker.displayName = 'MaterialTimePicker';
@@ -0,0 +1,80 @@
1
+ import React, { Suspense, useCallback } from 'react';
2
+ import { ActivityIndicator, View } from 'react-native';
3
+ import type { TimePickerProps, TimeValue } from '../../types/timepicker';
4
+ import { MaterialTimePicker } from './MaterialTimePicker';
5
+ import { styles } from './TimePicker.styles';
6
+
7
+ interface ExpoTimePickerProps {
8
+ value: TimeValue;
9
+ onChange: (time: TimeValue) => void;
10
+ is24Hour?: boolean;
11
+ }
12
+
13
+ // @ts-expect-error - React.lazy fallback returns null when @expo/ui unavailable
14
+ const ExpoTimePicker = React.lazy(async () => {
15
+ try {
16
+ // @ts-expect-error - @expo/ui types not available
17
+ const { DateTimePicker } = await import('@expo/ui/jetpack-compose');
18
+
19
+ return {
20
+ default: ({ value, onChange, is24Hour }: ExpoTimePickerProps) => {
21
+ // Create a Date object for the picker
22
+ const date = new Date();
23
+ date.setHours(value.hours, value.minutes, 0, 0);
24
+
25
+ const handleChange = (newDate: Date) => {
26
+ onChange({
27
+ hours: newDate.getHours(),
28
+ minutes: newDate.getMinutes(),
29
+ });
30
+ };
31
+
32
+ return (
33
+ <DateTimePicker
34
+ onDateSelected={handleChange}
35
+ displayedComponents="hourAndMinute"
36
+ initialDate={date.toISOString()}
37
+ variant="picker"
38
+ />
39
+ );
40
+ },
41
+ };
42
+ } catch {
43
+ // Fallback when @expo/ui is not available
44
+ return { default: (_props: ExpoTimePickerProps) => null };
45
+ }
46
+ });
47
+
48
+ export const TimePicker: React.FC<TimePickerProps> = ({
49
+ value,
50
+ onChange,
51
+ theme = 'native',
52
+ is24Hour = false,
53
+ }) => {
54
+ const handleTimeChange = useCallback(
55
+ (time: TimeValue) => {
56
+ onChange(time);
57
+ },
58
+ [onChange],
59
+ );
60
+
61
+ // Use native Android picker for 'native' theme
62
+ if (theme === 'native') {
63
+ return (
64
+ <View style={styles.container}>
65
+ <Suspense fallback={<ActivityIndicator />}>
66
+ <ExpoTimePicker
67
+ value={value}
68
+ onChange={handleTimeChange}
69
+ is24Hour={is24Hour}
70
+ />
71
+ </Suspense>
72
+ </View>
73
+ );
74
+ }
75
+
76
+ // Use Material-style picker for 'android' or 'ios' themes
77
+ return (
78
+ <MaterialTimePicker value={value} onChange={onChange} is24Hour={is24Hour} />
79
+ );
80
+ };
@@ -0,0 +1,88 @@
1
+ import React, { Suspense, useCallback } from 'react';
2
+ import { ActivityIndicator, View } from 'react-native';
3
+ import type { TimePickerProps, TimeValue } from '../../types/timepicker';
4
+ import { styles } from './TimePicker.styles';
5
+ import { TimeWheelPicker } from './TimeWheelPicker';
6
+
7
+ interface ExpoTimePickerProps {
8
+ value: TimeValue;
9
+ onChange: (time: TimeValue) => void;
10
+ is24Hour?: boolean;
11
+ }
12
+
13
+ // @ts-expect-error - React.lazy fallback returns null when @expo/ui unavailable
14
+ const ExpoTimePicker = React.lazy(async () => {
15
+ try {
16
+ // @ts-expect-error - @expo/ui types not available
17
+ const { DateTimePicker, Host } = await import('@expo/ui/swift-ui');
18
+
19
+ return {
20
+ default: ({ value, onChange, is24Hour }: ExpoTimePickerProps) => {
21
+ // Create a Date object for the picker
22
+ const date = new Date();
23
+ date.setHours(value.hours, value.minutes, 0, 0);
24
+
25
+ const handleChange = (newDate: Date) => {
26
+ onChange({
27
+ hours: newDate.getHours(),
28
+ minutes: newDate.getMinutes(),
29
+ });
30
+ };
31
+
32
+ return (
33
+ <Host matchContents>
34
+ <DateTimePicker
35
+ onDateSelected={handleChange}
36
+ displayedComponents="hourAndMinute"
37
+ initialDate={date.toISOString()}
38
+ variant="wheel"
39
+ />
40
+ </Host>
41
+ );
42
+ },
43
+ };
44
+ } catch {
45
+ // Fallback when @expo/ui is not available
46
+ return { default: (_props: ExpoTimePickerProps) => null };
47
+ }
48
+ });
49
+
50
+ export const TimePicker: React.FC<TimePickerProps> = ({
51
+ value,
52
+ onChange,
53
+ theme = 'native',
54
+ is24Hour = false,
55
+ minuteInterval = 1,
56
+ }) => {
57
+ const handleTimeChange = useCallback(
58
+ (time: TimeValue) => {
59
+ onChange(time);
60
+ },
61
+ [onChange],
62
+ );
63
+
64
+ // Use native iOS picker for 'native' or 'ios' theme
65
+ if (theme === 'native' || theme === 'ios') {
66
+ return (
67
+ <View style={styles.container}>
68
+ <Suspense fallback={<ActivityIndicator />}>
69
+ <ExpoTimePicker
70
+ value={value}
71
+ onChange={handleTimeChange}
72
+ is24Hour={is24Hour}
73
+ />
74
+ </Suspense>
75
+ </View>
76
+ );
77
+ }
78
+
79
+ // Fallback to wheel picker for other themes
80
+ return (
81
+ <TimeWheelPicker
82
+ value={value}
83
+ onChange={onChange}
84
+ is24Hour={is24Hour}
85
+ minuteInterval={minuteInterval}
86
+ />
87
+ );
88
+ };