@idealyst/datepicker 1.0.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.
- package/README.md +88 -0
- package/package.json +77 -0
- package/src/DatePicker/Calendar.native.tsx +159 -0
- package/src/DatePicker/Calendar.styles.tsx +224 -0
- package/src/DatePicker/Calendar.tsx +154 -0
- package/src/DatePicker/DatePicker.native.tsx +33 -0
- package/src/DatePicker/DatePicker.styles.tsx +69 -0
- package/src/DatePicker/DatePicker.web.tsx +31 -0
- package/src/DatePicker/index.native.ts +3 -0
- package/src/DatePicker/index.ts +3 -0
- package/src/DatePicker/types.ts +78 -0
- package/src/DateRangePicker/DateRangePicker.native.tsx +39 -0
- package/src/DateRangePicker/DateRangePicker.styles.tsx +83 -0
- package/src/DateRangePicker/DateRangePicker.web.tsx +40 -0
- package/src/DateRangePicker/RangeCalendar.native.tsx +267 -0
- package/src/DateRangePicker/RangeCalendar.styles.tsx +170 -0
- package/src/DateRangePicker/RangeCalendar.web.tsx +275 -0
- package/src/DateRangePicker/index.native.ts +3 -0
- package/src/DateRangePicker/index.ts +3 -0
- package/src/DateRangePicker/types.ts +98 -0
- package/src/DateTimePicker/DateTimePicker.native.tsx +82 -0
- package/src/DateTimePicker/DateTimePicker.styles.tsx +77 -0
- package/src/DateTimePicker/DateTimePicker.tsx +79 -0
- package/src/DateTimePicker/TimePicker.native.tsx +204 -0
- package/src/DateTimePicker/TimePicker.styles.tsx +116 -0
- package/src/DateTimePicker/TimePicker.tsx +406 -0
- package/src/DateTimePicker/index.native.ts +3 -0
- package/src/DateTimePicker/index.ts +3 -0
- package/src/DateTimePicker/types.ts +84 -0
- package/src/DateTimeRangePicker/DateTimeRangePicker.native.tsx +213 -0
- package/src/DateTimeRangePicker/DateTimeRangePicker.styles.tsx +95 -0
- package/src/DateTimeRangePicker/DateTimeRangePicker.web.tsx +141 -0
- package/src/DateTimeRangePicker/index.native.ts +2 -0
- package/src/DateTimeRangePicker/index.ts +2 -0
- package/src/DateTimeRangePicker/types.ts +72 -0
- package/src/examples/DatePickerExamples.tsx +274 -0
- package/src/examples/index.ts +1 -0
- package/src/index.native.ts +16 -0
- package/src/index.ts +16 -0
- package/src/primitives/CalendarGrid/CalendarGrid.styles.tsx +62 -0
- package/src/primitives/CalendarGrid/CalendarGrid.tsx +138 -0
- package/src/primitives/CalendarGrid/index.ts +1 -0
- package/src/primitives/CalendarHeader/CalendarHeader.styles.tsx +25 -0
- package/src/primitives/CalendarHeader/CalendarHeader.tsx +69 -0
- package/src/primitives/CalendarHeader/index.ts +1 -0
- package/src/primitives/CalendarOverlay/CalendarOverlay.styles.tsx +81 -0
- package/src/primitives/CalendarOverlay/CalendarOverlay.tsx +130 -0
- package/src/primitives/CalendarOverlay/index.ts +1 -0
- package/src/primitives/Wrapper/Wrapper.web.tsx +33 -0
- package/src/primitives/Wrapper/index.ts +1 -0
|
@@ -0,0 +1,406 @@
|
|
|
1
|
+
import React, { useState, useEffect, useRef } from 'react';
|
|
2
|
+
import { Button, View, Input } from '@idealyst/components';
|
|
3
|
+
import { TimePickerProps } from './types';
|
|
4
|
+
import { timePickerStyles } from './TimePicker.styles';
|
|
5
|
+
|
|
6
|
+
export const TimePicker: React.FC<TimePickerProps> = ({
|
|
7
|
+
value = new Date(),
|
|
8
|
+
onChange,
|
|
9
|
+
disabled = false,
|
|
10
|
+
mode = '12h',
|
|
11
|
+
showSeconds = false,
|
|
12
|
+
step = 1,
|
|
13
|
+
style,
|
|
14
|
+
testID,
|
|
15
|
+
}) => {
|
|
16
|
+
const [activeSelection, setActiveSelection] = useState<'hour' | 'minute'>('hour');
|
|
17
|
+
const [hourInputValue, setHourInputValue] = useState(String(value.getHours() > 12 && mode === '12h' ? value.getHours() - 12 : value.getHours()));
|
|
18
|
+
const [minuteInputValue, setMinuteInputValue] = useState(String(value.getMinutes()).padStart(2, '0'));
|
|
19
|
+
const [hourInputFocused, setHourInputFocused] = useState(false);
|
|
20
|
+
const [minuteInputFocused, setMinuteInputFocused] = useState(false);
|
|
21
|
+
const hourInputRef = useRef<HTMLInputElement | null>(null);
|
|
22
|
+
const minuteInputRef = useRef<HTMLInputElement | null>(null);
|
|
23
|
+
const hours = value.getHours();
|
|
24
|
+
const minutes = value.getMinutes();
|
|
25
|
+
const seconds = value.getSeconds();
|
|
26
|
+
|
|
27
|
+
const displayHours = mode === '12h' ? (hours === 0 ? 12 : hours > 12 ? hours - 12 : hours) : hours;
|
|
28
|
+
const ampm = mode === '12h' ? (hours >= 12 ? 'PM' : 'AM') : null;
|
|
29
|
+
|
|
30
|
+
// Sync input values when time changes from external sources (like clock clicks)
|
|
31
|
+
// Only update if the input is not currently focused
|
|
32
|
+
useEffect(() => {
|
|
33
|
+
if (!hourInputFocused) {
|
|
34
|
+
setHourInputValue(String(displayHours));
|
|
35
|
+
}
|
|
36
|
+
if (!minuteInputFocused) {
|
|
37
|
+
setMinuteInputValue(String(minutes).padStart(2, '0'));
|
|
38
|
+
}
|
|
39
|
+
}, [displayHours, minutes, hourInputFocused, minuteInputFocused]);
|
|
40
|
+
|
|
41
|
+
const updateTime = (newHours: number, newMinutes: number, newSeconds?: number) => {
|
|
42
|
+
const newDate = new Date(value);
|
|
43
|
+
newDate.setHours(newHours, newMinutes, newSeconds || 0);
|
|
44
|
+
onChange(newDate);
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const handleHourChange = (delta: number) => {
|
|
48
|
+
let newHours = hours + delta;
|
|
49
|
+
if (mode === '12h') {
|
|
50
|
+
if (newHours < 0) newHours = 23;
|
|
51
|
+
if (newHours > 23) newHours = 0;
|
|
52
|
+
} else {
|
|
53
|
+
if (newHours < 0) newHours = 23;
|
|
54
|
+
if (newHours > 23) newHours = 0;
|
|
55
|
+
}
|
|
56
|
+
updateTime(newHours, minutes, seconds);
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const handleMinuteChange = (delta: number) => {
|
|
60
|
+
let newMinutes = minutes + (delta * step);
|
|
61
|
+
let newHours = hours;
|
|
62
|
+
|
|
63
|
+
if (newMinutes < 0) {
|
|
64
|
+
newMinutes = 60 + newMinutes;
|
|
65
|
+
newHours = hours - 1;
|
|
66
|
+
if (newHours < 0) newHours = 23;
|
|
67
|
+
} else if (newMinutes >= 60) {
|
|
68
|
+
newMinutes = newMinutes - 60;
|
|
69
|
+
newHours = hours + 1;
|
|
70
|
+
if (newHours > 23) newHours = 0;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
updateTime(newHours, newMinutes, seconds);
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const handleSecondChange = (delta: number) => {
|
|
77
|
+
let newSeconds = seconds + delta;
|
|
78
|
+
let newMinutes = minutes;
|
|
79
|
+
let newHours = hours;
|
|
80
|
+
|
|
81
|
+
if (newSeconds < 0) {
|
|
82
|
+
newSeconds = 59;
|
|
83
|
+
newMinutes = minutes - 1;
|
|
84
|
+
if (newMinutes < 0) {
|
|
85
|
+
newMinutes = 59;
|
|
86
|
+
newHours = hours - 1;
|
|
87
|
+
if (newHours < 0) newHours = 23;
|
|
88
|
+
}
|
|
89
|
+
} else if (newSeconds >= 60) {
|
|
90
|
+
newSeconds = 0;
|
|
91
|
+
newMinutes = minutes + 1;
|
|
92
|
+
if (newMinutes >= 60) {
|
|
93
|
+
newMinutes = 0;
|
|
94
|
+
newHours = hours + 1;
|
|
95
|
+
if (newHours > 23) newHours = 0;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
updateTime(newHours, newMinutes, newSeconds);
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
const toggleAmPm = () => {
|
|
103
|
+
if (mode === '12h') {
|
|
104
|
+
const newHours = hours >= 12 ? hours - 12 : hours + 12;
|
|
105
|
+
updateTime(newHours, minutes, seconds);
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
const handleHourClick = (hour: number) => {
|
|
110
|
+
let hour24 = hour;
|
|
111
|
+
if (mode === '12h') {
|
|
112
|
+
const isPM = hours >= 12;
|
|
113
|
+
if (hour === 12) hour24 = isPM ? 12 : 0;
|
|
114
|
+
else hour24 = isPM ? hour + 12 : hour;
|
|
115
|
+
}
|
|
116
|
+
updateTime(hour24, minutes, seconds);
|
|
117
|
+
setActiveSelection('minute');
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
const handleMinuteClick = (minute: number) => {
|
|
121
|
+
updateTime(hours, minute, seconds);
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
const renderClockFace = () => {
|
|
125
|
+
// Clock configuration
|
|
126
|
+
const CLOCK_SIZE = 180;
|
|
127
|
+
const CENTER = CLOCK_SIZE / 2;
|
|
128
|
+
const CLOCK_RADIUS = CENTER - 5;
|
|
129
|
+
const NUMBER_RADIUS = CENTER - 24;
|
|
130
|
+
const HOUR_HAND_LENGTH = CENTER - 44;
|
|
131
|
+
const MINUTE_HAND_LENGTH = CENTER - 36;
|
|
132
|
+
const CIRCLE_RADIUS = 15;
|
|
133
|
+
|
|
134
|
+
if (activeSelection === 'hour') {
|
|
135
|
+
return (
|
|
136
|
+
<View style={timePickerStyles.clockContainer}>
|
|
137
|
+
<svg width={CLOCK_SIZE} height={CLOCK_SIZE} style={timePickerStyles.clockSvg}>
|
|
138
|
+
{/* Clock face */}
|
|
139
|
+
<circle cx={CENTER} cy={CENTER} r={CLOCK_RADIUS} fill="#f9fafb" stroke="#e5e7eb" strokeWidth="2"/>
|
|
140
|
+
|
|
141
|
+
{/* Hour numbers - clickable */}
|
|
142
|
+
{[...Array(12)].map((_, i) => {
|
|
143
|
+
const hour = i === 0 ? 12 : i;
|
|
144
|
+
const angle = (i * 30) - 90;
|
|
145
|
+
const x = CENTER + NUMBER_RADIUS * Math.cos(angle * Math.PI / 180);
|
|
146
|
+
const y = CENTER + NUMBER_RADIUS * Math.sin(angle * Math.PI / 180);
|
|
147
|
+
const isSelected = (mode === '12h' ? displayHours : hours) === (hour === 12 ? 0 : hour);
|
|
148
|
+
|
|
149
|
+
return (
|
|
150
|
+
<g key={i} onClick={() => handleHourClick(hour)}>
|
|
151
|
+
<circle
|
|
152
|
+
cx={x}
|
|
153
|
+
cy={y}
|
|
154
|
+
r={CIRCLE_RADIUS}
|
|
155
|
+
fill={isSelected ? '#3b82f6' : 'transparent'}
|
|
156
|
+
stroke={isSelected ? '#3b82f6' : '#e5e7eb'}
|
|
157
|
+
strokeWidth="1"
|
|
158
|
+
style={{ cursor: 'pointer' }}
|
|
159
|
+
/>
|
|
160
|
+
<text
|
|
161
|
+
x={x}
|
|
162
|
+
y={y + 4}
|
|
163
|
+
textAnchor="middle"
|
|
164
|
+
fontSize="14"
|
|
165
|
+
fill={isSelected ? '#ffffff' : '#374151'}
|
|
166
|
+
fontWeight="500"
|
|
167
|
+
style={{ cursor: 'pointer', userSelect: 'none', pointerEvents: 'none' }}
|
|
168
|
+
>
|
|
169
|
+
{hour}
|
|
170
|
+
</text>
|
|
171
|
+
</g>
|
|
172
|
+
);
|
|
173
|
+
})}
|
|
174
|
+
|
|
175
|
+
{/* Hour hand pointing to selected hour */}
|
|
176
|
+
{(() => {
|
|
177
|
+
const selectedHour = mode === '12h' ? displayHours : hours;
|
|
178
|
+
// Convert 12 to 0 for angle calculation, but keep others as-is
|
|
179
|
+
const hourFor12Clock = selectedHour === 12 ? 0 : selectedHour;
|
|
180
|
+
const hourAngle = (hourFor12Clock * 30) - 90;
|
|
181
|
+
const handX = CENTER + HOUR_HAND_LENGTH * Math.cos(hourAngle * Math.PI / 180);
|
|
182
|
+
const handY = CENTER + HOUR_HAND_LENGTH * Math.sin(hourAngle * Math.PI / 180);
|
|
183
|
+
|
|
184
|
+
return (
|
|
185
|
+
<line
|
|
186
|
+
x1={CENTER}
|
|
187
|
+
y1={CENTER}
|
|
188
|
+
x2={handX}
|
|
189
|
+
y2={handY}
|
|
190
|
+
stroke="#3b82f6"
|
|
191
|
+
strokeWidth="3"
|
|
192
|
+
strokeLinecap="round"
|
|
193
|
+
/>
|
|
194
|
+
);
|
|
195
|
+
})()}
|
|
196
|
+
|
|
197
|
+
{/* Center dot */}
|
|
198
|
+
<circle cx={CENTER} cy={CENTER} r="4" fill="#3b82f6"/>
|
|
199
|
+
</svg>
|
|
200
|
+
</View>
|
|
201
|
+
);
|
|
202
|
+
} else {
|
|
203
|
+
return (
|
|
204
|
+
<View style={timePickerStyles.clockContainer}>
|
|
205
|
+
<svg width={CLOCK_SIZE} height={CLOCK_SIZE} style={timePickerStyles.clockSvg}>
|
|
206
|
+
{/* Clock face */}
|
|
207
|
+
<circle cx={CENTER} cy={CENTER} r={CLOCK_RADIUS} fill="#f9fafb" stroke="#e5e7eb" strokeWidth="2"/>
|
|
208
|
+
|
|
209
|
+
{/* Minute markers - every 5 minutes */}
|
|
210
|
+
{[...Array(12)].map((_, i) => {
|
|
211
|
+
const minute = i * 5;
|
|
212
|
+
const angle = (i * 30) - 90;
|
|
213
|
+
const x = CENTER + NUMBER_RADIUS * Math.cos(angle * Math.PI / 180);
|
|
214
|
+
const y = CENTER + NUMBER_RADIUS * Math.sin(angle * Math.PI / 180);
|
|
215
|
+
const isSelected = Math.floor(minutes / 5) * 5 === minute;
|
|
216
|
+
|
|
217
|
+
return (
|
|
218
|
+
<g key={i} onClick={() => handleMinuteClick(minute)}>
|
|
219
|
+
<circle
|
|
220
|
+
cx={x}
|
|
221
|
+
cy={y}
|
|
222
|
+
r={CIRCLE_RADIUS}
|
|
223
|
+
fill={isSelected ? '#3b82f6' : 'transparent'}
|
|
224
|
+
stroke={isSelected ? '#3b82f6' : '#e5e7eb'}
|
|
225
|
+
strokeWidth="1"
|
|
226
|
+
style={{ cursor: 'pointer' }}
|
|
227
|
+
/>
|
|
228
|
+
<text
|
|
229
|
+
x={x}
|
|
230
|
+
y={y + 4}
|
|
231
|
+
textAnchor="middle"
|
|
232
|
+
fontSize="12"
|
|
233
|
+
fill={isSelected ? '#ffffff' : '#374151'}
|
|
234
|
+
fontWeight="500"
|
|
235
|
+
style={{ cursor: 'pointer', userSelect: 'none', pointerEvents: 'none' }}
|
|
236
|
+
>
|
|
237
|
+
{minute.toString().padStart(2, '0')}
|
|
238
|
+
</text>
|
|
239
|
+
</g>
|
|
240
|
+
);
|
|
241
|
+
})}
|
|
242
|
+
|
|
243
|
+
{/* Minute hand pointing to selected minute */}
|
|
244
|
+
{(() => {
|
|
245
|
+
const minuteAngle = (minutes * 6) - 90;
|
|
246
|
+
const handX = CENTER + MINUTE_HAND_LENGTH * Math.cos(minuteAngle * Math.PI / 180);
|
|
247
|
+
const handY = CENTER + MINUTE_HAND_LENGTH * Math.sin(minuteAngle * Math.PI / 180);
|
|
248
|
+
|
|
249
|
+
return (
|
|
250
|
+
<line
|
|
251
|
+
x1={CENTER}
|
|
252
|
+
y1={CENTER}
|
|
253
|
+
x2={handX}
|
|
254
|
+
y2={handY}
|
|
255
|
+
stroke="#3b82f6"
|
|
256
|
+
strokeWidth="2"
|
|
257
|
+
strokeLinecap="round"
|
|
258
|
+
/>
|
|
259
|
+
);
|
|
260
|
+
})()}
|
|
261
|
+
|
|
262
|
+
{/* Center dot */}
|
|
263
|
+
<circle cx={CENTER} cy={CENTER} r="4" fill="#3b82f6"/>
|
|
264
|
+
</svg>
|
|
265
|
+
</View>
|
|
266
|
+
);
|
|
267
|
+
}
|
|
268
|
+
};
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
return (
|
|
272
|
+
<View style={[timePickerStyles.container, style]} data-testid={testID}>
|
|
273
|
+
{/* Tab Bar */}
|
|
274
|
+
<View style={timePickerStyles.tabBar}>
|
|
275
|
+
<Button
|
|
276
|
+
onPress={() => setActiveSelection('hour')}
|
|
277
|
+
style={[
|
|
278
|
+
timePickerStyles.tabButton,
|
|
279
|
+
activeSelection === 'hour' ? timePickerStyles.activeTab : timePickerStyles.inactiveTab
|
|
280
|
+
]}
|
|
281
|
+
disabled={disabled}
|
|
282
|
+
>
|
|
283
|
+
Hour
|
|
284
|
+
</Button>
|
|
285
|
+
<Button
|
|
286
|
+
onPress={() => setActiveSelection('minute')}
|
|
287
|
+
style={[
|
|
288
|
+
timePickerStyles.tabButton,
|
|
289
|
+
activeSelection === 'minute' ? timePickerStyles.activeTab : timePickerStyles.inactiveTab
|
|
290
|
+
]}
|
|
291
|
+
disabled={disabled}
|
|
292
|
+
>
|
|
293
|
+
Minute
|
|
294
|
+
</Button>
|
|
295
|
+
</View>
|
|
296
|
+
|
|
297
|
+
{/* Interactive Clock Face */}
|
|
298
|
+
{renderClockFace()}
|
|
299
|
+
|
|
300
|
+
{/* Time Input Row */}
|
|
301
|
+
<View style={timePickerStyles.timeInputRow}>
|
|
302
|
+
<Input
|
|
303
|
+
ref={hourInputRef}
|
|
304
|
+
variant="bare"
|
|
305
|
+
value={hourInputValue}
|
|
306
|
+
onChangeText={(value) => {
|
|
307
|
+
setHourInputValue(value);
|
|
308
|
+
|
|
309
|
+
// Smart focus switching: if user types 2 or higher, focus on minutes
|
|
310
|
+
const num = parseInt(value);
|
|
311
|
+
if (!isNaN(num) && num >= 2 && mode === '12h') {
|
|
312
|
+
// Wait a moment then focus minutes
|
|
313
|
+
setTimeout(() => {
|
|
314
|
+
setActiveSelection('minute');
|
|
315
|
+
setHourInputFocused(false);
|
|
316
|
+
setMinuteInputFocused(true);
|
|
317
|
+
minuteInputRef.current?.focus();
|
|
318
|
+
}, 100);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// Try to update time if value is valid
|
|
322
|
+
const hour = parseInt(value);
|
|
323
|
+
if (!isNaN(hour)) {
|
|
324
|
+
const maxHour = mode === '12h' ? 12 : 23;
|
|
325
|
+
const minHour = mode === '12h' ? 1 : 0;
|
|
326
|
+
|
|
327
|
+
if (hour >= minHour && hour <= maxHour) {
|
|
328
|
+
let hour24 = hour;
|
|
329
|
+
if (mode === '12h') {
|
|
330
|
+
const isPM = hours >= 12;
|
|
331
|
+
if (hour === 12) hour24 = isPM ? 12 : 0;
|
|
332
|
+
else hour24 = isPM ? hour + 12 : hour;
|
|
333
|
+
}
|
|
334
|
+
updateTime(hour24, minutes, seconds);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
}}
|
|
338
|
+
onFocus={() => {
|
|
339
|
+
setActiveSelection('hour');
|
|
340
|
+
setHourInputFocused(true);
|
|
341
|
+
setHourInputValue(String(displayHours));
|
|
342
|
+
}}
|
|
343
|
+
onBlur={() => {
|
|
344
|
+
setHourInputFocused(false);
|
|
345
|
+
// Handle 0 -> 12 conversion for 12h mode
|
|
346
|
+
const hour = parseInt(hourInputValue);
|
|
347
|
+
if (hour === 0 && mode === '12h') {
|
|
348
|
+
const isPM = hours >= 12;
|
|
349
|
+
const hour24 = isPM ? 12 : 0;
|
|
350
|
+
updateTime(hour24, minutes, seconds);
|
|
351
|
+
}
|
|
352
|
+
setHourInputValue(String(displayHours));
|
|
353
|
+
}}
|
|
354
|
+
style={[
|
|
355
|
+
timePickerStyles.timeInput,
|
|
356
|
+
activeSelection === 'hour' ? timePickerStyles.activeInput : {}
|
|
357
|
+
]}
|
|
358
|
+
disabled={disabled}
|
|
359
|
+
/>
|
|
360
|
+
<View style={timePickerStyles.timeSeparator}>:</View>
|
|
361
|
+
<Input
|
|
362
|
+
ref={minuteInputRef}
|
|
363
|
+
variant="bare"
|
|
364
|
+
value={minuteInputValue}
|
|
365
|
+
onChangeText={(value) => {
|
|
366
|
+
setMinuteInputValue(value);
|
|
367
|
+
|
|
368
|
+
// Try to update time if value is valid
|
|
369
|
+
const minute = parseInt(value);
|
|
370
|
+
if (!isNaN(minute)) {
|
|
371
|
+
if (minute >= 0 && minute <= 59) {
|
|
372
|
+
updateTime(hours, minute, seconds);
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
}}
|
|
376
|
+
onFocus={() => {
|
|
377
|
+
setActiveSelection('minute');
|
|
378
|
+
setMinuteInputFocused(true);
|
|
379
|
+
setMinuteInputValue(String(minutes));
|
|
380
|
+
}}
|
|
381
|
+
onBlur={() => {
|
|
382
|
+
setMinuteInputFocused(false);
|
|
383
|
+
setMinuteInputValue(String(minutes).padStart(2, '0'));
|
|
384
|
+
}}
|
|
385
|
+
style={[
|
|
386
|
+
timePickerStyles.timeInput,
|
|
387
|
+
activeSelection === 'minute' ? timePickerStyles.activeInput : {}
|
|
388
|
+
]}
|
|
389
|
+
disabled={disabled}
|
|
390
|
+
/>
|
|
391
|
+
|
|
392
|
+
{mode === '12h' && ampm && (
|
|
393
|
+
<Button
|
|
394
|
+
variant="outlined"
|
|
395
|
+
size="small"
|
|
396
|
+
onPress={toggleAmPm}
|
|
397
|
+
disabled={disabled}
|
|
398
|
+
style={timePickerStyles.ampmButton}
|
|
399
|
+
>
|
|
400
|
+
{ampm}
|
|
401
|
+
</Button>
|
|
402
|
+
)}
|
|
403
|
+
</View>
|
|
404
|
+
</View>
|
|
405
|
+
);
|
|
406
|
+
};
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { ReactNode } from 'react';
|
|
2
|
+
import { ViewStyle } from 'react-native';
|
|
3
|
+
|
|
4
|
+
export interface DateTimePickerProps {
|
|
5
|
+
/** Current selected date and time */
|
|
6
|
+
value?: Date;
|
|
7
|
+
|
|
8
|
+
/** Called when date/time changes */
|
|
9
|
+
onChange: (date: Date | null) => void;
|
|
10
|
+
|
|
11
|
+
/** Minimum selectable date */
|
|
12
|
+
minDate?: Date;
|
|
13
|
+
|
|
14
|
+
/** Maximum selectable date */
|
|
15
|
+
maxDate?: Date;
|
|
16
|
+
|
|
17
|
+
/** Disabled state */
|
|
18
|
+
disabled?: boolean;
|
|
19
|
+
|
|
20
|
+
/** Placeholder text when no date is selected */
|
|
21
|
+
placeholder?: string;
|
|
22
|
+
|
|
23
|
+
/** Label for the picker */
|
|
24
|
+
label?: string;
|
|
25
|
+
|
|
26
|
+
/** Error message to display */
|
|
27
|
+
error?: string;
|
|
28
|
+
|
|
29
|
+
/** Helper text */
|
|
30
|
+
helperText?: string;
|
|
31
|
+
|
|
32
|
+
/** Date format for display (default: 'MM/dd/yyyy HH:mm') */
|
|
33
|
+
format?: string;
|
|
34
|
+
|
|
35
|
+
/** Locale for date formatting */
|
|
36
|
+
locale?: string;
|
|
37
|
+
|
|
38
|
+
/** Size variant */
|
|
39
|
+
size?: 'small' | 'medium' | 'large';
|
|
40
|
+
|
|
41
|
+
/** Visual variant */
|
|
42
|
+
variant?: 'outlined' | 'filled';
|
|
43
|
+
|
|
44
|
+
/** Time picker mode */
|
|
45
|
+
timeMode?: '12h' | '24h';
|
|
46
|
+
|
|
47
|
+
/** Show seconds in time picker */
|
|
48
|
+
showSeconds?: boolean;
|
|
49
|
+
|
|
50
|
+
/** Time step in minutes */
|
|
51
|
+
timeStep?: number;
|
|
52
|
+
|
|
53
|
+
/** Custom styles */
|
|
54
|
+
style?: ViewStyle;
|
|
55
|
+
|
|
56
|
+
/** Test ID for testing */
|
|
57
|
+
testID?: string;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export interface TimePickerProps {
|
|
61
|
+
/** Current selected time */
|
|
62
|
+
value?: Date;
|
|
63
|
+
|
|
64
|
+
/** Called when time is selected */
|
|
65
|
+
onChange: (time: Date) => void;
|
|
66
|
+
|
|
67
|
+
/** Disabled state */
|
|
68
|
+
disabled?: boolean;
|
|
69
|
+
|
|
70
|
+
/** Time picker mode */
|
|
71
|
+
mode?: '12h' | '24h';
|
|
72
|
+
|
|
73
|
+
/** Show seconds */
|
|
74
|
+
showSeconds?: boolean;
|
|
75
|
+
|
|
76
|
+
/** Time step in minutes */
|
|
77
|
+
step?: number;
|
|
78
|
+
|
|
79
|
+
/** Custom styles */
|
|
80
|
+
style?: ViewStyle;
|
|
81
|
+
|
|
82
|
+
/** Test ID for testing */
|
|
83
|
+
testID?: string;
|
|
84
|
+
}
|