@dreamstack-us/kaal 0.0.1 → 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +165 -0
- package/lib/module/components/CalendarGrid/CalendarGrid.js +125 -29
- package/lib/module/components/CalendarGrid/CalendarGrid.js.map +1 -1
- package/lib/module/components/CalendarGrid/CalendarGrid.styles.js +22 -17
- package/lib/module/components/CalendarGrid/CalendarGrid.styles.js.map +1 -1
- package/lib/module/components/CalendarGrid/CalendarGrid.web.js +265 -0
- package/lib/module/components/CalendarGrid/CalendarGrid.web.js.map +1 -0
- package/lib/module/components/CalendarGrid/DayCell.js +77 -53
- package/lib/module/components/CalendarGrid/DayCell.js.map +1 -1
- package/lib/module/components/CalendarGrid/DayCell.web.js +124 -0
- package/lib/module/components/CalendarGrid/DayCell.web.js.map +1 -0
- package/lib/module/components/CalendarGrid/index.js +1 -1
- package/lib/module/components/CalendarGrid/index.js.map +1 -1
- package/lib/module/components/DatePicker/DatePicker.android.js +53 -21
- package/lib/module/components/DatePicker/DatePicker.android.js.map +1 -1
- package/lib/module/components/DatePicker/DatePicker.ios.js +55 -23
- package/lib/module/components/DatePicker/DatePicker.ios.js.map +1 -1
- package/lib/module/components/DatePicker/DatePicker.js.map +1 -1
- package/lib/module/components/DatePicker/DatePicker.styles.js +19 -20
- package/lib/module/components/DatePicker/DatePicker.styles.js.map +1 -1
- package/lib/module/components/DatePicker/DatePicker.web.js +59 -21
- package/lib/module/components/DatePicker/DatePicker.web.js.map +1 -1
- package/lib/module/components/TimePicker/ClockFace.js +27 -7
- package/lib/module/components/TimePicker/ClockFace.js.map +1 -1
- package/lib/module/components/TimePicker/ClockFace.web.js +253 -0
- package/lib/module/components/TimePicker/ClockFace.web.js.map +1 -0
- package/lib/module/components/TimePicker/MaterialTimePicker.js +68 -16
- package/lib/module/components/TimePicker/MaterialTimePicker.js.map +1 -1
- package/lib/module/components/TimePicker/MaterialTimePicker.web.js +231 -0
- package/lib/module/components/TimePicker/MaterialTimePicker.web.js.map +1 -0
- package/lib/module/components/TimePicker/TimePicker.android.js +13 -6
- package/lib/module/components/TimePicker/TimePicker.android.js.map +1 -1
- package/lib/module/components/TimePicker/TimePicker.ios.js +14 -7
- package/lib/module/components/TimePicker/TimePicker.ios.js.map +1 -1
- package/lib/module/components/TimePicker/TimePicker.styles.js +53 -45
- package/lib/module/components/TimePicker/TimePicker.styles.js.map +1 -1
- package/lib/module/components/TimePicker/TimePicker.web.js +24 -12
- package/lib/module/components/TimePicker/TimePicker.web.js.map +1 -1
- package/lib/module/components/TimePicker/TimeWheelPicker.js +45 -10
- package/lib/module/components/TimePicker/TimeWheelPicker.js.map +1 -1
- package/lib/module/components/TimePicker/TimeWheelPicker.web.js +339 -0
- package/lib/module/components/TimePicker/TimeWheelPicker.web.js.map +1 -0
- package/lib/module/components/TimePicker/index.js +3 -3
- package/lib/module/components/TimePicker/index.js.map +1 -1
- package/lib/module/components/WheelPicker/WheelPicker.js +21 -2
- package/lib/module/components/WheelPicker/WheelPicker.js.map +1 -1
- package/lib/module/components/WheelPicker/WheelPicker.styles.js +13 -8
- package/lib/module/components/WheelPicker/WheelPicker.styles.js.map +1 -1
- package/lib/module/components/WheelPicker/WheelPicker.web.js +146 -57
- package/lib/module/components/WheelPicker/WheelPicker.web.js.map +1 -1
- package/lib/module/context/ThemeOverrideContext.js +34 -0
- package/lib/module/context/ThemeOverrideContext.js.map +1 -0
- package/lib/module/index.js +3 -0
- package/lib/module/index.js.map +1 -1
- package/lib/module/utils/validation.js +74 -34
- package/lib/module/utils/validation.js.map +1 -1
- package/lib/typescript/components/CalendarGrid/CalendarGrid.d.ts +24 -3
- package/lib/typescript/components/CalendarGrid/CalendarGrid.d.ts.map +1 -1
- package/lib/typescript/components/CalendarGrid/CalendarGrid.styles.d.ts +12 -10
- package/lib/typescript/components/CalendarGrid/CalendarGrid.styles.d.ts.map +1 -1
- package/lib/typescript/components/CalendarGrid/CalendarGrid.web.d.ts +33 -0
- package/lib/typescript/components/CalendarGrid/CalendarGrid.web.d.ts.map +1 -0
- package/lib/typescript/components/CalendarGrid/DayCell.d.ts +3 -0
- package/lib/typescript/components/CalendarGrid/DayCell.d.ts.map +1 -1
- package/lib/typescript/components/CalendarGrid/DayCell.web.d.ts +15 -0
- package/lib/typescript/components/CalendarGrid/DayCell.web.d.ts.map +1 -0
- package/lib/typescript/components/DatePicker/DatePicker.android.d.ts.map +1 -1
- package/lib/typescript/components/DatePicker/DatePicker.d.ts +27 -4
- package/lib/typescript/components/DatePicker/DatePicker.d.ts.map +1 -1
- package/lib/typescript/components/DatePicker/DatePicker.ios.d.ts.map +1 -1
- package/lib/typescript/components/DatePicker/DatePicker.styles.d.ts +12 -13
- package/lib/typescript/components/DatePicker/DatePicker.styles.d.ts.map +1 -1
- package/lib/typescript/components/DatePicker/DatePicker.web.d.ts.map +1 -1
- package/lib/typescript/components/TimePicker/ClockFace.d.ts.map +1 -1
- package/lib/typescript/components/TimePicker/ClockFace.web.d.ts +12 -0
- package/lib/typescript/components/TimePicker/ClockFace.web.d.ts.map +1 -0
- package/lib/typescript/components/TimePicker/MaterialTimePicker.d.ts.map +1 -1
- package/lib/typescript/components/TimePicker/MaterialTimePicker.web.d.ts +12 -0
- package/lib/typescript/components/TimePicker/MaterialTimePicker.web.d.ts.map +1 -0
- package/lib/typescript/components/TimePicker/TimePicker.android.d.ts.map +1 -1
- package/lib/typescript/components/TimePicker/TimePicker.ios.d.ts.map +1 -1
- package/lib/typescript/components/TimePicker/TimePicker.styles.d.ts +29 -25
- package/lib/typescript/components/TimePicker/TimePicker.styles.d.ts.map +1 -1
- package/lib/typescript/components/TimePicker/TimePicker.web.d.ts.map +1 -1
- package/lib/typescript/components/TimePicker/TimeWheelPicker.d.ts.map +1 -1
- package/lib/typescript/components/TimePicker/TimeWheelPicker.web.d.ts +11 -0
- package/lib/typescript/components/TimePicker/TimeWheelPicker.web.d.ts.map +1 -0
- package/lib/typescript/components/WheelPicker/WheelPicker.d.ts +14 -1
- package/lib/typescript/components/WheelPicker/WheelPicker.d.ts.map +1 -1
- package/lib/typescript/components/WheelPicker/WheelPicker.styles.d.ts +9 -7
- package/lib/typescript/components/WheelPicker/WheelPicker.styles.d.ts.map +1 -1
- package/lib/typescript/components/WheelPicker/WheelPicker.web.d.ts.map +1 -1
- package/lib/typescript/context/ThemeOverrideContext.d.ts +23 -0
- package/lib/typescript/context/ThemeOverrideContext.d.ts.map +1 -0
- package/lib/typescript/index.d.ts +4 -2
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/types/datepicker.d.ts +78 -3
- package/lib/typescript/types/datepicker.d.ts.map +1 -1
- package/lib/typescript/types/timepicker.d.ts +62 -0
- package/lib/typescript/types/timepicker.d.ts.map +1 -1
- package/lib/typescript/utils/validation.d.ts +47 -27
- package/lib/typescript/utils/validation.d.ts.map +1 -1
- package/package.json +8 -8
- package/src/components/CalendarGrid/CalendarGrid.styles.ts +21 -17
- package/src/components/CalendarGrid/CalendarGrid.tsx +265 -85
- package/src/components/CalendarGrid/CalendarGrid.web.tsx +396 -0
- package/src/components/CalendarGrid/DayCell.tsx +122 -61
- package/src/components/CalendarGrid/DayCell.web.tsx +171 -0
- package/src/components/DatePicker/DatePicker.android.tsx +48 -24
- package/src/components/DatePicker/DatePicker.ios.tsx +51 -27
- package/src/components/DatePicker/DatePicker.styles.ts +18 -22
- package/src/components/DatePicker/DatePicker.tsx +35 -4
- package/src/components/DatePicker/DatePicker.web.tsx +55 -23
- package/src/components/TimePicker/ClockFace.tsx +34 -8
- package/src/components/TimePicker/ClockFace.web.tsx +303 -0
- package/src/components/TimePicker/MaterialTimePicker.tsx +144 -13
- package/src/components/TimePicker/MaterialTimePicker.web.tsx +271 -0
- package/src/components/TimePicker/TimePicker.android.tsx +9 -1
- package/src/components/TimePicker/TimePicker.ios.tsx +10 -6
- package/src/components/TimePicker/TimePicker.styles.ts +52 -45
- package/src/components/TimePicker/TimePicker.web.tsx +17 -7
- package/src/components/TimePicker/TimeWheelPicker.tsx +60 -6
- package/src/components/TimePicker/TimeWheelPicker.web.tsx +401 -0
- package/src/components/WheelPicker/WheelPicker.styles.ts +12 -8
- package/src/components/WheelPicker/WheelPicker.tsx +24 -2
- package/src/components/WheelPicker/WheelPicker.web.tsx +153 -57
- package/src/context/ThemeOverrideContext.tsx +38 -0
- package/src/index.ts +13 -0
- package/src/types/datepicker.ts +87 -3
- package/src/types/timepicker.ts +74 -0
- package/src/utils/validation.ts +111 -55
- package/lib/module/unistyles.js +0 -9
- package/lib/module/unistyles.js.map +0 -1
- package/lib/typescript/unistyles.d.ts +0 -3
- package/lib/typescript/unistyles.d.ts.map +0 -1
- package/src/unistyles.ts +0 -6
|
@@ -8,22 +8,33 @@ import Animated, {
|
|
|
8
8
|
useSharedValue,
|
|
9
9
|
withSpring,
|
|
10
10
|
} from 'react-native-reanimated';
|
|
11
|
+
import { useTimePickerOverrides } from '../../context/ThemeOverrideContext';
|
|
11
12
|
import { to12Hour, to24Hour } from '../../hooks/useTimePicker';
|
|
12
13
|
import type { TimePeriod, TimeValue } from '../../types/timepicker';
|
|
13
|
-
import { styles } from './TimePicker.styles';
|
|
14
|
+
import { WHEEL_ITEM_HEIGHT, styles } from './TimePicker.styles';
|
|
14
15
|
|
|
15
|
-
const ITEM_HEIGHT =
|
|
16
|
+
const ITEM_HEIGHT = WHEEL_ITEM_HEIGHT;
|
|
16
17
|
const VISIBLE_ITEMS = 5;
|
|
17
18
|
const CONTAINER_HEIGHT = ITEM_HEIGHT * VISIBLE_ITEMS;
|
|
18
19
|
|
|
20
|
+
// Default colors (dark theme)
|
|
21
|
+
const DEFAULT_COLORS = {
|
|
22
|
+
containerBackground: '#2C2C2E',
|
|
23
|
+
selectionHighlight: 'rgba(120, 120, 128, 0.24)',
|
|
24
|
+
textColor: '#FFFFFF',
|
|
25
|
+
separatorColor: '#FFFFFF',
|
|
26
|
+
};
|
|
27
|
+
|
|
19
28
|
interface WheelColumnProps {
|
|
20
29
|
items: { value: number | string; label: string }[];
|
|
21
30
|
selectedIndex: number;
|
|
22
31
|
onSelect: (index: number) => void;
|
|
32
|
+
textColor: string;
|
|
33
|
+
highlightColor: string;
|
|
23
34
|
}
|
|
24
35
|
|
|
25
36
|
const WheelColumn: React.FC<WheelColumnProps> = memo(
|
|
26
|
-
({ items, selectedIndex, onSelect }) => {
|
|
37
|
+
({ items, selectedIndex, onSelect, textColor, highlightColor }) => {
|
|
27
38
|
const translateY = useSharedValue(-selectedIndex * ITEM_HEIGHT);
|
|
28
39
|
|
|
29
40
|
const handleSelect = useCallback(
|
|
@@ -66,7 +77,12 @@ const WheelColumn: React.FC<WheelColumnProps> = memo(
|
|
|
66
77
|
|
|
67
78
|
return (
|
|
68
79
|
<View style={styles.wheelColumn}>
|
|
69
|
-
<View
|
|
80
|
+
<View
|
|
81
|
+
style={[
|
|
82
|
+
styles.wheelSelectionIndicator,
|
|
83
|
+
{ backgroundColor: highlightColor },
|
|
84
|
+
]}
|
|
85
|
+
/>
|
|
70
86
|
|
|
71
87
|
<GestureDetector gesture={panGesture}>
|
|
72
88
|
<Animated.View style={[styles.wheelItemsContainer, animatedStyle]}>
|
|
@@ -79,6 +95,7 @@ const WheelColumn: React.FC<WheelColumnProps> = memo(
|
|
|
79
95
|
<Text
|
|
80
96
|
style={[
|
|
81
97
|
styles.wheelItemText,
|
|
98
|
+
{ color: textColor },
|
|
82
99
|
isSelected && styles.wheelItemTextSelected,
|
|
83
100
|
]}
|
|
84
101
|
>
|
|
@@ -107,6 +124,30 @@ interface TimeWheelPickerProps {
|
|
|
107
124
|
|
|
108
125
|
export const TimeWheelPicker: React.FC<TimeWheelPickerProps> = memo(
|
|
109
126
|
({ value, onChange, is24Hour = false, minuteInterval = 1 }) => {
|
|
127
|
+
const overrides = useTimePickerOverrides();
|
|
128
|
+
|
|
129
|
+
// Build colors from overrides (use backgroundColor as fallback for wheelContainerBackground)
|
|
130
|
+
const colors = useMemo(
|
|
131
|
+
() => ({
|
|
132
|
+
containerBackground:
|
|
133
|
+
overrides?.wheelContainerBackground ??
|
|
134
|
+
overrides?.backgroundColor ??
|
|
135
|
+
DEFAULT_COLORS.containerBackground,
|
|
136
|
+
selectionHighlight:
|
|
137
|
+
overrides?.wheelSelectionHighlight ??
|
|
138
|
+
DEFAULT_COLORS.selectionHighlight,
|
|
139
|
+
textColor:
|
|
140
|
+
overrides?.wheelTextColor ??
|
|
141
|
+
overrides?.textColor ??
|
|
142
|
+
DEFAULT_COLORS.textColor,
|
|
143
|
+
separatorColor:
|
|
144
|
+
overrides?.wheelSeparatorColor ??
|
|
145
|
+
overrides?.textColor ??
|
|
146
|
+
DEFAULT_COLORS.separatorColor,
|
|
147
|
+
}),
|
|
148
|
+
[overrides],
|
|
149
|
+
);
|
|
150
|
+
|
|
110
151
|
// Generate hour items
|
|
111
152
|
const hourItems = useMemo(() => {
|
|
112
153
|
if (is24Hour) {
|
|
@@ -181,19 +222,30 @@ export const TimeWheelPicker: React.FC<TimeWheelPickerProps> = memo(
|
|
|
181
222
|
);
|
|
182
223
|
|
|
183
224
|
return (
|
|
184
|
-
<View
|
|
225
|
+
<View
|
|
226
|
+
style={[
|
|
227
|
+
styles.wheelContainer,
|
|
228
|
+
{ backgroundColor: colors.containerBackground },
|
|
229
|
+
]}
|
|
230
|
+
>
|
|
185
231
|
<WheelColumn
|
|
186
232
|
items={hourItems}
|
|
187
233
|
selectedIndex={hourIndex}
|
|
188
234
|
onSelect={handleHourChange}
|
|
235
|
+
textColor={colors.textColor}
|
|
236
|
+
highlightColor={colors.selectionHighlight}
|
|
189
237
|
/>
|
|
190
238
|
|
|
191
|
-
<Text style={styles.wheelSeparator}
|
|
239
|
+
<Text style={[styles.wheelSeparator, { color: colors.separatorColor }]}>
|
|
240
|
+
:
|
|
241
|
+
</Text>
|
|
192
242
|
|
|
193
243
|
<WheelColumn
|
|
194
244
|
items={minuteItems}
|
|
195
245
|
selectedIndex={minuteIndex}
|
|
196
246
|
onSelect={handleMinuteChange}
|
|
247
|
+
textColor={colors.textColor}
|
|
248
|
+
highlightColor={colors.selectionHighlight}
|
|
197
249
|
/>
|
|
198
250
|
|
|
199
251
|
{!is24Hour && (
|
|
@@ -201,6 +253,8 @@ export const TimeWheelPicker: React.FC<TimeWheelPickerProps> = memo(
|
|
|
201
253
|
items={periodItems}
|
|
202
254
|
selectedIndex={periodIndex}
|
|
203
255
|
onSelect={handlePeriodChange}
|
|
256
|
+
textColor={colors.textColor}
|
|
257
|
+
highlightColor={colors.selectionHighlight}
|
|
204
258
|
/>
|
|
205
259
|
)}
|
|
206
260
|
</View>
|
|
@@ -0,0 +1,401 @@
|
|
|
1
|
+
/// <reference lib="dom" />
|
|
2
|
+
import type React from 'react';
|
|
3
|
+
import { memo, useCallback, useEffect, useMemo, useRef } from 'react';
|
|
4
|
+
import { StyleSheet as RNStyleSheet, Text, View } from 'react-native';
|
|
5
|
+
import { useTimePickerOverrides } from '../../context/ThemeOverrideContext';
|
|
6
|
+
import { to12Hour, to24Hour } from '../../hooks/useTimePicker';
|
|
7
|
+
import type { TimePeriod, TimeValue } from '../../types/timepicker';
|
|
8
|
+
|
|
9
|
+
const ITEM_HEIGHT = 44;
|
|
10
|
+
const VISIBLE_ITEMS = 5;
|
|
11
|
+
const CONTAINER_HEIGHT = ITEM_HEIGHT * VISIBLE_ITEMS;
|
|
12
|
+
|
|
13
|
+
// Default colors (dark theme)
|
|
14
|
+
const DEFAULT_COLORS = {
|
|
15
|
+
containerBackground: '#2C2C2E',
|
|
16
|
+
selectionHighlight: 'rgba(120, 120, 128, 0.24)',
|
|
17
|
+
textColor: '#FFFFFF',
|
|
18
|
+
separatorColor: '#FFFFFF',
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
interface WheelColumnProps {
|
|
22
|
+
items: { value: number | string; label: string }[];
|
|
23
|
+
selectedIndex: number;
|
|
24
|
+
onSelect: (index: number) => void;
|
|
25
|
+
textColor: string;
|
|
26
|
+
highlightColor: string;
|
|
27
|
+
label?: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const WheelColumn: React.FC<WheelColumnProps> = memo(
|
|
31
|
+
({
|
|
32
|
+
items,
|
|
33
|
+
selectedIndex,
|
|
34
|
+
onSelect,
|
|
35
|
+
textColor,
|
|
36
|
+
highlightColor,
|
|
37
|
+
label = 'Select value',
|
|
38
|
+
}) => {
|
|
39
|
+
const scrollRef = useRef<HTMLDivElement>(null);
|
|
40
|
+
const isScrolling = useRef(false);
|
|
41
|
+
const scrollTimeout = useRef<ReturnType<typeof setTimeout>>();
|
|
42
|
+
|
|
43
|
+
// Scroll to selected item on mount and when selection changes externally
|
|
44
|
+
useEffect(() => {
|
|
45
|
+
if (scrollRef.current && !isScrolling.current) {
|
|
46
|
+
scrollRef.current.scrollTop = selectedIndex * ITEM_HEIGHT;
|
|
47
|
+
}
|
|
48
|
+
}, [selectedIndex]);
|
|
49
|
+
|
|
50
|
+
const handleScroll = useCallback(() => {
|
|
51
|
+
if (!scrollRef.current) return;
|
|
52
|
+
|
|
53
|
+
isScrolling.current = true;
|
|
54
|
+
|
|
55
|
+
// Clear existing timeout
|
|
56
|
+
if (scrollTimeout.current) {
|
|
57
|
+
clearTimeout(scrollTimeout.current);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Debounce scroll end detection
|
|
61
|
+
scrollTimeout.current = setTimeout(() => {
|
|
62
|
+
if (!scrollRef.current) return;
|
|
63
|
+
|
|
64
|
+
const scrollTop = scrollRef.current.scrollTop;
|
|
65
|
+
const newIndex = Math.round(scrollTop / ITEM_HEIGHT);
|
|
66
|
+
const clampedIndex = Math.max(0, Math.min(items.length - 1, newIndex));
|
|
67
|
+
|
|
68
|
+
// Snap to nearest item
|
|
69
|
+
scrollRef.current.scrollTop = clampedIndex * ITEM_HEIGHT;
|
|
70
|
+
|
|
71
|
+
if (clampedIndex !== selectedIndex) {
|
|
72
|
+
onSelect(clampedIndex);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
isScrolling.current = false;
|
|
76
|
+
}, 100);
|
|
77
|
+
}, [items.length, selectedIndex, onSelect]);
|
|
78
|
+
|
|
79
|
+
// Keyboard navigation (like native input[type="time"])
|
|
80
|
+
const handleKeyDown = useCallback(
|
|
81
|
+
(e: React.KeyboardEvent) => {
|
|
82
|
+
let newIndex = selectedIndex;
|
|
83
|
+
|
|
84
|
+
switch (e.key) {
|
|
85
|
+
case 'ArrowUp':
|
|
86
|
+
e.preventDefault();
|
|
87
|
+
newIndex = Math.max(0, selectedIndex - 1);
|
|
88
|
+
break;
|
|
89
|
+
case 'ArrowDown':
|
|
90
|
+
e.preventDefault();
|
|
91
|
+
newIndex = Math.min(items.length - 1, selectedIndex + 1);
|
|
92
|
+
break;
|
|
93
|
+
case 'Home':
|
|
94
|
+
e.preventDefault();
|
|
95
|
+
newIndex = 0;
|
|
96
|
+
break;
|
|
97
|
+
case 'End':
|
|
98
|
+
e.preventDefault();
|
|
99
|
+
newIndex = items.length - 1;
|
|
100
|
+
break;
|
|
101
|
+
default:
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (newIndex !== selectedIndex) {
|
|
106
|
+
if (scrollRef.current) {
|
|
107
|
+
scrollRef.current.scrollTop = newIndex * ITEM_HEIGHT;
|
|
108
|
+
}
|
|
109
|
+
onSelect(newIndex);
|
|
110
|
+
}
|
|
111
|
+
},
|
|
112
|
+
[selectedIndex, items.length, onSelect],
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
const currentItem = items[selectedIndex];
|
|
116
|
+
|
|
117
|
+
return (
|
|
118
|
+
<View style={webStyles.column}>
|
|
119
|
+
<View
|
|
120
|
+
style={[
|
|
121
|
+
webStyles.selectionHighlight,
|
|
122
|
+
{ backgroundColor: highlightColor },
|
|
123
|
+
]}
|
|
124
|
+
/>
|
|
125
|
+
|
|
126
|
+
{/* Spinbutton - mimics input[type="time"] accessibility */}
|
|
127
|
+
<div
|
|
128
|
+
ref={scrollRef}
|
|
129
|
+
role="spinbutton"
|
|
130
|
+
tabIndex={0}
|
|
131
|
+
aria-label={label}
|
|
132
|
+
aria-valuenow={
|
|
133
|
+
typeof currentItem?.value === 'number' ? currentItem.value : 0
|
|
134
|
+
}
|
|
135
|
+
aria-valuemin={0}
|
|
136
|
+
aria-valuemax={items.length - 1}
|
|
137
|
+
aria-valuetext={currentItem?.label}
|
|
138
|
+
onScroll={handleScroll}
|
|
139
|
+
onKeyDown={handleKeyDown}
|
|
140
|
+
onClick={(e) => {
|
|
141
|
+
const target = e.target as HTMLElement;
|
|
142
|
+
const item = target.closest('[data-index]') as HTMLElement | null;
|
|
143
|
+
if (item && scrollRef.current) {
|
|
144
|
+
const index = Number(item.dataset.index);
|
|
145
|
+
scrollRef.current.scrollTop = index * ITEM_HEIGHT;
|
|
146
|
+
onSelect(index);
|
|
147
|
+
}
|
|
148
|
+
}}
|
|
149
|
+
style={{
|
|
150
|
+
height: CONTAINER_HEIGHT,
|
|
151
|
+
overflowY: 'auto',
|
|
152
|
+
scrollSnapType: 'y mandatory',
|
|
153
|
+
scrollBehavior: 'smooth',
|
|
154
|
+
position: 'relative',
|
|
155
|
+
zIndex: 1,
|
|
156
|
+
outline: 'none',
|
|
157
|
+
// Hide scrollbar
|
|
158
|
+
scrollbarWidth: 'none',
|
|
159
|
+
msOverflowStyle: 'none',
|
|
160
|
+
}}
|
|
161
|
+
>
|
|
162
|
+
{/* @ts-ignore - webkit scrollbar hiding */}
|
|
163
|
+
<style>{`
|
|
164
|
+
div::-webkit-scrollbar {
|
|
165
|
+
display: none;
|
|
166
|
+
}
|
|
167
|
+
`}</style>
|
|
168
|
+
|
|
169
|
+
{/* Top padding */}
|
|
170
|
+
<div style={{ height: ITEM_HEIGHT * 2, flexShrink: 0 }} />
|
|
171
|
+
|
|
172
|
+
{items.map((item, index) => {
|
|
173
|
+
const isSelected = index === selectedIndex;
|
|
174
|
+
return (
|
|
175
|
+
<div
|
|
176
|
+
key={`${item.value}`}
|
|
177
|
+
data-index={index}
|
|
178
|
+
style={{
|
|
179
|
+
height: ITEM_HEIGHT,
|
|
180
|
+
display: 'flex',
|
|
181
|
+
justifyContent: 'center',
|
|
182
|
+
alignItems: 'center',
|
|
183
|
+
scrollSnapAlign: 'center',
|
|
184
|
+
cursor: 'pointer',
|
|
185
|
+
}}
|
|
186
|
+
>
|
|
187
|
+
<Text
|
|
188
|
+
style={[
|
|
189
|
+
webStyles.itemText,
|
|
190
|
+
{ color: textColor },
|
|
191
|
+
isSelected && webStyles.itemTextSelected,
|
|
192
|
+
]}
|
|
193
|
+
>
|
|
194
|
+
{item.label}
|
|
195
|
+
</Text>
|
|
196
|
+
</div>
|
|
197
|
+
);
|
|
198
|
+
})}
|
|
199
|
+
|
|
200
|
+
{/* Bottom padding */}
|
|
201
|
+
<div style={{ height: ITEM_HEIGHT * 2, flexShrink: 0 }} />
|
|
202
|
+
</div>
|
|
203
|
+
</View>
|
|
204
|
+
);
|
|
205
|
+
},
|
|
206
|
+
);
|
|
207
|
+
|
|
208
|
+
WheelColumn.displayName = 'WheelColumn';
|
|
209
|
+
|
|
210
|
+
interface TimeWheelPickerProps {
|
|
211
|
+
value: TimeValue;
|
|
212
|
+
onChange: (time: TimeValue) => void;
|
|
213
|
+
is24Hour?: boolean;
|
|
214
|
+
minuteInterval?: number;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
export const TimeWheelPicker: React.FC<TimeWheelPickerProps> = memo(
|
|
218
|
+
({ value, onChange, is24Hour = false, minuteInterval = 1 }) => {
|
|
219
|
+
const overrides = useTimePickerOverrides();
|
|
220
|
+
|
|
221
|
+
// Build colors from overrides (use backgroundColor as fallback for wheelContainerBackground)
|
|
222
|
+
const colors = useMemo(
|
|
223
|
+
() => ({
|
|
224
|
+
containerBackground:
|
|
225
|
+
overrides?.wheelContainerBackground ??
|
|
226
|
+
overrides?.backgroundColor ??
|
|
227
|
+
DEFAULT_COLORS.containerBackground,
|
|
228
|
+
selectionHighlight:
|
|
229
|
+
overrides?.wheelSelectionHighlight ??
|
|
230
|
+
DEFAULT_COLORS.selectionHighlight,
|
|
231
|
+
textColor:
|
|
232
|
+
overrides?.wheelTextColor ??
|
|
233
|
+
overrides?.textColor ??
|
|
234
|
+
DEFAULT_COLORS.textColor,
|
|
235
|
+
separatorColor:
|
|
236
|
+
overrides?.wheelSeparatorColor ??
|
|
237
|
+
overrides?.textColor ??
|
|
238
|
+
DEFAULT_COLORS.separatorColor,
|
|
239
|
+
}),
|
|
240
|
+
[overrides],
|
|
241
|
+
);
|
|
242
|
+
|
|
243
|
+
// Generate hour items
|
|
244
|
+
const hourItems = useMemo(() => {
|
|
245
|
+
if (is24Hour) {
|
|
246
|
+
return Array.from({ length: 24 }, (_, i) => ({
|
|
247
|
+
value: i,
|
|
248
|
+
label: i.toString().padStart(2, '0'),
|
|
249
|
+
}));
|
|
250
|
+
}
|
|
251
|
+
// 12-hour format: 1-12
|
|
252
|
+
return Array.from({ length: 12 }, (_, i) => ({
|
|
253
|
+
value: i + 1,
|
|
254
|
+
label: (i + 1).toString().padStart(2, '0'),
|
|
255
|
+
}));
|
|
256
|
+
}, [is24Hour]);
|
|
257
|
+
|
|
258
|
+
// Generate minute items based on interval
|
|
259
|
+
const minuteItems = useMemo(() => {
|
|
260
|
+
const items: { value: number; label: string }[] = [];
|
|
261
|
+
for (let i = 0; i < 60; i += minuteInterval) {
|
|
262
|
+
items.push({
|
|
263
|
+
value: i,
|
|
264
|
+
label: i.toString().padStart(2, '0'),
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
return items;
|
|
268
|
+
}, [minuteInterval]);
|
|
269
|
+
|
|
270
|
+
// Period items for 12-hour format
|
|
271
|
+
const periodItems = useMemo(
|
|
272
|
+
() => [
|
|
273
|
+
{ value: 'AM' as const, label: 'AM' },
|
|
274
|
+
{ value: 'PM' as const, label: 'PM' },
|
|
275
|
+
],
|
|
276
|
+
[],
|
|
277
|
+
);
|
|
278
|
+
|
|
279
|
+
// Calculate selected indices
|
|
280
|
+
const { hour: hour12, period } = to12Hour(value.hours);
|
|
281
|
+
const hourIndex = is24Hour ? value.hours : hour12 - 1; // 12-hour is 1-indexed
|
|
282
|
+
const minuteIndex = Math.floor(value.minutes / minuteInterval);
|
|
283
|
+
const periodIndex = period === 'AM' ? 0 : 1;
|
|
284
|
+
|
|
285
|
+
const handleHourChange = useCallback(
|
|
286
|
+
(index: number) => {
|
|
287
|
+
let newHours: number;
|
|
288
|
+
if (is24Hour) {
|
|
289
|
+
newHours = index;
|
|
290
|
+
} else {
|
|
291
|
+
const hour12 = index + 1; // Convert from 0-indexed to 1-12
|
|
292
|
+
newHours = to24Hour(hour12, period);
|
|
293
|
+
}
|
|
294
|
+
onChange({ hours: newHours, minutes: value.minutes });
|
|
295
|
+
},
|
|
296
|
+
[is24Hour, period, value.minutes, onChange],
|
|
297
|
+
);
|
|
298
|
+
|
|
299
|
+
const handleMinuteChange = useCallback(
|
|
300
|
+
(index: number) => {
|
|
301
|
+
const newMinutes = index * minuteInterval;
|
|
302
|
+
onChange({ hours: value.hours, minutes: newMinutes });
|
|
303
|
+
},
|
|
304
|
+
[value.hours, minuteInterval, onChange],
|
|
305
|
+
);
|
|
306
|
+
|
|
307
|
+
const handlePeriodChange = useCallback(
|
|
308
|
+
(index: number) => {
|
|
309
|
+
const newPeriod: TimePeriod = index === 0 ? 'AM' : 'PM';
|
|
310
|
+
const newHours = to24Hour(hour12, newPeriod);
|
|
311
|
+
onChange({ hours: newHours, minutes: value.minutes });
|
|
312
|
+
},
|
|
313
|
+
[hour12, value.minutes, onChange],
|
|
314
|
+
);
|
|
315
|
+
|
|
316
|
+
return (
|
|
317
|
+
<View
|
|
318
|
+
style={[
|
|
319
|
+
webStyles.container,
|
|
320
|
+
{ backgroundColor: colors.containerBackground },
|
|
321
|
+
]}
|
|
322
|
+
>
|
|
323
|
+
<WheelColumn
|
|
324
|
+
items={hourItems}
|
|
325
|
+
selectedIndex={hourIndex}
|
|
326
|
+
onSelect={handleHourChange}
|
|
327
|
+
textColor={colors.textColor}
|
|
328
|
+
highlightColor={colors.selectionHighlight}
|
|
329
|
+
label="Hour"
|
|
330
|
+
/>
|
|
331
|
+
|
|
332
|
+
<Text style={[webStyles.separator, { color: colors.separatorColor }]}>
|
|
333
|
+
:
|
|
334
|
+
</Text>
|
|
335
|
+
|
|
336
|
+
<WheelColumn
|
|
337
|
+
items={minuteItems}
|
|
338
|
+
selectedIndex={minuteIndex}
|
|
339
|
+
onSelect={handleMinuteChange}
|
|
340
|
+
textColor={colors.textColor}
|
|
341
|
+
highlightColor={colors.selectionHighlight}
|
|
342
|
+
label="Minute"
|
|
343
|
+
/>
|
|
344
|
+
|
|
345
|
+
{!is24Hour && (
|
|
346
|
+
<WheelColumn
|
|
347
|
+
items={periodItems}
|
|
348
|
+
selectedIndex={periodIndex}
|
|
349
|
+
onSelect={handlePeriodChange}
|
|
350
|
+
textColor={colors.textColor}
|
|
351
|
+
highlightColor={colors.selectionHighlight}
|
|
352
|
+
label="AM/PM"
|
|
353
|
+
/>
|
|
354
|
+
)}
|
|
355
|
+
</View>
|
|
356
|
+
);
|
|
357
|
+
},
|
|
358
|
+
);
|
|
359
|
+
|
|
360
|
+
TimeWheelPicker.displayName = 'TimeWheelPicker';
|
|
361
|
+
|
|
362
|
+
const webStyles = RNStyleSheet.create({
|
|
363
|
+
container: {
|
|
364
|
+
flexDirection: 'row',
|
|
365
|
+
height: CONTAINER_HEIGHT,
|
|
366
|
+
borderRadius: 14,
|
|
367
|
+
overflow: 'hidden',
|
|
368
|
+
alignItems: 'center',
|
|
369
|
+
paddingHorizontal: 8,
|
|
370
|
+
},
|
|
371
|
+
column: {
|
|
372
|
+
flex: 1,
|
|
373
|
+
height: CONTAINER_HEIGHT,
|
|
374
|
+
overflow: 'hidden',
|
|
375
|
+
position: 'relative',
|
|
376
|
+
},
|
|
377
|
+
selectionHighlight: {
|
|
378
|
+
position: 'absolute',
|
|
379
|
+
top: ITEM_HEIGHT * 2,
|
|
380
|
+
left: 4,
|
|
381
|
+
right: 4,
|
|
382
|
+
height: ITEM_HEIGHT,
|
|
383
|
+
borderRadius: 8,
|
|
384
|
+
zIndex: 0,
|
|
385
|
+
},
|
|
386
|
+
itemText: {
|
|
387
|
+
fontSize: 22,
|
|
388
|
+
fontFamily: '-apple-system, BlinkMacSystemFont, "SF Pro Text", sans-serif',
|
|
389
|
+
fontWeight: '400',
|
|
390
|
+
opacity: 0.5,
|
|
391
|
+
},
|
|
392
|
+
itemTextSelected: {
|
|
393
|
+
fontWeight: '500',
|
|
394
|
+
opacity: 1,
|
|
395
|
+
},
|
|
396
|
+
separator: {
|
|
397
|
+
fontSize: 22,
|
|
398
|
+
fontWeight: '500',
|
|
399
|
+
paddingHorizontal: 4,
|
|
400
|
+
},
|
|
401
|
+
});
|
|
@@ -1,11 +1,15 @@
|
|
|
1
|
-
import { StyleSheet } from 'react-native
|
|
1
|
+
import { StyleSheet } from 'react-native';
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
/**
|
|
4
|
+
* Default styles for WheelPicker using plain React Native StyleSheet.
|
|
5
|
+
* Colors use dark theme defaults - consumers override via themeOverrides prop.
|
|
6
|
+
*/
|
|
7
|
+
export const styles = StyleSheet.create({
|
|
4
8
|
container: {
|
|
5
9
|
flexDirection: 'row',
|
|
6
10
|
height: 220,
|
|
7
|
-
backgroundColor:
|
|
8
|
-
borderRadius:
|
|
11
|
+
backgroundColor: '#2C2C2E',
|
|
12
|
+
borderRadius: 16,
|
|
9
13
|
overflow: 'hidden',
|
|
10
14
|
},
|
|
11
15
|
column: {
|
|
@@ -19,8 +23,8 @@ export const styles = StyleSheet.create((theme) => ({
|
|
|
19
23
|
left: 4,
|
|
20
24
|
right: 4,
|
|
21
25
|
height: 44,
|
|
22
|
-
backgroundColor:
|
|
23
|
-
borderRadius:
|
|
26
|
+
backgroundColor: 'rgba(120, 120, 128, 0.24)',
|
|
27
|
+
borderRadius: 8,
|
|
24
28
|
zIndex: 0,
|
|
25
29
|
},
|
|
26
30
|
itemsContainer: {
|
|
@@ -33,7 +37,7 @@ export const styles = StyleSheet.create((theme) => ({
|
|
|
33
37
|
},
|
|
34
38
|
itemText: {
|
|
35
39
|
fontSize: 21,
|
|
36
|
-
color:
|
|
40
|
+
color: '#FFFFFF',
|
|
37
41
|
fontWeight: '400',
|
|
38
42
|
},
|
|
39
|
-
})
|
|
43
|
+
});
|
|
@@ -1,2 +1,24 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import type React from 'react';
|
|
2
|
+
import { Text, View } from 'react-native';
|
|
3
|
+
|
|
4
|
+
interface WheelPickerProps {
|
|
5
|
+
value: Date;
|
|
6
|
+
onChange: (date: Date) => void;
|
|
7
|
+
minDate?: Date;
|
|
8
|
+
maxDate?: Date;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Native WheelPicker stub - native platforms should use native date pickers.
|
|
13
|
+
* This component exists for API compatibility but renders a placeholder.
|
|
14
|
+
* Use DatePicker with theme="native" for proper native picker experience.
|
|
15
|
+
*/
|
|
16
|
+
export const WheelPicker: React.FC<WheelPickerProps> = () => {
|
|
17
|
+
return (
|
|
18
|
+
<View style={{ padding: 20, alignItems: 'center' }}>
|
|
19
|
+
<Text style={{ color: '#666' }}>
|
|
20
|
+
Use DatePicker with theme="native" on this platform
|
|
21
|
+
</Text>
|
|
22
|
+
</View>
|
|
23
|
+
);
|
|
24
|
+
};
|