@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,213 @@
1
+ import React from 'react';
2
+ import { View, Text } from '@idealyst/components';
3
+ import { DateTimeRangePickerProps, DateTimeRange } from './types';
4
+ import { RangeCalendar } from '../DateRangePicker/RangeCalendar.native';
5
+ import { TimePicker } from '../DateTimePicker/TimePicker.native';
6
+ import { dateTimeRangePickerStyles } from './DateTimeRangePicker.styles';
7
+
8
+ const DateTimeRangePicker: React.FC<DateTimeRangePickerProps> = ({
9
+ value,
10
+ onChange,
11
+ minDate,
12
+ maxDate,
13
+ disabled = false,
14
+ placeholder = 'Select date/time range',
15
+ label,
16
+ error,
17
+ helperText,
18
+ format = 'MM/dd/yyyy HH:mm',
19
+ locale,
20
+ size = 'medium',
21
+ variant = 'outlined',
22
+ timeMode = '12h',
23
+ showSeconds = false,
24
+ timeStep = 1,
25
+ allowSameDay = true,
26
+ maxDays,
27
+ style,
28
+ testID,
29
+ }) => {
30
+ const formatDateTime = (date: Date): string => {
31
+ const month = String(date.getMonth() + 1).padStart(2, '0');
32
+ const day = String(date.getDate()).padStart(2, '0');
33
+ const year = date.getFullYear();
34
+
35
+ let hours = date.getHours();
36
+ const minutes = String(date.getMinutes()).padStart(2, '0');
37
+ const seconds = String(date.getSeconds()).padStart(2, '0');
38
+
39
+ let timeString = '';
40
+ if (timeMode === '12h') {
41
+ const ampm = hours >= 12 ? 'PM' : 'AM';
42
+ hours = hours === 0 ? 12 : hours > 12 ? hours - 12 : hours;
43
+ timeString = showSeconds
44
+ ? `${String(hours).padStart(2, '0')}:${minutes}:${seconds} ${ampm}`
45
+ : `${String(hours).padStart(2, '0')}:${minutes} ${ampm}`;
46
+ } else {
47
+ timeString = showSeconds
48
+ ? `${String(hours).padStart(2, '0')}:${minutes}:${seconds}`
49
+ : `${String(hours).padStart(2, '0')}:${minutes}`;
50
+ }
51
+
52
+ return `${month}/${day}/${year} ${timeString}`;
53
+ };
54
+
55
+ const formatRange = (range: DateTimeRange): string => {
56
+ if (!range.startDate) return '';
57
+ if (!range.endDate) return `${formatDateTime(range.startDate)} - ...`;
58
+ return `${formatDateTime(range.startDate)} - ${formatDateTime(range.endDate)}`;
59
+ };
60
+
61
+ const handleDateRangeChange = (newRange: { startDate?: Date; endDate?: Date }) => {
62
+ // Preserve existing times when date changes
63
+ const updatedRange: DateTimeRange = {};
64
+
65
+ if (newRange.startDate) {
66
+ updatedRange.startDate = new Date(newRange.startDate);
67
+ if (value?.startDate) {
68
+ updatedRange.startDate.setHours(
69
+ value.startDate.getHours(),
70
+ value.startDate.getMinutes(),
71
+ value.startDate.getSeconds()
72
+ );
73
+ }
74
+ }
75
+
76
+ if (newRange.endDate) {
77
+ updatedRange.endDate = new Date(newRange.endDate);
78
+ if (value?.endDate) {
79
+ updatedRange.endDate.setHours(
80
+ value.endDate.getHours(),
81
+ value.endDate.getMinutes(),
82
+ value.endDate.getSeconds()
83
+ );
84
+ }
85
+ }
86
+
87
+ onChange(updatedRange.startDate || updatedRange.endDate ? updatedRange : null);
88
+ };
89
+
90
+ const handleStartTimeChange = (newTime: Date) => {
91
+ if (value?.startDate) {
92
+ const updatedDate = new Date(value.startDate);
93
+ updatedDate.setHours(newTime.getHours(), newTime.getMinutes(), newTime.getSeconds());
94
+ onChange({ ...value, startDate: updatedDate });
95
+ }
96
+ };
97
+
98
+ const handleEndTimeChange = (newTime: Date) => {
99
+ if (value?.endDate) {
100
+ const updatedDate = new Date(value.endDate);
101
+ updatedDate.setHours(newTime.getHours(), newTime.getMinutes(), newTime.getSeconds());
102
+ onChange({ ...value, endDate: updatedDate });
103
+ }
104
+ };
105
+
106
+ const daysDifference = (range: DateTimeRange): number => {
107
+ if (!range.startDate || !range.endDate) return 0;
108
+ const diffTime = range.endDate.getTime() - range.startDate.getTime();
109
+ return Math.ceil(diffTime / (1000 * 60 * 60 * 24));
110
+ };
111
+
112
+ const hoursDifference = (range: DateTimeRange): number => {
113
+ if (!range.startDate || !range.endDate) return 0;
114
+ const diffTime = range.endDate.getTime() - range.startDate.getTime();
115
+ return Math.ceil(diffTime / (1000 * 60 * 60));
116
+ };
117
+
118
+ dateTimeRangePickerStyles.useVariants({});
119
+
120
+ return (
121
+ <View style={[dateTimeRangePickerStyles.container, style]} testID={testID}>
122
+ {label && (
123
+ <Text style={dateTimeRangePickerStyles.label}>
124
+ {label}
125
+ </Text>
126
+ )}
127
+
128
+ <View style={dateTimeRangePickerStyles.picker}>
129
+ {/* Selected Range Summary */}
130
+ {value?.startDate && value?.endDate && (
131
+ <View style={dateTimeRangePickerStyles.selectedRangeHeader}>
132
+ <Text style={dateTimeRangePickerStyles.selectedRangeLabel}>
133
+ Selected Range ({daysDifference(value)} days, {hoursDifference(value)} hours):
134
+ </Text>
135
+ <Text style={dateTimeRangePickerStyles.selectedRangeValue}>
136
+ {formatRange(value)}
137
+ </Text>
138
+ </View>
139
+ )}
140
+
141
+ {/* Date Section */}
142
+ <View style={dateTimeRangePickerStyles.section}>
143
+ <Text style={dateTimeRangePickerStyles.sectionLabel}>Date Range</Text>
144
+ <View style={dateTimeRangePickerStyles.sectionContent}>
145
+ <RangeCalendar
146
+ value={value || {}}
147
+ onChange={handleDateRangeChange}
148
+ minDate={minDate}
149
+ maxDate={maxDate}
150
+ disabled={disabled}
151
+ allowSameDay={allowSameDay}
152
+ maxDays={maxDays}
153
+ />
154
+ </View>
155
+ </View>
156
+
157
+ {/* Time Section */}
158
+ {(value?.startDate || value?.endDate) && (
159
+ <View style={dateTimeRangePickerStyles.section}>
160
+ <Text style={dateTimeRangePickerStyles.sectionLabel}>Time Selection</Text>
161
+ <View style={dateTimeRangePickerStyles.sectionContent}>
162
+ <View style={dateTimeRangePickerStyles.timeSection}>
163
+ {/* Start Time */}
164
+ {value?.startDate && (
165
+ <View style={dateTimeRangePickerStyles.timeGroup}>
166
+ <Text style={dateTimeRangePickerStyles.timeGroupLabel}>Start Time</Text>
167
+ <TimePicker
168
+ value={value.startDate}
169
+ onChange={handleStartTimeChange}
170
+ disabled={disabled}
171
+ mode={timeMode}
172
+ showSeconds={showSeconds}
173
+ step={timeStep}
174
+ />
175
+ </View>
176
+ )}
177
+
178
+ {/* End Time */}
179
+ {value?.endDate && (
180
+ <View style={dateTimeRangePickerStyles.timeGroup}>
181
+ <Text style={dateTimeRangePickerStyles.timeGroupLabel}>End Time</Text>
182
+ <TimePicker
183
+ value={value.endDate}
184
+ onChange={handleEndTimeChange}
185
+ disabled={disabled}
186
+ mode={timeMode}
187
+ showSeconds={showSeconds}
188
+ step={timeStep}
189
+ />
190
+ </View>
191
+ )}
192
+ </View>
193
+ </View>
194
+ </View>
195
+ )}
196
+ </View>
197
+
198
+ {error && (
199
+ <Text style={dateTimeRangePickerStyles.errorText}>
200
+ {error}
201
+ </Text>
202
+ )}
203
+
204
+ {helperText && !error && (
205
+ <Text style={dateTimeRangePickerStyles.helperText}>
206
+ {helperText}
207
+ </Text>
208
+ )}
209
+ </View>
210
+ );
211
+ };
212
+
213
+ export default DateTimeRangePicker;
@@ -0,0 +1,95 @@
1
+ import { StyleSheet } from 'react-native-unistyles';
2
+
3
+ export const dateTimeRangePickerStyles = 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
+ selectedRangeHeader: {
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
+ selectedRangeLabel: {
33
+ fontSize: theme.typography?.sizes?.small || 12,
34
+ fontWeight: '500',
35
+ color: theme.colors?.text?.secondary || '#6b7280',
36
+ },
37
+
38
+ selectedRangeValue: {
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
+ timeSection: {
67
+ flexDirection: 'row',
68
+ alignItems: 'flex-start',
69
+ gap: theme.spacing?.lg || 24,
70
+ },
71
+
72
+ timeGroup: {
73
+ flex: 1,
74
+ gap: theme.spacing?.xs || 8,
75
+ alignItems: 'center',
76
+ },
77
+
78
+ timeGroupLabel: {
79
+ fontSize: theme.typography?.sizes?.small || 12,
80
+ fontWeight: '500',
81
+ color: theme.colors?.text?.secondary || '#6b7280',
82
+ },
83
+
84
+ errorText: {
85
+ fontSize: theme.typography?.sizes?.small || 12,
86
+ color: theme.colors?.semantic?.error || '#dc2626',
87
+ marginTop: theme.spacing?.xs || 4,
88
+ },
89
+
90
+ helperText: {
91
+ fontSize: theme.typography?.sizes?.small || 12,
92
+ color: theme.colors?.text?.secondary || '#6b7280',
93
+ marginTop: theme.spacing?.xs || 4,
94
+ },
95
+ }));
@@ -0,0 +1,141 @@
1
+ import React from 'react';
2
+ import { View, Text } from '@idealyst/components';
3
+ import { getWebProps } from 'react-native-unistyles/web';
4
+ import { DateTimeRangePickerProps, DateTimeRange } from './types';
5
+ import { RangeCalendar } from '../DateRangePicker/RangeCalendar.web';
6
+ import { TimePicker } from '../DateTimePicker/TimePicker';
7
+ import { dateTimeRangePickerStyles } from './DateTimeRangePicker.styles';
8
+
9
+ const DateTimeRangePicker: React.FC<DateTimeRangePickerProps> = ({
10
+ value,
11
+ onChange,
12
+ minDate,
13
+ maxDate,
14
+ disabled = false,
15
+ timeMode = '12h',
16
+ showSeconds = false,
17
+ timeStep = 1,
18
+ allowSameDay = true,
19
+ maxDays,
20
+ style,
21
+ testID,
22
+ }) => {
23
+
24
+ const handleDateRangeChange = (newRange: { startDate?: Date; endDate?: Date }) => {
25
+ // Preserve existing times when date changes
26
+ const updatedRange: DateTimeRange = {};
27
+
28
+ if (newRange.startDate) {
29
+ updatedRange.startDate = new Date(newRange.startDate);
30
+ if (value?.startDate) {
31
+ updatedRange.startDate.setHours(
32
+ value.startDate.getHours(),
33
+ value.startDate.getMinutes(),
34
+ value.startDate.getSeconds()
35
+ );
36
+ }
37
+ }
38
+
39
+ if (newRange.endDate) {
40
+ updatedRange.endDate = new Date(newRange.endDate);
41
+ if (value?.endDate) {
42
+ updatedRange.endDate.setHours(
43
+ value.endDate.getHours(),
44
+ value.endDate.getMinutes(),
45
+ value.endDate.getSeconds()
46
+ );
47
+ }
48
+ }
49
+
50
+ onChange(updatedRange.startDate || updatedRange.endDate ? updatedRange : null);
51
+ };
52
+
53
+ const handleStartTimeChange = (newTime: Date) => {
54
+ if (value?.startDate) {
55
+ const updatedDate = new Date(value.startDate);
56
+ updatedDate.setHours(newTime.getHours(), newTime.getMinutes(), newTime.getSeconds());
57
+ onChange({ ...value, startDate: updatedDate });
58
+ }
59
+ };
60
+
61
+ const handleEndTimeChange = (newTime: Date) => {
62
+ if (value?.endDate) {
63
+ const updatedDate = new Date(value.endDate);
64
+ updatedDate.setHours(newTime.getHours(), newTime.getMinutes(), newTime.getSeconds());
65
+ onChange({ ...value, endDate: updatedDate });
66
+ }
67
+ };
68
+
69
+
70
+ dateTimeRangePickerStyles.useVariants({});
71
+
72
+ const containerProps = getWebProps([dateTimeRangePickerStyles.container, style]);
73
+ const sectionProps = getWebProps([dateTimeRangePickerStyles.section]);
74
+ const sectionLabelProps = getWebProps([dateTimeRangePickerStyles.sectionLabel]);
75
+ const sectionContentProps = getWebProps([dateTimeRangePickerStyles.sectionContent]);
76
+ const timeSectionProps = getWebProps([dateTimeRangePickerStyles.timeSection]);
77
+ const timeGroupProps = getWebProps([dateTimeRangePickerStyles.timeGroup]);
78
+ const timeGroupLabelProps = getWebProps([dateTimeRangePickerStyles.timeGroupLabel]);
79
+
80
+ return (
81
+ <div {...containerProps} data-testid={testID}>
82
+ {/* Date Section */}
83
+ <div {...sectionProps}>
84
+ <div {...sectionLabelProps}>Date Range</div>
85
+ <div {...sectionContentProps}>
86
+ <RangeCalendar
87
+ value={value || {}}
88
+ onChange={handleDateRangeChange}
89
+ minDate={minDate}
90
+ maxDate={maxDate}
91
+ disabled={disabled}
92
+ allowSameDay={allowSameDay}
93
+ maxDays={maxDays}
94
+ />
95
+ </div>
96
+ </div>
97
+
98
+ {/* Time Section */}
99
+ {(value?.startDate || value?.endDate) && (
100
+ <div {...sectionProps}>
101
+ <div {...sectionLabelProps}>Time Selection</div>
102
+ <div {...sectionContentProps}>
103
+ <div {...timeSectionProps}>
104
+ {/* Start Time */}
105
+ {value?.startDate && (
106
+ <div {...timeGroupProps}>
107
+ <div {...timeGroupLabelProps}>Start Time</div>
108
+ <TimePicker
109
+ value={value.startDate}
110
+ onChange={handleStartTimeChange}
111
+ disabled={disabled}
112
+ mode={timeMode}
113
+ showSeconds={showSeconds}
114
+ step={timeStep}
115
+ />
116
+ </div>
117
+ )}
118
+
119
+ {/* End Time */}
120
+ {value?.endDate && (
121
+ <div {...timeGroupProps}>
122
+ <div {...timeGroupLabelProps}>End Time</div>
123
+ <TimePicker
124
+ value={value.endDate}
125
+ onChange={handleEndTimeChange}
126
+ disabled={disabled}
127
+ mode={timeMode}
128
+ showSeconds={showSeconds}
129
+ step={timeStep}
130
+ />
131
+ </div>
132
+ )}
133
+ </div>
134
+ </div>
135
+ </div>
136
+ )}
137
+ </div>
138
+ );
139
+ };
140
+
141
+ export default DateTimeRangePicker;
@@ -0,0 +1,2 @@
1
+ export { default as DateTimeRangePicker } from './DateTimeRangePicker.native';
2
+ export * from './types';
@@ -0,0 +1,2 @@
1
+ export { default as DateTimeRangePicker } from './DateTimeRangePicker.web';
2
+ export * from './types';
@@ -0,0 +1,72 @@
1
+ import { ReactNode } from 'react';
2
+ import { ViewStyle } from 'react-native';
3
+
4
+ export interface DateTimeRange {
5
+ /** Start date and time of the range */
6
+ startDate?: Date;
7
+
8
+ /** End date and time of the range */
9
+ endDate?: Date;
10
+ }
11
+
12
+ export interface DateTimeRangePickerProps {
13
+ /** Current selected date/time range */
14
+ value?: DateTimeRange;
15
+
16
+ /** Called when date/time range changes */
17
+ onChange: (range: DateTimeRange | 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 HH:mm') */
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
+ /** Time picker mode */
53
+ timeMode?: '12h' | '24h';
54
+
55
+ /** Show seconds in time picker */
56
+ showSeconds?: boolean;
57
+
58
+ /** Time step in minutes */
59
+ timeStep?: number;
60
+
61
+ /** Allow same day selection for start and end */
62
+ allowSameDay?: boolean;
63
+
64
+ /** Maximum number of days in range */
65
+ maxDays?: number;
66
+
67
+ /** Custom styles */
68
+ style?: ViewStyle;
69
+
70
+ /** Test ID for testing */
71
+ testID?: string;
72
+ }