@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,209 @@
1
+ import { StyleSheet } from 'react-native-unistyles';
2
+
3
+ const ITEM_HEIGHT = 44;
4
+ const VISIBLE_ITEMS = 5;
5
+ const CONTAINER_HEIGHT = ITEM_HEIGHT * VISIBLE_ITEMS;
6
+
7
+ export const styles = StyleSheet.create((theme) => ({
8
+ // Container styles
9
+ container: {
10
+ backgroundColor: theme.colors.background.elevated,
11
+ borderRadius: theme.radii.card,
12
+ padding: theme.spacing(4),
13
+ },
14
+
15
+ // ============ Wheel Picker Styles (iOS) ============
16
+ wheelContainer: {
17
+ flexDirection: 'row',
18
+ height: CONTAINER_HEIGHT,
19
+ backgroundColor: theme.colors.background.elevated,
20
+ // @ts-ignore - web-only property
21
+ backdropFilter: 'blur(20px)',
22
+ borderRadius: theme.radii.card,
23
+ overflow: 'hidden',
24
+ alignItems: 'center',
25
+ paddingHorizontal: theme.spacing(2),
26
+ },
27
+
28
+ wheelColumn: {
29
+ flex: 1,
30
+ height: CONTAINER_HEIGHT,
31
+ overflow: 'hidden',
32
+ position: 'relative',
33
+ },
34
+
35
+ wheelSelectionIndicator: {
36
+ position: 'absolute',
37
+ top: ITEM_HEIGHT * 2,
38
+ left: 4,
39
+ right: 4,
40
+ height: ITEM_HEIGHT,
41
+ backgroundColor: theme.colors.datepicker.wheelHighlight,
42
+ borderRadius: theme.radii.button,
43
+ zIndex: 0,
44
+ },
45
+
46
+ wheelItemsContainer: {
47
+ zIndex: 1,
48
+ },
49
+
50
+ wheelItem: {
51
+ height: ITEM_HEIGHT,
52
+ justifyContent: 'center',
53
+ alignItems: 'center',
54
+ },
55
+
56
+ wheelItemText: {
57
+ fontSize: 22,
58
+ color: theme.colors.foreground.default,
59
+ fontWeight: '400',
60
+ opacity: 0.5,
61
+ },
62
+
63
+ wheelItemTextSelected: {
64
+ fontWeight: '500',
65
+ opacity: 1,
66
+ },
67
+
68
+ wheelSeparator: {
69
+ fontSize: 22,
70
+ fontWeight: '500',
71
+ color: theme.colors.foreground.default,
72
+ paddingHorizontal: theme.spacing(1),
73
+ },
74
+
75
+ // ============ Material Time Picker Styles ============
76
+ materialContainer: {
77
+ backgroundColor: theme.colors.background.subtle,
78
+ borderRadius: 28,
79
+ padding: theme.spacing(6),
80
+ width: 320,
81
+ },
82
+
83
+ materialHeader: {
84
+ fontSize: 12,
85
+ fontWeight: '500',
86
+ color: theme.colors.foreground.muted,
87
+ letterSpacing: 0.5,
88
+ marginBottom: theme.spacing(5),
89
+ textTransform: 'uppercase',
90
+ },
91
+
92
+ // Time input display
93
+ timeInputContainer: {
94
+ flexDirection: 'row',
95
+ alignItems: 'center',
96
+ justifyContent: 'center',
97
+ gap: theme.spacing(3),
98
+ marginBottom: theme.spacing(6),
99
+ },
100
+
101
+ timeFieldsContainer: {
102
+ flexDirection: 'row',
103
+ alignItems: 'center',
104
+ },
105
+
106
+ timeField: {
107
+ backgroundColor: theme.colors.background.elevated,
108
+ borderRadius: theme.radii.button,
109
+ paddingVertical: theme.spacing(3),
110
+ paddingHorizontal: theme.spacing(2),
111
+ minWidth: 80,
112
+ alignItems: 'center',
113
+ },
114
+
115
+ timeFieldActive: {
116
+ backgroundColor: theme.colors.primary.default,
117
+ },
118
+
119
+ timeFieldText: {
120
+ fontSize: 48,
121
+ fontWeight: '400',
122
+ color: theme.colors.foreground.default,
123
+ fontVariant: ['tabular-nums'],
124
+ },
125
+
126
+ timeFieldTextActive: {
127
+ color: '#FFFFFF',
128
+ },
129
+
130
+ timeSeparator: {
131
+ fontSize: 48,
132
+ fontWeight: '400',
133
+ color: theme.colors.foreground.default,
134
+ marginHorizontal: theme.spacing(1),
135
+ },
136
+
137
+ // Period toggle (AM/PM)
138
+ periodToggleContainer: {
139
+ borderWidth: 1,
140
+ borderColor: theme.colors.border.strong,
141
+ borderRadius: theme.radii.button,
142
+ overflow: 'hidden',
143
+ },
144
+
145
+ periodButton: {
146
+ paddingVertical: theme.spacing(2),
147
+ paddingHorizontal: theme.spacing(3),
148
+ backgroundColor: 'transparent',
149
+ },
150
+
151
+ periodButtonActive: {
152
+ backgroundColor: `${theme.colors.primary.default}20`, // 20% opacity
153
+ },
154
+
155
+ periodButtonTop: {
156
+ borderBottomWidth: 1,
157
+ borderBottomColor: theme.colors.border.strong,
158
+ },
159
+
160
+ periodButtonText: {
161
+ fontSize: 14,
162
+ fontWeight: '500',
163
+ color: theme.colors.foreground.muted,
164
+ },
165
+
166
+ periodButtonTextActive: {
167
+ color: theme.colors.primary.default,
168
+ },
169
+
170
+ // ============ Clock Face Styles ============
171
+ clockContainer: {
172
+ alignItems: 'center',
173
+ justifyContent: 'center',
174
+ marginBottom: theme.spacing(5),
175
+ },
176
+
177
+ // ============ Action Buttons ============
178
+ actionsContainer: {
179
+ flexDirection: 'row',
180
+ justifyContent: 'space-between',
181
+ alignItems: 'center',
182
+ },
183
+
184
+ keyboardButton: {
185
+ padding: theme.spacing(2),
186
+ },
187
+
188
+ keyboardButtonText: {
189
+ fontSize: 20,
190
+ color: theme.colors.foreground.muted,
191
+ },
192
+
193
+ actionButtonsContainer: {
194
+ flexDirection: 'row',
195
+ gap: theme.spacing(2),
196
+ },
197
+
198
+ actionButton: {
199
+ paddingVertical: theme.spacing(2.5),
200
+ paddingHorizontal: theme.spacing(4),
201
+ borderRadius: 20,
202
+ },
203
+
204
+ actionButtonText: {
205
+ fontSize: 14,
206
+ fontWeight: '500',
207
+ color: theme.colors.primary.default,
208
+ },
209
+ }));
@@ -0,0 +1,35 @@
1
+ import type React from 'react';
2
+ import type { TimePickerProps } from '../../types/timepicker';
3
+
4
+ // Re-export types for convenience
5
+ export type { TimePickerProps, TimeValue } from '../../types/timepicker';
6
+
7
+ /**
8
+ * TimePicker Component
9
+ *
10
+ * A cross-platform time picker with support for iOS wheel style and Material clock face.
11
+ *
12
+ * Platform-specific implementations are handled by Metro's file resolution:
13
+ * - iOS: Uses @expo/ui DateTimePicker in time mode, falls back to TimeWheelPicker
14
+ * - Android: Uses @expo/ui DateTimePicker or MaterialTimePicker with ClockFace
15
+ * - Web: TimeWheelPicker for iOS theme, MaterialTimePicker for Android theme
16
+ *
17
+ * @example
18
+ * ```tsx
19
+ * import { TimePicker } from '@dreamstack-us/kaal';
20
+ *
21
+ * const [time, setTime] = useState({ hours: 9, minutes: 30 });
22
+ *
23
+ * <TimePicker
24
+ * value={time}
25
+ * onChange={setTime}
26
+ * theme="ios"
27
+ * is24Hour={false}
28
+ * />
29
+ * ```
30
+ */
31
+ export const TimePicker: React.FC<TimePickerProps> = (_props) => {
32
+ // This file serves as type definition and fallback
33
+ // Metro resolves platform-specific files (.ios.tsx, .android.tsx, .web.tsx)
34
+ return null;
35
+ };
@@ -0,0 +1,35 @@
1
+ import type React from 'react';
2
+ import type { TimePickerProps } from '../../types/timepicker';
3
+ import { MaterialTimePicker } from './MaterialTimePicker';
4
+ import { TimeWheelPicker } from './TimeWheelPicker';
5
+
6
+ /**
7
+ * Web implementation of TimePicker
8
+ *
9
+ * - theme="ios": Uses TimeWheelPicker (iOS-style wheel)
10
+ * - theme="android" or "native": Uses MaterialTimePicker (Material clock face)
11
+ */
12
+ export const TimePicker: React.FC<TimePickerProps> = ({
13
+ value,
14
+ onChange,
15
+ theme = 'ios',
16
+ is24Hour = false,
17
+ minuteInterval = 1,
18
+ }) => {
19
+ // iOS theme uses wheel picker
20
+ if (theme === 'ios') {
21
+ return (
22
+ <TimeWheelPicker
23
+ value={value}
24
+ onChange={onChange}
25
+ is24Hour={is24Hour}
26
+ minuteInterval={minuteInterval}
27
+ />
28
+ );
29
+ }
30
+
31
+ // Android/native theme uses Material clock face
32
+ return (
33
+ <MaterialTimePicker value={value} onChange={onChange} is24Hour={is24Hour} />
34
+ );
35
+ };
@@ -0,0 +1,211 @@
1
+ import type React from 'react';
2
+ import { memo, useCallback, useMemo } from 'react';
3
+ import { Text, View } from 'react-native';
4
+ import { Gesture, GestureDetector } from 'react-native-gesture-handler';
5
+ import Animated, {
6
+ runOnJS,
7
+ useAnimatedStyle,
8
+ useSharedValue,
9
+ withSpring,
10
+ } from 'react-native-reanimated';
11
+ import { to12Hour, to24Hour } from '../../hooks/useTimePicker';
12
+ import type { TimePeriod, TimeValue } from '../../types/timepicker';
13
+ import { styles } from './TimePicker.styles';
14
+
15
+ const ITEM_HEIGHT = 44;
16
+ const VISIBLE_ITEMS = 5;
17
+ const CONTAINER_HEIGHT = ITEM_HEIGHT * VISIBLE_ITEMS;
18
+
19
+ interface WheelColumnProps {
20
+ items: { value: number | string; label: string }[];
21
+ selectedIndex: number;
22
+ onSelect: (index: number) => void;
23
+ }
24
+
25
+ const WheelColumn: React.FC<WheelColumnProps> = memo(
26
+ ({ items, selectedIndex, onSelect }) => {
27
+ const translateY = useSharedValue(-selectedIndex * ITEM_HEIGHT);
28
+
29
+ const handleSelect = useCallback(
30
+ (index: number) => {
31
+ onSelect(index);
32
+ },
33
+ [onSelect],
34
+ );
35
+
36
+ const panGesture = useMemo(
37
+ () =>
38
+ Gesture.Pan()
39
+ .onUpdate((e) => {
40
+ 'worklet';
41
+ translateY.value = e.translationY + -selectedIndex * ITEM_HEIGHT;
42
+ })
43
+ .onEnd((e) => {
44
+ 'worklet';
45
+ const velocity = e.velocityY;
46
+ const targetIndex = Math.round(-translateY.value / ITEM_HEIGHT);
47
+ const clampedIndex = Math.max(
48
+ 0,
49
+ Math.min(items.length - 1, targetIndex),
50
+ );
51
+
52
+ translateY.value = withSpring(-clampedIndex * ITEM_HEIGHT, {
53
+ velocity,
54
+ damping: 20,
55
+ stiffness: 200,
56
+ });
57
+
58
+ runOnJS(handleSelect)(clampedIndex);
59
+ }),
60
+ [selectedIndex, items.length, handleSelect, translateY],
61
+ );
62
+
63
+ const animatedStyle = useAnimatedStyle(() => ({
64
+ transform: [{ translateY: translateY.value }],
65
+ }));
66
+
67
+ return (
68
+ <View style={styles.wheelColumn}>
69
+ <View style={styles.wheelSelectionIndicator} />
70
+
71
+ <GestureDetector gesture={panGesture}>
72
+ <Animated.View style={[styles.wheelItemsContainer, animatedStyle]}>
73
+ <View style={{ height: ITEM_HEIGHT * 2 }} />
74
+
75
+ {items.map((item, index) => {
76
+ const isSelected = index === selectedIndex;
77
+ return (
78
+ <View key={`${item.value}`} style={styles.wheelItem}>
79
+ <Text
80
+ style={[
81
+ styles.wheelItemText,
82
+ isSelected && styles.wheelItemTextSelected,
83
+ ]}
84
+ >
85
+ {item.label}
86
+ </Text>
87
+ </View>
88
+ );
89
+ })}
90
+
91
+ <View style={{ height: ITEM_HEIGHT * 2 }} />
92
+ </Animated.View>
93
+ </GestureDetector>
94
+ </View>
95
+ );
96
+ },
97
+ );
98
+
99
+ WheelColumn.displayName = 'WheelColumn';
100
+
101
+ interface TimeWheelPickerProps {
102
+ value: TimeValue;
103
+ onChange: (time: TimeValue) => void;
104
+ is24Hour?: boolean;
105
+ minuteInterval?: number;
106
+ }
107
+
108
+ export const TimeWheelPicker: React.FC<TimeWheelPickerProps> = memo(
109
+ ({ value, onChange, is24Hour = false, minuteInterval = 1 }) => {
110
+ // Generate hour items
111
+ const hourItems = useMemo(() => {
112
+ if (is24Hour) {
113
+ return Array.from({ length: 24 }, (_, i) => ({
114
+ value: i,
115
+ label: i.toString().padStart(2, '0'),
116
+ }));
117
+ }
118
+ // 12-hour format: 1-12
119
+ return Array.from({ length: 12 }, (_, i) => ({
120
+ value: i + 1,
121
+ label: (i + 1).toString().padStart(2, '0'),
122
+ }));
123
+ }, [is24Hour]);
124
+
125
+ // Generate minute items based on interval
126
+ const minuteItems = useMemo(() => {
127
+ const items: { value: number; label: string }[] = [];
128
+ for (let i = 0; i < 60; i += minuteInterval) {
129
+ items.push({
130
+ value: i,
131
+ label: i.toString().padStart(2, '0'),
132
+ });
133
+ }
134
+ return items;
135
+ }, [minuteInterval]);
136
+
137
+ // Period items for 12-hour format
138
+ const periodItems = useMemo(
139
+ () => [
140
+ { value: 'AM' as const, label: 'AM' },
141
+ { value: 'PM' as const, label: 'PM' },
142
+ ],
143
+ [],
144
+ );
145
+
146
+ // Calculate selected indices
147
+ const { hour: hour12, period } = to12Hour(value.hours);
148
+ const hourIndex = is24Hour ? value.hours : hour12 - 1; // 12-hour is 1-indexed
149
+ const minuteIndex = Math.floor(value.minutes / minuteInterval);
150
+ const periodIndex = period === 'AM' ? 0 : 1;
151
+
152
+ const handleHourChange = useCallback(
153
+ (index: number) => {
154
+ let newHours: number;
155
+ if (is24Hour) {
156
+ newHours = index;
157
+ } else {
158
+ const hour12 = index + 1; // Convert from 0-indexed to 1-12
159
+ newHours = to24Hour(hour12, period);
160
+ }
161
+ onChange({ hours: newHours, minutes: value.minutes });
162
+ },
163
+ [is24Hour, period, value.minutes, onChange],
164
+ );
165
+
166
+ const handleMinuteChange = useCallback(
167
+ (index: number) => {
168
+ const newMinutes = index * minuteInterval;
169
+ onChange({ hours: value.hours, minutes: newMinutes });
170
+ },
171
+ [value.hours, minuteInterval, onChange],
172
+ );
173
+
174
+ const handlePeriodChange = useCallback(
175
+ (index: number) => {
176
+ const newPeriod: TimePeriod = index === 0 ? 'AM' : 'PM';
177
+ const newHours = to24Hour(hour12, newPeriod);
178
+ onChange({ hours: newHours, minutes: value.minutes });
179
+ },
180
+ [hour12, value.minutes, onChange],
181
+ );
182
+
183
+ return (
184
+ <View style={styles.wheelContainer}>
185
+ <WheelColumn
186
+ items={hourItems}
187
+ selectedIndex={hourIndex}
188
+ onSelect={handleHourChange}
189
+ />
190
+
191
+ <Text style={styles.wheelSeparator}>:</Text>
192
+
193
+ <WheelColumn
194
+ items={minuteItems}
195
+ selectedIndex={minuteIndex}
196
+ onSelect={handleMinuteChange}
197
+ />
198
+
199
+ {!is24Hour && (
200
+ <WheelColumn
201
+ items={periodItems}
202
+ selectedIndex={periodIndex}
203
+ onSelect={handlePeriodChange}
204
+ />
205
+ )}
206
+ </View>
207
+ );
208
+ },
209
+ );
210
+
211
+ TimeWheelPicker.displayName = 'TimeWheelPicker';
@@ -0,0 +1,5 @@
1
+ export { TimePicker } from './TimePicker';
2
+ export type { TimePickerProps, TimeValue } from './TimePicker';
3
+ export { TimeWheelPicker } from './TimeWheelPicker';
4
+ export { ClockFace } from './ClockFace';
5
+ export { MaterialTimePicker } from './MaterialTimePicker';
@@ -0,0 +1,39 @@
1
+ import { StyleSheet } from 'react-native-unistyles';
2
+
3
+ export const styles = StyleSheet.create((theme) => ({
4
+ container: {
5
+ flexDirection: 'row',
6
+ height: 220,
7
+ backgroundColor: theme.colors.background.elevated,
8
+ borderRadius: theme.radii.card,
9
+ overflow: 'hidden',
10
+ },
11
+ column: {
12
+ flex: 1,
13
+ height: 220,
14
+ overflow: 'hidden',
15
+ },
16
+ selectionHighlight: {
17
+ position: 'absolute',
18
+ top: 88,
19
+ left: 4,
20
+ right: 4,
21
+ height: 44,
22
+ backgroundColor: theme.colors.datepicker.wheelHighlight,
23
+ borderRadius: theme.radii.button,
24
+ zIndex: 0,
25
+ },
26
+ itemsContainer: {
27
+ zIndex: 1,
28
+ },
29
+ item: {
30
+ height: 44,
31
+ justifyContent: 'center',
32
+ alignItems: 'center',
33
+ },
34
+ itemText: {
35
+ fontSize: 21,
36
+ color: theme.colors.foreground.default,
37
+ fontWeight: '400',
38
+ },
39
+ }));
@@ -0,0 +1,2 @@
1
+ // Native platforms don't use custom wheel picker - they use native pickers
2
+ export { WheelPicker } from './WheelPicker.web';