@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.
Files changed (50) hide show
  1. package/README.md +88 -0
  2. package/package.json +77 -0
  3. package/src/DatePicker/Calendar.native.tsx +159 -0
  4. package/src/DatePicker/Calendar.styles.tsx +224 -0
  5. package/src/DatePicker/Calendar.tsx +154 -0
  6. package/src/DatePicker/DatePicker.native.tsx +33 -0
  7. package/src/DatePicker/DatePicker.styles.tsx +69 -0
  8. package/src/DatePicker/DatePicker.web.tsx +31 -0
  9. package/src/DatePicker/index.native.ts +3 -0
  10. package/src/DatePicker/index.ts +3 -0
  11. package/src/DatePicker/types.ts +78 -0
  12. package/src/DateRangePicker/DateRangePicker.native.tsx +39 -0
  13. package/src/DateRangePicker/DateRangePicker.styles.tsx +83 -0
  14. package/src/DateRangePicker/DateRangePicker.web.tsx +40 -0
  15. package/src/DateRangePicker/RangeCalendar.native.tsx +267 -0
  16. package/src/DateRangePicker/RangeCalendar.styles.tsx +170 -0
  17. package/src/DateRangePicker/RangeCalendar.web.tsx +275 -0
  18. package/src/DateRangePicker/index.native.ts +3 -0
  19. package/src/DateRangePicker/index.ts +3 -0
  20. package/src/DateRangePicker/types.ts +98 -0
  21. package/src/DateTimePicker/DateTimePicker.native.tsx +82 -0
  22. package/src/DateTimePicker/DateTimePicker.styles.tsx +77 -0
  23. package/src/DateTimePicker/DateTimePicker.tsx +79 -0
  24. package/src/DateTimePicker/TimePicker.native.tsx +204 -0
  25. package/src/DateTimePicker/TimePicker.styles.tsx +116 -0
  26. package/src/DateTimePicker/TimePicker.tsx +406 -0
  27. package/src/DateTimePicker/index.native.ts +3 -0
  28. package/src/DateTimePicker/index.ts +3 -0
  29. package/src/DateTimePicker/types.ts +84 -0
  30. package/src/DateTimeRangePicker/DateTimeRangePicker.native.tsx +213 -0
  31. package/src/DateTimeRangePicker/DateTimeRangePicker.styles.tsx +95 -0
  32. package/src/DateTimeRangePicker/DateTimeRangePicker.web.tsx +141 -0
  33. package/src/DateTimeRangePicker/index.native.ts +2 -0
  34. package/src/DateTimeRangePicker/index.ts +2 -0
  35. package/src/DateTimeRangePicker/types.ts +72 -0
  36. package/src/examples/DatePickerExamples.tsx +274 -0
  37. package/src/examples/index.ts +1 -0
  38. package/src/index.native.ts +16 -0
  39. package/src/index.ts +16 -0
  40. package/src/primitives/CalendarGrid/CalendarGrid.styles.tsx +62 -0
  41. package/src/primitives/CalendarGrid/CalendarGrid.tsx +138 -0
  42. package/src/primitives/CalendarGrid/index.ts +1 -0
  43. package/src/primitives/CalendarHeader/CalendarHeader.styles.tsx +25 -0
  44. package/src/primitives/CalendarHeader/CalendarHeader.tsx +69 -0
  45. package/src/primitives/CalendarHeader/index.ts +1 -0
  46. package/src/primitives/CalendarOverlay/CalendarOverlay.styles.tsx +81 -0
  47. package/src/primitives/CalendarOverlay/CalendarOverlay.tsx +130 -0
  48. package/src/primitives/CalendarOverlay/index.ts +1 -0
  49. package/src/primitives/Wrapper/Wrapper.web.tsx +33 -0
  50. package/src/primitives/Wrapper/index.ts +1 -0
@@ -0,0 +1,170 @@
1
+ import { StyleSheet } from 'react-native-unistyles';
2
+
3
+ export const rangeCalendarStyles = StyleSheet.create((theme) => ({
4
+ container: {
5
+ width: 256,
6
+ },
7
+
8
+ header: {
9
+ flexDirection: 'row',
10
+ justifyContent: 'space-between',
11
+ alignItems: 'center',
12
+ marginBottom: theme.spacing?.md || 16,
13
+ },
14
+
15
+ headerButton: {
16
+ minWidth: 32,
17
+ minHeight: 32,
18
+ paddingHorizontal: theme.spacing?.xs || 8,
19
+ paddingVertical: theme.spacing?.xs || 4,
20
+ },
21
+
22
+ headerTitle: {
23
+ flexDirection: 'row',
24
+ alignItems: 'center',
25
+ gap: theme.spacing?.xs || 8,
26
+ },
27
+
28
+ monthYearButton: {
29
+ paddingHorizontal: theme.spacing?.sm || 8,
30
+ paddingVertical: theme.spacing?.xs || 4,
31
+ },
32
+
33
+ weekdayHeader: {
34
+ display: 'grid',
35
+ gridTemplateColumns: 'repeat(7, 1fr)',
36
+ gap: 2,
37
+ marginBottom: theme.spacing?.xs || 8,
38
+
39
+ // Native fallback
40
+ _native: {
41
+ flexDirection: 'row',
42
+ },
43
+ },
44
+
45
+ weekdayCell: {
46
+ display: 'flex',
47
+ alignItems: 'center',
48
+ justifyContent: 'center',
49
+ paddingVertical: theme.spacing?.xs || 4,
50
+
51
+ // Native fallback
52
+ _native: {
53
+ flex: 1,
54
+ alignItems: 'center',
55
+ paddingVertical: theme.spacing?.xs || 4,
56
+ },
57
+ },
58
+
59
+ weekdayText: {
60
+ fontSize: theme.typography?.sizes?.small || 12,
61
+ fontWeight: '500',
62
+ color: theme.colors?.text?.secondary || '#6b7280',
63
+ },
64
+
65
+ calendarGrid: {
66
+ display: 'grid',
67
+ gridTemplateColumns: 'repeat(7, 1fr)',
68
+ gap: 2,
69
+ marginBottom: theme.spacing?.xs || 8,
70
+
71
+ // Native fallback
72
+ _native: {
73
+ flexDirection: 'row',
74
+ flexWrap: 'wrap',
75
+ },
76
+ },
77
+
78
+ dayCell: {
79
+ display: 'flex',
80
+ alignItems: 'center',
81
+ justifyContent: 'center',
82
+ aspectRatio: '1',
83
+ minHeight: 32,
84
+
85
+ // Native specific sizing
86
+ _native: {
87
+ width: '14.28%', // 100% / 7 days
88
+ aspectRatio: 1,
89
+ alignItems: 'center',
90
+ justifyContent: 'center',
91
+ },
92
+ },
93
+
94
+ dayButton: {
95
+ width: '100%',
96
+ height: '100%',
97
+ maxWidth: 36,
98
+ maxHeight: 36,
99
+ minWidth: 28,
100
+ minHeight: 28,
101
+ padding: 0,
102
+ borderRadius: theme.borderRadius?.sm || 6,
103
+ fontSize: theme.typography?.sizes?.small || 13,
104
+
105
+ variants: {
106
+ inRange: {
107
+ true: {
108
+ backgroundColor: theme.colors?.accent?.primary + '20' || '#3b82f620',
109
+ borderRadius: 0,
110
+ },
111
+ false: {},
112
+ },
113
+ isStart: {
114
+ true: {
115
+ backgroundColor: theme.colors?.accent?.primary || '#3b82f6',
116
+ borderTopLeftRadius: theme.borderRadius?.sm || 6,
117
+ borderBottomLeftRadius: theme.borderRadius?.sm || 6,
118
+ },
119
+ false: {},
120
+ },
121
+ isEnd: {
122
+ true: {
123
+ backgroundColor: theme.colors?.accent?.primary || '#3b82f6',
124
+ borderTopRightRadius: theme.borderRadius?.sm || 6,
125
+ borderBottomRightRadius: theme.borderRadius?.sm || 6,
126
+ },
127
+ false: {},
128
+ },
129
+ isSelected: {
130
+ true: {
131
+ backgroundColor: theme.colors?.accent?.primary || '#3b82f6',
132
+ fontWeight: '600',
133
+ },
134
+ false: {
135
+ fontWeight: '400',
136
+ },
137
+ },
138
+ },
139
+
140
+ // Native specific styling
141
+ _native: {
142
+ width: 28,
143
+ height: 28,
144
+ minWidth: 28,
145
+ minHeight: 28,
146
+ borderRadius: theme.borderRadius?.sm || 6,
147
+ },
148
+ },
149
+
150
+ rangePresets: {
151
+ paddingTop: theme.spacing?.sm || 12,
152
+ borderTopWidth: 1,
153
+ borderTopColor: theme.colors?.border?.secondary || '#f3f4f6',
154
+ gap: theme.spacing?.xs || 8,
155
+
156
+ // Web specific border
157
+ _web: {
158
+ borderTop: `1px solid ${theme.colors?.border?.secondary || '#f3f4f6'}`,
159
+ },
160
+ },
161
+
162
+ presetButton: {
163
+ width: '100%',
164
+ justifyContent: 'flex-start',
165
+ },
166
+
167
+ clearButton: {
168
+ width: '100%',
169
+ },
170
+ }));
@@ -0,0 +1,275 @@
1
+ import React, { useState, useMemo } from 'react';
2
+ import { View, Text, Button } from '@idealyst/components';
3
+ import { getWebProps } from 'react-native-unistyles/web';
4
+ import { RangeCalendarProps, DateRange } from './types';
5
+ import { rangeCalendarStyles } from './RangeCalendar.styles';
6
+
7
+ export const RangeCalendar: React.FC<RangeCalendarProps> = ({
8
+ value = {},
9
+ onChange,
10
+ minDate,
11
+ maxDate,
12
+ disabled = false,
13
+ currentMonth: controlledCurrentMonth,
14
+ onMonthChange,
15
+ allowSameDay = true,
16
+ maxDays,
17
+ style,
18
+ testID,
19
+ }) => {
20
+ const [internalCurrentMonth, setInternalCurrentMonth] = useState(
21
+ controlledCurrentMonth || value?.startDate || new Date()
22
+ );
23
+ const [selectingEnd, setSelectingEnd] = useState(false);
24
+
25
+ const currentMonth = controlledCurrentMonth || internalCurrentMonth;
26
+
27
+ const handleMonthChange = (newMonth: Date) => {
28
+ if (onMonthChange) {
29
+ onMonthChange(newMonth);
30
+ } else {
31
+ setInternalCurrentMonth(newMonth);
32
+ }
33
+ };
34
+
35
+ const { monthStart, monthEnd, daysInMonth, startingDayOfWeek } = useMemo(() => {
36
+ const monthStart = new Date(currentMonth.getFullYear(), currentMonth.getMonth(), 1);
37
+ const monthEnd = new Date(currentMonth.getFullYear(), currentMonth.getMonth() + 1, 0);
38
+ const daysInMonth = monthEnd.getDate();
39
+ const startingDayOfWeek = monthStart.getDay();
40
+
41
+ return { monthStart, monthEnd, daysInMonth, startingDayOfWeek };
42
+ }, [currentMonth]);
43
+
44
+ const isDateDisabled = (date: Date): boolean => {
45
+ if (disabled) return true;
46
+ if (minDate && date < minDate) return true;
47
+ if (maxDate && date > maxDate) return true;
48
+ return false;
49
+ };
50
+
51
+ const isDateInRange = (date: Date): boolean => {
52
+ const { startDate, endDate } = value || {};
53
+ if (!startDate || !endDate) return false;
54
+ return date > startDate && date < endDate;
55
+ };
56
+
57
+ const isDateRangeStart = (date: Date): boolean => {
58
+ const { startDate } = value || {};
59
+ if (!startDate) return false;
60
+ return (
61
+ date.getDate() === startDate.getDate() &&
62
+ date.getMonth() === startDate.getMonth() &&
63
+ date.getFullYear() === startDate.getFullYear()
64
+ );
65
+ };
66
+
67
+ const isDateRangeEnd = (date: Date): boolean => {
68
+ const { endDate } = value || {};
69
+ if (!endDate) return false;
70
+ return (
71
+ date.getDate() === endDate.getDate() &&
72
+ date.getMonth() === endDate.getMonth() &&
73
+ date.getFullYear() === endDate.getFullYear()
74
+ );
75
+ };
76
+
77
+ const isDateSelected = (date: Date): boolean => {
78
+ return isDateRangeStart(date) || isDateRangeEnd(date);
79
+ };
80
+
81
+ const handleDateClick = (date: Date) => {
82
+ if (isDateDisabled(date)) return;
83
+
84
+ const { startDate, endDate } = value || {};
85
+
86
+ // If no range is selected or we're starting fresh
87
+ if (!startDate || (startDate && endDate)) {
88
+ onChange({ startDate: date, endDate: undefined });
89
+ setSelectingEnd(true);
90
+ return;
91
+ }
92
+
93
+ // If we have a start date but no end date
94
+ if (startDate && !endDate) {
95
+ let newStartDate = startDate;
96
+ let newEndDate = date;
97
+
98
+ // Swap if end date is before start date
99
+ if (date < startDate) {
100
+ newStartDate = date;
101
+ newEndDate = startDate;
102
+ }
103
+
104
+ // Check if same day selection is allowed
105
+ if (!allowSameDay && newStartDate.getTime() === newEndDate.getTime()) {
106
+ return;
107
+ }
108
+
109
+ // Check max days constraint
110
+ if (maxDays) {
111
+ const daysDiff = Math.ceil((newEndDate.getTime() - newStartDate.getTime()) / (1000 * 60 * 60 * 24));
112
+ if (daysDiff > maxDays) {
113
+ return;
114
+ }
115
+ }
116
+
117
+ onChange({ startDate: newStartDate, endDate: newEndDate });
118
+ setSelectingEnd(false);
119
+ }
120
+ };
121
+
122
+ const goToPreviousMonth = () => {
123
+ const newMonth = new Date(currentMonth.getFullYear(), currentMonth.getMonth() - 1, 1);
124
+ handleMonthChange(newMonth);
125
+ };
126
+
127
+ const goToNextMonth = () => {
128
+ const newMonth = new Date(currentMonth.getFullYear(), currentMonth.getMonth() + 1, 1);
129
+ handleMonthChange(newMonth);
130
+ };
131
+
132
+ const handlePresetRange = (days: number) => {
133
+ const startDate = new Date();
134
+ const endDate = new Date();
135
+ endDate.setDate(endDate.getDate() + days - 1);
136
+
137
+ onChange({ startDate, endDate });
138
+ setSelectingEnd(false);
139
+ };
140
+
141
+ const clearRange = () => {
142
+ onChange({});
143
+ setSelectingEnd(false);
144
+ };
145
+
146
+ const monthName = currentMonth.toLocaleDateString('en-US', { month: 'long', year: 'numeric' });
147
+
148
+ // Create calendar grid
149
+ const calendarDays = [];
150
+
151
+ // Add empty cells for days before month starts
152
+ for (let i = 0; i < startingDayOfWeek; i++) {
153
+ calendarDays.push(null);
154
+ }
155
+
156
+ // Add days of the month
157
+ for (let day = 1; day <= daysInMonth; day++) {
158
+ const date = new Date(currentMonth.getFullYear(), currentMonth.getMonth(), day);
159
+ calendarDays.push(date);
160
+ }
161
+
162
+ rangeCalendarStyles.useVariants({});
163
+
164
+ const containerProps = getWebProps([rangeCalendarStyles.container, style]);
165
+ const headerProps = getWebProps([rangeCalendarStyles.header]);
166
+ const headerTitleProps = getWebProps([rangeCalendarStyles.headerTitle]);
167
+
168
+ return (
169
+ <div {...containerProps} data-testid={testID}>
170
+ {/* Header */}
171
+ <div {...headerProps}>
172
+ <Button
173
+ variant="text"
174
+ size="small"
175
+ onPress={goToPreviousMonth}
176
+ disabled={disabled}
177
+ style={getWebProps([rangeCalendarStyles.headerButton]).style}
178
+ >
179
+
180
+ </Button>
181
+
182
+ <div {...headerTitleProps}>
183
+ <Text weight="semibold">{monthName}</Text>
184
+ </div>
185
+
186
+ <Button
187
+ variant="text"
188
+ size="small"
189
+ onPress={goToNextMonth}
190
+ disabled={disabled}
191
+ style={getWebProps([rangeCalendarStyles.headerButton]).style}
192
+ >
193
+
194
+ </Button>
195
+ </div>
196
+
197
+ {/* Weekday headers */}
198
+ <div {...getWebProps([rangeCalendarStyles.weekdayHeader])}>
199
+ {['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa'].map((day) => (
200
+ <div key={day} {...getWebProps([rangeCalendarStyles.weekdayCell])}>
201
+ <div {...getWebProps([rangeCalendarStyles.weekdayText])}>
202
+ {day}
203
+ </div>
204
+ </div>
205
+ ))}
206
+ </div>
207
+
208
+ {/* Calendar grid */}
209
+ <div {...getWebProps([rangeCalendarStyles.calendarGrid])}>
210
+ {calendarDays.map((date, index) => (
211
+ <div key={index} {...getWebProps([rangeCalendarStyles.dayCell])}>
212
+ {date && (
213
+ <Button
214
+ variant="text"
215
+ disabled={isDateDisabled(date)}
216
+ onPress={() => handleDateClick(date)}
217
+ size="small"
218
+ style={{
219
+ ...getWebProps([rangeCalendarStyles.dayButton]).style,
220
+ backgroundColor: isDateSelected(date)
221
+ ? '#3b82f6'
222
+ : isDateInRange(date)
223
+ ? '#3b82f620'
224
+ : 'transparent',
225
+ color: isDateSelected(date) ? 'white' : 'black',
226
+ fontWeight: isDateSelected(date) ? '600' : '400',
227
+ borderRadius: isDateRangeStart(date)
228
+ ? '6px 0 0 6px'
229
+ : isDateRangeEnd(date)
230
+ ? '0 6px 6px 0'
231
+ : isDateInRange(date)
232
+ ? '0'
233
+ : '6px',
234
+ }}
235
+ >
236
+ {date.getDate()}
237
+ </Button>
238
+ )}
239
+ </div>
240
+ ))}
241
+ </div>
242
+
243
+ {/* Range presets */}
244
+ <div {...getWebProps([rangeCalendarStyles.rangePresets])}>
245
+ <Button
246
+ variant="text"
247
+ size="small"
248
+ onPress={() => handlePresetRange(7)}
249
+ disabled={disabled}
250
+ style={getWebProps([rangeCalendarStyles.presetButton]).style}
251
+ >
252
+ Next 7 days
253
+ </Button>
254
+ <Button
255
+ variant="text"
256
+ size="small"
257
+ onPress={() => handlePresetRange(30)}
258
+ disabled={disabled}
259
+ style={getWebProps([rangeCalendarStyles.presetButton]).style}
260
+ >
261
+ Next 30 days
262
+ </Button>
263
+ <Button
264
+ variant="outlined"
265
+ size="small"
266
+ onPress={clearRange}
267
+ disabled={disabled}
268
+ style={getWebProps([rangeCalendarStyles.clearButton]).style}
269
+ >
270
+ Clear Range
271
+ </Button>
272
+ </div>
273
+ </div>
274
+ );
275
+ };
@@ -0,0 +1,3 @@
1
+ export { default as DateRangePicker } from './DateRangePicker.native';
2
+ export { RangeCalendar } from './RangeCalendar.native';
3
+ export * from './types';
@@ -0,0 +1,3 @@
1
+ export { default as DateRangePicker } from './DateRangePicker.web';
2
+ export { RangeCalendar } from './RangeCalendar.web';
3
+ export * from './types';
@@ -0,0 +1,98 @@
1
+ import { ReactNode } from 'react';
2
+ import { ViewStyle } from 'react-native';
3
+
4
+ export interface DateRange {
5
+ /** Start date of the range */
6
+ startDate?: Date;
7
+
8
+ /** End date of the range */
9
+ endDate?: Date;
10
+ }
11
+
12
+ export interface DateRangePickerProps {
13
+ /** Current selected date range */
14
+ value?: DateRange;
15
+
16
+ /** Called when date range changes */
17
+ onChange: (range: DateRange | null) => void;
18
+
19
+ /** Minimum selectable date */
20
+ minDate?: Date;
21
+
22
+ /** Maximum selectable date */
23
+ maxDate?: Date;
24
+
25
+ /** Disabled state */
26
+ disabled?: boolean;
27
+
28
+ /** Placeholder text when no range is selected */
29
+ placeholder?: string;
30
+
31
+ /** Label for the picker */
32
+ label?: string;
33
+
34
+ /** Error message to display */
35
+ error?: string;
36
+
37
+ /** Helper text */
38
+ helperText?: string;
39
+
40
+ /** Date format for display (default: 'MM/dd/yyyy') */
41
+ format?: string;
42
+
43
+ /** Locale for date formatting */
44
+ locale?: string;
45
+
46
+ /** Size variant */
47
+ size?: 'small' | 'medium' | 'large';
48
+
49
+ /** Visual variant */
50
+ variant?: 'outlined' | 'filled';
51
+
52
+ /** Allow same day selection for start and end */
53
+ allowSameDay?: boolean;
54
+
55
+ /** Maximum number of days in range */
56
+ maxDays?: number;
57
+
58
+ /** Custom styles */
59
+ style?: ViewStyle;
60
+
61
+ /** Test ID for testing */
62
+ testID?: string;
63
+ }
64
+
65
+ export interface RangeCalendarProps {
66
+ /** Current selected date range */
67
+ value?: DateRange;
68
+
69
+ /** Called when range is selected */
70
+ onChange: (range: DateRange) => void;
71
+
72
+ /** Minimum selectable date */
73
+ minDate?: Date;
74
+
75
+ /** Maximum selectable date */
76
+ maxDate?: Date;
77
+
78
+ /** Disabled state */
79
+ disabled?: boolean;
80
+
81
+ /** Current month being viewed */
82
+ currentMonth?: Date;
83
+
84
+ /** Called when month changes */
85
+ onMonthChange?: (month: Date) => void;
86
+
87
+ /** Allow same day selection */
88
+ allowSameDay?: boolean;
89
+
90
+ /** Maximum number of days in range */
91
+ maxDays?: number;
92
+
93
+ /** Custom styles */
94
+ style?: ViewStyle;
95
+
96
+ /** Test ID for testing */
97
+ testID?: string;
98
+ }
@@ -0,0 +1,82 @@
1
+ import React from 'react';
2
+ import { View, Text } from '@idealyst/components';
3
+ import { DateTimePickerProps } from './types';
4
+ import { Calendar } from '../DatePicker/Calendar.native';
5
+ import { TimePicker } from './TimePicker.native';
6
+ import { dateTimePickerStyles } from './DateTimePicker.styles';
7
+
8
+ const DateTimePicker: React.FC<DateTimePickerProps> = ({
9
+ value,
10
+ onChange,
11
+ minDate,
12
+ maxDate,
13
+ disabled = false,
14
+ timeMode = '12h',
15
+ showSeconds = false,
16
+ timeStep = 1,
17
+ style,
18
+ testID,
19
+ }) => {
20
+
21
+ const handleDateChange = (newDate: Date) => {
22
+ if (value) {
23
+ // Preserve the time from current value
24
+ const updatedDate = new Date(newDate);
25
+ updatedDate.setHours(value.getHours(), value.getMinutes(), value.getSeconds());
26
+ onChange(updatedDate);
27
+ } else {
28
+ onChange(newDate);
29
+ }
30
+ };
31
+
32
+ const handleTimeChange = (newTime: Date) => {
33
+ if (value) {
34
+ // Update time while preserving the date
35
+ const updatedDate = new Date(value);
36
+ updatedDate.setHours(newTime.getHours(), newTime.getMinutes(), newTime.getSeconds());
37
+ onChange(updatedDate);
38
+ } else {
39
+ // If no date is selected, use today's date with the new time
40
+ const today = new Date();
41
+ today.setHours(newTime.getHours(), newTime.getMinutes(), newTime.getSeconds());
42
+ onChange(today);
43
+ }
44
+ };
45
+
46
+ dateTimePickerStyles.useVariants({});
47
+
48
+ return (
49
+ <View style={[dateTimePickerStyles.container, style]} testID={testID}>
50
+ {/* Date Section */}
51
+ <View style={dateTimePickerStyles.section}>
52
+ <Text style={dateTimePickerStyles.sectionLabel}>Date</Text>
53
+ <View style={dateTimePickerStyles.sectionContent}>
54
+ <Calendar
55
+ value={value}
56
+ onChange={handleDateChange}
57
+ minDate={minDate}
58
+ maxDate={maxDate}
59
+ disabled={disabled}
60
+ />
61
+ </View>
62
+ </View>
63
+
64
+ {/* Time Section */}
65
+ <View style={dateTimePickerStyles.section}>
66
+ <Text style={dateTimePickerStyles.sectionLabel}>Time</Text>
67
+ <View style={dateTimePickerStyles.sectionContent}>
68
+ <TimePicker
69
+ value={value || new Date()}
70
+ onChange={handleTimeChange}
71
+ disabled={disabled}
72
+ mode={timeMode}
73
+ showSeconds={showSeconds}
74
+ step={timeStep}
75
+ />
76
+ </View>
77
+ </View>
78
+ </View>
79
+ );
80
+ };
81
+
82
+ export default DateTimePicker;
@@ -0,0 +1,77 @@
1
+ import { StyleSheet } from 'react-native-unistyles';
2
+
3
+ export const dateTimePickerStyles = StyleSheet.create((theme) => ({
4
+ container: {
5
+ gap: theme.spacing?.md || 16,
6
+ },
7
+
8
+ label: {
9
+ fontSize: theme.typography?.sizes?.small || 14,
10
+ fontWeight: '600',
11
+ color: theme.colors?.text?.primary || '#111827',
12
+ marginBottom: theme.spacing?.xs || 4,
13
+ },
14
+
15
+ picker: {
16
+ gap: theme.spacing?.md || 16,
17
+ },
18
+
19
+ selectedDateTimeHeader: {
20
+ padding: theme.spacing?.sm || 12,
21
+ backgroundColor: theme.colors?.surface?.secondary || '#f9fafb',
22
+ borderRadius: theme.borderRadius?.md || 8,
23
+ borderWidth: 1,
24
+ borderColor: theme.colors?.border?.primary || '#e5e7eb',
25
+ gap: theme.spacing?.xs || 4,
26
+
27
+ _web: {
28
+ border: `1px solid ${theme.colors?.border?.primary || '#e5e7eb'}`,
29
+ },
30
+ },
31
+
32
+ selectedDateTimeLabel: {
33
+ fontSize: theme.typography?.sizes?.small || 12,
34
+ fontWeight: '500',
35
+ color: theme.colors?.text?.secondary || '#6b7280',
36
+ },
37
+
38
+ selectedDateTimeValue: {
39
+ fontSize: theme.typography?.sizes?.medium || 16,
40
+ fontWeight: '600',
41
+ color: theme.colors?.text?.primary || '#111827',
42
+ },
43
+
44
+ section: {
45
+ gap: theme.spacing?.xs || 8,
46
+ },
47
+
48
+ sectionLabel: {
49
+ fontSize: theme.typography?.sizes?.small || 14,
50
+ fontWeight: '600',
51
+ color: theme.colors?.text?.primary || '#111827',
52
+ },
53
+
54
+ sectionContent: {
55
+ padding: theme.spacing?.sm || 12,
56
+ borderWidth: 1,
57
+ borderColor: theme.colors?.border?.primary || '#e5e7eb',
58
+ borderRadius: theme.borderRadius?.md || 8,
59
+ backgroundColor: theme.colors?.surface?.primary || '#ffffff',
60
+
61
+ _web: {
62
+ border: `1px solid ${theme.colors?.border?.primary || '#e5e7eb'}`,
63
+ },
64
+ },
65
+
66
+ errorText: {
67
+ fontSize: theme.typography?.sizes?.small || 12,
68
+ color: theme.colors?.semantic?.error || '#dc2626',
69
+ marginTop: theme.spacing?.xs || 4,
70
+ },
71
+
72
+ helperText: {
73
+ fontSize: theme.typography?.sizes?.small || 12,
74
+ color: theme.colors?.text?.secondary || '#6b7280',
75
+ marginTop: theme.spacing?.xs || 4,
76
+ },
77
+ }));